@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,336 @@
|
|
|
1
|
+
import { Signer, ethers, AbiCoder } from 'ethers';
|
|
2
|
+
import { ACTPMessage, DeliveryProof } from '../types';
|
|
3
|
+
import { SignatureVerificationError } from '../errors';
|
|
4
|
+
import {
|
|
5
|
+
EIP712Domain,
|
|
6
|
+
getMessageTypes,
|
|
7
|
+
QuoteRequestData,
|
|
8
|
+
QuoteResponseData,
|
|
9
|
+
DeliveryProofData,
|
|
10
|
+
deliveryProofDataFromProof
|
|
11
|
+
} from '../types/eip712';
|
|
12
|
+
import { IReceivedNonceTracker } from '../utils/ReceivedNonceTracker';
|
|
13
|
+
|
|
14
|
+
// Legacy generic ACTP message types moved to types/eip712.ts
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* TypeScript interface for ethers v6 Signer with signTypedData method
|
|
18
|
+
*
|
|
19
|
+
* Note: ethers v6 uses signTypedData() (without underscore), not _signTypedData().
|
|
20
|
+
* This interface properly types the method for v6 compatibility.
|
|
21
|
+
*/
|
|
22
|
+
interface SignerWithTypedData extends Signer {
|
|
23
|
+
signTypedData(
|
|
24
|
+
domain: EIP712Domain,
|
|
25
|
+
types: Record<string, any>,
|
|
26
|
+
value: Record<string, any>
|
|
27
|
+
): Promise<string>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* MessageSigner - Cryptographic signing for ACTP messages with EIP-712
|
|
32
|
+
* Reference: Yellow Paper §11.4.2
|
|
33
|
+
*
|
|
34
|
+
* V4 Security Enhancement: Optional nonce replay protection via ReceivedNonceTracker
|
|
35
|
+
*/
|
|
36
|
+
export class MessageSigner {
|
|
37
|
+
private domain: EIP712Domain | null = null;
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
private readonly signer: Signer,
|
|
41
|
+
private readonly nonceTracker?: IReceivedNonceTracker
|
|
42
|
+
) {}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Initialize EIP-712 domain (must be called before signing)
|
|
46
|
+
* @param kernelAddress - Address of ACTP Kernel contract
|
|
47
|
+
* @param chainId - Optional chainId (defaults to signer's chainId or 84532 for Base Sepolia)
|
|
48
|
+
*/
|
|
49
|
+
async initDomain(kernelAddress: string, chainId?: number): Promise<void> {
|
|
50
|
+
let resolvedChainId: number;
|
|
51
|
+
|
|
52
|
+
if (chainId !== undefined) {
|
|
53
|
+
resolvedChainId = chainId;
|
|
54
|
+
} else {
|
|
55
|
+
try {
|
|
56
|
+
// ethers v6: signer.provider might be null, check first
|
|
57
|
+
if (this.signer.provider) {
|
|
58
|
+
const network = await this.signer.provider.getNetwork();
|
|
59
|
+
resolvedChainId = Number(network.chainId);
|
|
60
|
+
} else {
|
|
61
|
+
// Fallback to Base Sepolia for testing without provider
|
|
62
|
+
resolvedChainId = 84532;
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
// Fallback to Base Sepolia for testing without provider
|
|
66
|
+
resolvedChainId = 84532;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.domain = {
|
|
71
|
+
name: 'ACTP',
|
|
72
|
+
version: '1.0',
|
|
73
|
+
chainId: resolvedChainId,
|
|
74
|
+
verifyingContract: kernelAddress
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Sign ACTP message using EIP-712 typed data
|
|
80
|
+
* Uses ECDSA (secp256k1) with domain separation per Yellow Paper §11.4.2
|
|
81
|
+
*
|
|
82
|
+
* Generic ACTPMessage format (backward compatible).
|
|
83
|
+
* For strict typed AIP messages, use signQuoteRequest/signQuoteResponse/signDeliveryProof
|
|
84
|
+
*/
|
|
85
|
+
async signMessage(message: ACTPMessage): Promise<string> {
|
|
86
|
+
if (!this.domain) {
|
|
87
|
+
throw new Error('Domain not initialized. Call initDomain() first.');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const { type, version, from, to, timestamp, nonce, signature, ...payload } = message;
|
|
91
|
+
|
|
92
|
+
// Generic ACTPMessage with payload encoding (backward compatible)
|
|
93
|
+
const abiCoder = AbiCoder.defaultAbiCoder();
|
|
94
|
+
const payloadBytes = abiCoder.encode(
|
|
95
|
+
['string'],
|
|
96
|
+
[this.canonicalizePayload(payload)]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const typedMessage = {
|
|
100
|
+
type,
|
|
101
|
+
version,
|
|
102
|
+
from,
|
|
103
|
+
to,
|
|
104
|
+
timestamp,
|
|
105
|
+
nonce,
|
|
106
|
+
payload: payloadBytes
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Use generic ACTPMessage types
|
|
110
|
+
const messageTypes = getMessageTypes('default');
|
|
111
|
+
|
|
112
|
+
// Sign using EIP-712 (ethers v6 API)
|
|
113
|
+
const signer = this.signer as SignerWithTypedData;
|
|
114
|
+
const sig = await signer.signTypedData(this.domain, messageTypes, typedMessage);
|
|
115
|
+
|
|
116
|
+
return sig;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Sign typed QuoteRequest message
|
|
121
|
+
*/
|
|
122
|
+
async signQuoteRequest(data: QuoteRequestData): Promise<string> {
|
|
123
|
+
if (!this.domain) {
|
|
124
|
+
throw new Error('Domain not initialized. Call initDomain() first.');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const messageTypes = getMessageTypes('quote.request');
|
|
128
|
+
const signer = this.signer as SignerWithTypedData;
|
|
129
|
+
return await signer.signTypedData(this.domain, messageTypes, data);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Sign typed QuoteResponse message
|
|
134
|
+
*/
|
|
135
|
+
async signQuoteResponse(data: QuoteResponseData): Promise<string> {
|
|
136
|
+
if (!this.domain) {
|
|
137
|
+
throw new Error('Domain not initialized. Call initDomain() first.');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const messageTypes = getMessageTypes('quote.response');
|
|
141
|
+
const signer = this.signer as SignerWithTypedData;
|
|
142
|
+
return await signer.signTypedData(this.domain, messageTypes, data);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Sign typed DeliveryProof message
|
|
147
|
+
*/
|
|
148
|
+
async signDeliveryProof(data: DeliveryProofData): Promise<string> {
|
|
149
|
+
if (!this.domain) {
|
|
150
|
+
throw new Error('Domain not initialized. Call initDomain() first.');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const messageTypes = getMessageTypes('delivery.proof');
|
|
154
|
+
const signer = this.signer as SignerWithTypedData;
|
|
155
|
+
return await signer.signTypedData(this.domain, messageTypes, data);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Convenience helper to sign a DeliveryProof generated by ProofGenerator
|
|
160
|
+
*/
|
|
161
|
+
async signGeneratedDeliveryProof(proof: DeliveryProof): Promise<string> {
|
|
162
|
+
const typedData = deliveryProofDataFromProof(proof);
|
|
163
|
+
return await this.signDeliveryProof(typedData);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Verify message signature using EIP-712
|
|
168
|
+
* Uses generic ACTPMessage types (backward compatible)
|
|
169
|
+
*
|
|
170
|
+
* V4 Security: If nonceTracker is configured, validates nonce for replay protection
|
|
171
|
+
*/
|
|
172
|
+
async verifySignature(message: ACTPMessage, signature: string): Promise<boolean> {
|
|
173
|
+
if (!this.domain) {
|
|
174
|
+
throw new Error('Domain not initialized. Call initDomain() first.');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const { type, version, from, to, timestamp, nonce, signature: _, ...payload } = message;
|
|
178
|
+
|
|
179
|
+
const abiCoder = AbiCoder.defaultAbiCoder();
|
|
180
|
+
const payloadBytes = abiCoder.encode(
|
|
181
|
+
['string'],
|
|
182
|
+
[this.canonicalizePayload(payload)]
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const typedMessage = {
|
|
186
|
+
type,
|
|
187
|
+
version,
|
|
188
|
+
from,
|
|
189
|
+
to,
|
|
190
|
+
timestamp,
|
|
191
|
+
nonce,
|
|
192
|
+
payload: payloadBytes
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Use generic ACTPMessage types (backward compatible)
|
|
196
|
+
const messageTypes = getMessageTypes('default');
|
|
197
|
+
const recoveredAddress = ethers.verifyTypedData(
|
|
198
|
+
this.domain,
|
|
199
|
+
messageTypes,
|
|
200
|
+
typedMessage,
|
|
201
|
+
signature
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const expectedAddress = this.didToAddress(from);
|
|
205
|
+
|
|
206
|
+
// Verify signature matches sender
|
|
207
|
+
if (recoveredAddress.toLowerCase() !== expectedAddress.toLowerCase()) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// V4 Security: Validate nonce for replay protection (if tracker configured)
|
|
212
|
+
if (this.nonceTracker) {
|
|
213
|
+
const nonceValidation = this.nonceTracker.validateAndRecord(from, type, nonce);
|
|
214
|
+
if (!nonceValidation.valid) {
|
|
215
|
+
// Nonce replay detected - return false
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Verify signature and throw if invalid
|
|
225
|
+
* V4 Security: Throws specific error for nonce replay detection
|
|
226
|
+
*/
|
|
227
|
+
async verifySignatureOrThrow(message: ACTPMessage, signature: string): Promise<void> {
|
|
228
|
+
if (!this.domain) {
|
|
229
|
+
throw new Error('Domain not initialized');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const { type, version, from, to, timestamp, nonce, signature: _, ...payload } = message;
|
|
233
|
+
|
|
234
|
+
const abiCoder = AbiCoder.defaultAbiCoder();
|
|
235
|
+
const payloadBytes = abiCoder.encode(
|
|
236
|
+
['string'],
|
|
237
|
+
[this.canonicalizePayload(payload)]
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const typedMessage = { type, version, from, to, timestamp, nonce, payload: payloadBytes };
|
|
241
|
+
|
|
242
|
+
const messageTypes = getMessageTypes('default');
|
|
243
|
+
const recoveredAddress = ethers.verifyTypedData(
|
|
244
|
+
this.domain,
|
|
245
|
+
messageTypes,
|
|
246
|
+
typedMessage,
|
|
247
|
+
signature
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const expectedAddress = this.didToAddress(from);
|
|
251
|
+
|
|
252
|
+
// Check signature validity first
|
|
253
|
+
if (recoveredAddress.toLowerCase() !== expectedAddress.toLowerCase()) {
|
|
254
|
+
throw new SignatureVerificationError(expectedAddress, recoveredAddress);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// V4 Security: Validate nonce for replay protection (if tracker configured)
|
|
258
|
+
if (this.nonceTracker) {
|
|
259
|
+
const nonceValidation = this.nonceTracker.validateAndRecord(from, type, nonce);
|
|
260
|
+
if (!nonceValidation.valid) {
|
|
261
|
+
// Throw specific error for nonce replay
|
|
262
|
+
throw new Error(
|
|
263
|
+
`Nonce replay attack detected: ${nonceValidation.reason}. ` +
|
|
264
|
+
`Received nonce: ${nonceValidation.receivedNonce}. ` +
|
|
265
|
+
(nonceValidation.expectedMinimum ? `Expected minimum: ${nonceValidation.expectedMinimum}` : '')
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Canonicalize payload to deterministic string (recursively sorted keys)
|
|
273
|
+
* Prevents JSON serialization ambiguity across different JS runtimes
|
|
274
|
+
* Recursively handles nested objects and arrays
|
|
275
|
+
*/
|
|
276
|
+
private canonicalizePayload(payload: Record<string, any>): string {
|
|
277
|
+
return JSON.stringify(this.recursiveSort(payload));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Recursively sort object keys for deterministic JSON encoding
|
|
282
|
+
*/
|
|
283
|
+
private recursiveSort(obj: any): any {
|
|
284
|
+
if (obj === null || obj === undefined) {
|
|
285
|
+
return obj;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Handle arrays: recursively sort each element
|
|
289
|
+
if (Array.isArray(obj)) {
|
|
290
|
+
return obj.map((item) => this.recursiveSort(item));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Handle objects: sort keys and recursively sort values
|
|
294
|
+
if (typeof obj === 'object' && obj.constructor === Object) {
|
|
295
|
+
const sortedKeys = Object.keys(obj).sort();
|
|
296
|
+
const canonical: Record<string, any> = {};
|
|
297
|
+
|
|
298
|
+
for (const key of sortedKeys) {
|
|
299
|
+
canonical[key] = this.recursiveSort(obj[key]);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return canonical;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Primitives (string, number, boolean)
|
|
306
|
+
return obj;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Convert DID to Ethereum address
|
|
311
|
+
* MVP: Simple did:ethr → address conversion
|
|
312
|
+
*/
|
|
313
|
+
private didToAddress(did: string): string {
|
|
314
|
+
if (did.startsWith('did:ethr:')) {
|
|
315
|
+
return did.replace('did:ethr:', '');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// If already an address, return as-is
|
|
319
|
+
if (ethers.isAddress(did)) {
|
|
320
|
+
return did;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
throw new Error(`Invalid DID format: ${did}`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Convert Ethereum address to DID
|
|
328
|
+
*/
|
|
329
|
+
addressToDID(address: string): string {
|
|
330
|
+
if (!ethers.isAddress(address)) {
|
|
331
|
+
throw new Error(`Invalid Ethereum address: ${address}`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return `did:ethr:${address}`;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { keccak256, toUtf8Bytes, AbiCoder, BytesLike } from 'ethers';
|
|
2
|
+
import { DeliveryProof } from '../types';
|
|
3
|
+
import { DeliveryProofData, deliveryProofDataFromProof } from '../types/eip712';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ProofGenerator - Content hashing and delivery proofs
|
|
7
|
+
* Reference: Yellow Paper §11.4.1
|
|
8
|
+
*/
|
|
9
|
+
export class ProofGenerator {
|
|
10
|
+
/**
|
|
11
|
+
* Hash deliverable content
|
|
12
|
+
* Uses Keccak256 per Yellow Paper §11.4.1
|
|
13
|
+
*/
|
|
14
|
+
hashContent(content: string | Buffer): string {
|
|
15
|
+
const buffer = typeof content === 'string' ? toUtf8Bytes(content) : content;
|
|
16
|
+
|
|
17
|
+
return keccak256(buffer);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generate delivery proof (AIP-4)
|
|
22
|
+
* Reference: Yellow Paper §8.2
|
|
23
|
+
* Complete schema with type field for AIP compliance
|
|
24
|
+
* Computed fields (size, mimeType) cannot be overwritten
|
|
25
|
+
*/
|
|
26
|
+
generateDeliveryProof(params: {
|
|
27
|
+
txId: string;
|
|
28
|
+
deliverable: string | Buffer;
|
|
29
|
+
deliveryUrl?: string;
|
|
30
|
+
metadata?: Record<string, any>;
|
|
31
|
+
}): DeliveryProof {
|
|
32
|
+
const { txId, deliverable, deliveryUrl, metadata = {} } = params;
|
|
33
|
+
|
|
34
|
+
const contentHash = this.hashContent(deliverable);
|
|
35
|
+
const size =
|
|
36
|
+
typeof deliverable === 'string'
|
|
37
|
+
? Buffer.from(deliverable).length
|
|
38
|
+
: deliverable.length;
|
|
39
|
+
|
|
40
|
+
// Spread user metadata first, then enforce computed fields
|
|
41
|
+
// This prevents users from spoofing size/mimeType
|
|
42
|
+
const { size: _ignoredSize, mimeType: _ignoredMimeType, ...userMetadata } = metadata;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
type: 'delivery.proof', // Required per AIP-4
|
|
46
|
+
txId,
|
|
47
|
+
contentHash,
|
|
48
|
+
timestamp: Date.now(),
|
|
49
|
+
deliveryUrl, // Optional: IPFS/Arweave link
|
|
50
|
+
metadata: {
|
|
51
|
+
...userMetadata, // User-supplied fields (excluding reserved)
|
|
52
|
+
size, // Enforced: computed from deliverable
|
|
53
|
+
mimeType: metadata.mimeType || 'application/octet-stream' // Enforced with fallback
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Convert a generated delivery proof into typed EIP-712 data
|
|
60
|
+
*/
|
|
61
|
+
toDeliveryProofTypedData(proof: DeliveryProof): DeliveryProofData {
|
|
62
|
+
return deliveryProofDataFromProof(proof);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Encode proof for on-chain submission
|
|
67
|
+
*/
|
|
68
|
+
encodeProof(proof: DeliveryProof): BytesLike {
|
|
69
|
+
const abiCoder = AbiCoder.defaultAbiCoder();
|
|
70
|
+
return abiCoder.encode(
|
|
71
|
+
['bytes32', 'bytes32', 'uint256'],
|
|
72
|
+
[proof.txId, proof.contentHash, proof.timestamp]
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Decode proof from on-chain data
|
|
78
|
+
*/
|
|
79
|
+
decodeProof(proofData: BytesLike): {
|
|
80
|
+
txId: string;
|
|
81
|
+
contentHash: string;
|
|
82
|
+
timestamp: number;
|
|
83
|
+
} {
|
|
84
|
+
const abiCoder = AbiCoder.defaultAbiCoder();
|
|
85
|
+
const [txId, contentHash, timestamp] = abiCoder.decode(
|
|
86
|
+
['bytes32', 'bytes32', 'uint256'],
|
|
87
|
+
proofData
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
txId,
|
|
92
|
+
contentHash,
|
|
93
|
+
timestamp: Number(timestamp)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Verify deliverable matches expected hash
|
|
99
|
+
*/
|
|
100
|
+
verifyDeliverable(deliverable: string | Buffer, expectedHash: string): boolean {
|
|
101
|
+
const actualHash = this.hashContent(deliverable);
|
|
102
|
+
return actualHash.toLowerCase() === expectedHash.toLowerCase();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Generate content hash from URL (for IPFS/Arweave)
|
|
107
|
+
*/
|
|
108
|
+
async hashFromUrl(url: string): Promise<string> {
|
|
109
|
+
// In browser/Node.js environment with fetch
|
|
110
|
+
try {
|
|
111
|
+
const response = await fetch(url);
|
|
112
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
113
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
114
|
+
return this.hashContent(buffer);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
throw new Error(`Failed to fetch content from ${url}: ${error}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QuoteBuilder - AIP-2 Price Quote Construction
|
|
3
|
+
*
|
|
4
|
+
* This module re-exports the full QuoteBuilder implementation from builders/
|
|
5
|
+
* Reference: AIP-2 §6.1
|
|
6
|
+
*
|
|
7
|
+
* @deprecated Import from builders/QuoteBuilder directly
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
QuoteBuilder,
|
|
12
|
+
QuoteMessage,
|
|
13
|
+
QuoteParams,
|
|
14
|
+
AIP2QuoteTypes
|
|
15
|
+
} from '../builders/QuoteBuilder';
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { DeliveryProof } from './message';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EIP-712 Type Definitions for ACTP Messages
|
|
5
|
+
* Reference: Yellow Paper §11.4.2
|
|
6
|
+
*
|
|
7
|
+
* Each AIP message has explicit typed data for cross-language compatibility
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* EIP-712 Domain for ACTP
|
|
12
|
+
*/
|
|
13
|
+
export interface EIP712Domain {
|
|
14
|
+
name: string;
|
|
15
|
+
version: string;
|
|
16
|
+
chainId: number;
|
|
17
|
+
verifyingContract: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* QuoteRequest (AIP-2)
|
|
22
|
+
* Reference: Yellow Paper §4.2
|
|
23
|
+
*/
|
|
24
|
+
export const QuoteRequestTypes = {
|
|
25
|
+
QuoteRequest: [
|
|
26
|
+
{ name: 'from', type: 'string' }, // DID
|
|
27
|
+
{ name: 'to', type: 'string' }, // DID
|
|
28
|
+
{ name: 'timestamp', type: 'uint256' },
|
|
29
|
+
{ name: 'nonce', type: 'bytes32' },
|
|
30
|
+
{ name: 'serviceType', type: 'string' },
|
|
31
|
+
{ name: 'requirements', type: 'string' },
|
|
32
|
+
{ name: 'deadline', type: 'uint256' },
|
|
33
|
+
{ name: 'disputeWindow', type: 'uint256' }
|
|
34
|
+
]
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export interface QuoteRequestData {
|
|
38
|
+
from: string;
|
|
39
|
+
to: string;
|
|
40
|
+
timestamp: number;
|
|
41
|
+
nonce: string;
|
|
42
|
+
serviceType: string;
|
|
43
|
+
requirements: string;
|
|
44
|
+
deadline: number;
|
|
45
|
+
disputeWindow: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* QuoteResponse (AIP-2)
|
|
50
|
+
* Reference: Yellow Paper §4.2
|
|
51
|
+
*/
|
|
52
|
+
export const QuoteResponseTypes = {
|
|
53
|
+
QuoteResponse: [
|
|
54
|
+
{ name: 'from', type: 'string' },
|
|
55
|
+
{ name: 'to', type: 'string' },
|
|
56
|
+
{ name: 'timestamp', type: 'uint256' },
|
|
57
|
+
{ name: 'nonce', type: 'bytes32' },
|
|
58
|
+
{ name: 'requestId', type: 'bytes32' },
|
|
59
|
+
{ name: 'price', type: 'uint256' },
|
|
60
|
+
{ name: 'currency', type: 'address' },
|
|
61
|
+
{ name: 'deliveryTime', type: 'uint256' },
|
|
62
|
+
{ name: 'terms', type: 'string' }
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export interface QuoteResponseData {
|
|
67
|
+
from: string;
|
|
68
|
+
to: string;
|
|
69
|
+
timestamp: number;
|
|
70
|
+
nonce: string;
|
|
71
|
+
requestId: string;
|
|
72
|
+
price: string; // BigNumber as string
|
|
73
|
+
currency: string;
|
|
74
|
+
deliveryTime: number;
|
|
75
|
+
terms: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* DeliveryProof (AIP-4) - DEPRECATED
|
|
80
|
+
* @deprecated Use AIP4DeliveryProofTypes instead (AIP-4 v1.1)
|
|
81
|
+
*/
|
|
82
|
+
export const DeliveryProofTypes = {
|
|
83
|
+
DeliveryProof: [
|
|
84
|
+
{ name: 'txId', type: 'bytes32' },
|
|
85
|
+
{ name: 'contentHash', type: 'bytes32' },
|
|
86
|
+
{ name: 'timestamp', type: 'uint256' },
|
|
87
|
+
{ name: 'deliveryUrl', type: 'string' },
|
|
88
|
+
{ name: 'size', type: 'uint256' },
|
|
89
|
+
{ name: 'mimeType', type: 'string' }
|
|
90
|
+
]
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export interface DeliveryProofData {
|
|
94
|
+
txId: string;
|
|
95
|
+
contentHash: string;
|
|
96
|
+
timestamp: number;
|
|
97
|
+
deliveryUrl: string;
|
|
98
|
+
size: number;
|
|
99
|
+
mimeType: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function deliveryProofDataFromProof(proof: DeliveryProof): DeliveryProofData {
|
|
103
|
+
return {
|
|
104
|
+
txId: proof.txId,
|
|
105
|
+
contentHash: proof.contentHash,
|
|
106
|
+
timestamp: proof.timestamp,
|
|
107
|
+
deliveryUrl: proof.deliveryUrl || '',
|
|
108
|
+
size: proof.metadata.size,
|
|
109
|
+
mimeType: proof.metadata.mimeType
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* AIP-4 Delivery Proof (v1.1)
|
|
115
|
+
* Reference: AIP-4 §3.3
|
|
116
|
+
*/
|
|
117
|
+
export const AIP4DeliveryProofTypes = {
|
|
118
|
+
DeliveryProof: [
|
|
119
|
+
{ name: 'txId', type: 'bytes32' },
|
|
120
|
+
{ name: 'provider', type: 'string' },
|
|
121
|
+
{ name: 'consumer', type: 'string' },
|
|
122
|
+
{ name: 'resultCID', type: 'string' },
|
|
123
|
+
{ name: 'resultHash', type: 'bytes32' },
|
|
124
|
+
{ name: 'easAttestationUID', type: 'bytes32' },
|
|
125
|
+
{ name: 'deliveredAt', type: 'uint256' },
|
|
126
|
+
{ name: 'chainId', type: 'uint256' },
|
|
127
|
+
{ name: 'nonce', type: 'uint256' }
|
|
128
|
+
]
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export interface AIP4DeliveryProofData {
|
|
132
|
+
txId: string;
|
|
133
|
+
provider: string;
|
|
134
|
+
consumer: string;
|
|
135
|
+
resultCID: string;
|
|
136
|
+
resultHash: string;
|
|
137
|
+
easAttestationUID: string;
|
|
138
|
+
deliveredAt: number;
|
|
139
|
+
chainId: number;
|
|
140
|
+
nonce: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Generic ACTPMessage (fallback for custom AIPs)
|
|
145
|
+
*/
|
|
146
|
+
export const ACTPMessageTypes = {
|
|
147
|
+
ACTPMessage: [
|
|
148
|
+
{ name: 'type', type: 'string' },
|
|
149
|
+
{ name: 'version', type: 'string' },
|
|
150
|
+
{ name: 'from', type: 'string' },
|
|
151
|
+
{ name: 'to', type: 'string' },
|
|
152
|
+
{ name: 'timestamp', type: 'uint256' },
|
|
153
|
+
{ name: 'nonce', type: 'bytes32' },
|
|
154
|
+
{ name: 'payload', type: 'bytes' }
|
|
155
|
+
]
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Message type registry
|
|
160
|
+
*/
|
|
161
|
+
export const MESSAGE_TYPES = {
|
|
162
|
+
'quote.request': QuoteRequestTypes,
|
|
163
|
+
'quote.response': QuoteResponseTypes,
|
|
164
|
+
'delivery.proof': DeliveryProofTypes,
|
|
165
|
+
// Fallback for custom/future AIPs
|
|
166
|
+
default: ACTPMessageTypes
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get EIP-712 types for message type
|
|
171
|
+
*/
|
|
172
|
+
export function getMessageTypes(messageType: string): Record<string, any> {
|
|
173
|
+
return MESSAGE_TYPES[messageType as keyof typeof MESSAGE_TYPES] || MESSAGE_TYPES.default;
|
|
174
|
+
}
|
|
175
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escrow creation parameters
|
|
3
|
+
*/
|
|
4
|
+
export interface CreateEscrowParams {
|
|
5
|
+
kernelAddress: string;
|
|
6
|
+
txId: string;
|
|
7
|
+
token: string;
|
|
8
|
+
amount: bigint;
|
|
9
|
+
beneficiary: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Escrow state
|
|
14
|
+
*/
|
|
15
|
+
export interface Escrow {
|
|
16
|
+
escrowId: string;
|
|
17
|
+
kernel: string;
|
|
18
|
+
txId: string;
|
|
19
|
+
token: string;
|
|
20
|
+
amount: bigint;
|
|
21
|
+
beneficiary: string;
|
|
22
|
+
createdAt: number;
|
|
23
|
+
released: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|