@agirails/sdk 2.3.1 → 2.4.0
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 +10 -12
- package/dist/ACTPClient.d.ts +80 -3
- package/dist/ACTPClient.d.ts.map +1 -1
- package/dist/ACTPClient.js +213 -57
- package/dist/ACTPClient.js.map +1 -1
- package/dist/adapters/BasicAdapter.d.ts +13 -1
- package/dist/adapters/BasicAdapter.d.ts.map +1 -1
- package/dist/adapters/BasicAdapter.js +24 -3
- package/dist/adapters/BasicAdapter.js.map +1 -1
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +82 -237
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/publish.d.ts +11 -3
- package/dist/cli/commands/publish.d.ts.map +1 -1
- package/dist/cli/commands/publish.js +319 -80
- package/dist/cli/commands/publish.js.map +1 -1
- package/dist/cli/commands/register.d.ts.map +1 -1
- package/dist/cli/commands/register.js +10 -0
- package/dist/cli/commands/register.js.map +1 -1
- package/dist/cli/utils/config.d.ts +17 -2
- package/dist/cli/utils/config.d.ts.map +1 -1
- package/dist/cli/utils/config.js +9 -1
- package/dist/cli/utils/config.js.map +1 -1
- package/dist/cli/utils/wallet.d.ts +31 -0
- package/dist/cli/utils/wallet.d.ts.map +1 -0
- package/dist/cli/utils/wallet.js +114 -0
- package/dist/cli/utils/wallet.js.map +1 -0
- package/dist/config/pendingPublish.d.ts +79 -0
- package/dist/config/pendingPublish.d.ts.map +1 -0
- package/dist/config/pendingPublish.js +167 -0
- package/dist/config/pendingPublish.js.map +1 -0
- package/dist/config/publishPipeline.d.ts +33 -0
- package/dist/config/publishPipeline.d.ts.map +1 -1
- package/dist/config/publishPipeline.js +33 -2
- package/dist/config/publishPipeline.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/wallet/AutoWalletProvider.d.ts +2 -1
- package/dist/wallet/AutoWalletProvider.d.ts.map +1 -1
- package/dist/wallet/AutoWalletProvider.js +6 -2
- package/dist/wallet/AutoWalletProvider.js.map +1 -1
- package/dist/wallet/IWalletProvider.d.ts +4 -2
- package/dist/wallet/IWalletProvider.d.ts.map +1 -1
- package/dist/wallet/aa/BundlerClient.d.ts.map +1 -1
- package/dist/wallet/aa/BundlerClient.js +2 -1
- package/dist/wallet/aa/BundlerClient.js.map +1 -1
- package/dist/wallet/aa/DualNonceManager.d.ts +2 -1
- package/dist/wallet/aa/DualNonceManager.d.ts.map +1 -1
- package/dist/wallet/aa/DualNonceManager.js +16 -5
- package/dist/wallet/aa/DualNonceManager.js.map +1 -1
- package/dist/wallet/aa/PaymasterClient.d.ts.map +1 -1
- package/dist/wallet/aa/PaymasterClient.js +2 -1
- package/dist/wallet/aa/PaymasterClient.js.map +1 -1
- package/dist/wallet/aa/TransactionBatcher.d.ts +54 -0
- package/dist/wallet/aa/TransactionBatcher.d.ts.map +1 -1
- package/dist/wallet/aa/TransactionBatcher.js +67 -1
- package/dist/wallet/aa/TransactionBatcher.js.map +1 -1
- package/package.json +1 -1
- package/src/ACTPClient.ts +265 -49
- package/src/adapters/BasicAdapter.ts +48 -12
- package/src/cli/commands/init.ts +81 -281
- package/src/cli/commands/publish.ts +354 -87
- package/src/cli/commands/register.ts +14 -0
- package/src/cli/utils/config.ts +32 -2
- package/src/cli/utils/wallet.ts +109 -0
- package/src/config/pendingPublish.ts +226 -0
- package/src/config/publishPipeline.ts +82 -1
- package/src/index.ts +8 -0
- package/src/wallet/AutoWalletProvider.ts +7 -2
- package/src/wallet/IWalletProvider.ts +4 -2
- package/src/wallet/aa/BundlerClient.ts +2 -1
- package/src/wallet/aa/DualNonceManager.ts +19 -9
- package/src/wallet/aa/PaymasterClient.ts +2 -1
- package/src/wallet/aa/TransactionBatcher.ts +113 -0
package/src/ACTPClient.ts
CHANGED
|
@@ -11,10 +11,9 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @example
|
|
13
13
|
* ```typescript
|
|
14
|
-
* // Create client
|
|
14
|
+
* // Create client (auto-detects wallet from .actp/keystore.json or env vars)
|
|
15
15
|
* const client = await ACTPClient.create({
|
|
16
16
|
* mode: 'mock',
|
|
17
|
-
* requesterAddress: '0x1234...',
|
|
18
17
|
* });
|
|
19
18
|
*
|
|
20
19
|
* // Basic API - simplest approach
|
|
@@ -57,6 +56,9 @@ import { getNetwork } from './config/networks';
|
|
|
57
56
|
import { IWalletProvider } from './wallet/IWalletProvider';
|
|
58
57
|
import { EOAWalletProvider } from './wallet/EOAWalletProvider';
|
|
59
58
|
import { AutoWalletProvider } from './wallet/AutoWalletProvider';
|
|
59
|
+
import { SmartWalletCall } from './wallet/aa/constants';
|
|
60
|
+
import { buildActivationBatch, ActivationScenario } from './wallet/aa/TransactionBatcher';
|
|
61
|
+
import { loadPendingPublish, deletePendingPublish, PendingPublish } from './config/pendingPublish';
|
|
60
62
|
import { sdkLogger } from './utils/Logger';
|
|
61
63
|
|
|
62
64
|
// ============================================================================
|
|
@@ -74,18 +76,24 @@ import { sdkLogger } from './utils/Logger';
|
|
|
74
76
|
* @param stateDirectory - The directory path to validate
|
|
75
77
|
* @throws Error if path is unsafe
|
|
76
78
|
*/
|
|
79
|
+
/** On-chain agent state from AgentRegistry. */
|
|
80
|
+
export interface OnChainAgentState {
|
|
81
|
+
registeredAt: bigint;
|
|
82
|
+
configHash: string;
|
|
83
|
+
listed: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const ZERO_HASH = '0x' + '0'.repeat(64);
|
|
87
|
+
|
|
77
88
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* Uses minimal ABI fragment to avoid importing the full AgentRegistry class.
|
|
82
|
-
* Checks registeredAt field of AgentProfile struct (> 0 means registered).
|
|
89
|
+
* Read the on-chain agent state from AgentRegistry.
|
|
90
|
+
* Returns registeredAt, configHash, and listed fields.
|
|
83
91
|
*/
|
|
84
|
-
async function
|
|
92
|
+
export async function getOnChainAgentState(
|
|
85
93
|
provider: ethers.JsonRpcProvider,
|
|
86
94
|
registryAddress: string,
|
|
87
95
|
agentAddress: string
|
|
88
|
-
): Promise<
|
|
96
|
+
): Promise<OnChainAgentState> {
|
|
89
97
|
const contract = new ethers.Contract(
|
|
90
98
|
registryAddress,
|
|
91
99
|
[
|
|
@@ -98,7 +106,46 @@ async function checkRegistration(
|
|
|
98
106
|
provider
|
|
99
107
|
);
|
|
100
108
|
const profile = await contract.getAgent(agentAddress);
|
|
101
|
-
return
|
|
109
|
+
return {
|
|
110
|
+
registeredAt: profile.registeredAt,
|
|
111
|
+
configHash: profile.configHash,
|
|
112
|
+
listed: profile.listed,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Detect the lazy publish activation scenario.
|
|
118
|
+
*
|
|
119
|
+
* Decision matrix:
|
|
120
|
+
* - A: Not registered + has pending → first-time activation
|
|
121
|
+
* - B1: Registered + pending hash != on-chain hash + not listed → re-publish + list
|
|
122
|
+
* - B2: Registered + pending hash != on-chain hash + already listed → re-publish only
|
|
123
|
+
* - C: Pending hash == on-chain hash → stale pending, delete it
|
|
124
|
+
* - none: No pending publish file
|
|
125
|
+
*/
|
|
126
|
+
export function detectLazyPublishScenario(
|
|
127
|
+
onChainState: OnChainAgentState,
|
|
128
|
+
pendingPublish: PendingPublish | null
|
|
129
|
+
): ActivationScenario {
|
|
130
|
+
if (!pendingPublish) return 'none';
|
|
131
|
+
|
|
132
|
+
const isRegistered = onChainState.registeredAt > 0n;
|
|
133
|
+
const pendingHash = pendingPublish.configHash;
|
|
134
|
+
const onChainHash = onChainState.configHash;
|
|
135
|
+
|
|
136
|
+
if (!isRegistered) {
|
|
137
|
+
// Not registered — scenario A: full activation
|
|
138
|
+
return 'A';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Registered — check if pending hash differs from on-chain
|
|
142
|
+
if (pendingHash !== onChainHash) {
|
|
143
|
+
// Config differs — need to publish
|
|
144
|
+
return onChainState.listed ? 'B2' : 'B1';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Hash matches — stale pending
|
|
148
|
+
return 'C';
|
|
102
149
|
}
|
|
103
150
|
|
|
104
151
|
function validateStateDirectory(stateDirectory: string): void {
|
|
@@ -519,6 +566,39 @@ export class ACTPClient {
|
|
|
519
566
|
*/
|
|
520
567
|
private readonly walletProvider?: IWalletProvider;
|
|
521
568
|
|
|
569
|
+
/**
|
|
570
|
+
* Lazy Publish: Current activation scenario.
|
|
571
|
+
* Set during create(), consumed during first payACTPBatched().
|
|
572
|
+
* @internal
|
|
573
|
+
*/
|
|
574
|
+
private lazyScenario: ActivationScenario = 'none';
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Lazy Publish: Cached pending publish data.
|
|
578
|
+
* @internal
|
|
579
|
+
*/
|
|
580
|
+
private pendingPublish: PendingPublish | null = null;
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* AgentRegistry address (for lazy activation calls).
|
|
584
|
+
* @internal
|
|
585
|
+
*/
|
|
586
|
+
private agentRegistryAddress?: string;
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Network identifier (e.g. 'base-sepolia', 'base-mainnet').
|
|
590
|
+
* Used for chain-scoped pending-publish file operations.
|
|
591
|
+
* @internal
|
|
592
|
+
*/
|
|
593
|
+
private networkId?: string;
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Whether the pending publish config is stale (AGIRAILS.md changed since last publish).
|
|
597
|
+
* When true, getActivationCalls() returns empty to prevent stale config going on-chain.
|
|
598
|
+
* @internal
|
|
599
|
+
*/
|
|
600
|
+
private pendingIsStale = false;
|
|
601
|
+
|
|
522
602
|
/**
|
|
523
603
|
* Private constructor - use ACTPClient.create() factory method.
|
|
524
604
|
*/
|
|
@@ -530,14 +610,22 @@ export class ACTPClient {
|
|
|
530
610
|
erc8004Bridge?: ERC8004Bridge,
|
|
531
611
|
reputationReporter?: ReputationReporter,
|
|
532
612
|
walletProvider?: IWalletProvider,
|
|
533
|
-
contractAddresses?: { usdc: string; actpKernel: string; escrowVault: string }
|
|
613
|
+
contractAddresses?: { usdc: string; actpKernel: string; escrowVault: string },
|
|
614
|
+
lazyScenario: ActivationScenario = 'none',
|
|
615
|
+
pendingPublish: PendingPublish | null = null,
|
|
616
|
+
agentRegistryAddress?: string,
|
|
617
|
+
networkId?: string,
|
|
534
618
|
) {
|
|
535
619
|
this.runtime = runtime;
|
|
536
620
|
this.info = info;
|
|
537
621
|
this.easHelper = easHelper;
|
|
538
622
|
this.reputationReporter = reputationReporter;
|
|
539
623
|
this.walletProvider = walletProvider;
|
|
540
|
-
this.
|
|
624
|
+
this.lazyScenario = lazyScenario;
|
|
625
|
+
this.pendingPublish = pendingPublish;
|
|
626
|
+
this.agentRegistryAddress = agentRegistryAddress;
|
|
627
|
+
this.networkId = networkId;
|
|
628
|
+
this.basic = new BasicAdapter(runtime, requesterAddress, easHelper, walletProvider, contractAddresses, this);
|
|
541
629
|
this.standard = new StandardAdapter(runtime, requesterAddress, easHelper);
|
|
542
630
|
|
|
543
631
|
// Initialize registry and router
|
|
@@ -594,6 +682,10 @@ export class ACTPClient {
|
|
|
594
682
|
let walletProvider: IWalletProvider | undefined;
|
|
595
683
|
let requesterAddress: string;
|
|
596
684
|
let contractAddresses: { usdc: string; actpKernel: string; escrowVault: string } | undefined;
|
|
685
|
+
let lazyScenario: ActivationScenario = 'none';
|
|
686
|
+
let lazyPending: PendingPublish | null = null;
|
|
687
|
+
let registryAddr: string | undefined;
|
|
688
|
+
let networkId: string | undefined;
|
|
597
689
|
|
|
598
690
|
// If custom runtime provided, use it directly
|
|
599
691
|
if (config.runtime) {
|
|
@@ -613,17 +705,18 @@ export class ACTPClient {
|
|
|
613
705
|
// Initialize runtime based on mode
|
|
614
706
|
switch (config.mode) {
|
|
615
707
|
case 'mock': {
|
|
616
|
-
// Mock mode: requesterAddress is
|
|
617
|
-
if (
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
708
|
+
// Mock mode: requesterAddress is optional (auto-generate if missing)
|
|
709
|
+
if (config.requesterAddress) {
|
|
710
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(config.requesterAddress)) {
|
|
711
|
+
throw new Error(
|
|
712
|
+
`Invalid requesterAddress: "${config.requesterAddress}". ` +
|
|
713
|
+
'Must be a valid Ethereum address (0x-prefixed, 40 hex chars)'
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
requesterAddress = config.requesterAddress;
|
|
717
|
+
} else {
|
|
718
|
+
requesterAddress = ethers.Wallet.createRandom().address;
|
|
625
719
|
}
|
|
626
|
-
requesterAddress = config.requesterAddress;
|
|
627
720
|
|
|
628
721
|
// SECURITY FIX: Enhanced path validation to prevent path traversal attacks
|
|
629
722
|
if (config.stateDirectory) {
|
|
@@ -640,15 +733,27 @@ export class ACTPClient {
|
|
|
640
733
|
|
|
641
734
|
case 'testnet':
|
|
642
735
|
case 'mainnet': {
|
|
643
|
-
//
|
|
736
|
+
// Auto-detect private key from keystore / env var if not provided
|
|
644
737
|
if (!config.privateKey) {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
)
|
|
738
|
+
const { resolvePrivateKey } = await import('./wallet/keystore');
|
|
739
|
+
const resolved = await resolvePrivateKey(config.stateDirectory);
|
|
740
|
+
if (resolved) {
|
|
741
|
+
config = { ...config, privateKey: resolved };
|
|
742
|
+
} else {
|
|
743
|
+
throw new Error(
|
|
744
|
+
`No wallet found for ${config.mode} mode.\n\n` +
|
|
745
|
+
'Provide a private key via one of:\n' +
|
|
746
|
+
' 1. ACTP_KEY_PASSWORD env var + .actp/keystore.json (recommended)\n' +
|
|
747
|
+
' 2. ACTP_PRIVATE_KEY env var\n' +
|
|
748
|
+
' 3. privateKey option in ACTPClient.create()\n' +
|
|
749
|
+
' 4. Run "actp publish" to generate a wallet automatically'
|
|
750
|
+
);
|
|
751
|
+
}
|
|
648
752
|
}
|
|
649
753
|
|
|
650
754
|
// Map mode to network config
|
|
651
755
|
const network = config.mode === 'testnet' ? 'base-sepolia' : 'base-mainnet';
|
|
756
|
+
networkId = network;
|
|
652
757
|
const networkConfig = getNetwork(network);
|
|
653
758
|
|
|
654
759
|
// Default RPC URL from network config if not provided
|
|
@@ -661,7 +766,8 @@ export class ACTPClient {
|
|
|
661
766
|
|
|
662
767
|
// Create ethers provider and signer
|
|
663
768
|
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
664
|
-
const
|
|
769
|
+
const privateKey = config.privateKey!; // Guaranteed by auto-detect above
|
|
770
|
+
const signer = new ethers.Wallet(privateKey, provider);
|
|
665
771
|
|
|
666
772
|
// ====================================================================
|
|
667
773
|
// AIP-12: Wallet Provider Selection
|
|
@@ -705,43 +811,72 @@ export class ACTPClient {
|
|
|
705
811
|
},
|
|
706
812
|
});
|
|
707
813
|
|
|
708
|
-
// Check AgentRegistry
|
|
814
|
+
// Check AgentRegistry + Lazy Publish scenario
|
|
709
815
|
const smartWalletAddress = autoWallet.getAddress();
|
|
710
|
-
|
|
816
|
+
registryAddr = config.contracts?.agentRegistry
|
|
711
817
|
?? networkConfig.contracts.agentRegistry;
|
|
712
818
|
|
|
713
|
-
|
|
714
|
-
|
|
819
|
+
// Load pending publish (may be null) — chain-scoped
|
|
820
|
+
try {
|
|
821
|
+
lazyPending = loadPendingPublish(network);
|
|
822
|
+
} catch {
|
|
823
|
+
// Ignore file read errors
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
let useAutoWallet = false;
|
|
827
|
+
|
|
828
|
+
if (registryAddr) {
|
|
715
829
|
try {
|
|
716
|
-
|
|
717
|
-
provider,
|
|
830
|
+
const onChainState = await getOnChainAgentState(
|
|
831
|
+
provider, registryAddr, smartWalletAddress
|
|
718
832
|
);
|
|
833
|
+
lazyScenario = detectLazyPublishScenario(onChainState, lazyPending);
|
|
834
|
+
|
|
835
|
+
// Scenario C: stale pending — delete immediately
|
|
836
|
+
if (lazyScenario === 'C') {
|
|
837
|
+
deletePendingPublish(network);
|
|
838
|
+
lazyPending = null;
|
|
839
|
+
lazyScenario = 'none';
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Gate: configHash != ZERO || hasPendingPublish → use AutoWallet
|
|
843
|
+
const hasOnChainConfig = onChainState.configHash !== ZERO_HASH;
|
|
844
|
+
const hasPendingPublish = lazyPending !== null;
|
|
845
|
+
|
|
846
|
+
if (hasOnChainConfig || hasPendingPublish) {
|
|
847
|
+
useAutoWallet = true;
|
|
848
|
+
}
|
|
719
849
|
} catch {
|
|
720
|
-
// Registry check failed (e.g. RPC down)
|
|
721
|
-
//
|
|
722
|
-
//
|
|
723
|
-
|
|
724
|
-
|
|
850
|
+
// Registry check failed (e.g. RPC down).
|
|
851
|
+
// Fail-open only if pending publish exists (agent did `actp publish` → legitimate intent).
|
|
852
|
+
// Fail-closed otherwise to prevent unregistered agents getting free gas.
|
|
853
|
+
if (lazyPending) {
|
|
854
|
+
useAutoWallet = true;
|
|
855
|
+
sdkLogger.warn('AgentRegistry check failed, but pending publish found — proceeding with AA.');
|
|
856
|
+
} else {
|
|
857
|
+
sdkLogger.warn('AgentRegistry check failed and no pending publish — falling back to EOA.');
|
|
858
|
+
}
|
|
725
859
|
}
|
|
726
860
|
} else {
|
|
727
861
|
// No registry deployed — skip check (early testnet)
|
|
728
|
-
|
|
862
|
+
useAutoWallet = true;
|
|
729
863
|
}
|
|
730
864
|
|
|
731
|
-
if (
|
|
865
|
+
if (useAutoWallet) {
|
|
732
866
|
walletProvider = autoWallet;
|
|
733
867
|
requesterAddress = smartWalletAddress;
|
|
734
868
|
} else {
|
|
735
|
-
// Not
|
|
869
|
+
// Not published and no pending — fall back to EOA with warning
|
|
736
870
|
sdkLogger.warn(
|
|
737
|
-
'Agent not
|
|
871
|
+
'Agent not published on AgentRegistry and no pending publish found. ' +
|
|
738
872
|
'Falling back to EOA wallet (gas not sponsored). ' +
|
|
739
|
-
'Run "actp
|
|
873
|
+
'Run "actp publish" for gas-free transactions.'
|
|
740
874
|
);
|
|
741
875
|
walletProvider = new EOAWalletProvider(signer, networkConfig.chainId);
|
|
742
|
-
// Force signer.address — config.requesterAddress may be the Smart Wallet
|
|
743
|
-
// address (set by `actp init --wallet auto`), which would be wrong for EOA.
|
|
744
876
|
requesterAddress = signer.address;
|
|
877
|
+
// Reset since we're not using auto wallet
|
|
878
|
+
lazyScenario = 'none';
|
|
879
|
+
lazyPending = null;
|
|
745
880
|
}
|
|
746
881
|
} else {
|
|
747
882
|
// Tier 2: EOA Wallet (backward compatible)
|
|
@@ -824,11 +959,35 @@ export class ACTPClient {
|
|
|
824
959
|
walletTier: walletProvider?.getWalletInfo().tier,
|
|
825
960
|
};
|
|
826
961
|
|
|
827
|
-
//
|
|
962
|
+
// Staleness check: recompute hash if AGIRAILS.md exists and we have a pending publish
|
|
963
|
+
let pendingIsStale = false;
|
|
964
|
+
if (lazyPending && lazyScenario !== 'none' && lazyScenario !== 'C') {
|
|
965
|
+
try {
|
|
966
|
+
const mdPath = path.join(process.cwd(), 'AGIRAILS.md');
|
|
967
|
+
if (fs.existsSync(mdPath)) {
|
|
968
|
+
const { computeConfigHash } = await import('./config/agirailsmd');
|
|
969
|
+
const content = fs.readFileSync(mdPath, 'utf-8');
|
|
970
|
+
const { configHash: currentHash } = computeConfigHash(content);
|
|
971
|
+
if (currentHash !== lazyPending.configHash) {
|
|
972
|
+
pendingIsStale = true;
|
|
973
|
+
sdkLogger.warn(
|
|
974
|
+
'AGIRAILS.md changed since last publish. Activation skipped. ' +
|
|
975
|
+
'Run "actp publish" to update.'
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
} catch {
|
|
980
|
+
// Best-effort: staleness check should not block operation
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// Pass wallet provider, contract addresses, and lazy publish state to constructor
|
|
828
985
|
const client = new ACTPClient(
|
|
829
986
|
runtime, normalizedAddress, info, easHelper,
|
|
830
|
-
erc8004Bridge, reputationReporter, walletProvider, contractAddresses
|
|
987
|
+
erc8004Bridge, reputationReporter, walletProvider, contractAddresses,
|
|
988
|
+
lazyScenario, lazyPending, registryAddr, networkId,
|
|
831
989
|
);
|
|
990
|
+
client.pendingIsStale = pendingIsStale;
|
|
832
991
|
|
|
833
992
|
// Drift detection: non-blocking check for AGIRAILS.md sync status
|
|
834
993
|
if (config.mode !== 'mock') {
|
|
@@ -1239,7 +1398,7 @@ export class ACTPClient {
|
|
|
1239
1398
|
* @example
|
|
1240
1399
|
* ```typescript
|
|
1241
1400
|
* // Register a custom x402 adapter
|
|
1242
|
-
* client.registerAdapter(new X402Adapter(
|
|
1401
|
+
* client.registerAdapter(new X402Adapter(requesterAddress, { expectedNetwork, transferFn }));
|
|
1243
1402
|
* ```
|
|
1244
1403
|
*/
|
|
1245
1404
|
registerAdapter(adapter: IAdapter): void {
|
|
@@ -1299,6 +1458,56 @@ export class ACTPClient {
|
|
|
1299
1458
|
return this.walletProvider;
|
|
1300
1459
|
}
|
|
1301
1460
|
|
|
1461
|
+
/**
|
|
1462
|
+
* Get activation calls for lazy publish.
|
|
1463
|
+
*
|
|
1464
|
+
* Returns SmartWalletCall[] to prepend to the first payment UserOp,
|
|
1465
|
+
* plus an onSuccess callback that deletes pending-publish.json.
|
|
1466
|
+
*
|
|
1467
|
+
* Returns empty calls if no activation is needed (scenario C/none).
|
|
1468
|
+
* @internal
|
|
1469
|
+
*/
|
|
1470
|
+
getActivationCalls(): { calls: SmartWalletCall[]; onSuccess: () => void } {
|
|
1471
|
+
if (this.lazyScenario === 'none' || this.lazyScenario === 'C' || !this.agentRegistryAddress) {
|
|
1472
|
+
return { calls: [], onSuccess: () => {} };
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// Staleness check: AGIRAILS.md changed since last publish → skip activation
|
|
1476
|
+
if (this.pendingIsStale) {
|
|
1477
|
+
return { calls: [], onSuccess: () => {} };
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
const pending = this.pendingPublish;
|
|
1481
|
+
if (!pending) {
|
|
1482
|
+
return { calls: [], onSuccess: () => {} };
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
// Build activation batch params
|
|
1486
|
+
const params: import('./wallet/aa/TransactionBatcher').ActivationBatchParams = {
|
|
1487
|
+
scenario: this.lazyScenario,
|
|
1488
|
+
agentRegistryAddress: this.agentRegistryAddress,
|
|
1489
|
+
cid: pending.cid,
|
|
1490
|
+
configHash: pending.configHash,
|
|
1491
|
+
listed: true,
|
|
1492
|
+
};
|
|
1493
|
+
|
|
1494
|
+
// For scenario A, extract registration params from pending publish
|
|
1495
|
+
if (this.lazyScenario === 'A') {
|
|
1496
|
+
params.endpoint = pending.endpoint;
|
|
1497
|
+
params.serviceDescriptors = pending.serviceDescriptors;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
const calls = buildActivationBatch(params);
|
|
1501
|
+
|
|
1502
|
+
const onSuccess = () => {
|
|
1503
|
+
deletePendingPublish(this.networkId);
|
|
1504
|
+
this.lazyScenario = 'none';
|
|
1505
|
+
this.pendingPublish = null;
|
|
1506
|
+
};
|
|
1507
|
+
|
|
1508
|
+
return { calls, onSuccess };
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1302
1511
|
/**
|
|
1303
1512
|
* Non-blocking drift detection for AGIRAILS.md config.
|
|
1304
1513
|
* Checks if local AGIRAILS.md matches on-chain config hash.
|
|
@@ -1313,7 +1522,14 @@ export class ACTPClient {
|
|
|
1313
1522
|
// Look for AGIRAILS.md in cwd
|
|
1314
1523
|
const agirailsMdPath = join(process.cwd(), 'AGIRAILS.md');
|
|
1315
1524
|
if (!existsSync(agirailsMdPath)) {
|
|
1316
|
-
|
|
1525
|
+
// No local file — try cached hash from pending-publish.json
|
|
1526
|
+
const { loadPendingPublish: loadPP } = await import('./config/pendingPublish');
|
|
1527
|
+
const driftNetwork = config.mode === 'testnet' ? 'base-sepolia' : 'base-mainnet';
|
|
1528
|
+
const pp = loadPP(driftNetwork);
|
|
1529
|
+
if (pp) {
|
|
1530
|
+
sdkLogger.info('[AGIRAILS] No AGIRAILS.md found, using cached config hash from pending-publish.json');
|
|
1531
|
+
}
|
|
1532
|
+
return;
|
|
1317
1533
|
}
|
|
1318
1534
|
|
|
1319
1535
|
const network = config.mode === 'testnet' ? 'base-sepolia' : 'base-mainnet';
|
|
@@ -23,8 +23,17 @@ import {
|
|
|
23
23
|
UnifiedPayResult,
|
|
24
24
|
} from '../types/adapter';
|
|
25
25
|
import { IWalletProvider } from '../wallet/IWalletProvider';
|
|
26
|
+
import { SmartWalletCall } from '../wallet/aa/constants';
|
|
26
27
|
import { ethers } from 'ethers';
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Interface for lazy publish activation call provider.
|
|
31
|
+
* ACTPClient implements this to avoid circular dependency.
|
|
32
|
+
*/
|
|
33
|
+
export interface IActivationCallProvider {
|
|
34
|
+
getActivationCalls(): { calls: SmartWalletCall[]; onSuccess: () => void };
|
|
35
|
+
}
|
|
36
|
+
|
|
28
37
|
/**
|
|
29
38
|
* Parameters for creating a simple payment.
|
|
30
39
|
*
|
|
@@ -126,7 +135,8 @@ export class BasicAdapter extends BaseAdapter implements IAdapter {
|
|
|
126
135
|
requesterAddress: string,
|
|
127
136
|
private easHelper?: EASHelper,
|
|
128
137
|
private walletProvider?: IWalletProvider,
|
|
129
|
-
private contractAddresses?: { usdc: string; actpKernel: string; escrowVault: string }
|
|
138
|
+
private contractAddresses?: { usdc: string; actpKernel: string; escrowVault: string },
|
|
139
|
+
private activationProvider?: IActivationCallProvider,
|
|
130
140
|
) {
|
|
131
141
|
super(requesterAddress);
|
|
132
142
|
}
|
|
@@ -194,25 +204,51 @@ export class BasicAdapter extends BaseAdapter implements IAdapter {
|
|
|
194
204
|
}
|
|
195
205
|
|
|
196
206
|
// ====================================================================
|
|
197
|
-
// AIP-12: Batched payment via AA wallet (1 UserOp =
|
|
207
|
+
// AIP-12: Batched payment via AA wallet (1 UserOp = N on-chain calls)
|
|
208
|
+
// With lazy publish: activation calls prepended to first payment.
|
|
198
209
|
// ====================================================================
|
|
199
210
|
if (this.walletProvider?.payACTPBatched && this.contractAddresses) {
|
|
200
211
|
const serviceHash = ethers.ZeroHash;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
212
|
+
|
|
213
|
+
// Get lazy publish activation calls (if any)
|
|
214
|
+
let prependCalls: SmartWalletCall[] = [];
|
|
215
|
+
let onActivationSuccess: (() => void) | undefined;
|
|
216
|
+
|
|
217
|
+
if (this.activationProvider) {
|
|
218
|
+
const activation = this.activationProvider.getActivationCalls();
|
|
219
|
+
prependCalls = activation.calls;
|
|
220
|
+
if (prependCalls.length > 0) {
|
|
221
|
+
onActivationSuccess = activation.onSuccess;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const result = await this.walletProvider.payACTPBatched(
|
|
226
|
+
{
|
|
227
|
+
provider,
|
|
228
|
+
requester,
|
|
229
|
+
amount: amount.toString(),
|
|
230
|
+
deadline,
|
|
231
|
+
disputeWindow,
|
|
232
|
+
serviceHash,
|
|
233
|
+
agentId: agentId || '0',
|
|
234
|
+
contracts: this.contractAddresses,
|
|
235
|
+
},
|
|
236
|
+
prependCalls.length > 0 ? prependCalls : undefined,
|
|
237
|
+
);
|
|
211
238
|
|
|
212
239
|
if (!result.success) {
|
|
213
240
|
throw new Error(`Batched payment UserOp failed: ${result.hash}`);
|
|
214
241
|
}
|
|
215
242
|
|
|
243
|
+
// Delete pending-publish.json on successful activation (best-effort)
|
|
244
|
+
if (onActivationSuccess) {
|
|
245
|
+
try {
|
|
246
|
+
onActivationSuccess();
|
|
247
|
+
} catch {
|
|
248
|
+
// Best-effort: activation succeeded, don't crash over cleanup
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
216
252
|
return {
|
|
217
253
|
txId: result.txId,
|
|
218
254
|
provider,
|