@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.
- package/README.md +183 -0
- package/dist/ACTPClient.d.ts +52 -0
- package/dist/ACTPClient.d.ts.map +1 -0
- package/dist/ACTPClient.js +120 -0
- package/dist/ACTPClient.js.map +1 -0
- package/dist/abi/ACTPKernel.json +1340 -0
- package/dist/abi/ERC20.json +38 -0
- package/dist/abi/EscrowVault.json +64 -0
- package/dist/builders/DeliveryProofBuilder.d.ts +37 -0
- package/dist/builders/DeliveryProofBuilder.d.ts.map +1 -0
- package/dist/builders/DeliveryProofBuilder.js +165 -0
- package/dist/builders/DeliveryProofBuilder.js.map +1 -0
- package/dist/builders/QuoteBuilder.d.ts +68 -0
- package/dist/builders/QuoteBuilder.d.ts.map +1 -0
- package/dist/builders/QuoteBuilder.js +255 -0
- package/dist/builders/QuoteBuilder.js.map +1 -0
- package/dist/builders/index.d.ts +3 -0
- package/dist/builders/index.d.ts.map +1 -0
- package/dist/builders/index.js +10 -0
- package/dist/builders/index.js.map +1 -0
- package/dist/config/networks.d.ts +27 -0
- package/dist/config/networks.d.ts.map +1 -0
- package/dist/config/networks.js +103 -0
- package/dist/config/networks.js.map +1 -0
- package/dist/errors/index.d.ts +38 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +87 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/ACTPKernel.d.ts +30 -0
- package/dist/protocol/ACTPKernel.d.ts.map +1 -0
- package/dist/protocol/ACTPKernel.js +261 -0
- package/dist/protocol/ACTPKernel.js.map +1 -0
- package/dist/protocol/EASHelper.d.ts +23 -0
- package/dist/protocol/EASHelper.d.ts.map +1 -0
- package/dist/protocol/EASHelper.js +106 -0
- package/dist/protocol/EASHelper.js.map +1 -0
- package/dist/protocol/EscrowVault.d.ts +24 -0
- package/dist/protocol/EscrowVault.d.ts.map +1 -0
- package/dist/protocol/EscrowVault.js +114 -0
- package/dist/protocol/EscrowVault.js.map +1 -0
- package/dist/protocol/EventMonitor.d.ts +18 -0
- package/dist/protocol/EventMonitor.d.ts.map +1 -0
- package/dist/protocol/EventMonitor.js +92 -0
- package/dist/protocol/EventMonitor.js.map +1 -0
- package/dist/protocol/MessageSigner.d.ts +23 -0
- package/dist/protocol/MessageSigner.d.ts.map +1 -0
- package/dist/protocol/MessageSigner.js +178 -0
- package/dist/protocol/MessageSigner.js.map +1 -0
- package/dist/protocol/ProofGenerator.d.ts +22 -0
- package/dist/protocol/ProofGenerator.d.ts.map +1 -0
- package/dist/protocol/ProofGenerator.js +64 -0
- package/dist/protocol/ProofGenerator.js.map +1 -0
- package/dist/protocol/QuoteBuilder.d.ts +2 -0
- package/dist/protocol/QuoteBuilder.d.ts.map +1 -0
- package/dist/protocol/QuoteBuilder.js +7 -0
- package/dist/protocol/QuoteBuilder.js.map +1 -0
- package/dist/types/eip712.d.ts +106 -0
- package/dist/types/eip712.d.ts.map +1 -0
- package/dist/types/eip712.js +84 -0
- package/dist/types/eip712.js.map +1 -0
- package/dist/types/escrow.d.ts +18 -0
- package/dist/types/escrow.d.ts.map +1 -0
- package/dist/types/escrow.js +3 -0
- package/dist/types/escrow.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +22 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/message.d.ts +109 -0
- package/dist/types/message.d.ts.map +1 -0
- package/dist/types/message.js +3 -0
- package/dist/types/message.js.map +1 -0
- package/dist/types/state.d.ts +19 -0
- package/dist/types/state.d.ts.map +1 -0
- package/dist/types/state.js +49 -0
- package/dist/types/state.js.map +1 -0
- package/dist/types/transaction.d.ts +36 -0
- package/dist/types/transaction.d.ts.map +1 -0
- package/dist/types/transaction.js +3 -0
- package/dist/types/transaction.js.map +1 -0
- package/dist/utils/IPFSClient.d.ts +37 -0
- package/dist/utils/IPFSClient.d.ts.map +1 -0
- package/dist/utils/IPFSClient.js +128 -0
- package/dist/utils/IPFSClient.js.map +1 -0
- package/dist/utils/NonceManager.d.ts +34 -0
- package/dist/utils/NonceManager.d.ts.map +1 -0
- package/dist/utils/NonceManager.js +114 -0
- package/dist/utils/NonceManager.js.map +1 -0
- package/dist/utils/ReceivedNonceTracker.d.ts +35 -0
- package/dist/utils/ReceivedNonceTracker.d.ts.map +1 -0
- package/dist/utils/ReceivedNonceTracker.js +196 -0
- package/dist/utils/ReceivedNonceTracker.js.map +1 -0
- package/dist/utils/canonicalJson.d.ts +4 -0
- package/dist/utils/canonicalJson.d.ts.map +1 -0
- package/dist/utils/canonicalJson.js +21 -0
- package/dist/utils/canonicalJson.js.map +1 -0
- package/dist/utils/computeTypeHash.d.ts +3 -0
- package/dist/utils/computeTypeHash.d.ts.map +1 -0
- package/dist/utils/computeTypeHash.js +30 -0
- package/dist/utils/computeTypeHash.js.map +1 -0
- package/dist/utils/validation.d.ts +6 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +46 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +73 -0
- package/src/ACTPClient.ts +276 -0
- package/src/__tests__/ProofGenerator.test.ts +124 -0
- package/src/__tests__/QuoteBuilder.test.ts +516 -0
- package/src/__tests__/StateMachine.test.ts +82 -0
- package/src/__tests__/builders/DeliveryProofBuilder.test.ts +581 -0
- package/src/__tests__/integration/ACTPClient.test.ts +263 -0
- package/src/__tests__/integration.test.ts +289 -0
- package/src/__tests__/protocol/EASHelper.test.ts +472 -0
- package/src/__tests__/protocol/EventMonitor.test.ts +382 -0
- package/src/__tests__/security/ACTPKernel.security.test.ts +1167 -0
- package/src/__tests__/security/EscrowVault.security.test.ts +570 -0
- package/src/__tests__/security/MessageSigner.security.test.ts +286 -0
- package/src/__tests__/security/NonceReplay.security.test.ts +501 -0
- package/src/__tests__/security/validation.security.test.ts +376 -0
- package/src/__tests__/utils/IPFSClient.test.ts +262 -0
- package/src/__tests__/utils/NonceManager.test.ts +205 -0
- package/src/__tests__/utils/canonicalJson.test.ts +153 -0
- package/src/abi/ACTPKernel.json +1340 -0
- package/src/abi/ERC20.json +40 -0
- package/src/abi/EscrowVault.json +66 -0
- package/src/builders/DeliveryProofBuilder.ts +326 -0
- package/src/builders/QuoteBuilder.ts +483 -0
- package/src/builders/index.ts +17 -0
- package/src/config/networks.ts +165 -0
- package/src/errors/index.ts +130 -0
- package/src/index.ts +108 -0
- package/src/protocol/ACTPKernel.ts +625 -0
- package/src/protocol/EASHelper.ts +197 -0
- package/src/protocol/EscrowVault.ts +237 -0
- package/src/protocol/EventMonitor.ts +161 -0
- package/src/protocol/MessageSigner.ts +336 -0
- package/src/protocol/ProofGenerator.ts +119 -0
- package/src/protocol/QuoteBuilder.ts +15 -0
- package/src/types/eip712.ts +175 -0
- package/src/types/escrow.ts +26 -0
- package/src/types/index.ts +10 -0
- package/src/types/message.ts +145 -0
- package/src/types/state.ts +77 -0
- package/src/types/transaction.ts +54 -0
- package/src/utils/IPFSClient.ts +248 -0
- package/src/utils/NonceManager.ts +293 -0
- package/src/utils/ReceivedNonceTracker.ts +397 -0
- package/src/utils/canonicalJson.ts +38 -0
- package/src/utils/computeTypeHash.ts +50 -0
- 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
|
+
});
|