@agirails/sdk 2.5.3 → 2.5.5

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 (169) hide show
  1. package/dist/ACTPClient.d.ts +18 -0
  2. package/dist/ACTPClient.d.ts.map +1 -1
  3. package/dist/ACTPClient.js +72 -23
  4. package/dist/ACTPClient.js.map +1 -1
  5. package/dist/adapters/BasicAdapter.d.ts +15 -0
  6. package/dist/adapters/BasicAdapter.d.ts.map +1 -1
  7. package/dist/adapters/BasicAdapter.js +33 -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 +90 -12
  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/config/networks.d.ts +10 -2
  20. package/dist/config/networks.d.ts.map +1 -1
  21. package/dist/config/networks.js +31 -22
  22. package/dist/config/networks.js.map +1 -1
  23. package/dist/level0/request.d.ts.map +1 -1
  24. package/dist/level0/request.js +2 -1
  25. package/dist/level0/request.js.map +1 -1
  26. package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
  27. package/dist/runtime/BlockchainRuntime.js +11 -5
  28. package/dist/runtime/BlockchainRuntime.js.map +1 -1
  29. package/dist/utils/IPFSClient.d.ts +3 -1
  30. package/dist/utils/IPFSClient.d.ts.map +1 -1
  31. package/dist/utils/IPFSClient.js +27 -7
  32. package/dist/utils/IPFSClient.js.map +1 -1
  33. package/dist/wallet/AutoWalletProvider.d.ts +11 -1
  34. package/dist/wallet/AutoWalletProvider.d.ts.map +1 -1
  35. package/dist/wallet/AutoWalletProvider.js +84 -19
  36. package/dist/wallet/AutoWalletProvider.js.map +1 -1
  37. package/dist/wallet/IWalletProvider.d.ts +34 -0
  38. package/dist/wallet/IWalletProvider.d.ts.map +1 -1
  39. package/dist/wallet/SmartWalletRouter.d.ts +128 -0
  40. package/dist/wallet/SmartWalletRouter.d.ts.map +1 -0
  41. package/dist/wallet/SmartWalletRouter.js +248 -0
  42. package/dist/wallet/SmartWalletRouter.js.map +1 -0
  43. package/dist/wallet/aa/DualNonceManager.d.ts +26 -1
  44. package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
  45. package/dist/wallet/aa/DualNonceManager.js +140 -6
  46. package/dist/wallet/aa/DualNonceManager.js.map +1 -1
  47. package/package.json +3 -6
  48. package/src/ACTPClient.ts +0 -1579
  49. package/src/abi/ACTPKernel.json +0 -1356
  50. package/src/abi/AgentRegistry.json +0 -915
  51. package/src/abi/ERC20.json +0 -40
  52. package/src/abi/EscrowVault.json +0 -134
  53. package/src/abi/IdentityRegistry.json +0 -316
  54. package/src/adapters/AdapterRegistry.ts +0 -173
  55. package/src/adapters/AdapterRouter.ts +0 -416
  56. package/src/adapters/BaseAdapter.ts +0 -498
  57. package/src/adapters/BasicAdapter.ts +0 -514
  58. package/src/adapters/IAdapter.ts +0 -292
  59. package/src/adapters/StandardAdapter.ts +0 -555
  60. package/src/adapters/X402Adapter.ts +0 -731
  61. package/src/adapters/index.ts +0 -60
  62. package/src/builders/DeliveryProofBuilder.ts +0 -327
  63. package/src/builders/QuoteBuilder.ts +0 -483
  64. package/src/builders/index.ts +0 -17
  65. package/src/cli/commands/balance.ts +0 -110
  66. package/src/cli/commands/batch.ts +0 -487
  67. package/src/cli/commands/config.ts +0 -231
  68. package/src/cli/commands/deploy-check.ts +0 -364
  69. package/src/cli/commands/deploy-env.ts +0 -120
  70. package/src/cli/commands/diff.ts +0 -141
  71. package/src/cli/commands/init.ts +0 -469
  72. package/src/cli/commands/mint.ts +0 -116
  73. package/src/cli/commands/pay.ts +0 -113
  74. package/src/cli/commands/publish.ts +0 -475
  75. package/src/cli/commands/pull.ts +0 -124
  76. package/src/cli/commands/register.ts +0 -247
  77. package/src/cli/commands/simulate.ts +0 -345
  78. package/src/cli/commands/time.ts +0 -302
  79. package/src/cli/commands/tx.ts +0 -448
  80. package/src/cli/commands/watch.ts +0 -211
  81. package/src/cli/index.ts +0 -134
  82. package/src/cli/utils/client.ts +0 -252
  83. package/src/cli/utils/config.ts +0 -389
  84. package/src/cli/utils/output.ts +0 -465
  85. package/src/cli/utils/wallet.ts +0 -109
  86. package/src/config/agirailsmd.ts +0 -262
  87. package/src/config/networks.ts +0 -275
  88. package/src/config/pendingPublish.ts +0 -237
  89. package/src/config/publishPipeline.ts +0 -359
  90. package/src/config/syncOperations.ts +0 -279
  91. package/src/erc8004/ERC8004Bridge.ts +0 -462
  92. package/src/erc8004/ReputationReporter.ts +0 -468
  93. package/src/erc8004/index.ts +0 -61
  94. package/src/errors/index.ts +0 -427
  95. package/src/index.ts +0 -364
  96. package/src/level0/Provider.ts +0 -117
  97. package/src/level0/ServiceDirectory.ts +0 -131
  98. package/src/level0/index.ts +0 -10
  99. package/src/level0/provide.ts +0 -132
  100. package/src/level0/request.ts +0 -432
  101. package/src/level1/Agent.ts +0 -1426
  102. package/src/level1/index.ts +0 -10
  103. package/src/level1/pricing/PriceCalculator.ts +0 -255
  104. package/src/level1/pricing/PricingStrategy.ts +0 -198
  105. package/src/level1/types/Job.ts +0 -179
  106. package/src/level1/types/Options.ts +0 -291
  107. package/src/level1/types/index.ts +0 -8
  108. package/src/protocol/ACTPKernel.ts +0 -808
  109. package/src/protocol/AgentRegistry.ts +0 -559
  110. package/src/protocol/DIDManager.ts +0 -629
  111. package/src/protocol/DIDResolver.ts +0 -554
  112. package/src/protocol/EASHelper.ts +0 -378
  113. package/src/protocol/EscrowVault.ts +0 -255
  114. package/src/protocol/EventMonitor.ts +0 -204
  115. package/src/protocol/MessageSigner.ts +0 -510
  116. package/src/protocol/ProofGenerator.ts +0 -339
  117. package/src/protocol/QuoteBuilder.ts +0 -15
  118. package/src/registry/AgentRegistryClient.ts +0 -202
  119. package/src/runtime/BlockchainRuntime.ts +0 -1015
  120. package/src/runtime/IACTPRuntime.ts +0 -306
  121. package/src/runtime/MockRuntime.ts +0 -1298
  122. package/src/runtime/MockStateManager.ts +0 -577
  123. package/src/runtime/index.ts +0 -25
  124. package/src/runtime/types/MockState.ts +0 -237
  125. package/src/storage/ArchiveBundleBuilder.ts +0 -561
  126. package/src/storage/ArweaveClient.ts +0 -946
  127. package/src/storage/FilebaseClient.ts +0 -790
  128. package/src/storage/index.ts +0 -96
  129. package/src/storage/types.ts +0 -348
  130. package/src/types/adapter.ts +0 -310
  131. package/src/types/agent.ts +0 -79
  132. package/src/types/did.ts +0 -223
  133. package/src/types/eip712.ts +0 -175
  134. package/src/types/erc8004.ts +0 -293
  135. package/src/types/escrow.ts +0 -27
  136. package/src/types/index.ts +0 -17
  137. package/src/types/message.ts +0 -145
  138. package/src/types/state.ts +0 -87
  139. package/src/types/transaction.ts +0 -69
  140. package/src/types/x402.ts +0 -251
  141. package/src/utils/ErrorRecoveryGuide.ts +0 -676
  142. package/src/utils/Helpers.ts +0 -688
  143. package/src/utils/IPFSClient.ts +0 -368
  144. package/src/utils/Logger.ts +0 -484
  145. package/src/utils/NonceManager.ts +0 -591
  146. package/src/utils/RateLimiter.ts +0 -534
  147. package/src/utils/ReceivedNonceTracker.ts +0 -567
  148. package/src/utils/SDKLifecycle.ts +0 -416
  149. package/src/utils/SecureNonce.ts +0 -78
  150. package/src/utils/Semaphore.ts +0 -276
  151. package/src/utils/UsedAttestationTracker.ts +0 -385
  152. package/src/utils/canonicalJson.ts +0 -38
  153. package/src/utils/circuitBreaker.ts +0 -324
  154. package/src/utils/computeTypeHash.ts +0 -48
  155. package/src/utils/fsSafe.ts +0 -80
  156. package/src/utils/index.ts +0 -80
  157. package/src/utils/retry.ts +0 -364
  158. package/src/utils/security.ts +0 -418
  159. package/src/utils/validation.ts +0 -540
  160. package/src/wallet/AutoWalletProvider.ts +0 -299
  161. package/src/wallet/EOAWalletProvider.ts +0 -69
  162. package/src/wallet/IWalletProvider.ts +0 -135
  163. package/src/wallet/aa/BundlerClient.ts +0 -274
  164. package/src/wallet/aa/DualNonceManager.ts +0 -173
  165. package/src/wallet/aa/PaymasterClient.ts +0 -174
  166. package/src/wallet/aa/TransactionBatcher.ts +0 -353
  167. package/src/wallet/aa/UserOpBuilder.ts +0 -246
  168. package/src/wallet/aa/constants.ts +0 -60
  169. 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
- }