@agirails/sdk 2.2.3 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -31
- package/dist/ACTPClient.d.ts +42 -1
- package/dist/ACTPClient.d.ts.map +1 -1
- package/dist/ACTPClient.js +207 -22
- package/dist/ACTPClient.js.map +1 -1
- package/dist/abi/AgentRegistry.json +133 -0
- package/dist/adapters/AdapterRouter.d.ts.map +1 -1
- package/dist/adapters/AdapterRouter.js.map +1 -1
- package/dist/adapters/BasicAdapter.d.ts +10 -1
- package/dist/adapters/BasicAdapter.d.ts.map +1 -1
- package/dist/adapters/BasicAdapter.js +36 -1
- package/dist/adapters/BasicAdapter.js.map +1 -1
- package/dist/adapters/X402Adapter.d.ts +34 -7
- package/dist/adapters/X402Adapter.d.ts.map +1 -1
- package/dist/adapters/X402Adapter.js +36 -8
- package/dist/adapters/X402Adapter.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/cli/commands/diff.d.ts +11 -0
- package/dist/cli/commands/diff.d.ts.map +1 -0
- package/dist/cli/commands/diff.js +115 -0
- package/dist/cli/commands/diff.js.map +1 -0
- package/dist/cli/commands/init.d.ts +1 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +260 -19
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/publish.d.ts +11 -0
- package/dist/cli/commands/publish.d.ts.map +1 -0
- package/dist/cli/commands/publish.js +170 -0
- package/dist/cli/commands/publish.js.map +1 -0
- package/dist/cli/commands/pull.d.ts +12 -0
- package/dist/cli/commands/pull.d.ts.map +1 -0
- package/dist/cli/commands/pull.js +99 -0
- package/dist/cli/commands/pull.js.map +1 -0
- package/dist/cli/commands/register.d.ts +16 -0
- package/dist/cli/commands/register.d.ts.map +1 -0
- package/dist/cli/commands/register.js +211 -0
- package/dist/cli/commands/register.js.map +1 -0
- package/dist/cli/index.js +10 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/utils/config.d.ts +6 -0
- package/dist/cli/utils/config.d.ts.map +1 -1
- package/dist/cli/utils/config.js.map +1 -1
- package/dist/config/agirailsmd.d.ts +94 -0
- package/dist/config/agirailsmd.d.ts.map +1 -0
- package/dist/config/agirailsmd.js +209 -0
- package/dist/config/agirailsmd.js.map +1 -0
- package/dist/config/networks.d.ts +22 -4
- package/dist/config/networks.d.ts.map +1 -1
- package/dist/config/networks.js +64 -26
- package/dist/config/networks.js.map +1 -1
- package/dist/config/publishPipeline.d.ts +75 -0
- package/dist/config/publishPipeline.d.ts.map +1 -0
- package/dist/config/publishPipeline.js +193 -0
- package/dist/config/publishPipeline.js.map +1 -0
- package/dist/config/syncOperations.d.ts +67 -0
- package/dist/config/syncOperations.d.ts.map +1 -0
- package/dist/config/syncOperations.js +208 -0
- package/dist/config/syncOperations.js.map +1 -0
- package/dist/erc8004/ERC8004Bridge.d.ts.map +1 -1
- package/dist/erc8004/ERC8004Bridge.js +6 -5
- package/dist/erc8004/ERC8004Bridge.js.map +1 -1
- package/dist/erc8004/ReputationReporter.d.ts.map +1 -1
- package/dist/erc8004/ReputationReporter.js +9 -12
- package/dist/erc8004/ReputationReporter.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/dist/level0/request.d.ts.map +1 -1
- package/dist/level0/request.js +23 -86
- package/dist/level0/request.js.map +1 -1
- package/dist/level1/Agent.d.ts +0 -11
- package/dist/level1/Agent.d.ts.map +1 -1
- package/dist/level1/Agent.js +19 -36
- package/dist/level1/Agent.js.map +1 -1
- package/dist/protocol/ACTPKernel.d.ts +7 -1
- package/dist/protocol/ACTPKernel.d.ts.map +1 -1
- package/dist/protocol/ACTPKernel.js +13 -10
- package/dist/protocol/ACTPKernel.js.map +1 -1
- package/dist/protocol/EventMonitor.d.ts +14 -0
- package/dist/protocol/EventMonitor.d.ts.map +1 -1
- package/dist/protocol/EventMonitor.js +14 -0
- package/dist/protocol/EventMonitor.js.map +1 -1
- package/dist/registry/AgentRegistryClient.d.ts +75 -0
- package/dist/registry/AgentRegistryClient.d.ts.map +1 -0
- package/dist/registry/AgentRegistryClient.js +160 -0
- package/dist/registry/AgentRegistryClient.js.map +1 -0
- package/dist/runtime/BlockchainRuntime.d.ts +5 -0
- package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
- package/dist/runtime/BlockchainRuntime.js +1 -1
- package/dist/runtime/BlockchainRuntime.js.map +1 -1
- package/dist/storage/ArchiveBundleBuilder.d.ts.map +1 -1
- package/dist/storage/ArchiveBundleBuilder.js.map +1 -1
- package/dist/storage/ArweaveClient.d.ts.map +1 -1
- package/dist/storage/ArweaveClient.js +2 -0
- package/dist/storage/ArweaveClient.js.map +1 -1
- package/dist/storage/FilebaseClient.d.ts.map +1 -1
- package/dist/storage/FilebaseClient.js +2 -0
- package/dist/storage/FilebaseClient.js.map +1 -1
- package/dist/types/adapter.d.ts +39 -0
- package/dist/types/adapter.d.ts.map +1 -1
- package/dist/types/adapter.js +7 -0
- package/dist/types/adapter.js.map +1 -1
- package/dist/types/x402.d.ts +23 -0
- package/dist/types/x402.d.ts.map +1 -1
- package/dist/types/x402.js.map +1 -1
- package/dist/utils/ErrorRecoveryGuide.d.ts.map +1 -1
- package/dist/utils/ErrorRecoveryGuide.js +3 -2
- package/dist/utils/ErrorRecoveryGuide.js.map +1 -1
- package/dist/utils/IPFSClient.d.ts +3 -2
- package/dist/utils/IPFSClient.d.ts.map +1 -1
- package/dist/utils/IPFSClient.js +7 -5
- package/dist/utils/IPFSClient.js.map +1 -1
- package/dist/utils/computeTypeHash.js +1 -3
- package/dist/utils/computeTypeHash.js.map +1 -1
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +0 -1
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/validation.d.ts +2 -2
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +2 -2
- package/dist/utils/validation.js.map +1 -1
- package/dist/wallet/AutoWalletProvider.d.ts +77 -0
- package/dist/wallet/AutoWalletProvider.d.ts.map +1 -0
- package/dist/wallet/AutoWalletProvider.js +197 -0
- package/dist/wallet/AutoWalletProvider.js.map +1 -0
- package/dist/wallet/EOAWalletProvider.d.ts +21 -0
- package/dist/wallet/EOAWalletProvider.d.ts.map +1 -0
- package/dist/wallet/EOAWalletProvider.js +57 -0
- package/dist/wallet/EOAWalletProvider.js.map +1 -0
- package/dist/wallet/IWalletProvider.d.ts +115 -0
- package/dist/wallet/IWalletProvider.d.ts.map +1 -0
- package/dist/wallet/IWalletProvider.js +12 -0
- package/dist/wallet/IWalletProvider.js.map +1 -0
- package/dist/wallet/aa/BundlerClient.d.ts +70 -0
- package/dist/wallet/aa/BundlerClient.d.ts.map +1 -0
- package/dist/wallet/aa/BundlerClient.js +183 -0
- package/dist/wallet/aa/BundlerClient.js.map +1 -0
- package/dist/wallet/aa/DualNonceManager.d.ts +55 -0
- package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -0
- package/dist/wallet/aa/DualNonceManager.js +131 -0
- package/dist/wallet/aa/DualNonceManager.js.map +1 -0
- package/dist/wallet/aa/PaymasterClient.d.ts +52 -0
- package/dist/wallet/aa/PaymasterClient.d.ts.map +1 -0
- package/dist/wallet/aa/PaymasterClient.js +115 -0
- package/dist/wallet/aa/PaymasterClient.js.map +1 -0
- package/dist/wallet/aa/TransactionBatcher.d.ts +87 -0
- package/dist/wallet/aa/TransactionBatcher.d.ts.map +1 -0
- package/dist/wallet/aa/TransactionBatcher.js +148 -0
- package/dist/wallet/aa/TransactionBatcher.js.map +1 -0
- package/dist/wallet/aa/UserOpBuilder.d.ts +71 -0
- package/dist/wallet/aa/UserOpBuilder.d.ts.map +1 -0
- package/dist/wallet/aa/UserOpBuilder.js +196 -0
- package/dist/wallet/aa/UserOpBuilder.js.map +1 -0
- package/dist/wallet/aa/constants.d.ts +54 -0
- package/dist/wallet/aa/constants.d.ts.map +1 -0
- package/dist/wallet/aa/constants.js +18 -0
- package/dist/wallet/aa/constants.js.map +1 -0
- package/dist/wallet/keystore.d.ts +16 -0
- package/dist/wallet/keystore.d.ts.map +1 -0
- package/dist/wallet/keystore.js +132 -0
- package/dist/wallet/keystore.js.map +1 -0
- package/package.json +5 -2
- package/src/ACTPClient.ts +275 -27
- package/src/abi/AgentRegistry.json +133 -0
- package/src/adapters/AdapterRouter.ts +0 -1
- package/src/adapters/BasicAdapter.ts +41 -1
- package/src/adapters/X402Adapter.ts +94 -16
- package/src/adapters/index.ts +9 -1
- package/src/cli/commands/diff.ts +141 -0
- package/src/cli/commands/init.ts +311 -22
- package/src/cli/commands/publish.ts +208 -0
- package/src/cli/commands/pull.ts +124 -0
- package/src/cli/commands/register.ts +233 -0
- package/src/cli/index.ts +12 -0
- package/src/cli/utils/config.ts +9 -0
- package/src/config/agirailsmd.ts +262 -0
- package/src/config/networks.ts +89 -26
- package/src/config/publishPipeline.ts +276 -0
- package/src/config/syncOperations.ts +279 -0
- package/src/erc8004/ERC8004Bridge.ts +6 -5
- package/src/erc8004/ReputationReporter.ts +14 -18
- package/src/index.ts +15 -0
- package/src/level0/request.ts +27 -88
- package/src/level1/Agent.ts +21 -37
- package/src/protocol/ACTPKernel.ts +20 -10
- package/src/protocol/EventMonitor.ts +14 -0
- package/src/registry/AgentRegistryClient.ts +202 -0
- package/src/runtime/BlockchainRuntime.ts +7 -1
- package/src/storage/ArchiveBundleBuilder.ts +0 -2
- package/src/storage/ArweaveClient.ts +2 -1
- package/src/storage/FilebaseClient.ts +3 -3
- package/src/types/adapter.ts +14 -0
- package/src/types/x402.ts +32 -0
- package/src/utils/ErrorRecoveryGuide.ts +4 -2
- package/src/utils/IPFSClient.ts +9 -7
- package/src/utils/computeTypeHash.ts +1 -3
- package/src/utils/retry.ts +0 -1
- package/src/utils/validation.ts +2 -2
- package/src/wallet/AutoWalletProvider.ts +294 -0
- package/src/wallet/EOAWalletProvider.ts +69 -0
- package/src/wallet/IWalletProvider.ts +133 -0
- package/src/wallet/aa/BundlerClient.ts +273 -0
- package/src/wallet/aa/DualNonceManager.ts +163 -0
- package/src/wallet/aa/PaymasterClient.ts +173 -0
- package/src/wallet/aa/TransactionBatcher.ts +240 -0
- package/src/wallet/aa/UserOpBuilder.ts +246 -0
- package/src/wallet/aa/constants.ts +60 -0
- package/src/wallet/keystore.ts +119 -0
package/src/level0/request.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { NoProviderFoundError, TimeoutError, ValidationError } from '../errors';
|
|
|
14
14
|
import { safeJSONParse, validateServiceName, isValidAddress } from '../utils/security';
|
|
15
15
|
import { Logger } from '../utils/Logger';
|
|
16
16
|
import { ethers } from 'ethers';
|
|
17
|
+
import { resolvePrivateKey } from '../wallet/keystore';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Request a service
|
|
@@ -61,7 +62,6 @@ export async function request(
|
|
|
61
62
|
service: string,
|
|
62
63
|
options: RequestOptions
|
|
63
64
|
): Promise<RequestResult> {
|
|
64
|
-
// SECURITY FIX (H-2): Validate service name to prevent injection
|
|
65
65
|
const validatedService = validateServiceName(service);
|
|
66
66
|
|
|
67
67
|
const logger = new Logger({ source: 'request' });
|
|
@@ -75,12 +75,8 @@ export async function request(
|
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
// SECURITY FIX (RPCURL): Use rpcUrl from options or fallback to network default
|
|
79
|
-
// This allows Level0 request() to work with testnet/mainnet without requiring
|
|
80
|
-
// explicit rpcUrl if user is okay with public RPC endpoints.
|
|
81
78
|
let rpcUrl = options.rpcUrl;
|
|
82
79
|
if (!rpcUrl && (options.network === 'testnet' || options.network === 'mainnet')) {
|
|
83
|
-
// Import getNetwork to get default rpcUrl from network config
|
|
84
80
|
const { getNetwork } = await import('../config/networks');
|
|
85
81
|
const networkName = options.network === 'testnet' ? 'base-sepolia' : 'base-mainnet';
|
|
86
82
|
const networkConfig = getNetwork(networkName);
|
|
@@ -88,27 +84,27 @@ export async function request(
|
|
|
88
84
|
logger.info(`Using default RPC URL for ${networkName}: ${rpcUrl}`);
|
|
89
85
|
}
|
|
90
86
|
|
|
91
|
-
|
|
87
|
+
const resolvedKey = await resolveKeyIfNeeded(options.wallet, options.network, options.stateDirectory);
|
|
88
|
+
const resolvedAddress = resolvedKey
|
|
89
|
+
? new ethers.Wallet(resolvedKey).address.toLowerCase()
|
|
90
|
+
: undefined;
|
|
91
|
+
|
|
92
92
|
const client = await ACTPClient.create({
|
|
93
93
|
mode: options.network === 'testnet' ? 'testnet' : options.network === 'mainnet' ? 'mainnet' : 'mock',
|
|
94
|
-
requesterAddress: getRequesterAddress(options.wallet),
|
|
94
|
+
requesterAddress: resolvedAddress || getRequesterAddress(options.wallet),
|
|
95
95
|
stateDirectory: options.stateDirectory,
|
|
96
|
-
privateKey: getPrivateKey(options.wallet),
|
|
96
|
+
privateKey: resolvedKey || getPrivateKey(options.wallet),
|
|
97
97
|
rpcUrl,
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
// Calculate deadline
|
|
101
100
|
const deadline = calculateDeadline(options.deadline, options.timeout);
|
|
102
|
-
|
|
103
|
-
// Create transaction with service metadata
|
|
104
101
|
const startTime = Date.now();
|
|
105
102
|
|
|
106
103
|
try {
|
|
107
|
-
const requesterAddress = getRequesterAddress(options.wallet);
|
|
104
|
+
const requesterAddress = resolvedAddress || getRequesterAddress(options.wallet);
|
|
108
105
|
const amountWei = (options.budget * 1_000_000).toString(); // Convert to USDC wei (6 decimals)
|
|
109
106
|
|
|
110
107
|
// In mock mode, ensure requester has enough funds
|
|
111
|
-
// This is a convenience feature for testing - won't exist in production
|
|
112
108
|
if (client.runtime && 'mintTokens' in client.runtime) {
|
|
113
109
|
const mockRuntime = client.runtime as any;
|
|
114
110
|
const balance = await mockRuntime.getBalance(requesterAddress);
|
|
@@ -122,30 +118,19 @@ export async function request(
|
|
|
122
118
|
}
|
|
123
119
|
}
|
|
124
120
|
|
|
125
|
-
// ARCHITECTURE FIX: Service metadata handling
|
|
126
|
-
// - MockRuntime: Store plaintext for provider matching and input extraction
|
|
127
|
-
// - BlockchainRuntime: Hashes plaintext internally before sending on-chain
|
|
128
|
-
//
|
|
129
|
-
// Provider needs plaintext to:
|
|
130
|
-
// 1. Match service name to handler (findServiceHandler)
|
|
131
|
-
// 2. Extract input data for job execution (extractJobInput)
|
|
132
|
-
//
|
|
133
|
-
// On-chain storage uses bytes32 hash (BlockchainRuntime.validateServiceHash handles this)
|
|
134
121
|
const serviceMetadata = JSON.stringify({
|
|
135
122
|
service: validatedService,
|
|
136
123
|
input: options.input,
|
|
137
124
|
timestamp: Date.now(),
|
|
138
125
|
});
|
|
139
126
|
|
|
140
|
-
// Create transaction with structured metadata
|
|
141
|
-
// MockRuntime stores as-is, BlockchainRuntime hashes for on-chain
|
|
142
127
|
const txId = await client.runtime.createTransaction({
|
|
143
128
|
provider,
|
|
144
129
|
requester: requesterAddress,
|
|
145
130
|
amount: amountWei,
|
|
146
131
|
deadline,
|
|
147
|
-
disputeWindow: options.disputeWindow ?? 172800,
|
|
148
|
-
serviceDescription: serviceMetadata,
|
|
132
|
+
disputeWindow: options.disputeWindow ?? 172800,
|
|
133
|
+
serviceDescription: serviceMetadata,
|
|
149
134
|
});
|
|
150
135
|
|
|
151
136
|
// Call onProgress if provided
|
|
@@ -187,12 +172,8 @@ export async function request(
|
|
|
187
172
|
attempts++;
|
|
188
173
|
}
|
|
189
174
|
|
|
190
|
-
// Check if we got a result
|
|
191
175
|
if (!tx || (tx.state !== 'DELIVERED' && tx.state !== 'SETTLED')) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
// SECURITY FIX (H-3): Auto-cancel transaction on timeout if still in early state
|
|
195
|
-
// This prevents funds from being locked indefinitely if provider never responds
|
|
176
|
+
// Auto-cancel on timeout if still in early state
|
|
196
177
|
if (tx && (tx.state === 'INITIATED' || tx.state === 'COMMITTED')) {
|
|
197
178
|
try {
|
|
198
179
|
logger.warn('Transaction timed out, cancelling to release funds', {
|
|
@@ -200,8 +181,6 @@ export async function request(
|
|
|
200
181
|
state: tx.state,
|
|
201
182
|
});
|
|
202
183
|
|
|
203
|
-
// ACTUALLY CANCEL THE TRANSACTION
|
|
204
|
-
// Check if runtime has cancelTransaction method
|
|
205
184
|
if ('cancelTransaction' in client.runtime) {
|
|
206
185
|
await (client.runtime as any).cancelTransaction(txId);
|
|
207
186
|
logger.info('Transaction cancelled successfully', { txId });
|
|
@@ -211,7 +190,6 @@ export async function request(
|
|
|
211
190
|
(error as any).wasCancelled = true;
|
|
212
191
|
throw error;
|
|
213
192
|
} else {
|
|
214
|
-
// Fallback: Transition to CANCELLED state
|
|
215
193
|
await client.runtime.transitionState(txId, 'CANCELLED');
|
|
216
194
|
logger.info('Transaction cancelled successfully (via transitionState)', { txId });
|
|
217
195
|
|
|
@@ -222,7 +200,6 @@ export async function request(
|
|
|
222
200
|
}
|
|
223
201
|
} catch (cancelError) {
|
|
224
202
|
logger.error('Failed to cancel timed-out transaction', { txId }, cancelError as Error);
|
|
225
|
-
// Continue with original timeout error
|
|
226
203
|
}
|
|
227
204
|
}
|
|
228
205
|
|
|
@@ -232,13 +209,8 @@ export async function request(
|
|
|
232
209
|
throw error;
|
|
233
210
|
}
|
|
234
211
|
|
|
235
|
-
// SECURITY FIX (C-3): Safe JSON parsing with schema validation
|
|
236
|
-
// Extract result from delivery proof
|
|
237
212
|
let deliveredResult: any = {};
|
|
238
213
|
if (tx.deliveryProof) {
|
|
239
|
-
// Define expected schema for delivery proof
|
|
240
|
-
// NOTE: 'type' field with value 'delivery.proof' is the unique wrapper marker
|
|
241
|
-
// (set by ProofGenerator.generateDeliveryProof) that handlers won't naturally return
|
|
242
214
|
const DELIVERY_PROOF_SCHEMA: Record<string, string> = {
|
|
243
215
|
result: 'any',
|
|
244
216
|
data: 'any',
|
|
@@ -247,28 +219,18 @@ export async function request(
|
|
|
247
219
|
timestamp: 'number',
|
|
248
220
|
contentHash: 'string',
|
|
249
221
|
txId: 'string',
|
|
250
|
-
type: 'string',
|
|
222
|
+
type: 'string',
|
|
251
223
|
};
|
|
252
224
|
|
|
253
|
-
// Use safeJSONParse with schema validation which:
|
|
254
|
-
// 1. Validates JSON structure
|
|
255
|
-
// 2. Removes __proto__, constructor, prototype properties
|
|
256
|
-
// 3. Prevents prototype pollution attacks
|
|
257
|
-
// 4. Validates against expected schema
|
|
258
|
-
// 5. Checks size limits to prevent DoS
|
|
259
|
-
// 6. Returns null if parsing fails
|
|
260
225
|
const parsed = safeJSONParse(tx.deliveryProof, DELIVERY_PROOF_SCHEMA);
|
|
261
226
|
|
|
262
227
|
if (parsed !== null) {
|
|
263
228
|
deliveredResult = parsed;
|
|
264
229
|
} else {
|
|
265
|
-
// If parsing failed, treat as plain text (but don't execute or eval)
|
|
266
230
|
deliveredResult = { data: tx.deliveryProof };
|
|
267
231
|
logger.warn('Failed to parse delivery proof as JSON', { txId });
|
|
268
232
|
}
|
|
269
233
|
} else if (options.network === 'testnet' || options.network === 'mainnet') {
|
|
270
|
-
// KNOWN LIMITATION: BlockchainRuntime doesn't fetch deliveryProof from IPFS yet
|
|
271
|
-
// Result will be empty until this is implemented
|
|
272
234
|
logger.warn(
|
|
273
235
|
'Delivery proof retrieval not yet implemented for testnet/mainnet. ' +
|
|
274
236
|
'Result may be empty. Use ACTPClient with manual proof handling for production.',
|
|
@@ -276,17 +238,11 @@ export async function request(
|
|
|
276
238
|
);
|
|
277
239
|
}
|
|
278
240
|
|
|
279
|
-
// SECURITY FIX (CRITICAL-2): Release escrow only after proper validation
|
|
280
|
-
// For mock mode, auto-release is safe. For testnet/mainnet, require attestation.
|
|
281
241
|
if (tx.state === 'DELIVERED' && tx.escrowId) {
|
|
282
|
-
// Wait for dispute window to expire
|
|
283
242
|
const disputeWindowEnd = (tx.completedAt ?? 0) + tx.disputeWindow;
|
|
284
243
|
const currentTime = client.runtime.time.now();
|
|
285
244
|
|
|
286
245
|
if (currentTime >= disputeWindowEnd) {
|
|
287
|
-
// SECURITY FIX (CRITICAL-2): Only auto-release in mock mode
|
|
288
|
-
// For real networks, the requester should manually verify and release
|
|
289
|
-
// or use attestation-based verification
|
|
290
246
|
const isMockMode = options.network !== 'testnet' && options.network !== 'mainnet';
|
|
291
247
|
|
|
292
248
|
if (isMockMode) {
|
|
@@ -353,6 +309,20 @@ export async function request(
|
|
|
353
309
|
}
|
|
354
310
|
}
|
|
355
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Resolve private key from keystore if wallet is auto/undefined and network is testnet/mainnet.
|
|
314
|
+
* Returns undefined if wallet is explicitly set (caller should use getPrivateKey instead).
|
|
315
|
+
*/
|
|
316
|
+
async function resolveKeyIfNeeded(
|
|
317
|
+
wallet?: 'auto' | 'connect' | string | { privateKey: string },
|
|
318
|
+
network?: string,
|
|
319
|
+
stateDirectory?: string
|
|
320
|
+
): Promise<string | undefined> {
|
|
321
|
+
if (wallet && wallet !== 'auto') return undefined; // explicit wallet, skip auto-detect
|
|
322
|
+
if (network !== 'testnet' && network !== 'mainnet') return undefined;
|
|
323
|
+
return resolvePrivateKey(stateDirectory);
|
|
324
|
+
}
|
|
325
|
+
|
|
356
326
|
/**
|
|
357
327
|
* Find provider for service
|
|
358
328
|
*
|
|
@@ -381,23 +351,10 @@ function findProvider(
|
|
|
381
351
|
return providers[0];
|
|
382
352
|
}
|
|
383
353
|
|
|
384
|
-
/**
|
|
385
|
-
* Get requester address from wallet option
|
|
386
|
-
*
|
|
387
|
-
* SECURITY FIX (HIGH): Properly derive addresses from private keys using ethers
|
|
388
|
-
* Never fabricate addresses or use partial key slices as addresses.
|
|
389
|
-
*
|
|
390
|
-
* @param wallet - Wallet configuration
|
|
391
|
-
* @returns Ethereum address
|
|
392
|
-
* @throws {ValidationError} If address format is invalid
|
|
393
|
-
*/
|
|
394
354
|
function getRequesterAddress(
|
|
395
355
|
wallet?: 'auto' | 'connect' | string | { privateKey: string }
|
|
396
356
|
): string {
|
|
397
|
-
// For mock mode only: generate deterministic address
|
|
398
|
-
// This is only safe because mock mode doesn't involve real funds
|
|
399
357
|
if (!wallet || wallet === 'auto') {
|
|
400
|
-
// Create a valid Ethereum address (40 hex chars) - ONLY for mock mode
|
|
401
358
|
const hex = Buffer.from('requester').toString('hex');
|
|
402
359
|
return '0x' + hex.padEnd(40, '0');
|
|
403
360
|
}
|
|
@@ -407,15 +364,12 @@ function getRequesterAddress(
|
|
|
407
364
|
}
|
|
408
365
|
|
|
409
366
|
if (typeof wallet === 'string') {
|
|
410
|
-
// SECURITY FIX (HIGH): Validate address format
|
|
411
367
|
if (!isValidAddress(wallet)) {
|
|
412
368
|
throw new ValidationError('wallet', `Invalid Ethereum address format: ${wallet}`);
|
|
413
369
|
}
|
|
414
370
|
return wallet.toLowerCase();
|
|
415
371
|
}
|
|
416
372
|
|
|
417
|
-
// SECURITY FIX (HIGH): Derive address from private key using ethers
|
|
418
|
-
// This is the correct way to get address from a private key
|
|
419
373
|
try {
|
|
420
374
|
const walletInstance = new ethers.Wallet(wallet.privateKey);
|
|
421
375
|
return walletInstance.address.toLowerCase();
|
|
@@ -424,15 +378,6 @@ function getRequesterAddress(
|
|
|
424
378
|
}
|
|
425
379
|
}
|
|
426
380
|
|
|
427
|
-
/**
|
|
428
|
-
* Get private key from wallet option
|
|
429
|
-
*
|
|
430
|
-
* SECURITY FIX (HIGH): Validate private key format before use
|
|
431
|
-
*
|
|
432
|
-
* @param wallet - Wallet configuration
|
|
433
|
-
* @returns Private key or undefined
|
|
434
|
-
* @throws {ValidationError} If private key format is invalid
|
|
435
|
-
*/
|
|
436
381
|
function getPrivateKey(
|
|
437
382
|
wallet?: 'auto' | 'connect' | string | { privateKey: string }
|
|
438
383
|
): string | undefined {
|
|
@@ -440,12 +385,8 @@ function getPrivateKey(
|
|
|
440
385
|
return undefined;
|
|
441
386
|
}
|
|
442
387
|
|
|
443
|
-
// If wallet is a string that looks like a private key (0x + 64 hex chars), use it
|
|
444
|
-
// Otherwise treat it as an address and return undefined
|
|
445
388
|
if (typeof wallet === 'string') {
|
|
446
|
-
// Check if it looks like a private key (0x + 64 hex chars)
|
|
447
389
|
if (/^0x[0-9a-fA-F]{64}$/.test(wallet)) {
|
|
448
|
-
// Validate by trying to create a wallet
|
|
449
390
|
try {
|
|
450
391
|
new ethers.Wallet(wallet);
|
|
451
392
|
return wallet;
|
|
@@ -453,11 +394,9 @@ function getPrivateKey(
|
|
|
453
394
|
throw new ValidationError('wallet', 'Invalid private key format');
|
|
454
395
|
}
|
|
455
396
|
}
|
|
456
|
-
// It's an address, not a private key
|
|
457
397
|
return undefined;
|
|
458
398
|
}
|
|
459
399
|
|
|
460
|
-
// Validate private key format
|
|
461
400
|
if (wallet.privateKey) {
|
|
462
401
|
try {
|
|
463
402
|
new ethers.Wallet(wallet.privateKey);
|
package/src/level1/Agent.ts
CHANGED
|
@@ -13,12 +13,13 @@ import * as os from 'os';
|
|
|
13
13
|
import * as fs from 'fs';
|
|
14
14
|
import { ethers } from 'ethers';
|
|
15
15
|
import { ACTPClient } from '../ACTPClient';
|
|
16
|
+
import { resolvePrivateKey } from '../wallet/keystore';
|
|
16
17
|
import { Job, JobHandler, JobContext } from './types/Job';
|
|
17
18
|
import { RequestOptions, RequestResult, NetworkOption } from './types/Options';
|
|
18
19
|
import { PricingStrategy } from './pricing/PricingStrategy';
|
|
19
20
|
import { AgentLifecycleError, ServiceConfigError, ValidationError } from '../errors';
|
|
20
21
|
import { validateServiceName, validatePath, LRUCache } from '../utils/security';
|
|
21
|
-
import { Logger } from '../utils/Logger';
|
|
22
|
+
import { Logger, sdkLogger } from '../utils/Logger';
|
|
22
23
|
import { ServiceHash } from '../utils/Helpers';
|
|
23
24
|
import { Semaphore } from '../utils/Semaphore';
|
|
24
25
|
import { ProofGenerator } from '../protocol/ProofGenerator';
|
|
@@ -386,12 +387,8 @@ export class Agent extends EventEmitter {
|
|
|
386
387
|
this.emit('starting');
|
|
387
388
|
|
|
388
389
|
try {
|
|
389
|
-
// SECURITY FIX (RPCURL): Use rpcUrl from config or fallback to network default
|
|
390
|
-
// This allows Agent to work with testnet/mainnet without requiring explicit rpcUrl
|
|
391
|
-
// if user is okay with public RPC endpoints.
|
|
392
390
|
let rpcUrl = this.config.rpcUrl;
|
|
393
391
|
if (!rpcUrl && (this.network === 'testnet' || this.network === 'mainnet')) {
|
|
394
|
-
// Import getNetwork to get default rpcUrl from network config
|
|
395
392
|
const { getNetwork } = await import('../config/networks');
|
|
396
393
|
const networkName = this.network === 'testnet' ? 'base-sepolia' : 'base-mainnet';
|
|
397
394
|
const networkConfig = getNetwork(networkName);
|
|
@@ -399,16 +396,14 @@ export class Agent extends EventEmitter {
|
|
|
399
396
|
this.logger.info(`Using default RPC URL for ${networkName}: ${rpcUrl}`);
|
|
400
397
|
}
|
|
401
398
|
|
|
402
|
-
// Initialize ACTP client
|
|
403
399
|
this._client = await ACTPClient.create({
|
|
404
400
|
mode: this.network === 'testnet' ? 'testnet' : this.network === 'mainnet' ? 'mainnet' : 'mock',
|
|
405
|
-
requesterAddress: this.address || this.generateAddress(),
|
|
401
|
+
requesterAddress: this.address || await this.generateAddress(),
|
|
406
402
|
stateDirectory: this.config.stateDirectory,
|
|
407
|
-
privateKey: this.getPrivateKey(),
|
|
403
|
+
privateKey: await this.getPrivateKey(),
|
|
408
404
|
rpcUrl,
|
|
409
405
|
});
|
|
410
406
|
|
|
411
|
-
// Start polling for jobs
|
|
412
407
|
this.startPolling();
|
|
413
408
|
|
|
414
409
|
this._status = 'running';
|
|
@@ -1316,21 +1311,21 @@ export class Agent extends EventEmitter {
|
|
|
1316
1311
|
log: {
|
|
1317
1312
|
debug: (message: string, meta?: any) => {
|
|
1318
1313
|
if (agent.config.logging?.level === 'debug') {
|
|
1319
|
-
|
|
1314
|
+
sdkLogger.debug(`[${job.id}] ${message}`, meta);
|
|
1320
1315
|
}
|
|
1321
1316
|
},
|
|
1322
1317
|
info: (message: string, meta?: any) => {
|
|
1323
1318
|
if (['debug', 'info'].includes(agent.config.logging?.level || 'info')) {
|
|
1324
|
-
|
|
1319
|
+
sdkLogger.info(`[${job.id}] ${message}`, meta);
|
|
1325
1320
|
}
|
|
1326
1321
|
},
|
|
1327
1322
|
warn: (message: string, meta?: any) => {
|
|
1328
1323
|
if (['debug', 'info', 'warn'].includes(agent.config.logging?.level || 'info')) {
|
|
1329
|
-
|
|
1324
|
+
sdkLogger.warn(`[${job.id}] ${message}`, meta);
|
|
1330
1325
|
}
|
|
1331
1326
|
},
|
|
1332
1327
|
error: (message: string, meta?: any) => {
|
|
1333
|
-
|
|
1328
|
+
sdkLogger.error(`[${job.id}] ${message}`, meta);
|
|
1334
1329
|
},
|
|
1335
1330
|
},
|
|
1336
1331
|
|
|
@@ -1371,15 +1366,8 @@ export class Agent extends EventEmitter {
|
|
|
1371
1366
|
}
|
|
1372
1367
|
}
|
|
1373
1368
|
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
*
|
|
1377
|
-
* SECURITY FIX (HIGH): For testnet/mainnet, MUST derive from private key.
|
|
1378
|
-
* For mock mode, can use deterministic address for convenience.
|
|
1379
|
-
*/
|
|
1380
|
-
private generateAddress(): string {
|
|
1381
|
-
// If wallet has private key, ALWAYS derive address from it
|
|
1382
|
-
const privateKey = this.getPrivateKey();
|
|
1369
|
+
private async generateAddress(): Promise<string> {
|
|
1370
|
+
const privateKey = await this.getPrivateKey();
|
|
1383
1371
|
if (privateKey) {
|
|
1384
1372
|
try {
|
|
1385
1373
|
const wallet = new ethers.Wallet(privateKey);
|
|
@@ -1389,33 +1377,31 @@ export class Agent extends EventEmitter {
|
|
|
1389
1377
|
}
|
|
1390
1378
|
}
|
|
1391
1379
|
|
|
1392
|
-
// For non-mock networks, require a valid private key or address
|
|
1393
1380
|
if (this.network === 'testnet' || this.network === 'mainnet') {
|
|
1394
1381
|
throw new ValidationError(
|
|
1395
1382
|
'wallet',
|
|
1396
|
-
`${this.network} mode requires a valid private key or address in wallet configuration`
|
|
1383
|
+
`${this.network} mode requires a valid private key or address in wallet configuration.\n` +
|
|
1384
|
+
'Run "actp init" to generate a keystore, or set ACTP_PRIVATE_KEY env var.'
|
|
1397
1385
|
);
|
|
1398
1386
|
}
|
|
1399
1387
|
|
|
1400
|
-
// For mock mode only: generate deterministic address from agent name
|
|
1401
|
-
// This is safe because mock mode doesn't involve real funds
|
|
1402
1388
|
return `0x${Buffer.from(this.name).toString('hex').padEnd(40, '0').slice(0, 40)}`;
|
|
1403
1389
|
}
|
|
1404
1390
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1391
|
+
private async getPrivateKey(): Promise<string | undefined> {
|
|
1392
|
+
if (!this.config.wallet || this.config.wallet === 'auto') {
|
|
1393
|
+
if (this.network === 'testnet' || this.network === 'mainnet') {
|
|
1394
|
+
return resolvePrivateKey(this.config.stateDirectory);
|
|
1395
|
+
}
|
|
1396
|
+
return undefined;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
if (this.config.wallet === 'connect') {
|
|
1412
1400
|
return undefined;
|
|
1413
1401
|
}
|
|
1414
1402
|
|
|
1415
1403
|
if (typeof this.config.wallet === 'string') {
|
|
1416
|
-
// Check if it looks like a private key (0x + 64 hex chars)
|
|
1417
1404
|
if (/^0x[0-9a-fA-F]{64}$/.test(this.config.wallet)) {
|
|
1418
|
-
// Validate by trying to create a wallet
|
|
1419
1405
|
try {
|
|
1420
1406
|
new ethers.Wallet(this.config.wallet);
|
|
1421
1407
|
return this.config.wallet;
|
|
@@ -1423,11 +1409,9 @@ export class Agent extends EventEmitter {
|
|
|
1423
1409
|
throw new ValidationError('wallet', 'Invalid private key format');
|
|
1424
1410
|
}
|
|
1425
1411
|
}
|
|
1426
|
-
// It's an address, not a private key
|
|
1427
1412
|
return undefined;
|
|
1428
1413
|
}
|
|
1429
1414
|
|
|
1430
|
-
// Validate private key format
|
|
1431
1415
|
if (this.config.wallet.privateKey) {
|
|
1432
1416
|
try {
|
|
1433
1417
|
new ethers.Wallet(this.config.wallet.privateKey);
|
|
@@ -37,14 +37,25 @@ interface GasOptions {
|
|
|
37
37
|
export class ACTPKernel {
|
|
38
38
|
private contract: Contract;
|
|
39
39
|
private readonly gasSettings?: GasOptions;
|
|
40
|
+
/**
|
|
41
|
+
* Number of block confirmations to wait after each state-changing tx.
|
|
42
|
+
* Default: 2 (Base L2 reorg safety — ~4-6 s on Base's 2 s blocks).
|
|
43
|
+
* Set to 1 for faster feedback on testnet; never set to 0 in production.
|
|
44
|
+
*/
|
|
45
|
+
private readonly confirmations: number;
|
|
40
46
|
|
|
41
47
|
constructor(
|
|
42
48
|
private readonly address: string,
|
|
43
49
|
signer: Signer,
|
|
44
|
-
gasSettings?: GasOptions
|
|
50
|
+
gasSettings?: GasOptions,
|
|
51
|
+
confirmations: number = 2
|
|
45
52
|
) {
|
|
53
|
+
if (confirmations < 1) {
|
|
54
|
+
throw new Error(`confirmations must be >= 1, got ${confirmations}`);
|
|
55
|
+
}
|
|
46
56
|
this.contract = new Contract(address, ACTPKernelABI, signer);
|
|
47
57
|
this.gasSettings = gasSettings;
|
|
58
|
+
this.confirmations = confirmations;
|
|
48
59
|
}
|
|
49
60
|
|
|
50
61
|
/**
|
|
@@ -213,7 +224,7 @@ export class ACTPKernel {
|
|
|
213
224
|
txOptions
|
|
214
225
|
);
|
|
215
226
|
|
|
216
|
-
const receipt = await tx.wait(
|
|
227
|
+
const receipt = await tx.wait(this.confirmations);
|
|
217
228
|
if (!receipt) {
|
|
218
229
|
throw new Error('Transaction receipt not available');
|
|
219
230
|
}
|
|
@@ -280,7 +291,7 @@ export class ACTPKernel {
|
|
|
280
291
|
|
|
281
292
|
const tx = await transitionFunc(txId, newState, proof, txOptions);
|
|
282
293
|
|
|
283
|
-
await tx.wait(
|
|
294
|
+
await tx.wait(this.confirmations);
|
|
284
295
|
} catch (error: any) {
|
|
285
296
|
throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
|
|
286
297
|
}
|
|
@@ -400,8 +411,7 @@ export class ACTPKernel {
|
|
|
400
411
|
|
|
401
412
|
const tx = await linkEscrowFunc(txId, escrowContract, escrowId, txOptions);
|
|
402
413
|
|
|
403
|
-
|
|
404
|
-
await tx.wait(2);
|
|
414
|
+
await tx.wait(this.confirmations);
|
|
405
415
|
} catch (error: any) {
|
|
406
416
|
throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
|
|
407
417
|
}
|
|
@@ -437,7 +447,7 @@ export class ACTPKernel {
|
|
|
437
447
|
|
|
438
448
|
const tx = await releaseMilestoneFunc(txId, amount, txOptions);
|
|
439
449
|
|
|
440
|
-
await tx.wait(
|
|
450
|
+
await tx.wait(this.confirmations);
|
|
441
451
|
} catch (error: any) {
|
|
442
452
|
throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
|
|
443
453
|
}
|
|
@@ -521,7 +531,7 @@ export class ACTPKernel {
|
|
|
521
531
|
|
|
522
532
|
const tx = await releaseEscrowFunc(txId, txOptions);
|
|
523
533
|
|
|
524
|
-
await tx.wait(
|
|
534
|
+
await tx.wait(this.confirmations);
|
|
525
535
|
} catch (error: any) {
|
|
526
536
|
throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
|
|
527
537
|
}
|
|
@@ -669,7 +679,7 @@ export class ACTPKernel {
|
|
|
669
679
|
txOptions
|
|
670
680
|
);
|
|
671
681
|
|
|
672
|
-
await tx.wait(
|
|
682
|
+
await tx.wait(this.confirmations);
|
|
673
683
|
} catch (error: any) {
|
|
674
684
|
throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
|
|
675
685
|
}
|
|
@@ -735,7 +745,7 @@ export class ACTPKernel {
|
|
|
735
745
|
txOptions
|
|
736
746
|
);
|
|
737
747
|
|
|
738
|
-
await tx.wait(
|
|
748
|
+
await tx.wait(this.confirmations);
|
|
739
749
|
} catch (error: any) {
|
|
740
750
|
throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
|
|
741
751
|
}
|
|
@@ -789,7 +799,7 @@ export class ACTPKernel {
|
|
|
789
799
|
txOptions
|
|
790
800
|
);
|
|
791
801
|
|
|
792
|
-
await tx.wait(
|
|
802
|
+
await tx.wait(this.confirmations);
|
|
793
803
|
} catch (error: any) {
|
|
794
804
|
throw new TransactionRevertedError(error.transactionHash, error.reason || error.message);
|
|
795
805
|
}
|
|
@@ -4,6 +4,20 @@ import { State, Transaction } from '../types';
|
|
|
4
4
|
/**
|
|
5
5
|
* EventMonitor - Listen to blockchain events
|
|
6
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
|
+
*
|
|
7
21
|
* SECURITY FIX (EVENT-MONITOR): Corrected event parameter order to match ABI.
|
|
8
22
|
* Per ACTPKernel.json, TransactionCreated signature is:
|
|
9
23
|
* (bytes32 indexed transactionId, address indexed requester, address indexed provider, uint256 amount, bytes32 serviceHash)
|