@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.
- package/dist/ACTPClient.d.ts +18 -0
- package/dist/ACTPClient.d.ts.map +1 -1
- package/dist/ACTPClient.js +72 -23
- package/dist/ACTPClient.js.map +1 -1
- package/dist/adapters/BasicAdapter.d.ts +15 -0
- package/dist/adapters/BasicAdapter.d.ts.map +1 -1
- package/dist/adapters/BasicAdapter.js +33 -4
- package/dist/adapters/BasicAdapter.js.map +1 -1
- package/dist/adapters/StandardAdapter.d.ts +20 -3
- package/dist/adapters/StandardAdapter.d.ts.map +1 -1
- package/dist/adapters/StandardAdapter.js +90 -12
- package/dist/adapters/StandardAdapter.js.map +1 -1
- package/dist/cli/commands/publish.js +16 -4
- package/dist/cli/commands/publish.js.map +1 -1
- package/dist/cli/commands/register.js +16 -4
- package/dist/cli/commands/register.js.map +1 -1
- package/dist/cli/commands/tx.js +31 -3
- package/dist/cli/commands/tx.js.map +1 -1
- package/dist/config/networks.d.ts +10 -2
- package/dist/config/networks.d.ts.map +1 -1
- package/dist/config/networks.js +31 -22
- package/dist/config/networks.js.map +1 -1
- package/dist/level0/request.d.ts.map +1 -1
- package/dist/level0/request.js +2 -1
- package/dist/level0/request.js.map +1 -1
- package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
- package/dist/runtime/BlockchainRuntime.js +11 -5
- package/dist/runtime/BlockchainRuntime.js.map +1 -1
- package/dist/utils/IPFSClient.d.ts +3 -1
- package/dist/utils/IPFSClient.d.ts.map +1 -1
- package/dist/utils/IPFSClient.js +27 -7
- package/dist/utils/IPFSClient.js.map +1 -1
- package/dist/wallet/AutoWalletProvider.d.ts +11 -1
- package/dist/wallet/AutoWalletProvider.d.ts.map +1 -1
- package/dist/wallet/AutoWalletProvider.js +84 -19
- package/dist/wallet/AutoWalletProvider.js.map +1 -1
- package/dist/wallet/IWalletProvider.d.ts +34 -0
- package/dist/wallet/IWalletProvider.d.ts.map +1 -1
- package/dist/wallet/SmartWalletRouter.d.ts +128 -0
- package/dist/wallet/SmartWalletRouter.d.ts.map +1 -0
- package/dist/wallet/SmartWalletRouter.js +248 -0
- package/dist/wallet/SmartWalletRouter.js.map +1 -0
- package/dist/wallet/aa/DualNonceManager.d.ts +26 -1
- package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
- package/dist/wallet/aa/DualNonceManager.js +140 -6
- package/dist/wallet/aa/DualNonceManager.js.map +1 -1
- package/package.json +3 -6
- package/src/ACTPClient.ts +0 -1579
- package/src/abi/ACTPKernel.json +0 -1356
- package/src/abi/AgentRegistry.json +0 -915
- package/src/abi/ERC20.json +0 -40
- package/src/abi/EscrowVault.json +0 -134
- package/src/abi/IdentityRegistry.json +0 -316
- package/src/adapters/AdapterRegistry.ts +0 -173
- package/src/adapters/AdapterRouter.ts +0 -416
- package/src/adapters/BaseAdapter.ts +0 -498
- package/src/adapters/BasicAdapter.ts +0 -514
- package/src/adapters/IAdapter.ts +0 -292
- package/src/adapters/StandardAdapter.ts +0 -555
- package/src/adapters/X402Adapter.ts +0 -731
- package/src/adapters/index.ts +0 -60
- package/src/builders/DeliveryProofBuilder.ts +0 -327
- package/src/builders/QuoteBuilder.ts +0 -483
- package/src/builders/index.ts +0 -17
- package/src/cli/commands/balance.ts +0 -110
- package/src/cli/commands/batch.ts +0 -487
- package/src/cli/commands/config.ts +0 -231
- package/src/cli/commands/deploy-check.ts +0 -364
- package/src/cli/commands/deploy-env.ts +0 -120
- package/src/cli/commands/diff.ts +0 -141
- package/src/cli/commands/init.ts +0 -469
- package/src/cli/commands/mint.ts +0 -116
- package/src/cli/commands/pay.ts +0 -113
- package/src/cli/commands/publish.ts +0 -475
- package/src/cli/commands/pull.ts +0 -124
- package/src/cli/commands/register.ts +0 -247
- package/src/cli/commands/simulate.ts +0 -345
- package/src/cli/commands/time.ts +0 -302
- package/src/cli/commands/tx.ts +0 -448
- package/src/cli/commands/watch.ts +0 -211
- package/src/cli/index.ts +0 -134
- package/src/cli/utils/client.ts +0 -252
- package/src/cli/utils/config.ts +0 -389
- package/src/cli/utils/output.ts +0 -465
- package/src/cli/utils/wallet.ts +0 -109
- package/src/config/agirailsmd.ts +0 -262
- package/src/config/networks.ts +0 -275
- package/src/config/pendingPublish.ts +0 -237
- package/src/config/publishPipeline.ts +0 -359
- package/src/config/syncOperations.ts +0 -279
- package/src/erc8004/ERC8004Bridge.ts +0 -462
- package/src/erc8004/ReputationReporter.ts +0 -468
- package/src/erc8004/index.ts +0 -61
- package/src/errors/index.ts +0 -427
- package/src/index.ts +0 -364
- package/src/level0/Provider.ts +0 -117
- package/src/level0/ServiceDirectory.ts +0 -131
- package/src/level0/index.ts +0 -10
- package/src/level0/provide.ts +0 -132
- package/src/level0/request.ts +0 -432
- package/src/level1/Agent.ts +0 -1426
- package/src/level1/index.ts +0 -10
- package/src/level1/pricing/PriceCalculator.ts +0 -255
- package/src/level1/pricing/PricingStrategy.ts +0 -198
- package/src/level1/types/Job.ts +0 -179
- package/src/level1/types/Options.ts +0 -291
- package/src/level1/types/index.ts +0 -8
- package/src/protocol/ACTPKernel.ts +0 -808
- package/src/protocol/AgentRegistry.ts +0 -559
- package/src/protocol/DIDManager.ts +0 -629
- package/src/protocol/DIDResolver.ts +0 -554
- package/src/protocol/EASHelper.ts +0 -378
- package/src/protocol/EscrowVault.ts +0 -255
- package/src/protocol/EventMonitor.ts +0 -204
- package/src/protocol/MessageSigner.ts +0 -510
- package/src/protocol/ProofGenerator.ts +0 -339
- package/src/protocol/QuoteBuilder.ts +0 -15
- package/src/registry/AgentRegistryClient.ts +0 -202
- package/src/runtime/BlockchainRuntime.ts +0 -1015
- package/src/runtime/IACTPRuntime.ts +0 -306
- package/src/runtime/MockRuntime.ts +0 -1298
- package/src/runtime/MockStateManager.ts +0 -577
- package/src/runtime/index.ts +0 -25
- package/src/runtime/types/MockState.ts +0 -237
- package/src/storage/ArchiveBundleBuilder.ts +0 -561
- package/src/storage/ArweaveClient.ts +0 -946
- package/src/storage/FilebaseClient.ts +0 -790
- package/src/storage/index.ts +0 -96
- package/src/storage/types.ts +0 -348
- package/src/types/adapter.ts +0 -310
- package/src/types/agent.ts +0 -79
- package/src/types/did.ts +0 -223
- package/src/types/eip712.ts +0 -175
- package/src/types/erc8004.ts +0 -293
- package/src/types/escrow.ts +0 -27
- package/src/types/index.ts +0 -17
- package/src/types/message.ts +0 -145
- package/src/types/state.ts +0 -87
- package/src/types/transaction.ts +0 -69
- package/src/types/x402.ts +0 -251
- package/src/utils/ErrorRecoveryGuide.ts +0 -676
- package/src/utils/Helpers.ts +0 -688
- package/src/utils/IPFSClient.ts +0 -368
- package/src/utils/Logger.ts +0 -484
- package/src/utils/NonceManager.ts +0 -591
- package/src/utils/RateLimiter.ts +0 -534
- package/src/utils/ReceivedNonceTracker.ts +0 -567
- package/src/utils/SDKLifecycle.ts +0 -416
- package/src/utils/SecureNonce.ts +0 -78
- package/src/utils/Semaphore.ts +0 -276
- package/src/utils/UsedAttestationTracker.ts +0 -385
- package/src/utils/canonicalJson.ts +0 -38
- package/src/utils/circuitBreaker.ts +0 -324
- package/src/utils/computeTypeHash.ts +0 -48
- package/src/utils/fsSafe.ts +0 -80
- package/src/utils/index.ts +0 -80
- package/src/utils/retry.ts +0 -364
- package/src/utils/security.ts +0 -418
- package/src/utils/validation.ts +0 -540
- package/src/wallet/AutoWalletProvider.ts +0 -299
- package/src/wallet/EOAWalletProvider.ts +0 -69
- package/src/wallet/IWalletProvider.ts +0 -135
- package/src/wallet/aa/BundlerClient.ts +0 -274
- package/src/wallet/aa/DualNonceManager.ts +0 -173
- package/src/wallet/aa/PaymasterClient.ts +0 -174
- package/src/wallet/aa/TransactionBatcher.ts +0 -353
- package/src/wallet/aa/UserOpBuilder.ts +0 -246
- package/src/wallet/aa/constants.ts +0 -60
- 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
|
-
}
|