@agirails/sdk 2.5.2 → 2.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/dist/ACTPClient.d.ts +18 -0
  2. package/dist/ACTPClient.d.ts.map +1 -1
  3. package/dist/ACTPClient.js +67 -22
  4. package/dist/ACTPClient.js.map +1 -1
  5. package/dist/adapters/BasicAdapter.d.ts +12 -0
  6. package/dist/adapters/BasicAdapter.d.ts.map +1 -1
  7. package/dist/adapters/BasicAdapter.js +30 -4
  8. package/dist/adapters/BasicAdapter.js.map +1 -1
  9. package/dist/adapters/StandardAdapter.d.ts +20 -3
  10. package/dist/adapters/StandardAdapter.d.ts.map +1 -1
  11. package/dist/adapters/StandardAdapter.js +45 -11
  12. package/dist/adapters/StandardAdapter.js.map +1 -1
  13. package/dist/cli/commands/publish.js +16 -4
  14. package/dist/cli/commands/publish.js.map +1 -1
  15. package/dist/cli/commands/register.js +16 -4
  16. package/dist/cli/commands/register.js.map +1 -1
  17. package/dist/cli/commands/tx.js +31 -3
  18. package/dist/cli/commands/tx.js.map +1 -1
  19. package/dist/cli/utils/client.d.ts.map +1 -1
  20. package/dist/cli/utils/client.js +1 -0
  21. package/dist/cli/utils/client.js.map +1 -1
  22. package/dist/config/networks.d.ts +2 -2
  23. package/dist/config/networks.d.ts.map +1 -1
  24. package/dist/config/networks.js +27 -22
  25. package/dist/config/networks.js.map +1 -1
  26. package/dist/level0/request.d.ts.map +1 -1
  27. package/dist/level0/request.js +2 -1
  28. package/dist/level0/request.js.map +1 -1
  29. package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
  30. package/dist/runtime/BlockchainRuntime.js +11 -5
  31. package/dist/runtime/BlockchainRuntime.js.map +1 -1
  32. package/dist/runtime/MockStateManager.d.ts.map +1 -1
  33. package/dist/runtime/MockStateManager.js +2 -1
  34. package/dist/runtime/MockStateManager.js.map +1 -1
  35. package/dist/utils/IPFSClient.d.ts +3 -1
  36. package/dist/utils/IPFSClient.d.ts.map +1 -1
  37. package/dist/utils/IPFSClient.js +27 -7
  38. package/dist/utils/IPFSClient.js.map +1 -1
  39. package/dist/wallet/AutoWalletProvider.d.ts.map +1 -1
  40. package/dist/wallet/AutoWalletProvider.js +52 -18
  41. package/dist/wallet/AutoWalletProvider.js.map +1 -1
  42. package/dist/wallet/SmartWalletRouter.d.ts +116 -0
  43. package/dist/wallet/SmartWalletRouter.d.ts.map +1 -0
  44. package/dist/wallet/SmartWalletRouter.js +212 -0
  45. package/dist/wallet/SmartWalletRouter.js.map +1 -0
  46. package/dist/wallet/aa/DualNonceManager.d.ts +19 -0
  47. package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
  48. package/dist/wallet/aa/DualNonceManager.js +100 -5
  49. package/dist/wallet/aa/DualNonceManager.js.map +1 -1
  50. package/package.json +3 -6
  51. package/src/ACTPClient.ts +0 -1579
  52. package/src/abi/ACTPKernel.json +0 -1356
  53. package/src/abi/AgentRegistry.json +0 -915
  54. package/src/abi/ERC20.json +0 -40
  55. package/src/abi/EscrowVault.json +0 -134
  56. package/src/abi/IdentityRegistry.json +0 -316
  57. package/src/adapters/AdapterRegistry.ts +0 -173
  58. package/src/adapters/AdapterRouter.ts +0 -416
  59. package/src/adapters/BaseAdapter.ts +0 -498
  60. package/src/adapters/BasicAdapter.ts +0 -514
  61. package/src/adapters/IAdapter.ts +0 -292
  62. package/src/adapters/StandardAdapter.ts +0 -555
  63. package/src/adapters/X402Adapter.ts +0 -731
  64. package/src/adapters/index.ts +0 -60
  65. package/src/builders/DeliveryProofBuilder.ts +0 -327
  66. package/src/builders/QuoteBuilder.ts +0 -483
  67. package/src/builders/index.ts +0 -17
  68. package/src/cli/commands/balance.ts +0 -110
  69. package/src/cli/commands/batch.ts +0 -487
  70. package/src/cli/commands/config.ts +0 -231
  71. package/src/cli/commands/deploy-check.ts +0 -364
  72. package/src/cli/commands/deploy-env.ts +0 -120
  73. package/src/cli/commands/diff.ts +0 -141
  74. package/src/cli/commands/init.ts +0 -469
  75. package/src/cli/commands/mint.ts +0 -116
  76. package/src/cli/commands/pay.ts +0 -113
  77. package/src/cli/commands/publish.ts +0 -475
  78. package/src/cli/commands/pull.ts +0 -124
  79. package/src/cli/commands/register.ts +0 -247
  80. package/src/cli/commands/simulate.ts +0 -345
  81. package/src/cli/commands/time.ts +0 -302
  82. package/src/cli/commands/tx.ts +0 -448
  83. package/src/cli/commands/watch.ts +0 -211
  84. package/src/cli/index.ts +0 -134
  85. package/src/cli/utils/client.ts +0 -251
  86. package/src/cli/utils/config.ts +0 -389
  87. package/src/cli/utils/output.ts +0 -465
  88. package/src/cli/utils/wallet.ts +0 -109
  89. package/src/config/agirailsmd.ts +0 -262
  90. package/src/config/networks.ts +0 -275
  91. package/src/config/pendingPublish.ts +0 -237
  92. package/src/config/publishPipeline.ts +0 -359
  93. package/src/config/syncOperations.ts +0 -279
  94. package/src/erc8004/ERC8004Bridge.ts +0 -462
  95. package/src/erc8004/ReputationReporter.ts +0 -468
  96. package/src/erc8004/index.ts +0 -61
  97. package/src/errors/index.ts +0 -427
  98. package/src/index.ts +0 -364
  99. package/src/level0/Provider.ts +0 -117
  100. package/src/level0/ServiceDirectory.ts +0 -131
  101. package/src/level0/index.ts +0 -10
  102. package/src/level0/provide.ts +0 -132
  103. package/src/level0/request.ts +0 -432
  104. package/src/level1/Agent.ts +0 -1426
  105. package/src/level1/index.ts +0 -10
  106. package/src/level1/pricing/PriceCalculator.ts +0 -255
  107. package/src/level1/pricing/PricingStrategy.ts +0 -198
  108. package/src/level1/types/Job.ts +0 -179
  109. package/src/level1/types/Options.ts +0 -291
  110. package/src/level1/types/index.ts +0 -8
  111. package/src/protocol/ACTPKernel.ts +0 -808
  112. package/src/protocol/AgentRegistry.ts +0 -559
  113. package/src/protocol/DIDManager.ts +0 -629
  114. package/src/protocol/DIDResolver.ts +0 -554
  115. package/src/protocol/EASHelper.ts +0 -378
  116. package/src/protocol/EscrowVault.ts +0 -255
  117. package/src/protocol/EventMonitor.ts +0 -204
  118. package/src/protocol/MessageSigner.ts +0 -510
  119. package/src/protocol/ProofGenerator.ts +0 -339
  120. package/src/protocol/QuoteBuilder.ts +0 -15
  121. package/src/registry/AgentRegistryClient.ts +0 -202
  122. package/src/runtime/BlockchainRuntime.ts +0 -1015
  123. package/src/runtime/IACTPRuntime.ts +0 -306
  124. package/src/runtime/MockRuntime.ts +0 -1298
  125. package/src/runtime/MockStateManager.ts +0 -576
  126. package/src/runtime/index.ts +0 -25
  127. package/src/runtime/types/MockState.ts +0 -237
  128. package/src/storage/ArchiveBundleBuilder.ts +0 -561
  129. package/src/storage/ArweaveClient.ts +0 -946
  130. package/src/storage/FilebaseClient.ts +0 -790
  131. package/src/storage/index.ts +0 -96
  132. package/src/storage/types.ts +0 -348
  133. package/src/types/adapter.ts +0 -310
  134. package/src/types/agent.ts +0 -79
  135. package/src/types/did.ts +0 -223
  136. package/src/types/eip712.ts +0 -175
  137. package/src/types/erc8004.ts +0 -293
  138. package/src/types/escrow.ts +0 -27
  139. package/src/types/index.ts +0 -17
  140. package/src/types/message.ts +0 -145
  141. package/src/types/state.ts +0 -87
  142. package/src/types/transaction.ts +0 -69
  143. package/src/types/x402.ts +0 -251
  144. package/src/utils/ErrorRecoveryGuide.ts +0 -676
  145. package/src/utils/Helpers.ts +0 -688
  146. package/src/utils/IPFSClient.ts +0 -368
  147. package/src/utils/Logger.ts +0 -484
  148. package/src/utils/NonceManager.ts +0 -591
  149. package/src/utils/RateLimiter.ts +0 -534
  150. package/src/utils/ReceivedNonceTracker.ts +0 -567
  151. package/src/utils/SDKLifecycle.ts +0 -416
  152. package/src/utils/SecureNonce.ts +0 -78
  153. package/src/utils/Semaphore.ts +0 -276
  154. package/src/utils/UsedAttestationTracker.ts +0 -385
  155. package/src/utils/canonicalJson.ts +0 -38
  156. package/src/utils/circuitBreaker.ts +0 -324
  157. package/src/utils/computeTypeHash.ts +0 -48
  158. package/src/utils/fsSafe.ts +0 -80
  159. package/src/utils/index.ts +0 -80
  160. package/src/utils/retry.ts +0 -364
  161. package/src/utils/security.ts +0 -418
  162. package/src/utils/validation.ts +0 -540
  163. package/src/wallet/AutoWalletProvider.ts +0 -299
  164. package/src/wallet/EOAWalletProvider.ts +0 -69
  165. package/src/wallet/IWalletProvider.ts +0 -135
  166. package/src/wallet/aa/BundlerClient.ts +0 -274
  167. package/src/wallet/aa/DualNonceManager.ts +0 -173
  168. package/src/wallet/aa/PaymasterClient.ts +0 -174
  169. package/src/wallet/aa/TransactionBatcher.ts +0 -353
  170. package/src/wallet/aa/UserOpBuilder.ts +0 -246
  171. package/src/wallet/aa/constants.ts +0 -60
  172. package/src/wallet/keystore.ts +0 -240
@@ -1,204 +0,0 @@
1
- import { Contract, EventLog } from 'ethers';
2
- import { State, Transaction } from '../types';
3
-
4
- /**
5
- * EventMonitor - Listen to blockchain events
6
- *
7
- * ## Confirmation Policy
8
- *
9
- * Events received by EventMonitor are already confirmed. ACTPKernel waits
10
- * for N block confirmations (default 2, configurable via `confirmations`
11
- * parameter in BlockchainRuntimeConfig) before returning from state-changing
12
- * operations. On Base L2 (~2 s blocks), the default means events arrive
13
- * ~4-6 s after submission and are safe from reorgs.
14
- *
15
- * Confirmation flow:
16
- * User calls ACTPKernel.createTransaction()
17
- * → tx.wait(confirmations) blocks until N confirmations
18
- * → Event emitted (already confirmed)
19
- * → EventMonitor receives event (instant)
20
- *
21
- * SECURITY FIX (EVENT-MONITOR): Corrected event parameter order to match ABI.
22
- * Per ACTPKernel.json, TransactionCreated signature is:
23
- * (bytes32 indexed transactionId, address indexed requester, address indexed provider, uint256 amount, bytes32 serviceHash)
24
- *
25
- * Previous code had requester/provider swapped which caused wrong filter results.
26
- */
27
- export class EventMonitor {
28
- constructor(
29
- private readonly kernelContract: Contract,
30
- _escrowContract: Contract
31
- ) {}
32
-
33
- /**
34
- * Watch transaction state changes
35
- * Returns cleanup function to stop watching
36
- */
37
- watchTransaction(txId: string, callback: (state: State) => void): () => void {
38
- const filter = this.kernelContract.filters.StateTransitioned(txId);
39
-
40
- const listener = (_eventTxId: string, _from: number, to: number) => {
41
- callback(to as State);
42
- };
43
-
44
- this.kernelContract.on(filter, listener);
45
-
46
- // Return cleanup function
47
- return () => {
48
- this.kernelContract.off(filter, listener);
49
- };
50
- }
51
-
52
- /**
53
- * Wait for specific state
54
- */
55
- async waitForState(
56
- txId: string,
57
- targetState: State,
58
- timeoutMs: number = 60000
59
- ): Promise<void> {
60
- return new Promise((resolve, reject) => {
61
- const timer = setTimeout(() => {
62
- cleanup();
63
- reject(new Error(`Timeout waiting for state ${State[targetState]}`));
64
- }, timeoutMs);
65
-
66
- const cleanup = this.watchTransaction(txId, (state) => {
67
- if (state === targetState) {
68
- clearTimeout(timer);
69
- cleanup();
70
- resolve();
71
- }
72
- });
73
- });
74
- }
75
-
76
- /**
77
- * Get all transactions for an address
78
- *
79
- * SECURITY FIX (EVENT-MONITOR): Corrected filter parameter order.
80
- * Per ACTPKernel.json ABI, TransactionCreated event signature is:
81
- * (bytes32 indexed transactionId, address indexed requester, address indexed provider, uint256 amount, bytes32 serviceHash)
82
- *
83
- * Filter order: TransactionCreated(txId, requester, provider)
84
- * - To filter by requester: (null, address, null)
85
- * - To filter by provider: (null, null, address)
86
- *
87
- * SECURITY FIX (EVENT-MONITOR): Use getTransaction() instead of transactions()
88
- * The kernel contract exposes getTransaction(bytes32) not transactions(bytes32).
89
- */
90
- async getTransactionHistory(
91
- address: string,
92
- role: 'requester' | 'provider' = 'requester'
93
- ): Promise<Transaction[]> {
94
- // TransactionCreated event signature per ABI:
95
- // (bytes32 indexed transactionId, address indexed requester, address indexed provider, uint256 amount, bytes32 serviceHash)
96
- // Filter format: TransactionCreated(txId, requester, provider)
97
- const filter =
98
- role === 'requester'
99
- ? this.kernelContract.filters.TransactionCreated(null, address, null) // Match requester (2nd indexed param)
100
- : this.kernelContract.filters.TransactionCreated(null, null, address); // Match provider (3rd indexed param)
101
-
102
- const events = await this.kernelContract.queryFilter(filter);
103
-
104
- return Promise.all(
105
- events.map(async (event) => {
106
- // ethers v6: EventLog has args, Log does not
107
- if (!('args' in event)) {
108
- throw new Error('Event does not contain args (not an EventLog)');
109
- }
110
- const txId = (event as EventLog).args?.transactionId;
111
-
112
- // SECURITY FIX: Use getTransaction() - the actual ABI function
113
- // Previous code called transactions(txId) which doesn't exist in ABI
114
- const txData = await this.kernelContract.getTransaction(txId);
115
-
116
- return {
117
- txId: txData.transactionId || txId,
118
- requester: txData.requester,
119
- provider: txData.provider,
120
- amount: txData.amount,
121
- state: (typeof txData.state === 'bigint' ? Number(txData.state) : txData.state) as State,
122
- createdAt: Number(txData.createdAt),
123
- updatedAt: Number(txData.updatedAt),
124
- deadline: Number(txData.deadline),
125
- disputeWindow: Number(txData.disputeWindow),
126
- escrowContract: txData.escrowContract,
127
- escrowId: txData.escrowId,
128
- serviceHash: txData.serviceHash,
129
- attestationUID: txData.attestationUID,
130
- // Use metadata field (quote hash for QUOTED state) if available, fallback to serviceHash
131
- metadata: txData.metadata || txData.serviceHash,
132
- platformFeeBpsLocked: Number(txData.platformFeeBpsLocked)
133
- };
134
- })
135
- );
136
- }
137
-
138
- /**
139
- * Subscribe to transaction creation events
140
- *
141
- * SECURITY FIX (EVENT-MONITOR): Corrected event parameter order.
142
- * Per ACTPKernel.json ABI:
143
- * TransactionCreated(bytes32 indexed transactionId, address indexed requester, address indexed provider, uint256 amount, bytes32 serviceHash)
144
- */
145
- onTransactionCreated(
146
- callback: (tx: { txId: string; requester: string; provider: string; amount: bigint; serviceHash?: string }) => void
147
- ): () => void {
148
- const filter = this.kernelContract.filters.TransactionCreated();
149
-
150
- // Event signature per ABI: (txId, requester, provider, amount, serviceHash)
151
- const listener = async (
152
- txId: string,
153
- requester: string,
154
- provider: string,
155
- amount: bigint,
156
- serviceHash?: string
157
- ) => {
158
- callback({ txId, requester, provider, amount, serviceHash });
159
- };
160
-
161
- this.kernelContract.on(filter, listener);
162
-
163
- return () => {
164
- this.kernelContract.off(filter, listener);
165
- };
166
- }
167
-
168
- /**
169
- * Subscribe to state change events
170
- */
171
- onStateChanged(
172
- callback: (txId: string, from: State, to: State) => void
173
- ): () => void {
174
- const filter = this.kernelContract.filters.StateTransitioned();
175
-
176
- const listener = (txId: string, from: number, to: number) => {
177
- callback(txId, from as State, to as State);
178
- };
179
-
180
- this.kernelContract.on(filter, listener);
181
-
182
- return () => {
183
- this.kernelContract.off(filter, listener);
184
- };
185
- }
186
-
187
- /**
188
- * Subscribe to escrow release events
189
- */
190
- onEscrowReleased(callback: (txId: string, amount: bigint) => void): () => void {
191
- const filter = this.kernelContract.filters.EscrowReleased();
192
-
193
- const listener = (txId: string, amount: bigint) => {
194
- callback(txId, amount);
195
- };
196
-
197
- this.kernelContract.on(filter, listener);
198
-
199
- return () => {
200
- this.kernelContract.off(filter, listener);
201
- };
202
- }
203
- }
204
-
@@ -1,510 +0,0 @@
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
- import { sdkLogger } from '../utils/Logger';
14
-
15
- // Legacy generic ACTP message types moved to types/eip712.ts
16
-
17
- /**
18
- * TypeScript interface for ethers v6 Signer with signTypedData method
19
- *
20
- * Note: ethers v6 uses signTypedData() (without underscore), not _signTypedData().
21
- * This interface properly types the method for v6 compatibility.
22
- */
23
- interface SignerWithTypedData extends Signer {
24
- signTypedData(
25
- domain: EIP712Domain,
26
- types: Record<string, any>,
27
- value: Record<string, any>
28
- ): Promise<string>;
29
- }
30
-
31
- /**
32
- * MessageSigner - Cryptographic signing for ACTP messages with EIP-712
33
- * Reference: Yellow Paper §11.4.2
34
- *
35
- * V4 Security Enhancement: Optional nonce replay protection via ReceivedNonceTracker
36
- *
37
- * IMPORTANT: Use MessageSigner.create() factory method to ensure domain is initialized.
38
- */
39
- export class MessageSigner {
40
- private domain: EIP712Domain | null = null;
41
-
42
- /**
43
- * SECURITY FIX (H-5): Private constructor - MUST use MessageSigner.create() factory method
44
- *
45
- * This ensures EIP-712 domain is ALWAYS initialized before use (prevents race conditions).
46
- * Direct construction would allow calling sign/verify without domain initialization.
47
- */
48
- private constructor(
49
- private readonly signer: Signer,
50
- private readonly nonceTracker?: IReceivedNonceTracker
51
- ) {}
52
-
53
- /**
54
- * SECURITY FIX (H-4): Factory method to create MessageSigner with guaranteed domain initialization
55
- *
56
- * This factory ensures the EIP-712 domain is always properly initialized before use.
57
- * Prevents the common bug of calling sign/verify without initializing domain first.
58
- *
59
- * @param signer - Ethers signer for signing messages
60
- * @param kernelAddress - Address of ACTP Kernel contract (for domain separation)
61
- * @param options - Optional configuration (chainId, nonceTracker)
62
- * @returns Promise resolving to initialized MessageSigner
63
- *
64
- * @example
65
- * ```typescript
66
- * const messageSigner = await MessageSigner.create(
67
- * signer,
68
- * KERNEL_ADDRESS,
69
- * { chainId: 84532 }
70
- * );
71
- * const signature = await messageSigner.signMessage(message);
72
- * ```
73
- */
74
- static async create(
75
- signer: Signer,
76
- kernelAddress: string,
77
- options?: {
78
- chainId?: number;
79
- nonceTracker?: IReceivedNonceTracker;
80
- }
81
- ): Promise<MessageSigner> {
82
- const messageSigner = new MessageSigner(signer, options?.nonceTracker);
83
- await messageSigner.initDomain(kernelAddress, options?.chainId);
84
- return messageSigner;
85
- }
86
-
87
- /**
88
- * Check if domain is initialized
89
- * @returns true if domain has been initialized
90
- */
91
- isDomainInitialized(): boolean {
92
- return this.domain !== null;
93
- }
94
-
95
- /**
96
- * Get the current domain (throws if not initialized)
97
- * @returns Current EIP-712 domain
98
- * @throws Error if domain not initialized
99
- */
100
- getDomain(): EIP712Domain {
101
- if (!this.domain) {
102
- throw new Error(
103
- 'Domain not initialized. Use MessageSigner.create() factory or call initDomain() first.'
104
- );
105
- }
106
- return this.domain;
107
- }
108
-
109
- /**
110
- * Initialize EIP-712 domain (must be called before signing)
111
- * @param kernelAddress - Address of ACTP Kernel contract
112
- * @param chainId - Optional chainId (defaults to signer's chainId or 84532 for Base Sepolia)
113
- */
114
- async initDomain(kernelAddress: string, chainId?: number): Promise<void> {
115
- let resolvedChainId: number;
116
-
117
- if (chainId !== undefined) {
118
- resolvedChainId = chainId;
119
- } else {
120
- try {
121
- // ethers v6: signer.provider might be null, check first
122
- if (this.signer.provider) {
123
- const network = await this.signer.provider.getNetwork();
124
- resolvedChainId = Number(network.chainId);
125
- } else {
126
- // Fallback to Base Sepolia for testing without provider
127
- resolvedChainId = 84532;
128
- }
129
- } catch (error) {
130
- // Fallback to Base Sepolia for testing without provider
131
- resolvedChainId = 84532;
132
- }
133
- }
134
-
135
- // SECURITY FIX (H-6): Standardize domain name to 'AGIRAILS' for brand consistency
136
- // Note: This change requires coordination with any existing signed messages
137
- this.domain = {
138
- name: 'AGIRAILS',
139
- version: '1.0',
140
- chainId: resolvedChainId,
141
- verifyingContract: kernelAddress
142
- };
143
- }
144
-
145
- /**
146
- * Sign ACTP message using EIP-712 typed data
147
- * Uses ECDSA (secp256k1) with domain separation per Yellow Paper §11.4.2
148
- *
149
- * SECURITY FIX (H-3): Validates nonce format and warns about sequential nonces
150
- *
151
- * Generic ACTPMessage format (backward compatible).
152
- * For strict typed AIP messages, use signQuoteRequest/signQuoteResponse/signDeliveryProof
153
- */
154
- async signMessage(message: ACTPMessage): Promise<string> {
155
- if (!this.domain) {
156
- throw new Error(
157
- 'Domain not initialized. Use MessageSigner.create() factory or call initDomain() first.'
158
- );
159
- }
160
-
161
- const { type, version, from, to, timestamp, nonce, signature: _sig, ...payload } = message;
162
-
163
- // SECURITY FIX (H-3): Validate nonce format (must be bytes32)
164
- if (!nonce || !/^0x[a-fA-F0-9]{64}$/.test(nonce)) {
165
- throw new Error(
166
- `Invalid nonce format: "${nonce}". ` +
167
- `Nonce MUST be a bytes32 hex string (0x + 64 hex chars). ` +
168
- `Use SecureNonce.generateSecureNonce() to generate cryptographically secure nonces. ` +
169
- `Never use sequential integers (1, 2, 3...) or timestamps as nonces.`
170
- );
171
- }
172
-
173
- // SECURITY FIX (H-3): Warn about sequential nonces (low entropy)
174
- // Sequential nonces like 0x0000...0001, 0x0000...0002 are weak
175
- // Check if nonce has low entropy (e.g., last 8 bytes are zero, or all same digits)
176
- const nonceValue = BigInt(nonce);
177
- if (nonceValue < 0xFFFFFFFFn) {
178
- // Nonce is suspiciously small (< 4 billion = likely sequential)
179
- sdkLogger.warn('Nonce appears sequential - use SecureNonce.generateSecureNonce()', { nonce });
180
- }
181
-
182
- // Check if nonce has all same digits (e.g., 0x111...111 or 0x000...000)
183
- const hexDigits = nonce.slice(2); // Remove '0x'
184
- const firstDigit = hexDigits[0];
185
- if (hexDigits.split('').every(d => d === firstDigit)) {
186
- sdkLogger.warn('Nonce has low entropy - use SecureNonce.generateSecureNonce()', { nonce, repeatedDigit: firstDigit });
187
- }
188
-
189
- // Generic ACTPMessage with payload encoding (backward compatible)
190
- const abiCoder = AbiCoder.defaultAbiCoder();
191
- const payloadBytes = abiCoder.encode(
192
- ['string'],
193
- [this.canonicalizePayload(payload)]
194
- );
195
-
196
- const typedMessage = {
197
- type,
198
- version,
199
- from,
200
- to,
201
- timestamp,
202
- nonce,
203
- payload: payloadBytes
204
- };
205
-
206
- // Use generic ACTPMessage types
207
- const messageTypes = getMessageTypes('default');
208
-
209
- // Sign using EIP-712 (ethers v6 API)
210
- const signer = this.signer as SignerWithTypedData;
211
- const sig = await signer.signTypedData(this.domain, messageTypes, typedMessage);
212
-
213
- return sig;
214
- }
215
-
216
- /**
217
- * Sign typed QuoteRequest message
218
- */
219
- async signQuoteRequest(data: QuoteRequestData): Promise<string> {
220
- if (!this.domain) {
221
- throw new Error(
222
- 'Domain not initialized. Use MessageSigner.create() factory or call initDomain() first.'
223
- );
224
- }
225
-
226
- const messageTypes = getMessageTypes('quote.request');
227
- const signer = this.signer as SignerWithTypedData;
228
- return await signer.signTypedData(this.domain, messageTypes, data);
229
- }
230
-
231
- /**
232
- * Sign typed QuoteResponse message
233
- */
234
- async signQuoteResponse(data: QuoteResponseData): Promise<string> {
235
- if (!this.domain) {
236
- throw new Error(
237
- 'Domain not initialized. Use MessageSigner.create() factory or call initDomain() first.'
238
- );
239
- }
240
-
241
- const messageTypes = getMessageTypes('quote.response');
242
- const signer = this.signer as SignerWithTypedData;
243
- return await signer.signTypedData(this.domain, messageTypes, data);
244
- }
245
-
246
- /**
247
- * Sign typed DeliveryProof message
248
- */
249
- async signDeliveryProof(data: DeliveryProofData): Promise<string> {
250
- if (!this.domain) {
251
- throw new Error(
252
- 'Domain not initialized. Use MessageSigner.create() factory or call initDomain() first.'
253
- );
254
- }
255
-
256
- const messageTypes = getMessageTypes('delivery.proof');
257
- const signer = this.signer as SignerWithTypedData;
258
- return await signer.signTypedData(this.domain, messageTypes, data);
259
- }
260
-
261
- /**
262
- * Convenience helper to sign a DeliveryProof generated by ProofGenerator
263
- */
264
- async signGeneratedDeliveryProof(proof: DeliveryProof): Promise<string> {
265
- const typedData = deliveryProofDataFromProof(proof);
266
- return await this.signDeliveryProof(typedData);
267
- }
268
-
269
- /**
270
- * Verify message signature using EIP-712
271
- * Uses generic ACTPMessage types (backward compatible)
272
- *
273
- * V4 Security: If nonceTracker is configured, validates nonce for replay protection
274
- */
275
- async verifySignature(message: ACTPMessage, signature: string): Promise<boolean> {
276
- if (!this.domain) {
277
- throw new Error(
278
- 'Domain not initialized. Use MessageSigner.create() factory or call initDomain() first.'
279
- );
280
- }
281
-
282
- const { type, version, from, to, timestamp, nonce, signature: _, ...payload } = message;
283
-
284
- const abiCoder = AbiCoder.defaultAbiCoder();
285
- const payloadBytes = abiCoder.encode(
286
- ['string'],
287
- [this.canonicalizePayload(payload)]
288
- );
289
-
290
- const typedMessage = {
291
- type,
292
- version,
293
- from,
294
- to,
295
- timestamp,
296
- nonce,
297
- payload: payloadBytes
298
- };
299
-
300
- // Use generic ACTPMessage types (backward compatible)
301
- const messageTypes = getMessageTypes('default');
302
- const recoveredAddress = ethers.verifyTypedData(
303
- this.domain,
304
- messageTypes,
305
- typedMessage,
306
- signature
307
- );
308
-
309
- const expectedAddress = this.didToAddress(from);
310
-
311
- // Verify signature matches sender
312
- if (recoveredAddress.toLowerCase() !== expectedAddress.toLowerCase()) {
313
- return false;
314
- }
315
-
316
- // V4 Security: Validate nonce for replay protection (if tracker configured)
317
- if (this.nonceTracker) {
318
- const nonceValidation = this.nonceTracker.validateAndRecord(from, type, nonce);
319
- if (!nonceValidation.valid) {
320
- // Nonce replay detected - return false
321
- return false;
322
- }
323
- }
324
-
325
- return true;
326
- }
327
-
328
- /**
329
- * Verify signature and throw if invalid
330
- * V4 Security: Throws specific error for nonce replay detection
331
- */
332
- async verifySignatureOrThrow(message: ACTPMessage, signature: string): Promise<void> {
333
- if (!this.domain) {
334
- throw new Error('Domain not initialized');
335
- }
336
-
337
- const { type, version, from, to, timestamp, nonce, signature: _, ...payload } = message;
338
-
339
- const abiCoder = AbiCoder.defaultAbiCoder();
340
- const payloadBytes = abiCoder.encode(
341
- ['string'],
342
- [this.canonicalizePayload(payload)]
343
- );
344
-
345
- const typedMessage = { type, version, from, to, timestamp, nonce, payload: payloadBytes };
346
-
347
- const messageTypes = getMessageTypes('default');
348
- const recoveredAddress = ethers.verifyTypedData(
349
- this.domain,
350
- messageTypes,
351
- typedMessage,
352
- signature
353
- );
354
-
355
- const expectedAddress = this.didToAddress(from);
356
-
357
- // Check signature validity first
358
- if (recoveredAddress.toLowerCase() !== expectedAddress.toLowerCase()) {
359
- throw new SignatureVerificationError(expectedAddress, recoveredAddress);
360
- }
361
-
362
- // V4 Security: Validate nonce for replay protection (if tracker configured)
363
- if (this.nonceTracker) {
364
- const nonceValidation = this.nonceTracker.validateAndRecord(from, type, nonce);
365
- if (!nonceValidation.valid) {
366
- // Throw specific error for nonce replay
367
- throw new Error(
368
- `Nonce replay attack detected: ${nonceValidation.reason}. ` +
369
- `Received nonce: ${nonceValidation.receivedNonce}. ` +
370
- (nonceValidation.expectedMinimum ? `Expected minimum: ${nonceValidation.expectedMinimum}` : '')
371
- );
372
- }
373
- }
374
- }
375
-
376
- /**
377
- * Canonicalize payload to deterministic string (recursively sorted keys)
378
- * Prevents JSON serialization ambiguity across different JS runtimes
379
- * Recursively handles nested objects and arrays
380
- */
381
- private canonicalizePayload(payload: Record<string, any>): string {
382
- return JSON.stringify(this.recursiveSort(payload));
383
- }
384
-
385
- /**
386
- * Recursively sort object keys for deterministic JSON encoding
387
- */
388
- private recursiveSort(obj: any): any {
389
- if (obj === null || obj === undefined) {
390
- return obj;
391
- }
392
-
393
- // Handle arrays: recursively sort each element
394
- if (Array.isArray(obj)) {
395
- return obj.map((item) => this.recursiveSort(item));
396
- }
397
-
398
- // Handle objects: sort keys and recursively sort values
399
- if (typeof obj === 'object' && obj.constructor === Object) {
400
- const sortedKeys = Object.keys(obj).sort();
401
- const canonical: Record<string, any> = {};
402
-
403
- for (const key of sortedKeys) {
404
- canonical[key] = this.recursiveSort(obj[key]);
405
- }
406
-
407
- return canonical;
408
- }
409
-
410
- // Primitives (string, number, boolean)
411
- return obj;
412
- }
413
-
414
- /**
415
- * Convert DID to Ethereum address
416
- *
417
- * SECURITY FIX (DID-FORMAT): Handles both DID formats:
418
- * - Legacy: did:ethr:<address>
419
- * - Canonical (EIP-3770): did:ethr:<chainId>:<address>
420
- *
421
- * Examples:
422
- * - "did:ethr:0x1234...abcd" → "0x1234...abcd"
423
- * - "did:ethr:84532:0x1234...abcd" → "0x1234...abcd"
424
- * - "0x1234...abcd" → "0x1234...abcd" (raw address passthrough)
425
- */
426
- private didToAddress(did: string): string {
427
- // Check for DID format first
428
- const DID_PREFIX = 'did:ethr:';
429
- if (did.startsWith(DID_PREFIX)) {
430
- const remainder = did.slice(DID_PREFIX.length);
431
-
432
- // Check if it's canonical format: did:ethr:<chainId>:<address>
433
- // chainId is numeric, address starts with 0x
434
- const parts = remainder.split(':');
435
-
436
- if (parts.length === 2) {
437
- // Canonical format: did:ethr:<chainId>:<address>
438
- const [chainIdStr, address] = parts;
439
- const chainId = parseInt(chainIdStr, 10);
440
-
441
- if (isNaN(chainId)) {
442
- throw new Error(
443
- `Invalid DID format: ${did}. ` +
444
- `Expected did:ethr:<chainId>:<address> but chainId "${chainIdStr}" is not a number.`
445
- );
446
- }
447
-
448
- if (!ethers.isAddress(address)) {
449
- throw new Error(
450
- `Invalid DID format: ${did}. ` +
451
- `Expected did:ethr:<chainId>:<address> but "${address}" is not a valid Ethereum address.`
452
- );
453
- }
454
-
455
- // SECURITY: Optionally validate chainId matches domain chainId
456
- // This prevents cross-chain replay attacks where a message signed for one chain
457
- // is replayed on another. For now, we just extract the address but log a warning.
458
- if (this.domain && this.domain.chainId !== chainId) {
459
- sdkLogger.warn('DID chainId mismatch - potential cross-chain replay attempt', {
460
- didChainId: chainId,
461
- domainChainId: this.domain.chainId,
462
- did,
463
- });
464
- }
465
-
466
- return address;
467
- } else if (parts.length === 1 && ethers.isAddress(parts[0])) {
468
- // Legacy format: did:ethr:<address>
469
- return parts[0];
470
- } else {
471
- throw new Error(
472
- `Invalid DID format: ${did}. ` +
473
- `Expected did:ethr:<address> or did:ethr:<chainId>:<address>.`
474
- );
475
- }
476
- }
477
-
478
- // If already an address (raw 0x format), return as-is
479
- if (ethers.isAddress(did)) {
480
- return did;
481
- }
482
-
483
- throw new Error(
484
- `Invalid DID format: ${did}. ` +
485
- `Expected Ethereum address (0x...) or DID (did:ethr:...).`
486
- );
487
- }
488
-
489
- /**
490
- * Convert Ethereum address to DID
491
- *
492
- * SECURITY FIX (DID-FORMAT): Now generates canonical DID format
493
- * with chainId when domain is initialized: did:ethr:<chainId>:<address>
494
- *
495
- * Falls back to legacy format if domain not initialized.
496
- */
497
- addressToDID(address: string): string {
498
- if (!ethers.isAddress(address)) {
499
- throw new Error(`Invalid Ethereum address: ${address}`);
500
- }
501
-
502
- // Use canonical format with chainId if domain is initialized
503
- if (this.domain && this.domain.chainId) {
504
- return `did:ethr:${this.domain.chainId}:${address}`;
505
- }
506
-
507
- // Fallback to legacy format (backward compatible)
508
- return `did:ethr:${address}`;
509
- }
510
- }