@agirails/sdk 2.0.0-beta

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 (154) hide show
  1. package/README.md +183 -0
  2. package/dist/ACTPClient.d.ts +52 -0
  3. package/dist/ACTPClient.d.ts.map +1 -0
  4. package/dist/ACTPClient.js +120 -0
  5. package/dist/ACTPClient.js.map +1 -0
  6. package/dist/abi/ACTPKernel.json +1340 -0
  7. package/dist/abi/ERC20.json +38 -0
  8. package/dist/abi/EscrowVault.json +64 -0
  9. package/dist/builders/DeliveryProofBuilder.d.ts +37 -0
  10. package/dist/builders/DeliveryProofBuilder.d.ts.map +1 -0
  11. package/dist/builders/DeliveryProofBuilder.js +165 -0
  12. package/dist/builders/DeliveryProofBuilder.js.map +1 -0
  13. package/dist/builders/QuoteBuilder.d.ts +68 -0
  14. package/dist/builders/QuoteBuilder.d.ts.map +1 -0
  15. package/dist/builders/QuoteBuilder.js +255 -0
  16. package/dist/builders/QuoteBuilder.js.map +1 -0
  17. package/dist/builders/index.d.ts +3 -0
  18. package/dist/builders/index.d.ts.map +1 -0
  19. package/dist/builders/index.js +10 -0
  20. package/dist/builders/index.js.map +1 -0
  21. package/dist/config/networks.d.ts +27 -0
  22. package/dist/config/networks.d.ts.map +1 -0
  23. package/dist/config/networks.js +103 -0
  24. package/dist/config/networks.js.map +1 -0
  25. package/dist/errors/index.d.ts +38 -0
  26. package/dist/errors/index.d.ts.map +1 -0
  27. package/dist/errors/index.js +87 -0
  28. package/dist/errors/index.js.map +1 -0
  29. package/dist/index.d.ts +19 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +68 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/protocol/ACTPKernel.d.ts +30 -0
  34. package/dist/protocol/ACTPKernel.d.ts.map +1 -0
  35. package/dist/protocol/ACTPKernel.js +261 -0
  36. package/dist/protocol/ACTPKernel.js.map +1 -0
  37. package/dist/protocol/EASHelper.d.ts +23 -0
  38. package/dist/protocol/EASHelper.d.ts.map +1 -0
  39. package/dist/protocol/EASHelper.js +106 -0
  40. package/dist/protocol/EASHelper.js.map +1 -0
  41. package/dist/protocol/EscrowVault.d.ts +24 -0
  42. package/dist/protocol/EscrowVault.d.ts.map +1 -0
  43. package/dist/protocol/EscrowVault.js +114 -0
  44. package/dist/protocol/EscrowVault.js.map +1 -0
  45. package/dist/protocol/EventMonitor.d.ts +18 -0
  46. package/dist/protocol/EventMonitor.d.ts.map +1 -0
  47. package/dist/protocol/EventMonitor.js +92 -0
  48. package/dist/protocol/EventMonitor.js.map +1 -0
  49. package/dist/protocol/MessageSigner.d.ts +23 -0
  50. package/dist/protocol/MessageSigner.d.ts.map +1 -0
  51. package/dist/protocol/MessageSigner.js +178 -0
  52. package/dist/protocol/MessageSigner.js.map +1 -0
  53. package/dist/protocol/ProofGenerator.d.ts +22 -0
  54. package/dist/protocol/ProofGenerator.d.ts.map +1 -0
  55. package/dist/protocol/ProofGenerator.js +64 -0
  56. package/dist/protocol/ProofGenerator.js.map +1 -0
  57. package/dist/protocol/QuoteBuilder.d.ts +2 -0
  58. package/dist/protocol/QuoteBuilder.d.ts.map +1 -0
  59. package/dist/protocol/QuoteBuilder.js +7 -0
  60. package/dist/protocol/QuoteBuilder.js.map +1 -0
  61. package/dist/types/eip712.d.ts +106 -0
  62. package/dist/types/eip712.d.ts.map +1 -0
  63. package/dist/types/eip712.js +84 -0
  64. package/dist/types/eip712.js.map +1 -0
  65. package/dist/types/escrow.d.ts +18 -0
  66. package/dist/types/escrow.d.ts.map +1 -0
  67. package/dist/types/escrow.js +3 -0
  68. package/dist/types/escrow.js.map +1 -0
  69. package/dist/types/index.d.ts +6 -0
  70. package/dist/types/index.d.ts.map +1 -0
  71. package/dist/types/index.js +22 -0
  72. package/dist/types/index.js.map +1 -0
  73. package/dist/types/message.d.ts +109 -0
  74. package/dist/types/message.d.ts.map +1 -0
  75. package/dist/types/message.js +3 -0
  76. package/dist/types/message.js.map +1 -0
  77. package/dist/types/state.d.ts +19 -0
  78. package/dist/types/state.d.ts.map +1 -0
  79. package/dist/types/state.js +49 -0
  80. package/dist/types/state.js.map +1 -0
  81. package/dist/types/transaction.d.ts +36 -0
  82. package/dist/types/transaction.d.ts.map +1 -0
  83. package/dist/types/transaction.js +3 -0
  84. package/dist/types/transaction.js.map +1 -0
  85. package/dist/utils/IPFSClient.d.ts +37 -0
  86. package/dist/utils/IPFSClient.d.ts.map +1 -0
  87. package/dist/utils/IPFSClient.js +128 -0
  88. package/dist/utils/IPFSClient.js.map +1 -0
  89. package/dist/utils/NonceManager.d.ts +34 -0
  90. package/dist/utils/NonceManager.d.ts.map +1 -0
  91. package/dist/utils/NonceManager.js +114 -0
  92. package/dist/utils/NonceManager.js.map +1 -0
  93. package/dist/utils/ReceivedNonceTracker.d.ts +35 -0
  94. package/dist/utils/ReceivedNonceTracker.d.ts.map +1 -0
  95. package/dist/utils/ReceivedNonceTracker.js +196 -0
  96. package/dist/utils/ReceivedNonceTracker.js.map +1 -0
  97. package/dist/utils/canonicalJson.d.ts +4 -0
  98. package/dist/utils/canonicalJson.d.ts.map +1 -0
  99. package/dist/utils/canonicalJson.js +21 -0
  100. package/dist/utils/canonicalJson.js.map +1 -0
  101. package/dist/utils/computeTypeHash.d.ts +3 -0
  102. package/dist/utils/computeTypeHash.d.ts.map +1 -0
  103. package/dist/utils/computeTypeHash.js +30 -0
  104. package/dist/utils/computeTypeHash.js.map +1 -0
  105. package/dist/utils/validation.d.ts +6 -0
  106. package/dist/utils/validation.d.ts.map +1 -0
  107. package/dist/utils/validation.js +46 -0
  108. package/dist/utils/validation.js.map +1 -0
  109. package/package.json +73 -0
  110. package/src/ACTPClient.ts +276 -0
  111. package/src/__tests__/ProofGenerator.test.ts +124 -0
  112. package/src/__tests__/QuoteBuilder.test.ts +516 -0
  113. package/src/__tests__/StateMachine.test.ts +82 -0
  114. package/src/__tests__/builders/DeliveryProofBuilder.test.ts +581 -0
  115. package/src/__tests__/integration/ACTPClient.test.ts +263 -0
  116. package/src/__tests__/integration.test.ts +289 -0
  117. package/src/__tests__/protocol/EASHelper.test.ts +472 -0
  118. package/src/__tests__/protocol/EventMonitor.test.ts +382 -0
  119. package/src/__tests__/security/ACTPKernel.security.test.ts +1167 -0
  120. package/src/__tests__/security/EscrowVault.security.test.ts +570 -0
  121. package/src/__tests__/security/MessageSigner.security.test.ts +286 -0
  122. package/src/__tests__/security/NonceReplay.security.test.ts +501 -0
  123. package/src/__tests__/security/validation.security.test.ts +376 -0
  124. package/src/__tests__/utils/IPFSClient.test.ts +262 -0
  125. package/src/__tests__/utils/NonceManager.test.ts +205 -0
  126. package/src/__tests__/utils/canonicalJson.test.ts +153 -0
  127. package/src/abi/ACTPKernel.json +1340 -0
  128. package/src/abi/ERC20.json +40 -0
  129. package/src/abi/EscrowVault.json +66 -0
  130. package/src/builders/DeliveryProofBuilder.ts +326 -0
  131. package/src/builders/QuoteBuilder.ts +483 -0
  132. package/src/builders/index.ts +17 -0
  133. package/src/config/networks.ts +165 -0
  134. package/src/errors/index.ts +130 -0
  135. package/src/index.ts +108 -0
  136. package/src/protocol/ACTPKernel.ts +625 -0
  137. package/src/protocol/EASHelper.ts +197 -0
  138. package/src/protocol/EscrowVault.ts +237 -0
  139. package/src/protocol/EventMonitor.ts +161 -0
  140. package/src/protocol/MessageSigner.ts +336 -0
  141. package/src/protocol/ProofGenerator.ts +119 -0
  142. package/src/protocol/QuoteBuilder.ts +15 -0
  143. package/src/types/eip712.ts +175 -0
  144. package/src/types/escrow.ts +26 -0
  145. package/src/types/index.ts +10 -0
  146. package/src/types/message.ts +145 -0
  147. package/src/types/state.ts +77 -0
  148. package/src/types/transaction.ts +54 -0
  149. package/src/utils/IPFSClient.ts +248 -0
  150. package/src/utils/NonceManager.ts +293 -0
  151. package/src/utils/ReceivedNonceTracker.ts +397 -0
  152. package/src/utils/canonicalJson.ts +38 -0
  153. package/src/utils/computeTypeHash.ts +50 -0
  154. package/src/utils/validation.ts +82 -0
@@ -0,0 +1,625 @@
1
+ import { Contract, Signer, BytesLike, ethers, AbiCoder } from 'ethers';
2
+ import ACTPKernelABI from '../abi/ACTPKernel.json';
3
+ import {
4
+ State,
5
+ StateMachine,
6
+ Transaction,
7
+ CreateTransactionParams,
8
+ DisputeResolution,
9
+ EconomicParams
10
+ } from '../types';
11
+ import {
12
+ TransactionNotFoundError,
13
+ TransactionRevertedError,
14
+ InvalidStateTransitionError,
15
+ ValidationError
16
+ } from '../errors';
17
+ import {
18
+ validateAddress,
19
+ validateAmount,
20
+ validateDeadline,
21
+ validateDisputeWindow,
22
+ validateTxId
23
+ } from '../utils/validation';
24
+
25
+ /**
26
+ * Gas options for transactions
27
+ */
28
+ interface GasOptions {
29
+ maxFeePerGas?: bigint;
30
+ maxPriorityFeePerGas?: bigint;
31
+ }
32
+
33
+ /**
34
+ * ACTPKernel - Smart contract wrapper
35
+ * Reference: Yellow Paper §3 (ACTP Kernel Specification)
36
+ */
37
+ export class ACTPKernel {
38
+ private contract: Contract;
39
+ private readonly gasSettings?: GasOptions;
40
+
41
+ constructor(
42
+ private readonly address: string,
43
+ signer: Signer,
44
+ gasSettings?: GasOptions
45
+ ) {
46
+ this.contract = new Contract(address, ACTPKernelABI, signer);
47
+ this.gasSettings = gasSettings;
48
+ }
49
+
50
+ /**
51
+ * Get kernel contract address
52
+ */
53
+ getAddress(): string {
54
+ return this.address;
55
+ }
56
+
57
+ /**
58
+ * Get gas buffer multiplier based on operation complexity
59
+ * V6 Security Enhancement: Operation-specific gas buffers
60
+ * Reference: SDK_SECURITY_ANALYSIS-Ultra-Think.md Lines 326-337
61
+ */
62
+ private getGasBufferMultiplier(operation: string): number {
63
+ const buffers: Record<string, number> = {
64
+ 'createTransaction': 1.15, // 15% - Simple state initialization
65
+ 'transitionState': 1.20, // 20% - Standard state change
66
+ 'releaseEscrow': 1.30, // 30% - Multi-recipient disbursement
67
+ 'raiseDispute': 1.25, // 25% - Large proof data handling
68
+ 'resolveDispute': 1.30, // 30% - Complex multi-party settlement
69
+ 'cancelTransaction': 1.15, // 15% - Simple state change
70
+ 'anchorAttestation': 1.15 // 15% - Simple attestation anchoring
71
+ };
72
+
73
+ return buffers[operation] || 1.20; // Default 20% for unknown operations
74
+ }
75
+
76
+ /**
77
+ * Build transaction options with gas settings and estimated gas
78
+ * V6 Enhancement: Dynamic buffer based on operation type
79
+ */
80
+ private buildTxOptions(estimatedGas: bigint, operation: string = 'default'): any {
81
+ const bufferMultiplier = this.getGasBufferMultiplier(operation);
82
+
83
+ const options: any = {
84
+ gasLimit: (estimatedGas * BigInt(Math.round(bufferMultiplier * 100))) / 100n
85
+ };
86
+
87
+ if (this.gasSettings?.maxFeePerGas) {
88
+ options.maxFeePerGas = this.gasSettings.maxFeePerGas;
89
+ }
90
+ if (this.gasSettings?.maxPriorityFeePerGas) {
91
+ options.maxPriorityFeePerGas = this.gasSettings.maxPriorityFeePerGas;
92
+ }
93
+
94
+ return options;
95
+ }
96
+
97
+ /**
98
+ * Create a new transaction
99
+ * Reference: Yellow Paper §3.4.1
100
+ *
101
+ * Contract signature: createTransaction(provider, requester, amount, deadline, disputeWindow, serviceHash)
102
+ * Returns: bytes32 transactionId (generated by contract)
103
+ */
104
+ async createTransaction(params: CreateTransactionParams): Promise<string> {
105
+ const {
106
+ provider,
107
+ requester,
108
+ amount,
109
+ deadline,
110
+ disputeWindow,
111
+ metadata = '0x0000000000000000000000000000000000000000000000000000000000000000'
112
+ } = params;
113
+
114
+ // Input validation
115
+ validateAddress(provider, 'provider');
116
+ validateAddress(requester, 'requester');
117
+ validateAmount(amount, 'amount');
118
+ validateDeadline(deadline, 'deadline');
119
+ validateDisputeWindow(disputeWindow, 'disputeWindow');
120
+
121
+ try {
122
+ // ethers v6: use getFunction() for typed access
123
+ const createTxFunc = this.contract.getFunction('createTransaction');
124
+
125
+ // Contract signature: createTransaction(provider, requester, amount, deadline, disputeWindow, serviceHash)
126
+ const estimatedGas = await createTxFunc.estimateGas(
127
+ provider,
128
+ requester,
129
+ amount,
130
+ deadline,
131
+ disputeWindow,
132
+ metadata // serviceHash
133
+ );
134
+
135
+ // Build tx options with gas settings (15% buffer for simple state initialization)
136
+ const txOptions = this.buildTxOptions(estimatedGas, 'createTransaction');
137
+
138
+ // Per ABI: createTransaction returns transactionId directly
139
+ // Contract signature: function createTransaction(...) external returns (bytes32 transactionId)
140
+ const tx = await createTxFunc(
141
+ provider,
142
+ requester,
143
+ amount,
144
+ deadline,
145
+ disputeWindow,
146
+ metadata, // serviceHash
147
+ txOptions
148
+ );
149
+
150
+ const receipt = await tx.wait(2); // Wait for 2 confirmations (Base L2 reorg safety)
151
+ if (!receipt) {
152
+ throw new Error('Transaction receipt not available');
153
+ }
154
+
155
+ // Extract transactionId from TransactionCreated event
156
+ // Event signature: TransactionCreated(bytes32 indexed transactionId, ...)
157
+ for (const log of receipt.logs) {
158
+ try {
159
+ const parsedLog = this.contract.interface.parseLog({
160
+ topics: [...log.topics],
161
+ data: log.data
162
+ });
163
+
164
+ if (parsedLog && parsedLog.name === 'TransactionCreated') {
165
+ return parsedLog.args.transactionId || parsedLog.args[0];
166
+ }
167
+ } catch (e) {
168
+ // Skip logs that don't match our interface
169
+ continue;
170
+ }
171
+ }
172
+
173
+ throw new Error('TransactionCreated event not found in receipt');
174
+ } catch (error: any) {
175
+ throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Transition transaction state
181
+ * Reference: Yellow Paper §3.2
182
+ */
183
+ async transitionState(
184
+ txId: string,
185
+ newState: State,
186
+ proof: BytesLike = '0x'
187
+ ): Promise<void> {
188
+ // Input validation
189
+ validateTxId(txId, 'txId');
190
+
191
+ // Validate transition
192
+ const currentTx = await this.getTransaction(txId);
193
+ if (!StateMachine.isValidTransition(currentTx.state, newState)) {
194
+ const validStates = StateMachine.getNextValidStates(currentTx.state).map((s) =>
195
+ State[s]
196
+ );
197
+ throw new InvalidStateTransitionError(currentTx.state, newState, validStates);
198
+ }
199
+
200
+ try {
201
+ // ethers v6: use getFunction()
202
+ const transitionFunc = this.contract.getFunction('transitionState');
203
+
204
+ // Estimate gas with safety buffer (20% for standard state transitions)
205
+ const estimatedGas = await transitionFunc.estimateGas(txId, newState, proof);
206
+ const txOptions = this.buildTxOptions(estimatedGas, 'transitionState');
207
+
208
+ const tx = await transitionFunc(txId, newState, proof, txOptions);
209
+
210
+ await tx.wait(2); // Wait for 2 confirmations (Base L2 reorg safety)
211
+ } catch (error: any) {
212
+ throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Submit quote for transaction (AIP-2)
218
+ * Reference: AIP-2 §4.1 (Provider workflow)
219
+ *
220
+ * Transitions transaction from INITIATED → QUOTED with quote hash stored on-chain
221
+ *
222
+ * @param txId - Transaction ID (bytes32)
223
+ * @param quoteHash - Keccak256 hash of canonical JSON quote message
224
+ */
225
+ async submitQuote(txId: string, quoteHash: string): Promise<void> {
226
+ // Input validation
227
+ validateTxId(txId, 'txId');
228
+
229
+ if (!/^0x[a-fA-F0-9]{64}$/.test(quoteHash)) {
230
+ throw new ValidationError('quoteHash', 'Must be valid bytes32 hex string');
231
+ }
232
+
233
+ if (quoteHash === '0x0000000000000000000000000000000000000000000000000000000000000000') {
234
+ throw new ValidationError('quoteHash', 'Cannot be zero hash');
235
+ }
236
+
237
+ // Validate current state is INITIATED
238
+ const currentTx = await this.getTransaction(txId);
239
+ if (currentTx.state !== State.INITIATED) {
240
+ throw new InvalidStateTransitionError(
241
+ currentTx.state,
242
+ State.QUOTED,
243
+ ['INITIATED']
244
+ );
245
+ }
246
+
247
+ // Encode quote hash as bytes proof
248
+ const abiCoder = AbiCoder.defaultAbiCoder();
249
+ const proof = abiCoder.encode(['bytes32'], [quoteHash]);
250
+
251
+ // Transition to QUOTED state with quote hash
252
+ await this.transitionState(txId, State.QUOTED, proof);
253
+ }
254
+
255
+ /**
256
+ * Link escrow to transaction
257
+ *
258
+ * CRITICAL: This is the ONLY way to create escrow per AIP-3 spec.
259
+ * SDK should NOT call EscrowVault.createEscrow() directly (onlyKernel modifier).
260
+ *
261
+ * What happens internally:
262
+ * 1. ACTPKernel validates transaction state, permissions, deadline
263
+ * 2. Kernel calls IEscrowValidator(escrowContract).createEscrow(...)
264
+ * 3. EscrowVault pulls USDC from consumer (must approve USDC first!)
265
+ * 4. Events emitted: EscrowLinked
266
+ * 5. **State transition behavior varies** (see below)
267
+ *
268
+ * STATE TRANSITION BEHAVIOR:
269
+ * - **AIP-3 Spec (Source Code)**: Should auto-transition INITIATED/QUOTED → COMMITTED
270
+ * - **Deployed Contract**: Behavior is INCONSISTENT - sometimes auto-transitions, sometimes doesn't
271
+ * - **Recommended Practice**: Always check state after linkEscrow() and manually transition if needed
272
+ *
273
+ * ```typescript
274
+ * await client.kernel.linkEscrow(txId, escrowVault, escrowId);
275
+ * let tx = await client.kernel.getTransaction(txId);
276
+ * if (tx.state !== State.COMMITTED) {
277
+ * await client.kernel.transitionState(txId, State.COMMITTED);
278
+ * }
279
+ * ```
280
+ *
281
+ * Prerequisites:
282
+ * - Transaction in INITIATED or QUOTED state
283
+ * - Consumer has approved USDC to EscrowVault address
284
+ * (use EscrowVault.approveToken() before calling this)
285
+ *
286
+ * @param txId - Transaction ID (bytes32)
287
+ * @param escrowContract - EscrowVault contract address
288
+ * @param escrowId - Unique escrow identifier (bytes32, consumer-generated)
289
+ * @throws {ValidationError} If inputs invalid
290
+ * @throws {TransactionRevertedError} If state invalid, deadline passed, or insufficient USDC
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * // Step 1: Approve USDC to EscrowVault (NOT to Kernel!)
295
+ * await client.escrow.approveToken(BASE_SEPOLIA.contracts.usdc, amount);
296
+ *
297
+ * // Step 2: Generate unique escrow ID
298
+ * const escrowId = ethers.id(`escrow-${Date.now()}`);
299
+ *
300
+ * // Step 3: Link escrow (creates escrow + auto-transitions to COMMITTED)
301
+ * await client.kernel.linkEscrow(txId, escrowVaultAddress, escrowId);
302
+ *
303
+ * // Step 4: Verify state is COMMITTED (auto-transitioned, no manual call needed)
304
+ * const tx = await client.kernel.getTransaction(txId);
305
+ * expect(tx.state).to.equal(State.COMMITTED);
306
+ * ```
307
+ *
308
+ * Reference: Yellow Paper §3.4.2, AIP-3 §3.2 (ACTPKernel.sol lines 244-276)
309
+ */
310
+ async linkEscrow(
311
+ txId: string,
312
+ escrowContract: string,
313
+ escrowId: string
314
+ ): Promise<void> {
315
+ // Input validation
316
+ validateTxId(txId, 'txId');
317
+ validateAddress(escrowContract, 'escrowContract');
318
+ validateTxId(escrowId, 'escrowId'); // escrowId is also bytes32
319
+
320
+ try {
321
+ // ethers v6: use getFunction()
322
+ const linkEscrowFunc = this.contract.getFunction('linkEscrow');
323
+
324
+ // Estimate gas with safety buffer (20% for linking escrow)
325
+ const estimatedGas = await linkEscrowFunc.estimateGas(txId, escrowContract, escrowId);
326
+ const txOptions = this.buildTxOptions(estimatedGas, 'transitionState');
327
+
328
+ const tx = await linkEscrowFunc(txId, escrowContract, escrowId, txOptions);
329
+
330
+ // Wait for 2 confirmations to ensure state is updated on RPC nodes (Base Sepolia reorg safety)
331
+ await tx.wait(2);
332
+ } catch (error: any) {
333
+ throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Release milestone payment
339
+ */
340
+ async releaseMilestone(
341
+ txId: string,
342
+ milestoneId: number,
343
+ amount: bigint
344
+ ): Promise<void> {
345
+ // Input validation
346
+ validateTxId(txId, 'txId');
347
+ validateAmount(amount, 'amount');
348
+ if (milestoneId < 0) {
349
+ throw new ValidationError('milestoneId', 'Milestone ID cannot be negative');
350
+ }
351
+
352
+ try {
353
+ // ethers v6: use getFunction()
354
+ const releaseMilestoneFunc = this.contract.getFunction('releaseMilestone');
355
+
356
+ // Estimate gas with safety buffer (30% for escrow release operations)
357
+ const estimatedGas = await releaseMilestoneFunc.estimateGas(txId, milestoneId, amount);
358
+ const txOptions = this.buildTxOptions(estimatedGas, 'releaseEscrow');
359
+
360
+ const tx = await releaseMilestoneFunc(txId, milestoneId, amount, txOptions);
361
+
362
+ await tx.wait(2); // Wait for 2 confirmations (Base L2 reorg safety)
363
+ } catch (error: any) {
364
+ throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Release full escrow (settle transaction)
370
+ *
371
+ * SECURITY WARNING (V1): ACTPKernel V1 contract accepts any attestationUID without validation.
372
+ * This means a malicious provider could submit attestation from different transaction.
373
+ *
374
+ * To protect against this, you can either:
375
+ * 1. Use ACTPClient's wrapper method that automatically verifies
376
+ * 2. Manually verify attestation before calling this method (see EASHelper.verifyDeliveryAttestation)
377
+ *
378
+ * Note: Attestation verification is OPTIONAL here because some transactions may not use EAS.
379
+ * However, for transactions with delivery proofs, consumers SHOULD verify before settling.
380
+ *
381
+ * @param txId - Transaction ID to settle
382
+ * @throws {ValidationError} If txId is invalid
383
+ * @throws {TransactionRevertedError} If contract reverts
384
+ */
385
+ async releaseEscrow(txId: string): Promise<void> {
386
+ // Input validation
387
+ validateTxId(txId, 'txId');
388
+
389
+ try {
390
+ // ethers v6: use getFunction()
391
+ const releaseEscrowFunc = this.contract.getFunction('releaseEscrow');
392
+
393
+ // Estimate gas with safety buffer (30% for escrow release operations)
394
+ const estimatedGas = await releaseEscrowFunc.estimateGas(txId);
395
+ const txOptions = this.buildTxOptions(estimatedGas, 'releaseEscrow');
396
+
397
+ const tx = await releaseEscrowFunc(txId, txOptions);
398
+
399
+ await tx.wait(2); // Wait for 2 confirmations (Base L2 reorg safety)
400
+ } catch (error: any) {
401
+ throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Get transaction by ID
407
+ */
408
+ async getTransaction(txId: string): Promise<Transaction> {
409
+ const txData = await this.contract.getTransaction(txId);
410
+
411
+ // Check if transaction exists (createdAt !== 0)
412
+ if (txData.createdAt === 0 || txData.createdAt === 0n) {
413
+ throw new TransactionNotFoundError(txId);
414
+ }
415
+
416
+ return {
417
+ txId: txData.transactionId,
418
+ requester: txData.requester,
419
+ provider: txData.provider,
420
+ amount: txData.amount,
421
+ state: (typeof txData.state === 'bigint' ? Number(txData.state) : txData.state) as State,
422
+ createdAt: typeof txData.createdAt === 'bigint' ? Number(txData.createdAt) : txData.createdAt,
423
+ deadline: typeof txData.deadline === 'bigint' ? Number(txData.deadline) : txData.deadline,
424
+ disputeWindow: typeof txData.disputeWindow === 'bigint' ? Number(txData.disputeWindow) : txData.disputeWindow,
425
+ escrowContract: txData.escrowContract,
426
+ escrowId: txData.escrowId,
427
+ // Use metadata field (quote hash for QUOTED state) if available, fallback to serviceHash
428
+ metadata: txData.metadata || txData.serviceHash
429
+ };
430
+ }
431
+
432
+ /**
433
+ * Get economic parameters (fee structure)
434
+ * Fixed: Don't hardcode values, read from contract
435
+ */
436
+ async getEconomicParams(): Promise<EconomicParams> {
437
+ const params = await this.contract.getEconomicParams();
438
+
439
+ // Contract returns: (platformFeeBps, requesterPenaltyBps, feeRecipient)
440
+ return {
441
+ baseFeeNumerator: Number(params.platformFeeBps || params[0]),
442
+ baseFeeDenominator: 10000, // BPS is always out of 10000
443
+ feeRecipient: params.feeRecipient || params[2],
444
+ requesterPenaltyBps: Number(params.requesterPenaltyBps || params[1]),
445
+ providerPenaltyBps: 0 // Not in current contract ABI, will be added in future version
446
+ };
447
+ }
448
+
449
+ /**
450
+ * Estimate gas for transaction creation
451
+ */
452
+ async estimateCreateTransaction(params: CreateTransactionParams): Promise<bigint> {
453
+ const { provider, requester, amount, deadline, disputeWindow, metadata = '0x0000000000000000000000000000000000000000000000000000000000000000' } = params;
454
+
455
+ // ethers v6: use getFunction()
456
+ const createTxFunc = this.contract.getFunction('createTransaction');
457
+ return await createTxFunc.estimateGas(
458
+ provider,
459
+ requester,
460
+ amount,
461
+ deadline,
462
+ disputeWindow,
463
+ metadata
464
+ );
465
+ }
466
+
467
+ /**
468
+ * Raise dispute on delivered transaction
469
+ * Reference: Yellow Paper §3.4 (Dispute Management)
470
+ */
471
+ async raiseDispute(txId: string, reason: string, evidence: string): Promise<void> {
472
+ validateTxId(txId, 'txId');
473
+
474
+ // Encode dispute proof with reason and evidence (IPFS hash)
475
+ const abiCoder = AbiCoder.defaultAbiCoder();
476
+ const proofData = abiCoder.encode(
477
+ ['string', 'string'],
478
+ [reason, evidence]
479
+ );
480
+
481
+ try {
482
+ // ethers v6: use getFunction()
483
+ const transitionFunc = this.contract.getFunction('transitionState');
484
+
485
+ // Estimate gas with safety buffer (25% for large proof data)
486
+ const estimatedGas = await transitionFunc.estimateGas(
487
+ txId,
488
+ State.DISPUTED,
489
+ proofData
490
+ );
491
+
492
+ const txOptions = this.buildTxOptions(estimatedGas, 'raiseDispute');
493
+
494
+ const tx = await transitionFunc(
495
+ txId,
496
+ State.DISPUTED,
497
+ proofData,
498
+ txOptions
499
+ );
500
+
501
+ await tx.wait(2); // Wait for 2 confirmations (Base L2 reorg safety)
502
+ } catch (error: any) {
503
+ throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
504
+ }
505
+ }
506
+
507
+ /**
508
+ * Resolve/settle dispute with payment split
509
+ * Reference: Yellow Paper §3.4
510
+ *
511
+ * Disputes are settled via transitionState(SETTLED, proof) per §3.2
512
+ * The kernel contract decodes the proof and handles escrow disbursement
513
+ */
514
+ async resolveDispute(txId: string, resolution: DisputeResolution): Promise<void> {
515
+ validateTxId(txId, 'txId');
516
+
517
+ const { requesterAmount, providerAmount, mediatorAmount, mediator } = resolution;
518
+
519
+ // Validate amounts are non-negative
520
+ if (requesterAmount < 0n || providerAmount < 0n || mediatorAmount < 0n) {
521
+ throw new Error('Dispute resolution amounts cannot be negative');
522
+ }
523
+
524
+ // Validate mediator address if mediator amount > 0
525
+ if (mediatorAmount > 0n) {
526
+ if (!mediator) {
527
+ throw new Error('Mediator address required when mediator amount > 0');
528
+ }
529
+ validateAddress(mediator, 'mediator');
530
+ }
531
+
532
+ // Encode resolution proof (128 bytes: 3x uint256 + address)
533
+ // Kernel contract will decode this in _decodeResolutionProof and disburse funds
534
+ const abiCoder = AbiCoder.defaultAbiCoder();
535
+ const proofData = abiCoder.encode(
536
+ ['uint256', 'uint256', 'uint256', 'address'],
537
+ [
538
+ requesterAmount,
539
+ providerAmount,
540
+ mediatorAmount,
541
+ mediator || ethers.getAddress('0x0000000000000000000000000000000000000000')
542
+ ]
543
+ );
544
+
545
+ try {
546
+ // ethers v6: use getFunction()
547
+ const transitionFunc = this.contract.getFunction('transitionState');
548
+
549
+ // Settle dispute via state transition to SETTLED with resolution proof (30% buffer)
550
+ const estimatedGas = await transitionFunc.estimateGas(
551
+ txId,
552
+ State.SETTLED,
553
+ proofData
554
+ );
555
+
556
+ const txOptions = this.buildTxOptions(estimatedGas, 'resolveDispute');
557
+
558
+ const tx = await transitionFunc(
559
+ txId,
560
+ State.SETTLED,
561
+ proofData,
562
+ txOptions
563
+ );
564
+
565
+ await tx.wait(2); // Wait for 2 confirmations (Base L2 reorg safety)
566
+ } catch (error: any) {
567
+ throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
568
+ }
569
+ }
570
+
571
+ /**
572
+ * Settle disputed transaction (alias for resolveDispute)
573
+ */
574
+ async settleDispute(txId: string, resolution: DisputeResolution): Promise<void> {
575
+ return this.resolveDispute(txId, resolution);
576
+ }
577
+
578
+ /**
579
+ * Anchor an EAS attestation UID to a transaction (delivery proof)
580
+ * Reference: AIP-4 (Delivery Proof and EAS Attestation Standard)
581
+ *
582
+ * @param txId - Transaction ID
583
+ * @param attestationUID - EAS attestation UID from provider
584
+ * @throws {ValidationError} If inputs are invalid
585
+ * @throws {TransactionRevertedError} If contract reverts
586
+ *
587
+ * @example
588
+ * ```typescript
589
+ * const easHelper = new EASHelper(signer, easConfig);
590
+ * const attestation = await easHelper.attestDeliveryProof(proof, recipient);
591
+ * await kernel.anchorAttestation(txId, attestation.uid);
592
+ * ```
593
+ */
594
+ async anchorAttestation(txId: string, attestationUID: string): Promise<void> {
595
+ validateTxId(txId, 'txId');
596
+
597
+ // Validate attestationUID format (32-byte hex string)
598
+ if (!attestationUID || !/^0x[a-fA-F0-9]{64}$/.test(attestationUID)) {
599
+ throw new ValidationError('attestationUID', 'Must be 32-byte hex string (0x...)');
600
+ }
601
+
602
+ try {
603
+ // ethers v6: use getFunction()
604
+ const anchorFunc = this.contract.getFunction('anchorAttestation');
605
+
606
+ const estimatedGas = await anchorFunc.estimateGas(
607
+ txId,
608
+ attestationUID
609
+ );
610
+
611
+ const txOptions = this.buildTxOptions(estimatedGas, 'anchorAttestation');
612
+
613
+ const tx = await anchorFunc(
614
+ txId,
615
+ attestationUID,
616
+ txOptions
617
+ );
618
+
619
+ await tx.wait(2); // Wait for 2 confirmations (Base L2 reorg safety)
620
+ } catch (error: any) {
621
+ throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
622
+ }
623
+ }
624
+
625
+ }