@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,581 @@
1
+ /**
2
+ * DeliveryProofBuilder Test Suite
3
+ *
4
+ * Coverage Target: 80%+ (statements, functions, lines, branches)
5
+ *
6
+ * Test Categories:
7
+ * 1. Delivery Proof Building (5 tests)
8
+ * 2. Delivery Proof Verification (5 tests)
9
+ * 3. Signature Operations (3 tests)
10
+ * 4. DID Parsing (2 tests)
11
+ *
12
+ * References:
13
+ * - DeliveryProofBuilder.ts implementation
14
+ * - AIP-4 §9.1 (Delivery Proof Construction)
15
+ */
16
+
17
+ import { Wallet } from 'ethers';
18
+ import { DeliveryProofBuilder, AGIRAILS_DELIVERY_SCHEMA_UID } from '../../builders/DeliveryProofBuilder';
19
+ import { IPFSClient } from '../../utils/IPFSClient';
20
+ import { NonceManager } from '../../utils/NonceManager';
21
+ import { EAS } from '@ethereum-attestation-service/eas-sdk';
22
+
23
+ // Mock IPFSClient
24
+ const mockIPFS: jest.Mocked<IPFSClient> = {
25
+ add: jest.fn(),
26
+ pin: jest.fn(),
27
+ get: jest.fn(),
28
+ cat: jest.fn()
29
+ } as any;
30
+
31
+ // Mock NonceManager
32
+ const mockNonceManager: jest.Mocked<NonceManager> = {
33
+ getNextNonce: jest.fn(),
34
+ recordNonce: jest.fn(),
35
+ getCurrentNonce: jest.fn()
36
+ } as any;
37
+
38
+ // Mock EAS
39
+ const mockEAS: jest.Mocked<EAS> = {
40
+ attest: jest.fn(),
41
+ getAttestation: jest.fn()
42
+ } as any;
43
+
44
+ // Test signer
45
+ const testPrivateKey = '0x' + '1'.repeat(64);
46
+ const testSigner = new Wallet(testPrivateKey);
47
+ const testProviderAddress = testSigner.address.toLowerCase();
48
+
49
+ describe('DeliveryProofBuilder - Delivery Proof Building', () => {
50
+ let builder: DeliveryProofBuilder;
51
+
52
+ beforeEach(() => {
53
+ jest.clearAllMocks();
54
+ builder = new DeliveryProofBuilder(mockIPFS, testSigner, mockNonceManager, mockEAS);
55
+
56
+ // Default mock implementations
57
+ mockIPFS.add.mockResolvedValue('QmTestCID123456789');
58
+ mockIPFS.pin.mockResolvedValue(undefined);
59
+ mockNonceManager.getNextNonce.mockReturnValue(1);
60
+ mockNonceManager.recordNonce.mockReturnValue(undefined);
61
+ });
62
+
63
+ it('should build complete delivery proof with IPFS upload', async () => {
64
+ const mockAttestationUID = '0x' + '2'.repeat(64);
65
+ mockEAS.attest.mockResolvedValue({
66
+ wait: jest.fn().mockResolvedValue({
67
+ newAttestationUID: mockAttestationUID
68
+ })
69
+ } as any);
70
+
71
+ const params = {
72
+ txId: '0x' + '1'.repeat(64),
73
+ provider: `did:ethr:84532:${testProviderAddress}`,
74
+ consumer: 'did:ethr:84532:0x742d35cc6634c0532925a3b844bc9e7595f0beb0',
75
+ resultData: { result: 'success', output: 'test-output' },
76
+ metadata: {
77
+ executionTime: 1500,
78
+ outputFormat: 'json',
79
+ outputSize: 256
80
+ },
81
+ chainId: 84532,
82
+ kernelAddress: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
83
+ };
84
+
85
+ const result = await builder.build(params);
86
+
87
+ // Verify IPFS uploads
88
+ expect(mockIPFS.add).toHaveBeenCalledTimes(2); // resultData + deliveryProof
89
+ expect(mockIPFS.pin).toHaveBeenCalledTimes(2);
90
+ expect(mockIPFS.add).toHaveBeenCalledWith(JSON.stringify(params.resultData));
91
+
92
+ // Verify EAS attestation
93
+ expect(mockEAS.attest).toHaveBeenCalledWith(
94
+ expect.objectContaining({
95
+ schema: AGIRAILS_DELIVERY_SCHEMA_UID,
96
+ data: expect.objectContaining({
97
+ recipient: '0x742d35cc6634c0532925a3b844bc9e7595f0beb0',
98
+ revocable: false
99
+ })
100
+ })
101
+ );
102
+
103
+ // Verify nonce management
104
+ expect(mockNonceManager.getNextNonce).toHaveBeenCalledWith('agirails.delivery.v1');
105
+ expect(mockNonceManager.recordNonce).toHaveBeenCalledWith('agirails.delivery.v1', 1);
106
+
107
+ // Verify result structure
108
+ expect(result.deliveryProof).toMatchObject({
109
+ type: 'agirails.delivery.v1',
110
+ version: '1.0.0',
111
+ txId: params.txId,
112
+ provider: params.provider,
113
+ consumer: params.consumer,
114
+ resultCID: 'QmTestCID123456789',
115
+ easAttestationUID: mockAttestationUID,
116
+ chainId: params.chainId,
117
+ nonce: 1
118
+ });
119
+
120
+ expect(result.deliveryProof.signature).toBeTruthy();
121
+ expect(result.deliveryProof.signature.startsWith('0x')).toBe(true);
122
+ expect(result.deliveryProofCID).toBe('QmTestCID123456789');
123
+ expect(result.attestationUID).toBe(mockAttestationUID);
124
+ });
125
+
126
+ it('should build delivery proof with empty metadata', async () => {
127
+ const mockAttestationUID = '0x' + '2'.repeat(64);
128
+ mockEAS.attest.mockResolvedValue({
129
+ wait: jest.fn().mockResolvedValue({
130
+ newAttestationUID: mockAttestationUID
131
+ })
132
+ } as any);
133
+
134
+ const params = {
135
+ txId: '0x' + '1'.repeat(64),
136
+ provider: `did:ethr:84532:${testProviderAddress}`,
137
+ consumer: 'did:ethr:84532:0x742d35cc6634c0532925a3b844bc9e7595f0beb0',
138
+ resultData: { result: 'success' },
139
+ chainId: 84532,
140
+ kernelAddress: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
141
+ };
142
+
143
+ const result = await builder.build(params);
144
+
145
+ expect(result.deliveryProof.metadata).toEqual({});
146
+ });
147
+
148
+ it('should compute result hash correctly', async () => {
149
+ const mockAttestationUID = '0x' + '2'.repeat(64);
150
+ mockEAS.attest.mockResolvedValue({
151
+ wait: jest.fn().mockResolvedValue({
152
+ newAttestationUID: mockAttestationUID
153
+ })
154
+ } as any);
155
+
156
+ const params = {
157
+ txId: '0x' + '1'.repeat(64),
158
+ provider: `did:ethr:84532:${testProviderAddress}`,
159
+ consumer: 'did:ethr:84532:0x742d35cc6634c0532925a3b844bc9e7595f0beb0',
160
+ resultData: { a: 1, b: 2 },
161
+ chainId: 84532,
162
+ kernelAddress: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
163
+ };
164
+
165
+ const result = await builder.build(params);
166
+
167
+ // resultHash should be a bytes32 hex string
168
+ expect(result.deliveryProof.resultHash).toMatch(/^0x[a-fA-F0-9]{64}$/);
169
+ });
170
+
171
+ it('should set deliveredAt timestamp correctly', async () => {
172
+ const beforeTime = Math.floor(Date.now() / 1000);
173
+
174
+ const mockAttestationUID = '0x' + '2'.repeat(64);
175
+ mockEAS.attest.mockResolvedValue({
176
+ wait: jest.fn().mockResolvedValue({
177
+ newAttestationUID: mockAttestationUID
178
+ })
179
+ } as any);
180
+
181
+ const params = {
182
+ txId: '0x' + '1'.repeat(64),
183
+ provider: `did:ethr:84532:${testProviderAddress}`,
184
+ consumer: 'did:ethr:84532:0x742d35cc6634c0532925a3b844bc9e7595f0beb0',
185
+ resultData: { result: 'success' },
186
+ chainId: 84532,
187
+ kernelAddress: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
188
+ };
189
+
190
+ const result = await builder.build(params);
191
+
192
+ const afterTime = Math.floor(Date.now() / 1000);
193
+
194
+ expect(result.deliveryProof.deliveredAt).toBeGreaterThanOrEqual(beforeTime);
195
+ expect(result.deliveryProof.deliveredAt).toBeLessThanOrEqual(afterTime);
196
+ });
197
+
198
+ it('should handle different chain IDs', async () => {
199
+ const mockAttestationUID = '0x' + '2'.repeat(64);
200
+ mockEAS.attest.mockResolvedValue({
201
+ wait: jest.fn().mockResolvedValue({
202
+ newAttestationUID: mockAttestationUID
203
+ })
204
+ } as any);
205
+
206
+ const paramsMainnet = {
207
+ txId: '0x' + '1'.repeat(64),
208
+ provider: `did:ethr:8453:${testProviderAddress}`,
209
+ consumer: 'did:ethr:8453:0x742d35cc6634c0532925a3b844bc9e7595f0beb0',
210
+ resultData: { result: 'success' },
211
+ chainId: 8453, // Base Mainnet
212
+ kernelAddress: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
213
+ };
214
+
215
+ const result = await builder.build(paramsMainnet);
216
+
217
+ expect(result.deliveryProof.chainId).toBe(8453);
218
+ });
219
+ });
220
+
221
+ describe('DeliveryProofBuilder - Delivery Proof Verification', () => {
222
+ let builder: DeliveryProofBuilder;
223
+
224
+ beforeEach(() => {
225
+ jest.clearAllMocks();
226
+ builder = new DeliveryProofBuilder(mockIPFS, testSigner, mockNonceManager, mockEAS);
227
+ });
228
+
229
+ it('should verify valid delivery proof', async () => {
230
+ const resultData = { result: 'success', output: 'test' };
231
+ const txId = '0x' + '1'.repeat(64);
232
+ const mockAttestationUID = '0x' + '2'.repeat(64);
233
+ const kernelAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
234
+
235
+ // Build a real delivery proof first
236
+ mockIPFS.add.mockResolvedValue('QmTestCID');
237
+ mockIPFS.pin.mockResolvedValue(undefined);
238
+ mockNonceManager.getNextNonce.mockReturnValue(1);
239
+ mockEAS.attest.mockResolvedValue({
240
+ wait: jest.fn().mockResolvedValue({
241
+ newAttestationUID: mockAttestationUID
242
+ })
243
+ } as any);
244
+
245
+ const { deliveryProof } = await builder.build({
246
+ txId,
247
+ provider: `did:ethr:84532:${testProviderAddress}`,
248
+ consumer: 'did:ethr:84532:0x742d35cc6634c0532925a3b844bc9e7595f0beb0',
249
+ resultData,
250
+ chainId: 84532,
251
+ kernelAddress
252
+ });
253
+
254
+ // Mock EAS attestation retrieval
255
+ mockEAS.getAttestation.mockResolvedValue({
256
+ schema: AGIRAILS_DELIVERY_SCHEMA_UID,
257
+ data: '0x', // Will be decoded by SchemaEncoder
258
+ revoked: false
259
+ } as any);
260
+
261
+ // Mock SchemaEncoder decodeData
262
+ const originalSchemaEncoder = jest.requireActual('@ethereum-attestation-service/eas-sdk').SchemaEncoder;
263
+ jest.spyOn(originalSchemaEncoder.prototype, 'decodeData').mockReturnValue([
264
+ { name: 'txId', value: { value: txId } },
265
+ { name: 'resultCID', value: { value: deliveryProof.resultCID } },
266
+ { name: 'resultHash', value: { value: deliveryProof.resultHash } },
267
+ { name: 'deliveredAt', value: { value: deliveryProof.deliveredAt } }
268
+ ]);
269
+
270
+ const isValid = await builder.verify(deliveryProof, resultData, kernelAddress);
271
+
272
+ expect(isValid).toBe(true);
273
+ expect(mockEAS.getAttestation).toHaveBeenCalledWith(mockAttestationUID);
274
+ });
275
+
276
+ it('should reject delivery proof with invalid signature', async () => {
277
+ const resultData = { result: 'success' };
278
+ const deliveryProof = {
279
+ type: 'agirails.delivery.v1' as const,
280
+ version: '1.0.0',
281
+ txId: '0x' + '1'.repeat(64),
282
+ provider: 'did:ethr:84532:0x742d35cc6634c0532925a3b844bc9e7595f0beb0', // Wrong provider
283
+ consumer: 'did:ethr:84532:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
284
+ resultCID: 'QmTestCID',
285
+ resultHash: '0x' + '3'.repeat(64),
286
+ metadata: {},
287
+ easAttestationUID: '0x' + '2'.repeat(64),
288
+ deliveredAt: Math.floor(Date.now() / 1000),
289
+ chainId: 84532,
290
+ nonce: 1,
291
+ signature: '0x' + '9'.repeat(130) // Invalid signature
292
+ };
293
+
294
+ await expect(
295
+ builder.verify(deliveryProof, resultData, '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
296
+ ).rejects.toThrow('Invalid signature');
297
+ });
298
+
299
+ it('should reject delivery proof with tampered result data', async () => {
300
+ const originalData = { result: 'success', output: 'original' };
301
+ const tamperedData = { result: 'success', output: 'tampered' };
302
+ const txId = '0x' + '1'.repeat(64);
303
+ const mockAttestationUID = '0x' + '2'.repeat(64);
304
+ const kernelAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
305
+
306
+ // Build proof with original data
307
+ mockIPFS.add.mockResolvedValue('QmTestCID');
308
+ mockIPFS.pin.mockResolvedValue(undefined);
309
+ mockNonceManager.getNextNonce.mockReturnValue(1);
310
+ mockEAS.attest.mockResolvedValue({
311
+ wait: jest.fn().mockResolvedValue({
312
+ newAttestationUID: mockAttestationUID
313
+ })
314
+ } as any);
315
+
316
+ const { deliveryProof } = await builder.build({
317
+ txId,
318
+ provider: `did:ethr:84532:${testProviderAddress}`,
319
+ consumer: 'did:ethr:84532:0x742d35cc6634c0532925a3b844bc9e7595f0beb0',
320
+ resultData: originalData,
321
+ chainId: 84532,
322
+ kernelAddress
323
+ });
324
+
325
+ // Try to verify with tampered data
326
+ await expect(
327
+ builder.verify(deliveryProof, tamperedData, kernelAddress)
328
+ ).rejects.toThrow('Result hash mismatch');
329
+ });
330
+
331
+ it('should reject delivery proof with non-existent EAS attestation', async () => {
332
+ const resultData = { result: 'success' };
333
+ const txId = '0x' + '1'.repeat(64);
334
+ const mockAttestationUID = '0x' + '2'.repeat(64);
335
+ const kernelAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
336
+
337
+ mockIPFS.add.mockResolvedValue('QmTestCID');
338
+ mockIPFS.pin.mockResolvedValue(undefined);
339
+ mockNonceManager.getNextNonce.mockReturnValue(1);
340
+ mockEAS.attest.mockResolvedValue({
341
+ wait: jest.fn().mockResolvedValue({
342
+ newAttestationUID: mockAttestationUID
343
+ })
344
+ } as any);
345
+
346
+ const { deliveryProof } = await builder.build({
347
+ txId,
348
+ provider: `did:ethr:84532:${testProviderAddress}`,
349
+ consumer: 'did:ethr:84532:0x742d35cc6634c0532925a3b844bc9e7595f0beb0',
350
+ resultData,
351
+ chainId: 84532,
352
+ kernelAddress
353
+ });
354
+
355
+ // Mock attestation not found
356
+ mockEAS.getAttestation.mockResolvedValue(null as any);
357
+
358
+ await expect(
359
+ builder.verify(deliveryProof, resultData, kernelAddress)
360
+ ).rejects.toThrow('Attestation not found on EAS');
361
+ });
362
+
363
+ it('should reject revoked EAS attestation', async () => {
364
+ const resultData = { result: 'success' };
365
+ const txId = '0x' + '1'.repeat(64);
366
+ const mockAttestationUID = '0x' + '2'.repeat(64);
367
+ const kernelAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
368
+
369
+ mockIPFS.add.mockResolvedValue('QmTestCID');
370
+ mockIPFS.pin.mockResolvedValue(undefined);
371
+ mockNonceManager.getNextNonce.mockReturnValue(1);
372
+ mockEAS.attest.mockResolvedValue({
373
+ wait: jest.fn().mockResolvedValue({
374
+ newAttestationUID: mockAttestationUID
375
+ })
376
+ } as any);
377
+
378
+ const { deliveryProof } = await builder.build({
379
+ txId,
380
+ provider: `did:ethr:84532:${testProviderAddress}`,
381
+ consumer: 'did:ethr:84532:0x742d35cc6634c0532925a3b844bc9e7595f0beb0',
382
+ resultData,
383
+ chainId: 84532,
384
+ kernelAddress
385
+ });
386
+
387
+ // Mock revoked attestation
388
+ mockEAS.getAttestation.mockResolvedValue({
389
+ schema: AGIRAILS_DELIVERY_SCHEMA_UID,
390
+ data: '0x',
391
+ revoked: true,
392
+ revocationTime: 1234567890
393
+ } as any);
394
+
395
+ await expect(
396
+ builder.verify(deliveryProof, resultData, kernelAddress)
397
+ ).rejects.toThrow('Attestation was revoked');
398
+ });
399
+ });
400
+
401
+ describe('DeliveryProofBuilder - Signature Operations', () => {
402
+ let builder: DeliveryProofBuilder;
403
+
404
+ beforeEach(() => {
405
+ jest.clearAllMocks();
406
+ builder = new DeliveryProofBuilder(mockIPFS, testSigner, mockNonceManager, mockEAS);
407
+ });
408
+
409
+ it('should sign delivery proof with EIP-712', async () => {
410
+ const mockAttestationUID = '0x' + '2'.repeat(64);
411
+ mockIPFS.add.mockResolvedValue('QmTestCID');
412
+ mockIPFS.pin.mockResolvedValue(undefined);
413
+ mockNonceManager.getNextNonce.mockReturnValue(1);
414
+ mockEAS.attest.mockResolvedValue({
415
+ wait: jest.fn().mockResolvedValue({
416
+ newAttestationUID: mockAttestationUID
417
+ })
418
+ } as any);
419
+
420
+ const params = {
421
+ txId: '0x' + '1'.repeat(64),
422
+ provider: `did:ethr:84532:${testProviderAddress}`,
423
+ consumer: 'did:ethr:84532:0x742d35cc6634c0532925a3b844bc9e7595f0beb0',
424
+ resultData: { result: 'success' },
425
+ chainId: 84532,
426
+ kernelAddress: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
427
+ };
428
+
429
+ const { deliveryProof } = await builder.build(params);
430
+
431
+ // Signature should be 0x-prefixed, 130 characters (65 bytes * 2 hex chars)
432
+ expect(deliveryProof.signature).toMatch(/^0x[a-fA-F0-9]{130}$/);
433
+ });
434
+
435
+ it('should recover correct signer from signature', async () => {
436
+ const mockAttestationUID = '0x' + '2'.repeat(64);
437
+ mockIPFS.add.mockResolvedValue('QmTestCID');
438
+ mockIPFS.pin.mockResolvedValue(undefined);
439
+ mockNonceManager.getNextNonce.mockReturnValue(1);
440
+ mockEAS.attest.mockResolvedValue({
441
+ wait: jest.fn().mockResolvedValue({
442
+ newAttestationUID: mockAttestationUID
443
+ })
444
+ } as any);
445
+
446
+ const params = {
447
+ txId: '0x' + '1'.repeat(64),
448
+ provider: `did:ethr:84532:${testProviderAddress}`,
449
+ consumer: 'did:ethr:84532:0x742d35cc6634c0532925a3b844bc9e7595f0beb0',
450
+ resultData: { result: 'success' },
451
+ chainId: 84532,
452
+ kernelAddress: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
453
+ };
454
+
455
+ const { deliveryProof } = await builder.build(params);
456
+
457
+ // Recover signer by verifying with same data
458
+ mockEAS.getAttestation.mockResolvedValue({
459
+ schema: AGIRAILS_DELIVERY_SCHEMA_UID,
460
+ data: '0x',
461
+ revoked: false
462
+ } as any);
463
+
464
+ const originalSchemaEncoder = jest.requireActual('@ethereum-attestation-service/eas-sdk').SchemaEncoder;
465
+ jest.spyOn(originalSchemaEncoder.prototype, 'decodeData').mockReturnValue([
466
+ { name: 'txId', value: { value: params.txId } },
467
+ { name: 'resultCID', value: { value: deliveryProof.resultCID } },
468
+ { name: 'resultHash', value: { value: deliveryProof.resultHash } },
469
+ { name: 'deliveredAt', value: { value: deliveryProof.deliveredAt } }
470
+ ]);
471
+
472
+ const isValid = await builder.verify(deliveryProof, params.resultData, params.kernelAddress);
473
+ expect(isValid).toBe(true);
474
+ });
475
+
476
+ it('should throw error if signer does not support EIP-712', async () => {
477
+ const invalidSigner = {
478
+ getAddress: jest.fn().mockResolvedValue('0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
479
+ // Missing _signTypedData
480
+ } as any;
481
+
482
+ const builderWithInvalidSigner = new DeliveryProofBuilder(mockIPFS, invalidSigner, mockNonceManager, mockEAS);
483
+
484
+ mockIPFS.add.mockResolvedValue('QmTestCID');
485
+ mockIPFS.pin.mockResolvedValue(undefined);
486
+ mockNonceManager.getNextNonce.mockReturnValue(1);
487
+ mockEAS.attest.mockResolvedValue({
488
+ wait: jest.fn().mockResolvedValue({
489
+ newAttestationUID: '0x' + '2'.repeat(64)
490
+ })
491
+ } as any);
492
+
493
+ const params = {
494
+ txId: '0x' + '1'.repeat(64),
495
+ provider: 'did:ethr:84532:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
496
+ consumer: 'did:ethr:84532:0x742d35cc6634c0532925a3b844bc9e7595f0beb0',
497
+ resultData: { result: 'success' },
498
+ chainId: 84532,
499
+ kernelAddress: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
500
+ };
501
+
502
+ await expect(builderWithInvalidSigner.build(params)).rejects.toThrow(
503
+ 'Signer does not support EIP-712 typed data signing'
504
+ );
505
+ });
506
+ });
507
+
508
+ describe('DeliveryProofBuilder - DID Parsing', () => {
509
+ let builder: DeliveryProofBuilder;
510
+
511
+ beforeEach(() => {
512
+ jest.clearAllMocks();
513
+ builder = new DeliveryProofBuilder(mockIPFS, testSigner, mockNonceManager, mockEAS);
514
+ });
515
+
516
+ it('should extract address from DID with chain ID', async () => {
517
+ const mockAttestationUID = '0x' + '2'.repeat(64);
518
+ mockIPFS.add.mockResolvedValue('QmTestCID');
519
+ mockIPFS.pin.mockResolvedValue(undefined);
520
+ mockNonceManager.getNextNonce.mockReturnValue(1);
521
+ mockEAS.attest.mockResolvedValue({
522
+ wait: jest.fn().mockResolvedValue({
523
+ newAttestationUID: mockAttestationUID
524
+ })
525
+ } as any);
526
+
527
+ const consumerAddress = '0x742d35cc6634c0532925a3b844bc9e7595f0beb0';
528
+ const params = {
529
+ txId: '0x' + '1'.repeat(64),
530
+ provider: `did:ethr:84532:${testProviderAddress}`,
531
+ consumer: `did:ethr:84532:${consumerAddress}`,
532
+ resultData: { result: 'success' },
533
+ chainId: 84532,
534
+ kernelAddress: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
535
+ };
536
+
537
+ await builder.build(params);
538
+
539
+ // Verify EAS attestation was created with correct recipient
540
+ expect(mockEAS.attest).toHaveBeenCalledWith(
541
+ expect.objectContaining({
542
+ data: expect.objectContaining({
543
+ recipient: consumerAddress
544
+ })
545
+ })
546
+ );
547
+ });
548
+
549
+ it('should extract address from DID without chain ID', async () => {
550
+ const mockAttestationUID = '0x' + '2'.repeat(64);
551
+ mockIPFS.add.mockResolvedValue('QmTestCID');
552
+ mockIPFS.pin.mockResolvedValue(undefined);
553
+ mockNonceManager.getNextNonce.mockReturnValue(1);
554
+ mockEAS.attest.mockResolvedValue({
555
+ wait: jest.fn().mockResolvedValue({
556
+ newAttestationUID: mockAttestationUID
557
+ })
558
+ } as any);
559
+
560
+ const consumerAddress = '0x742d35cc6634c0532925a3b844bc9e7595f0beb0';
561
+ const params = {
562
+ txId: '0x' + '1'.repeat(64),
563
+ provider: `did:ethr:${testProviderAddress}`,
564
+ consumer: `did:ethr:${consumerAddress}`,
565
+ resultData: { result: 'success' },
566
+ chainId: 84532,
567
+ kernelAddress: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
568
+ };
569
+
570
+ await builder.build(params);
571
+
572
+ // Verify EAS attestation was created with correct recipient
573
+ expect(mockEAS.attest).toHaveBeenCalledWith(
574
+ expect.objectContaining({
575
+ data: expect.objectContaining({
576
+ recipient: consumerAddress
577
+ })
578
+ })
579
+ );
580
+ });
581
+ });