@agirails/sdk 2.5.2 → 2.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ACTPClient.d.ts +18 -0
- package/dist/ACTPClient.d.ts.map +1 -1
- package/dist/ACTPClient.js +67 -22
- package/dist/ACTPClient.js.map +1 -1
- package/dist/adapters/BasicAdapter.d.ts +12 -0
- package/dist/adapters/BasicAdapter.d.ts.map +1 -1
- package/dist/adapters/BasicAdapter.js +30 -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 +45 -11
- 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/cli/utils/client.d.ts.map +1 -1
- package/dist/cli/utils/client.js +1 -0
- package/dist/cli/utils/client.js.map +1 -1
- package/dist/config/networks.d.ts +2 -2
- package/dist/config/networks.d.ts.map +1 -1
- package/dist/config/networks.js +27 -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/runtime/MockStateManager.d.ts.map +1 -1
- package/dist/runtime/MockStateManager.js +2 -1
- package/dist/runtime/MockStateManager.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.map +1 -1
- package/dist/wallet/AutoWalletProvider.js +52 -18
- package/dist/wallet/AutoWalletProvider.js.map +1 -1
- package/dist/wallet/SmartWalletRouter.d.ts +116 -0
- package/dist/wallet/SmartWalletRouter.d.ts.map +1 -0
- package/dist/wallet/SmartWalletRouter.js +212 -0
- package/dist/wallet/SmartWalletRouter.js.map +1 -0
- package/dist/wallet/aa/DualNonceManager.d.ts +19 -0
- package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
- package/dist/wallet/aa/DualNonceManager.js +100 -5
- 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 -251
- 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 -576
- 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,1015 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BlockchainRuntime - Real blockchain implementation of ACTP protocol
|
|
3
|
-
*
|
|
4
|
-
* Implements IACTPRuntime interface using actual smart contracts deployed
|
|
5
|
-
* on Base Sepolia (testnet) or Base Mainnet (production).
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Real blockchain transactions via ethers.js
|
|
9
|
-
* - ACTPKernel smart contract integration
|
|
10
|
-
* - EscrowVault contract integration
|
|
11
|
-
* - EIP-712 message signing support
|
|
12
|
-
* - Event monitoring and indexing
|
|
13
|
-
* - Gas optimization
|
|
14
|
-
*
|
|
15
|
-
* @module runtime/BlockchainRuntime
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { ethers, Signer, JsonRpcProvider, keccak256, toUtf8Bytes } from 'ethers';
|
|
19
|
-
import { ACTPKernel } from '../protocol/ACTPKernel';
|
|
20
|
-
import { EscrowVault } from '../protocol/EscrowVault';
|
|
21
|
-
import { EventMonitor } from '../protocol/EventMonitor';
|
|
22
|
-
import { MessageSigner } from '../protocol/MessageSigner';
|
|
23
|
-
import { EASHelper, EASConfig } from '../protocol/EASHelper';
|
|
24
|
-
import { NetworkConfig, getNetwork } from '../config/networks';
|
|
25
|
-
import { IACTPRuntime, CreateTransactionParams } from './IACTPRuntime';
|
|
26
|
-
import { MockTransaction, TransactionState } from './types/MockState';
|
|
27
|
-
import { ValidationError } from '../errors';
|
|
28
|
-
import { ServiceHash, DisputeWindow } from '../utils/Helpers';
|
|
29
|
-
import { IUsedAttestationTracker, createUsedAttestationTracker } from '../utils/UsedAttestationTracker';
|
|
30
|
-
import { IReceivedNonceTracker, createReceivedNonceTracker } from '../utils/ReceivedNonceTracker';
|
|
31
|
-
import { sdkLogger } from '../utils/Logger';
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Configuration for BlockchainRuntime
|
|
35
|
-
*/
|
|
36
|
-
export interface BlockchainRuntimeConfig {
|
|
37
|
-
/** Network to connect to */
|
|
38
|
-
network: 'base-sepolia' | 'base-mainnet';
|
|
39
|
-
/** Ethers signer for transaction signing */
|
|
40
|
-
signer: Signer;
|
|
41
|
-
/** Ethers provider for blockchain queries */
|
|
42
|
-
provider: JsonRpcProvider;
|
|
43
|
-
/** Optional contract address overrides */
|
|
44
|
-
contracts?: {
|
|
45
|
-
actpKernel?: string;
|
|
46
|
-
escrowVault?: string;
|
|
47
|
-
usdc?: string;
|
|
48
|
-
eas?: string;
|
|
49
|
-
};
|
|
50
|
-
/** Optional gas settings */
|
|
51
|
-
gasSettings?: {
|
|
52
|
-
maxFeePerGas?: bigint;
|
|
53
|
-
maxPriorityFeePerGas?: bigint;
|
|
54
|
-
};
|
|
55
|
-
/** EAS (Ethereum Attestation Service) configuration for delivery proof verification */
|
|
56
|
-
easConfig?: EASConfig;
|
|
57
|
-
/**
|
|
58
|
-
* SECURITY FIX (CRITICAL-2): Require attestation verification before escrow release
|
|
59
|
-
* When true, releaseEscrow() will require a valid EAS attestation
|
|
60
|
-
* Default: false for backward compatibility, SHOULD be true in production
|
|
61
|
-
*/
|
|
62
|
-
requireAttestation?: boolean;
|
|
63
|
-
/**
|
|
64
|
-
* State directory for persistent attestation tracking
|
|
65
|
-
* If provided, attestation replay protection will survive restarts
|
|
66
|
-
*/
|
|
67
|
-
stateDirectory?: string;
|
|
68
|
-
/**
|
|
69
|
-
* Number of block confirmations to wait after each state-changing tx.
|
|
70
|
-
* Default: 2 (Base L2 reorg safety). Set to 1 on testnet for speed.
|
|
71
|
-
*/
|
|
72
|
-
confirmations?: number;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* BlockchainRuntime - Production blockchain implementation
|
|
77
|
-
*
|
|
78
|
-
* Bridges the IACTPRuntime interface to actual smart contracts.
|
|
79
|
-
* Provides seamless migration path from MockRuntime to production.
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* ```typescript
|
|
83
|
-
* const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
84
|
-
* const signer = new ethers.Wallet(privateKey, provider);
|
|
85
|
-
*
|
|
86
|
-
* const runtime = new BlockchainRuntime({
|
|
87
|
-
* network: 'base-sepolia',
|
|
88
|
-
* signer,
|
|
89
|
-
* provider
|
|
90
|
-
* });
|
|
91
|
-
*
|
|
92
|
-
* // Now use with adapters
|
|
93
|
-
* const adapter = new BasicAdapter(runtime, requesterAddress);
|
|
94
|
-
* await adapter.createJob({...});
|
|
95
|
-
* ```
|
|
96
|
-
*/
|
|
97
|
-
export class BlockchainRuntime implements IACTPRuntime {
|
|
98
|
-
private readonly kernel: ACTPKernel;
|
|
99
|
-
private readonly escrow: EscrowVault;
|
|
100
|
-
private readonly events: EventMonitor;
|
|
101
|
-
// SECURITY FIX (H-4): MessageSigner created via factory in initialize()
|
|
102
|
-
private messageSigner: MessageSigner | null = null;
|
|
103
|
-
// SECURITY FIX (CRITICAL-2): EAS helper for attestation verification
|
|
104
|
-
private easHelper: EASHelper | null = null;
|
|
105
|
-
// SECURITY FIX (HIGH-3): Attestation tracker for replay protection
|
|
106
|
-
private readonly attestationTracker: IUsedAttestationTracker;
|
|
107
|
-
// SECURITY FIX (CRITICAL-2): Flag to require attestation before release
|
|
108
|
-
private readonly requireAttestation: boolean;
|
|
109
|
-
// SECURITY FIX (MEDIUM-9): Nonce tracker for message replay protection
|
|
110
|
-
private readonly nonceTracker: IReceivedNonceTracker;
|
|
111
|
-
private readonly networkConfig: NetworkConfig;
|
|
112
|
-
private readonly provider: JsonRpcProvider;
|
|
113
|
-
private readonly signer: Signer;
|
|
114
|
-
private readonly easConfig?: EASConfig;
|
|
115
|
-
|
|
116
|
-
// SECURITY FIX (HIGH-3): Provider reconnection with exponential backoff
|
|
117
|
-
private reconnectAttempts = 0;
|
|
118
|
-
private readonly maxReconnectAttempts = 5;
|
|
119
|
-
private readonly baseReconnectDelay = 1000; // 1 second
|
|
120
|
-
private lastConnectionCheck = 0;
|
|
121
|
-
private readonly connectionCheckInterval = 30000; // 30 seconds
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Create new BlockchainRuntime instance
|
|
125
|
-
*
|
|
126
|
-
* @param config - Runtime configuration
|
|
127
|
-
*/
|
|
128
|
-
constructor(config: BlockchainRuntimeConfig) {
|
|
129
|
-
this.provider = config.provider;
|
|
130
|
-
this.signer = config.signer;
|
|
131
|
-
|
|
132
|
-
// Get network configuration
|
|
133
|
-
this.networkConfig = getNetwork(config.network);
|
|
134
|
-
|
|
135
|
-
// Apply contract address overrides if provided
|
|
136
|
-
if (config.contracts) {
|
|
137
|
-
this.networkConfig = {
|
|
138
|
-
...this.networkConfig,
|
|
139
|
-
contracts: {
|
|
140
|
-
...this.networkConfig.contracts,
|
|
141
|
-
...config.contracts,
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// NOTE (GAS DEFAULTS):
|
|
147
|
-
// We intentionally do NOT force default maxFee/maxPriority caps unless the caller
|
|
148
|
-
// explicitly provides gasSettings. Hardcoded caps can cause "insufficient funds for
|
|
149
|
-
// intrinsic transaction cost" even when the wallet has enough ETH for the *actual*
|
|
150
|
-
// network fee (ethers uses maxFee * gasLimit for the balance check).
|
|
151
|
-
|
|
152
|
-
// SECURITY FIX (CRITICAL-2): Store EAS config for initialization
|
|
153
|
-
this.easConfig = config.easConfig;
|
|
154
|
-
|
|
155
|
-
// SECURITY FIX (CRITICAL-2): Default to NOT requiring attestation for backward compatibility
|
|
156
|
-
// Production deployments SHOULD set this to true
|
|
157
|
-
this.requireAttestation = config.requireAttestation ?? false;
|
|
158
|
-
|
|
159
|
-
// SECURITY FIX (HIGH-3): Create attestation tracker with optional persistence
|
|
160
|
-
// If stateDirectory is provided, attestations survive process restarts
|
|
161
|
-
this.attestationTracker = createUsedAttestationTracker(config.stateDirectory);
|
|
162
|
-
|
|
163
|
-
// SECURITY FIX (MEDIUM-9): Create nonce tracker for message replay protection
|
|
164
|
-
// Uses memory-efficient strategy (tracks highest nonce per sender+type)
|
|
165
|
-
this.nonceTracker = createReceivedNonceTracker('memory-efficient');
|
|
166
|
-
|
|
167
|
-
// Initialize protocol modules
|
|
168
|
-
this.kernel = new ACTPKernel(
|
|
169
|
-
this.networkConfig.contracts.actpKernel,
|
|
170
|
-
this.signer,
|
|
171
|
-
config.gasSettings,
|
|
172
|
-
config.confirmations
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
this.escrow = new EscrowVault(
|
|
176
|
-
this.networkConfig.contracts.escrowVault,
|
|
177
|
-
this.signer,
|
|
178
|
-
config.gasSettings
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
// SECURITY FIX (C-3): Use public getters instead of private field access
|
|
182
|
-
this.events = new EventMonitor(
|
|
183
|
-
this.kernel.getContract(),
|
|
184
|
-
this.escrow.getContract()
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
// SECURITY FIX (H-4): MessageSigner is created in initialize() using factory pattern
|
|
188
|
-
// This ensures EIP-712 domain is always properly initialized before use
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Initialize async components (must be called after construction)
|
|
193
|
-
*
|
|
194
|
-
* CRITICAL: This method MUST be called before using the runtime.
|
|
195
|
-
* It initializes the MessageSigner with proper EIP-712 domain and
|
|
196
|
-
* optionally the EASHelper for attestation verification.
|
|
197
|
-
*
|
|
198
|
-
* SECURITY FIX (CHAINID-VALIDATION): Validates that the connected network
|
|
199
|
-
* matches the expected network configuration to prevent cross-chain attacks.
|
|
200
|
-
*
|
|
201
|
-
* @example
|
|
202
|
-
* ```typescript
|
|
203
|
-
* const runtime = new BlockchainRuntime(config);
|
|
204
|
-
* await runtime.initialize();
|
|
205
|
-
* ```
|
|
206
|
-
*/
|
|
207
|
-
async initialize(): Promise<void> {
|
|
208
|
-
// SECURITY FIX (CHAINID-VALIDATION): Verify connected network matches config
|
|
209
|
-
// This prevents:
|
|
210
|
-
// 1. Cross-chain replay attacks (signing for one chain, replaying on another)
|
|
211
|
-
// 2. Misconfigured RPC endpoints (connecting to wrong network)
|
|
212
|
-
// 3. Man-in-the-middle RPC attacks (redirected to wrong chain)
|
|
213
|
-
try {
|
|
214
|
-
const network = await this.provider.getNetwork();
|
|
215
|
-
const connectedChainId = Number(network.chainId);
|
|
216
|
-
const expectedChainId = this.networkConfig.chainId;
|
|
217
|
-
|
|
218
|
-
if (connectedChainId !== expectedChainId) {
|
|
219
|
-
throw new Error(
|
|
220
|
-
`Network mismatch: Connected to chainId ${connectedChainId}, ` +
|
|
221
|
-
`but expected ${expectedChainId} (${this.networkConfig.name}). ` +
|
|
222
|
-
`This could indicate a misconfigured RPC endpoint or cross-chain attack. ` +
|
|
223
|
-
`Please verify your RPC URL points to the correct network.`
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
sdkLogger.info(`Connected to ${this.networkConfig.name}`, { chainId: connectedChainId });
|
|
228
|
-
} catch (error) {
|
|
229
|
-
if (error instanceof Error && error.message.includes('Network mismatch')) {
|
|
230
|
-
throw error; // Re-throw our validation error
|
|
231
|
-
}
|
|
232
|
-
// For other errors (e.g., network issues), log warning but continue
|
|
233
|
-
// This allows initialization to proceed even if network check fails temporarily
|
|
234
|
-
sdkLogger.warn('Could not verify network chainId, proceeding with expected value', {
|
|
235
|
-
error: error instanceof Error ? error.message : String(error),
|
|
236
|
-
expectedChainId: this.networkConfig.chainId,
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// SECURITY FIX (H-4): Use factory pattern to guarantee domain initialization
|
|
241
|
-
// This prevents runtime errors from uninitialized domain
|
|
242
|
-
// SECURITY FIX (MEDIUM-9): Wire nonce tracker for message replay protection
|
|
243
|
-
this.messageSigner = await MessageSigner.create(
|
|
244
|
-
this.signer,
|
|
245
|
-
this.networkConfig.contracts.actpKernel,
|
|
246
|
-
{
|
|
247
|
-
chainId: this.networkConfig.chainId,
|
|
248
|
-
nonceTracker: this.nonceTracker,
|
|
249
|
-
}
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
// SECURITY FIX (CRITICAL-2): Initialize EAS helper if config provided
|
|
253
|
-
// This enables attestation verification for escrow release
|
|
254
|
-
if (this.easConfig) {
|
|
255
|
-
this.easHelper = new EASHelper(
|
|
256
|
-
this.signer,
|
|
257
|
-
this.easConfig,
|
|
258
|
-
this.attestationTracker
|
|
259
|
-
);
|
|
260
|
-
} else if (this.requireAttestation) {
|
|
261
|
-
sdkLogger.warn('requireAttestation is true but no EAS config provided - attestation verification will fail');
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Check if runtime has been initialized
|
|
267
|
-
* @returns true if initialize() has been called
|
|
268
|
-
*/
|
|
269
|
-
isInitialized(): boolean {
|
|
270
|
-
return this.messageSigner !== null;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Require initialization before use
|
|
275
|
-
*
|
|
276
|
-
* SECURITY FIX (M-4): Enforces initialize() call before operations
|
|
277
|
-
* @throws Error if initialize() has not been called
|
|
278
|
-
*/
|
|
279
|
-
private requireInitialized(): void {
|
|
280
|
-
if (!this.messageSigner) {
|
|
281
|
-
throw new Error(
|
|
282
|
-
'BlockchainRuntime not initialized. Call initialize() before using any methods. ' +
|
|
283
|
-
'This ensures proper EIP-712 domain setup and prevents runtime errors.'
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Ensure provider connection is healthy with automatic reconnection
|
|
290
|
-
*
|
|
291
|
-
* SECURITY FIX (HIGH-3): Implements exponential backoff reconnection
|
|
292
|
-
* to handle transient network failures gracefully.
|
|
293
|
-
*
|
|
294
|
-
* SECURITY FIX (H-4): Converted from recursive to iterative loop
|
|
295
|
-
* to prevent stack overflow on prolonged network failures.
|
|
296
|
-
*
|
|
297
|
-
* @throws Error if connection cannot be established after max attempts
|
|
298
|
-
*/
|
|
299
|
-
private async ensureConnected(): Promise<void> {
|
|
300
|
-
const now = Date.now();
|
|
301
|
-
|
|
302
|
-
// Skip check if we recently verified connection
|
|
303
|
-
if (now - this.lastConnectionCheck < this.connectionCheckInterval) {
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// SECURITY FIX (H-4): Iterative loop instead of recursion (prevents stack overflow)
|
|
308
|
-
for (let attempt = 0; attempt <= this.maxReconnectAttempts; attempt++) {
|
|
309
|
-
try {
|
|
310
|
-
// Test connection with a simple call
|
|
311
|
-
await this.provider.getNetwork();
|
|
312
|
-
|
|
313
|
-
// Connection successful
|
|
314
|
-
this.reconnectAttempts = 0;
|
|
315
|
-
this.lastConnectionCheck = now;
|
|
316
|
-
return; // Exit successfully
|
|
317
|
-
} catch (error) {
|
|
318
|
-
if (attempt < this.maxReconnectAttempts) {
|
|
319
|
-
// Not the last attempt - retry with exponential backoff
|
|
320
|
-
this.reconnectAttempts = attempt + 1;
|
|
321
|
-
const delay = this.baseReconnectDelay * Math.pow(2, attempt);
|
|
322
|
-
|
|
323
|
-
sdkLogger.warn('Provider connection lost, attempting reconnection', {
|
|
324
|
-
attempt: attempt + 1,
|
|
325
|
-
maxAttempts: this.maxReconnectAttempts,
|
|
326
|
-
delayMs: delay,
|
|
327
|
-
error: error instanceof Error ? error.message : String(error),
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
// Wait before next attempt
|
|
331
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
332
|
-
} else {
|
|
333
|
-
// Last attempt failed - throw error
|
|
334
|
-
throw new Error(
|
|
335
|
-
`Provider connection lost after ${this.maxReconnectAttempts} reconnection attempts. ` +
|
|
336
|
-
`Last error: ${error instanceof Error ? error.message : String(error)}. ` +
|
|
337
|
-
`Please check your network connectivity and RPC endpoint.`
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Get provider connection status
|
|
346
|
-
*
|
|
347
|
-
* SECURITY FIX (HIGH-3): Monitoring method for connection health
|
|
348
|
-
*
|
|
349
|
-
* @returns Connection status information
|
|
350
|
-
*/
|
|
351
|
-
getConnectionStatus(): {
|
|
352
|
-
isHealthy: boolean;
|
|
353
|
-
reconnectAttempts: number;
|
|
354
|
-
maxReconnectAttempts: number;
|
|
355
|
-
lastCheckTimestamp: number;
|
|
356
|
-
} {
|
|
357
|
-
return {
|
|
358
|
-
isHealthy: this.reconnectAttempts === 0,
|
|
359
|
-
reconnectAttempts: this.reconnectAttempts,
|
|
360
|
-
maxReconnectAttempts: this.maxReconnectAttempts,
|
|
361
|
-
lastCheckTimestamp: this.lastConnectionCheck,
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Creates a new transaction on-chain
|
|
367
|
-
*
|
|
368
|
-
* @param params - Transaction creation parameters
|
|
369
|
-
* @returns Promise resolving to transaction ID (bytes32 hex string)
|
|
370
|
-
*/
|
|
371
|
-
async createTransaction(params: CreateTransactionParams): Promise<string> {
|
|
372
|
-
// SECURITY FIX (M-4): Enforce initialization
|
|
373
|
-
this.requireInitialized();
|
|
374
|
-
|
|
375
|
-
// SECURITY FIX (HIGH-3): Ensure provider connection before transaction
|
|
376
|
-
await this.ensureConnected();
|
|
377
|
-
|
|
378
|
-
// Validate parameters
|
|
379
|
-
if (!params.provider || !ethers.isAddress(params.provider)) {
|
|
380
|
-
throw new ValidationError('provider', 'Invalid provider address');
|
|
381
|
-
}
|
|
382
|
-
if (!params.requester || !ethers.isAddress(params.requester)) {
|
|
383
|
-
throw new ValidationError('requester', 'Invalid requester address');
|
|
384
|
-
}
|
|
385
|
-
if (BigInt(params.amount) <= 0n) {
|
|
386
|
-
throw new ValidationError('amount', 'Amount must be positive');
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const now = Math.floor(Date.now() / 1000);
|
|
390
|
-
if (params.deadline <= now) {
|
|
391
|
-
throw new ValidationError('deadline', 'Deadline must be in the future');
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Call ACTPKernel contract
|
|
395
|
-
const txId = await this.kernel.createTransaction({
|
|
396
|
-
provider: params.provider,
|
|
397
|
-
requester: params.requester,
|
|
398
|
-
amount: BigInt(params.amount),
|
|
399
|
-
deadline: params.deadline,
|
|
400
|
-
disputeWindow: params.disputeWindow || 172800, // Default 2 days
|
|
401
|
-
// SECURITY FIX (CRITICAL): serviceDescription should be a bytes32 hash
|
|
402
|
-
// If caller passes raw string, it will fail on-chain. Basic/Standard API now hash before calling.
|
|
403
|
-
metadata: this.validateServiceHash(params.serviceDescription),
|
|
404
|
-
// ERC-8004 agent ID - pass through if provided (see ADR-001)
|
|
405
|
-
agentId: params.agentId,
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
return txId;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Links escrow to a transaction and locks funds
|
|
413
|
-
*
|
|
414
|
-
* SIMPLIFICATION (ESCROW-ID): Uses txId as escrowId.
|
|
415
|
-
* Per ACTP standard, escrowId = txId simplifies tracking and eliminates
|
|
416
|
-
* the need for separate escrowId→txId mapping.
|
|
417
|
-
*
|
|
418
|
-
* @param txId - Transaction ID
|
|
419
|
-
* @param amount - Amount to lock (must match transaction amount)
|
|
420
|
-
* @returns Promise resolving to escrow ID (same as txId)
|
|
421
|
-
*/
|
|
422
|
-
async linkEscrow(txId: string, amount: string): Promise<string> {
|
|
423
|
-
// SECURITY FIX (M-4): Enforce initialization
|
|
424
|
-
this.requireInitialized();
|
|
425
|
-
|
|
426
|
-
// SECURITY FIX (HIGH-3): Ensure provider connection before transaction
|
|
427
|
-
await this.ensureConnected();
|
|
428
|
-
|
|
429
|
-
// Validate transaction exists and get details
|
|
430
|
-
const tx = await this.getTransaction(txId);
|
|
431
|
-
if (!tx) {
|
|
432
|
-
throw new Error(`Transaction not found: ${txId}`);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// Validate state is INITIATED or QUOTED
|
|
436
|
-
if (tx.state !== 'INITIATED' && tx.state !== 'QUOTED') {
|
|
437
|
-
throw new Error(
|
|
438
|
-
`Cannot link escrow in current state ${tx.state}. Must be INITIATED or QUOTED.`
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// Validate amount matches transaction
|
|
443
|
-
if (amount !== tx.amount) {
|
|
444
|
-
throw new ValidationError('amount', 'Amount must match transaction amount');
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Approve USDC to escrow vault
|
|
448
|
-
await this.escrow.approveToken(this.networkConfig.contracts.usdc, BigInt(amount));
|
|
449
|
-
|
|
450
|
-
// SIMPLIFICATION (ESCROW-ID): Use txId as escrowId
|
|
451
|
-
// This aligns with ACTP standard where escrowId = txId
|
|
452
|
-
// Benefits: No mapping needed, simpler tracking, direct correlation
|
|
453
|
-
const escrowId = txId;
|
|
454
|
-
|
|
455
|
-
// Link escrow to transaction
|
|
456
|
-
await this.kernel.linkEscrow(txId, this.networkConfig.contracts.escrowVault, escrowId);
|
|
457
|
-
|
|
458
|
-
return escrowId;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Transitions a transaction to a new state
|
|
463
|
-
*
|
|
464
|
-
* SECURITY FIX (PROOF-PARAM): Added optional proof parameter for DELIVERED state.
|
|
465
|
-
* The kernel contract uses proof data for dispute window configuration and
|
|
466
|
-
* delivery verification. Without proof, default dispute window applies.
|
|
467
|
-
*
|
|
468
|
-
* @param txId - Transaction ID
|
|
469
|
-
* @param newState - Target state
|
|
470
|
-
* @param proof - Optional proof data (hex string, e.g., ABI-encoded delivery proof)
|
|
471
|
-
*/
|
|
472
|
-
async transitionState(txId: string, newState: TransactionState, proof?: string): Promise<void> {
|
|
473
|
-
// SECURITY FIX (M-4): Enforce initialization
|
|
474
|
-
this.requireInitialized();
|
|
475
|
-
|
|
476
|
-
// SECURITY FIX (HIGH-3): Ensure provider connection before transaction
|
|
477
|
-
await this.ensureConnected();
|
|
478
|
-
|
|
479
|
-
// Map TransactionState string to State enum value
|
|
480
|
-
const stateMap: Record<TransactionState, number> = {
|
|
481
|
-
INITIATED: 0,
|
|
482
|
-
QUOTED: 1,
|
|
483
|
-
COMMITTED: 2,
|
|
484
|
-
IN_PROGRESS: 3,
|
|
485
|
-
DELIVERED: 4,
|
|
486
|
-
SETTLED: 5,
|
|
487
|
-
DISPUTED: 6,
|
|
488
|
-
CANCELLED: 7,
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
const stateValue = stateMap[newState];
|
|
492
|
-
if (stateValue === undefined) {
|
|
493
|
-
throw new ValidationError('state', `Invalid state: ${newState}`);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// SECURITY FIX (PROOF-PARAM): Pass proof to kernel if provided
|
|
497
|
-
// Default to empty bytes (0x) if no proof provided
|
|
498
|
-
const proofBytes = proof || '0x';
|
|
499
|
-
await this.kernel.transitionState(txId, stateValue, proofBytes);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* Gets a transaction by ID
|
|
504
|
-
*
|
|
505
|
-
* @param txId - Transaction ID
|
|
506
|
-
* @returns Promise resolving to transaction or null if not found
|
|
507
|
-
*/
|
|
508
|
-
async getTransaction(txId: string): Promise<MockTransaction | null> {
|
|
509
|
-
try {
|
|
510
|
-
const tx = await this.kernel.getTransaction(txId);
|
|
511
|
-
|
|
512
|
-
// Check if transaction exists (zero address = not found)
|
|
513
|
-
if (tx.requester === ethers.ZeroAddress) {
|
|
514
|
-
return null;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// Map blockchain transaction to MockTransaction format
|
|
518
|
-
const stateMap: Record<number, TransactionState> = {
|
|
519
|
-
0: 'INITIATED',
|
|
520
|
-
1: 'QUOTED',
|
|
521
|
-
2: 'COMMITTED',
|
|
522
|
-
3: 'IN_PROGRESS',
|
|
523
|
-
4: 'DELIVERED',
|
|
524
|
-
5: 'SETTLED',
|
|
525
|
-
6: 'DISPUTED',
|
|
526
|
-
7: 'CANCELLED',
|
|
527
|
-
};
|
|
528
|
-
|
|
529
|
-
// SECURITY FIX (H-2): Throw error for unknown states instead of silent fallback
|
|
530
|
-
const mappedState = stateMap[tx.state];
|
|
531
|
-
if (mappedState === undefined) {
|
|
532
|
-
throw new Error(
|
|
533
|
-
`Unknown transaction state: ${tx.state}. ` +
|
|
534
|
-
`Valid states are 0-7 (INITIATED through CANCELLED). ` +
|
|
535
|
-
`This may indicate a contract version mismatch.`
|
|
536
|
-
);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
return {
|
|
540
|
-
id: txId,
|
|
541
|
-
provider: tx.provider,
|
|
542
|
-
requester: tx.requester,
|
|
543
|
-
amount: tx.amount.toString(),
|
|
544
|
-
state: mappedState,
|
|
545
|
-
deadline: Number(tx.deadline),
|
|
546
|
-
disputeWindow: tx.disputeWindow !== undefined ? Number(tx.disputeWindow) : 172800, // Default 2 days
|
|
547
|
-
escrowId: tx.escrowId,
|
|
548
|
-
createdAt: Number(tx.createdAt),
|
|
549
|
-
updatedAt: Number(tx.updatedAt),
|
|
550
|
-
// LIMITATION (V2): completedAt requires event indexing to fetch DELIVERED event timestamp.
|
|
551
|
-
// Currently 0, which means SDK-side dispute window check in releaseEscrow() is bypassed.
|
|
552
|
-
// On-chain contract still enforces dispute window correctly via _validateSettlementConditions().
|
|
553
|
-
// V2 will implement EventMonitor to track this properly.
|
|
554
|
-
completedAt: 0,
|
|
555
|
-
serviceDescription: '', // V2: Decode from on-chain serviceHash
|
|
556
|
-
deliveryProof: '', // V2: Fetch from EAS attestation
|
|
557
|
-
events: [], // V2: Populate via EventMonitor.getTransactionEvents()
|
|
558
|
-
};
|
|
559
|
-
} catch (error) {
|
|
560
|
-
// If contract call fails, return null
|
|
561
|
-
return null;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
/**
|
|
566
|
-
* Gets all transactions
|
|
567
|
-
*
|
|
568
|
-
* @returns Promise resolving to array of all transactions
|
|
569
|
-
*/
|
|
570
|
-
async getAllTransactions(): Promise<MockTransaction[]> {
|
|
571
|
-
// V2: Implement event-based transaction indexing via EventMonitor
|
|
572
|
-
// For now, return empty array as this requires off-chain indexer
|
|
573
|
-
sdkLogger.warn('getAllTransactions() not implemented - use EventMonitor for event-based queries');
|
|
574
|
-
return [];
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
/**
|
|
578
|
-
* Releases escrow funds to provider by settling the transaction
|
|
579
|
-
*
|
|
580
|
-
* SECURITY FIX (CRITICAL-2): This method now validates:
|
|
581
|
-
* 1. Transaction state is DELIVERED
|
|
582
|
-
* 2. Dispute window has elapsed
|
|
583
|
-
* 3. EAS attestation is valid (if requireAttestation is true)
|
|
584
|
-
*
|
|
585
|
-
* SECURITY FIX (SETTLEMENT-FLOW): Uses transitionState(SETTLED) instead of
|
|
586
|
-
* direct releaseEscrow() call. Per ACTPKernel.sol, settlement via state transition
|
|
587
|
-
* automatically handles escrow release through _releaseEscrow() internal call.
|
|
588
|
-
* This ensures proper state machine progression and event emission.
|
|
589
|
-
*
|
|
590
|
-
* SIMPLIFICATION (ESCROW-ID): Uses escrowId = txId standard.
|
|
591
|
-
* The on-chain contract uses txId as the escrow identifier, so we simply
|
|
592
|
-
* treat escrowId and txId as equivalent (no complex parsing needed).
|
|
593
|
-
*
|
|
594
|
-
* @param escrowId - Escrow ID (equivalent to txId in ACTP standard)
|
|
595
|
-
* @param attestationUID - Optional EAS attestation UID for verification
|
|
596
|
-
* @throws Error if transaction not found, not in DELIVERED state, or attestation invalid
|
|
597
|
-
*/
|
|
598
|
-
async releaseEscrow(escrowId: string, attestationUID?: string): Promise<void> {
|
|
599
|
-
// SECURITY FIX (M-4): Enforce initialization
|
|
600
|
-
this.requireInitialized();
|
|
601
|
-
|
|
602
|
-
// SECURITY FIX (HIGH-3): Ensure provider connection before transaction
|
|
603
|
-
await this.ensureConnected();
|
|
604
|
-
|
|
605
|
-
// SIMPLIFICATION (ESCROW-ID): escrowId = txId standard
|
|
606
|
-
// On-chain, escrowId IS the txId. No need for complex parsing.
|
|
607
|
-
// Support legacy format "escrow-{txId}-{timestamp}" for backward compatibility
|
|
608
|
-
let txId: string;
|
|
609
|
-
const legacyMatch = escrowId.match(/^escrow-(.+)-\d+$/);
|
|
610
|
-
if (legacyMatch) {
|
|
611
|
-
// Legacy SDK format - extract txId
|
|
612
|
-
txId = legacyMatch[1];
|
|
613
|
-
sdkLogger.warn('Using legacy escrowId format - please update to use txId directly as escrowId');
|
|
614
|
-
} else {
|
|
615
|
-
// Standard: escrowId = txId
|
|
616
|
-
txId = escrowId;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// SECURITY FIX (MEDIUM-1): Fetch transaction and validate state
|
|
620
|
-
const tx = await this.getTransaction(txId);
|
|
621
|
-
if (!tx) {
|
|
622
|
-
throw new Error(`Transaction not found: ${txId}`);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// SECURITY FIX (MEDIUM-1): Validate transaction is in DELIVERED state
|
|
626
|
-
if (tx.state !== 'DELIVERED') {
|
|
627
|
-
throw new Error(
|
|
628
|
-
`Cannot release escrow: transaction ${txId} is in state ${tx.state}, expected DELIVERED. ` +
|
|
629
|
-
`Escrow can only be released after delivery is confirmed.`
|
|
630
|
-
);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// SECURITY FIX (MEDIUM-1): Validate dispute window has elapsed
|
|
634
|
-
if (tx.completedAt && tx.disputeWindow) {
|
|
635
|
-
if (DisputeWindow.isActive(tx.completedAt, tx.disputeWindow)) {
|
|
636
|
-
const remaining = DisputeWindow.remaining(tx.completedAt, tx.disputeWindow);
|
|
637
|
-
throw new Error(
|
|
638
|
-
`Cannot release escrow: dispute window still active for transaction ${txId}. ` +
|
|
639
|
-
`Window expires in ${remaining} seconds. ` +
|
|
640
|
-
`Wait for dispute window to close before releasing funds.`
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// SECURITY FIX (CRITICAL-2): Verify EAS attestation if required
|
|
646
|
-
if (this.requireAttestation) {
|
|
647
|
-
if (!attestationUID) {
|
|
648
|
-
throw new Error(
|
|
649
|
-
`Cannot release escrow: attestation verification is required but no attestationUID provided. ` +
|
|
650
|
-
`Call releaseEscrow(escrowId, attestationUID) with a valid EAS attestation UID.`
|
|
651
|
-
);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
if (!this.easHelper) {
|
|
655
|
-
throw new Error(
|
|
656
|
-
`Cannot release escrow: attestation verification is required but EAS helper not initialized. ` +
|
|
657
|
-
`Provide easConfig in BlockchainRuntimeConfig and call initialize().`
|
|
658
|
-
);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
// Verify attestation is valid for this transaction
|
|
662
|
-
try {
|
|
663
|
-
await this.easHelper.verifyAndRecordForRelease(txId, attestationUID);
|
|
664
|
-
sdkLogger.info('Attestation verified for release', { txId, attestationUID });
|
|
665
|
-
} catch (error) {
|
|
666
|
-
throw new Error(
|
|
667
|
-
`Cannot release escrow: attestation verification failed for transaction ${txId}. ` +
|
|
668
|
-
`Error: ${error instanceof Error ? error.message : String(error)}`
|
|
669
|
-
);
|
|
670
|
-
}
|
|
671
|
-
} else if (attestationUID && this.easHelper) {
|
|
672
|
-
// Even if not required, verify attestation if provided (best effort)
|
|
673
|
-
try {
|
|
674
|
-
await this.easHelper.verifyAndRecordForRelease(txId, attestationUID);
|
|
675
|
-
sdkLogger.info('Attestation verified (optional)', { txId, attestationUID });
|
|
676
|
-
} catch (error) {
|
|
677
|
-
sdkLogger.warn('Attestation verification failed but not required, proceeding', {
|
|
678
|
-
txId,
|
|
679
|
-
error: error instanceof Error ? error.message : String(error),
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
} else {
|
|
683
|
-
// No attestation verification
|
|
684
|
-
sdkLogger.info('Settling transaction without attestation verification', { txId });
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// SECURITY FIX (SETTLEMENT-FLOW): Use transitionState(SETTLED) instead of releaseEscrow()
|
|
688
|
-
// Per ACTPKernel.sol, the settlement flow is:
|
|
689
|
-
// transitionState(txId, SETTLED, proof) → internally calls _releaseEscrow(txn)
|
|
690
|
-
// This ensures proper state machine progression and emits correct events.
|
|
691
|
-
// Direct kernel.releaseEscrow() may not properly update transaction state.
|
|
692
|
-
await this.kernel.transitionState(txId, 5); // 5 = State.SETTLED
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
/**
|
|
696
|
-
* Gets escrow balance
|
|
697
|
-
*
|
|
698
|
-
* MEDIUM: Returns the locked balance for a transaction from the escrow vault.
|
|
699
|
-
* Queries the EscrowVault contract for the actual balance.
|
|
700
|
-
*
|
|
701
|
-
* @param escrowId - Escrow ID or transaction ID
|
|
702
|
-
* @returns Promise resolving to balance as string (in USDC wei)
|
|
703
|
-
*/
|
|
704
|
-
async getEscrowBalance(escrowId: string): Promise<string> {
|
|
705
|
-
// SECURITY FIX (M-4): Enforce initialization
|
|
706
|
-
this.requireInitialized();
|
|
707
|
-
|
|
708
|
-
try {
|
|
709
|
-
// Try to get balance from escrow vault
|
|
710
|
-
// The escrow vault tracks balances by transaction ID
|
|
711
|
-
const match = escrowId.match(/^escrow-(.+)-\d+$/);
|
|
712
|
-
const txId = match ? match[1] : escrowId;
|
|
713
|
-
|
|
714
|
-
// Query the transaction to get the locked amount
|
|
715
|
-
const tx = await this.getTransaction(txId);
|
|
716
|
-
if (!tx) {
|
|
717
|
-
return '0';
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
// If transaction is in an active state (COMMITTED, IN_PROGRESS, DELIVERED),
|
|
721
|
-
// the escrow balance is the transaction amount
|
|
722
|
-
if (tx.state === 'COMMITTED' || tx.state === 'IN_PROGRESS' || tx.state === 'DELIVERED') {
|
|
723
|
-
return tx.amount;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// For settled or cancelled transactions, escrow is released
|
|
727
|
-
return '0';
|
|
728
|
-
} catch (error) {
|
|
729
|
-
// If query fails, return 0
|
|
730
|
-
sdkLogger.warn('getEscrowBalance query failed', { error: error instanceof Error ? error.message : String(error) });
|
|
731
|
-
return '0';
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
/**
|
|
736
|
-
* Get USDC balance for an address.
|
|
737
|
-
*
|
|
738
|
-
* Queries the USDC token contract's balanceOf function on-chain.
|
|
739
|
-
*
|
|
740
|
-
* @param address - Address to check balance for
|
|
741
|
-
* @returns Promise resolving to balance in USDC wei (6 decimals)
|
|
742
|
-
*/
|
|
743
|
-
async getBalance(address: string): Promise<string> {
|
|
744
|
-
this.requireInitialized();
|
|
745
|
-
|
|
746
|
-
const usdcContract = new ethers.Contract(
|
|
747
|
-
this.networkConfig.contracts.usdc,
|
|
748
|
-
['function balanceOf(address account) view returns (uint256)'],
|
|
749
|
-
this.provider
|
|
750
|
-
);
|
|
751
|
-
const balance = await usdcContract.balanceOf(address);
|
|
752
|
-
return balance.toString();
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
/**
|
|
756
|
-
* Time interface (uses real blockchain time)
|
|
757
|
-
*/
|
|
758
|
-
public readonly time = {
|
|
759
|
-
/**
|
|
760
|
-
* Get current blockchain timestamp
|
|
761
|
-
*/
|
|
762
|
-
now: (): number => {
|
|
763
|
-
return Math.floor(Date.now() / 1000);
|
|
764
|
-
},
|
|
765
|
-
};
|
|
766
|
-
|
|
767
|
-
// ============================================================================
|
|
768
|
-
// Utility Methods (Not in IACTPRuntime but useful for blockchain runtime)
|
|
769
|
-
// ============================================================================
|
|
770
|
-
|
|
771
|
-
/**
|
|
772
|
-
* Get the current signer address
|
|
773
|
-
*/
|
|
774
|
-
async getAddress(): Promise<string> {
|
|
775
|
-
return await this.signer.getAddress();
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
/**
|
|
779
|
-
* Get current block number
|
|
780
|
-
*/
|
|
781
|
-
async getBlockNumber(): Promise<number> {
|
|
782
|
-
return await this.provider.getBlockNumber();
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
/**
|
|
786
|
-
* Get network configuration
|
|
787
|
-
*/
|
|
788
|
-
getNetworkConfig(): NetworkConfig {
|
|
789
|
-
return this.networkConfig;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
/**
|
|
793
|
-
* Maximum transaction amount in USDC (human-readable).
|
|
794
|
-
*
|
|
795
|
-
* SECURITY: Limits exposure on unaudited mainnet contracts.
|
|
796
|
-
* Returns undefined if no limit (testnet).
|
|
797
|
-
*/
|
|
798
|
-
get maxTransactionAmount(): number | undefined {
|
|
799
|
-
return this.networkConfig.maxTransactionAmount;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
/**
|
|
803
|
-
* Get ACTPKernel instance (for advanced usage)
|
|
804
|
-
*/
|
|
805
|
-
getKernel(): ACTPKernel {
|
|
806
|
-
return this.kernel;
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
/**
|
|
810
|
-
* Get EscrowVault instance (for advanced usage)
|
|
811
|
-
*/
|
|
812
|
-
getEscrow(): EscrowVault {
|
|
813
|
-
return this.escrow;
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
/**
|
|
817
|
-
* Get EventMonitor instance (for advanced usage)
|
|
818
|
-
*/
|
|
819
|
-
getEvents(): EventMonitor {
|
|
820
|
-
return this.events;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
/**
|
|
824
|
-
* Get MessageSigner instance (for advanced usage)
|
|
825
|
-
*
|
|
826
|
-
* @throws Error if initialize() has not been called
|
|
827
|
-
*/
|
|
828
|
-
getMessageSigner(): MessageSigner {
|
|
829
|
-
if (!this.messageSigner) {
|
|
830
|
-
throw new Error(
|
|
831
|
-
'BlockchainRuntime not initialized. Call initialize() before using MessageSigner. ' +
|
|
832
|
-
'This is required for proper EIP-712 domain setup.'
|
|
833
|
-
);
|
|
834
|
-
}
|
|
835
|
-
return this.messageSigner;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
/**
|
|
839
|
-
* Get EASHelper instance (for attestation operations)
|
|
840
|
-
*
|
|
841
|
-
* @throws Error if EAS config not provided or initialize() not called
|
|
842
|
-
*/
|
|
843
|
-
getEASHelper(): EASHelper {
|
|
844
|
-
if (!this.easHelper) {
|
|
845
|
-
throw new Error(
|
|
846
|
-
'EASHelper not initialized. Provide easConfig in BlockchainRuntimeConfig and call initialize().'
|
|
847
|
-
);
|
|
848
|
-
}
|
|
849
|
-
return this.easHelper;
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
/**
|
|
853
|
-
* Get attestation tracker instance
|
|
854
|
-
*/
|
|
855
|
-
getAttestationTracker(): IUsedAttestationTracker {
|
|
856
|
-
return this.attestationTracker;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
/**
|
|
860
|
-
* Get nonce tracker instance (for monitoring/debugging)
|
|
861
|
-
*
|
|
862
|
-
* SECURITY FIX (MEDIUM-9): Exposed for monitoring nonce replay protection
|
|
863
|
-
*/
|
|
864
|
-
getNonceTracker(): IReceivedNonceTracker {
|
|
865
|
-
return this.nonceTracker;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
/**
|
|
869
|
-
* Check if attestation verification is required
|
|
870
|
-
*/
|
|
871
|
-
isAttestationRequired(): boolean {
|
|
872
|
-
return this.requireAttestation;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
/**
|
|
876
|
-
* Validate and normalize service hash for on-chain storage
|
|
877
|
-
*
|
|
878
|
-
* SECURITY FIX (CRITICAL): ACTPKernel expects bytes32 serviceHash.
|
|
879
|
-
* This method validates format and hashes raw strings if needed.
|
|
880
|
-
*
|
|
881
|
-
* @param serviceDescription - Service hash or description string
|
|
882
|
-
* @returns Valid bytes32 hash
|
|
883
|
-
*/
|
|
884
|
-
private validateServiceHash(serviceDescription?: string): string {
|
|
885
|
-
const ZERO_HASH = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
|
886
|
-
|
|
887
|
-
if (!serviceDescription) {
|
|
888
|
-
return ZERO_HASH;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
// If already a valid bytes32 hash, use it directly
|
|
892
|
-
if (ServiceHash.isValidHash(serviceDescription)) {
|
|
893
|
-
return serviceDescription;
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
// SECURITY FIX (CRITICAL): If it's a raw string (legacy format), hash it
|
|
897
|
-
// This ensures on-chain compatibility with the contract's bytes32 expectation
|
|
898
|
-
sdkLogger.warn('serviceDescription is not a valid bytes32 hash - hashing now (use ServiceHash.hash() for best practice)');
|
|
899
|
-
return keccak256(toUtf8Bytes(serviceDescription));
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
// ============================================================================
|
|
903
|
-
// Gas Estimation (M-2)
|
|
904
|
-
// ============================================================================
|
|
905
|
-
|
|
906
|
-
/**
|
|
907
|
-
* Estimate gas for createTransaction operation
|
|
908
|
-
*
|
|
909
|
-
* SECURITY FIX (M-2): Pre-transaction gas estimation helps:
|
|
910
|
-
* - Prevent failed transactions due to insufficient gas
|
|
911
|
-
* - Allow users to make informed decisions about costs
|
|
912
|
-
* - Catch potential issues before spending gas
|
|
913
|
-
*
|
|
914
|
-
* @param params - Transaction parameters
|
|
915
|
-
* @returns Estimated gas limit and cost in wei
|
|
916
|
-
*/
|
|
917
|
-
async estimateCreateTransactionGas(_params: CreateTransactionParams): Promise<{
|
|
918
|
-
gasLimit: bigint;
|
|
919
|
-
gasCostWei: bigint;
|
|
920
|
-
gasCostGwei: string;
|
|
921
|
-
}> {
|
|
922
|
-
// Get current gas price
|
|
923
|
-
const feeData = await this.provider.getFeeData();
|
|
924
|
-
const gasPrice = feeData.gasPrice ?? 0n;
|
|
925
|
-
|
|
926
|
-
// V2: Use kernel.estimateGas.createTransaction() for precise estimation
|
|
927
|
-
// Current: Conservative estimate based on typical createTransaction costs
|
|
928
|
-
const estimatedGasLimit = 150000n;
|
|
929
|
-
|
|
930
|
-
const gasCostWei = estimatedGasLimit * gasPrice;
|
|
931
|
-
const gasCostGwei = (Number(gasCostWei) / 1e9).toFixed(4);
|
|
932
|
-
|
|
933
|
-
return {
|
|
934
|
-
gasLimit: estimatedGasLimit,
|
|
935
|
-
gasCostWei,
|
|
936
|
-
gasCostGwei,
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
/**
|
|
941
|
-
* Estimate gas for linkEscrow operation
|
|
942
|
-
*
|
|
943
|
-
* @param txId - Transaction ID
|
|
944
|
-
* @returns Estimated gas limit and cost
|
|
945
|
-
*/
|
|
946
|
-
async estimateLinkEscrowGas(_txId: string): Promise<{
|
|
947
|
-
gasLimit: bigint;
|
|
948
|
-
gasCostWei: bigint;
|
|
949
|
-
gasCostGwei: string;
|
|
950
|
-
}> {
|
|
951
|
-
const feeData = await this.provider.getFeeData();
|
|
952
|
-
const gasPrice = feeData.gasPrice ?? 0n;
|
|
953
|
-
|
|
954
|
-
// linkEscrow includes USDC approve + contract call
|
|
955
|
-
const estimatedGasLimit = 200000n; // Conservative estimate
|
|
956
|
-
|
|
957
|
-
const gasCostWei = estimatedGasLimit * gasPrice;
|
|
958
|
-
const gasCostGwei = (Number(gasCostWei) / 1e9).toFixed(4);
|
|
959
|
-
|
|
960
|
-
return {
|
|
961
|
-
gasLimit: estimatedGasLimit,
|
|
962
|
-
gasCostWei,
|
|
963
|
-
gasCostGwei,
|
|
964
|
-
};
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
/**
|
|
968
|
-
* Estimate gas for state transition
|
|
969
|
-
*
|
|
970
|
-
* @param txId - Transaction ID
|
|
971
|
-
* @param newState - Target state
|
|
972
|
-
* @returns Estimated gas limit and cost
|
|
973
|
-
*/
|
|
974
|
-
async estimateTransitionGas(_txId: string, _newState: string): Promise<{
|
|
975
|
-
gasLimit: bigint;
|
|
976
|
-
gasCostWei: bigint;
|
|
977
|
-
gasCostGwei: string;
|
|
978
|
-
}> {
|
|
979
|
-
const feeData = await this.provider.getFeeData();
|
|
980
|
-
const gasPrice = feeData.gasPrice ?? 0n;
|
|
981
|
-
|
|
982
|
-
// State transitions are relatively cheap
|
|
983
|
-
const estimatedGasLimit = 80000n; // Conservative estimate
|
|
984
|
-
|
|
985
|
-
const gasCostWei = estimatedGasLimit * gasPrice;
|
|
986
|
-
const gasCostGwei = (Number(gasCostWei) / 1e9).toFixed(4);
|
|
987
|
-
|
|
988
|
-
return {
|
|
989
|
-
gasLimit: estimatedGasLimit,
|
|
990
|
-
gasCostWei,
|
|
991
|
-
gasCostGwei,
|
|
992
|
-
};
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
/**
|
|
996
|
-
* Get current gas price information
|
|
997
|
-
*
|
|
998
|
-
* @returns Current gas price data
|
|
999
|
-
*/
|
|
1000
|
-
async getGasPrice(): Promise<{
|
|
1001
|
-
gasPrice: bigint;
|
|
1002
|
-
gasPriceGwei: string;
|
|
1003
|
-
maxFeePerGas?: bigint;
|
|
1004
|
-
maxPriorityFeePerGas?: bigint;
|
|
1005
|
-
}> {
|
|
1006
|
-
const feeData = await this.provider.getFeeData();
|
|
1007
|
-
|
|
1008
|
-
return {
|
|
1009
|
-
gasPrice: feeData.gasPrice ?? 0n,
|
|
1010
|
-
gasPriceGwei: ((Number(feeData.gasPrice ?? 0n) / 1e9)).toFixed(4),
|
|
1011
|
-
maxFeePerGas: feeData.maxFeePerGas ?? undefined,
|
|
1012
|
-
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? undefined,
|
|
1013
|
-
};
|
|
1014
|
-
}
|
|
1015
|
-
}
|