@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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet Utilities — Shared wallet generation and Smart Wallet derivation.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from init.ts for reuse by both `actp init` and `actp publish`.
|
|
5
|
+
*
|
|
6
|
+
* @module cli/utils/wallet
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import * as readline from 'readline';
|
|
12
|
+
import { Output } from './output';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate a new encrypted wallet keystore.
|
|
16
|
+
*
|
|
17
|
+
* - Creates a random ethers.Wallet
|
|
18
|
+
* - Encrypts with password (from ACTP_KEY_PASSWORD env or interactive prompt)
|
|
19
|
+
* - Saves to `{actpDir}/keystore.json` with 0o600 permissions
|
|
20
|
+
*
|
|
21
|
+
* @param actpDir - Path to .actp directory
|
|
22
|
+
* @param output - CLI output handler
|
|
23
|
+
* @returns The generated wallet's address
|
|
24
|
+
*/
|
|
25
|
+
export async function generateWallet(actpDir: string, output: Output): Promise<string> {
|
|
26
|
+
const { Wallet } = await import('ethers');
|
|
27
|
+
|
|
28
|
+
const wallet = Wallet.createRandom();
|
|
29
|
+
|
|
30
|
+
// Get password from env var or interactive prompt
|
|
31
|
+
let password = process.env.ACTP_KEY_PASSWORD;
|
|
32
|
+
if (!password) {
|
|
33
|
+
password = await promptPassword();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!password || password.length < 8) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
'Wallet password required (minimum 8 characters).\n' +
|
|
39
|
+
'Set ACTP_KEY_PASSWORD env var or enter when prompted.'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Encrypt with Keystore V3 (scrypt + AES-128-CTR)
|
|
44
|
+
output.info('Encrypting wallet (this takes a few seconds)...');
|
|
45
|
+
const keystore = await wallet.encrypt(password);
|
|
46
|
+
|
|
47
|
+
// Save with restrictive permissions
|
|
48
|
+
const keystorePath = path.join(actpDir, 'keystore.json');
|
|
49
|
+
fs.writeFileSync(keystorePath, keystore, { mode: 0o600 });
|
|
50
|
+
|
|
51
|
+
output.success('Key securely saved and encrypted');
|
|
52
|
+
output.info(`Address: ${wallet.address}`);
|
|
53
|
+
output.warning('Back up your password — it cannot be recovered.');
|
|
54
|
+
|
|
55
|
+
return wallet.address;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Compute the Smart Wallet address for an EOA signer.
|
|
60
|
+
* Uses CREATE2 counterfactual derivation — no deployment needed.
|
|
61
|
+
*
|
|
62
|
+
* @param eoaAddress - The EOA signer address
|
|
63
|
+
* @param mode - 'testnet' or 'mainnet'
|
|
64
|
+
* @param output - CLI output handler
|
|
65
|
+
* @returns The derived Smart Wallet address
|
|
66
|
+
*/
|
|
67
|
+
export async function computeSmartWalletInit(
|
|
68
|
+
eoaAddress: string,
|
|
69
|
+
mode: string,
|
|
70
|
+
output: Output
|
|
71
|
+
): Promise<string> {
|
|
72
|
+
const { ethers } = await import('ethers');
|
|
73
|
+
const { getNetwork } = await import('../../config/networks');
|
|
74
|
+
const { computeSmartWalletAddress } = await import('../../wallet/aa/UserOpBuilder');
|
|
75
|
+
|
|
76
|
+
const network = mode === 'testnet' ? 'base-sepolia' : 'base-mainnet';
|
|
77
|
+
const networkConfig = getNetwork(network);
|
|
78
|
+
const rpcUrl = networkConfig.rpcUrl;
|
|
79
|
+
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
80
|
+
|
|
81
|
+
output.info('Computing Smart Wallet address...');
|
|
82
|
+
const smartWalletAddress = await computeSmartWalletAddress(eoaAddress, provider);
|
|
83
|
+
|
|
84
|
+
output.success(`Smart Wallet: ${smartWalletAddress}`);
|
|
85
|
+
|
|
86
|
+
return smartWalletAddress;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Interactive password prompt (TTY only).
|
|
91
|
+
* Returns empty string in non-TTY environments (piped/agent mode).
|
|
92
|
+
*/
|
|
93
|
+
async function promptPassword(): Promise<string> {
|
|
94
|
+
if (!process.stdin.isTTY) {
|
|
95
|
+
return '';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const rl = readline.createInterface({
|
|
99
|
+
input: process.stdin,
|
|
100
|
+
output: process.stdout,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return new Promise((resolve) => {
|
|
104
|
+
rl.question('Enter password for wallet encryption (min 8 chars): ', (answer) => {
|
|
105
|
+
rl.close();
|
|
106
|
+
resolve(answer.trim());
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending Publish Module — Deferred on-chain activation for Lazy Publish.
|
|
3
|
+
*
|
|
4
|
+
* When `actp publish` runs, it saves a `pending-publish.{network}.json` file
|
|
5
|
+
* instead of making on-chain calls. The first real payment triggers activation
|
|
6
|
+
* (registerAgent, publishConfig, setListed) in a single UserOp alongside the
|
|
7
|
+
* payment calls.
|
|
8
|
+
*
|
|
9
|
+
* Files are chain-scoped: testnet and mainnet pending publishes coexist independently.
|
|
10
|
+
* Legacy `pending-publish.json` (unscoped) is supported for migration.
|
|
11
|
+
*
|
|
12
|
+
* The file is deleted after successful on-chain activation.
|
|
13
|
+
*
|
|
14
|
+
* @module config/pendingPublish
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync } from 'fs';
|
|
18
|
+
import { join } from 'path';
|
|
19
|
+
import { ServiceDescriptor } from '../types/agent';
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Types
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Serializable representation of a ServiceDescriptor.
|
|
27
|
+
* BigInt fields are stored as strings for JSON compatibility.
|
|
28
|
+
*/
|
|
29
|
+
interface SerializedServiceDescriptor {
|
|
30
|
+
serviceTypeHash: string;
|
|
31
|
+
serviceType: string;
|
|
32
|
+
schemaURI: string;
|
|
33
|
+
minPrice: string;
|
|
34
|
+
maxPrice: string;
|
|
35
|
+
avgCompletionTime: number;
|
|
36
|
+
metadataCID: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Pending publish state — saved to `.actp/pending-publish.{network}.json`.
|
|
41
|
+
*/
|
|
42
|
+
export interface PendingPublish {
|
|
43
|
+
/** Schema version */
|
|
44
|
+
version: 1;
|
|
45
|
+
/** Canonical config hash (bytes32) */
|
|
46
|
+
configHash: string;
|
|
47
|
+
/** IPFS CID of uploaded AGIRAILS.md */
|
|
48
|
+
cid: string;
|
|
49
|
+
/** Agent endpoint URL */
|
|
50
|
+
endpoint: string;
|
|
51
|
+
/** Service descriptors from AGIRAILS.md frontmatter */
|
|
52
|
+
serviceDescriptors: ServiceDescriptor[];
|
|
53
|
+
/** ISO 8601 timestamp of when pending-publish.json was created */
|
|
54
|
+
createdAt: string;
|
|
55
|
+
/** Network identifier (e.g. 'base-sepolia', 'base-mainnet') */
|
|
56
|
+
network?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* JSON-serializable form of PendingPublish (bigints as strings).
|
|
61
|
+
*/
|
|
62
|
+
interface SerializedPendingPublish {
|
|
63
|
+
version: 1;
|
|
64
|
+
configHash: string;
|
|
65
|
+
cid: string;
|
|
66
|
+
endpoint: string;
|
|
67
|
+
serviceDescriptors: SerializedServiceDescriptor[];
|
|
68
|
+
createdAt: string;
|
|
69
|
+
network?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// Serialization Helpers
|
|
74
|
+
// ============================================================================
|
|
75
|
+
|
|
76
|
+
function serializeDescriptor(sd: ServiceDescriptor): SerializedServiceDescriptor {
|
|
77
|
+
return {
|
|
78
|
+
serviceTypeHash: sd.serviceTypeHash,
|
|
79
|
+
serviceType: sd.serviceType,
|
|
80
|
+
schemaURI: sd.schemaURI,
|
|
81
|
+
minPrice: sd.minPrice.toString(),
|
|
82
|
+
maxPrice: sd.maxPrice.toString(),
|
|
83
|
+
avgCompletionTime: sd.avgCompletionTime,
|
|
84
|
+
metadataCID: sd.metadataCID,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function deserializeDescriptor(sd: SerializedServiceDescriptor): ServiceDescriptor {
|
|
89
|
+
return {
|
|
90
|
+
serviceTypeHash: sd.serviceTypeHash,
|
|
91
|
+
serviceType: sd.serviceType,
|
|
92
|
+
schemaURI: sd.schemaURI,
|
|
93
|
+
minPrice: BigInt(sd.minPrice),
|
|
94
|
+
maxPrice: BigInt(sd.maxPrice),
|
|
95
|
+
avgCompletionTime: sd.avgCompletionTime,
|
|
96
|
+
metadataCID: sd.metadataCID,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ============================================================================
|
|
101
|
+
// Public API
|
|
102
|
+
// ============================================================================
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get the .actp directory path.
|
|
106
|
+
*
|
|
107
|
+
* Respects `ACTP_DIR` env var for custom locations.
|
|
108
|
+
* Defaults to `{cwd}/.actp`.
|
|
109
|
+
*/
|
|
110
|
+
export function getActpDir(): string {
|
|
111
|
+
return process.env.ACTP_DIR || join(process.cwd(), '.actp');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get the path to a pending-publish file.
|
|
116
|
+
*
|
|
117
|
+
* @param network - Optional network identifier. If provided, returns
|
|
118
|
+
* `pending-publish.{network}.json`. Otherwise returns legacy `pending-publish.json`.
|
|
119
|
+
*/
|
|
120
|
+
export function getPendingPublishPath(network?: string): string {
|
|
121
|
+
if (network) {
|
|
122
|
+
return join(getActpDir(), `pending-publish.${network}.json`);
|
|
123
|
+
}
|
|
124
|
+
return join(getActpDir(), 'pending-publish.json');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Save a pending publish to `.actp/pending-publish.{network}.json`.
|
|
129
|
+
*
|
|
130
|
+
* Creates the .actp directory if it doesn't exist.
|
|
131
|
+
* File is written atomically with mode 0o600 (owner read/write only).
|
|
132
|
+
*
|
|
133
|
+
* If `pending.network` is set, saves to network-scoped file.
|
|
134
|
+
* Otherwise saves to legacy `pending-publish.json`.
|
|
135
|
+
*/
|
|
136
|
+
export function savePendingPublish(pending: PendingPublish): void {
|
|
137
|
+
const dir = getActpDir();
|
|
138
|
+
if (!existsSync(dir)) {
|
|
139
|
+
mkdirSync(dir, { recursive: true });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const serialized: SerializedPendingPublish = {
|
|
143
|
+
version: pending.version,
|
|
144
|
+
configHash: pending.configHash,
|
|
145
|
+
cid: pending.cid,
|
|
146
|
+
endpoint: pending.endpoint,
|
|
147
|
+
serviceDescriptors: pending.serviceDescriptors.map(serializeDescriptor),
|
|
148
|
+
createdAt: pending.createdAt,
|
|
149
|
+
...(pending.network ? { network: pending.network } : {}),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const filePath = getPendingPublishPath(pending.network);
|
|
153
|
+
writeFileSync(filePath, JSON.stringify(serialized, null, 2), { mode: 0o600 });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Load a pending publish from `.actp/pending-publish.{network}.json`.
|
|
158
|
+
*
|
|
159
|
+
* If `network` is provided:
|
|
160
|
+
* 1. Try `pending-publish.{network}.json`
|
|
161
|
+
* 2. Fall back to legacy `pending-publish.json` (migration)
|
|
162
|
+
*
|
|
163
|
+
* If no `network`: loads legacy `pending-publish.json`.
|
|
164
|
+
*
|
|
165
|
+
* Returns null if no file found.
|
|
166
|
+
*/
|
|
167
|
+
export function loadPendingPublish(network?: string): PendingPublish | null {
|
|
168
|
+
// Try network-scoped file first
|
|
169
|
+
if (network) {
|
|
170
|
+
const scopedPath = getPendingPublishPath(network);
|
|
171
|
+
if (existsSync(scopedPath)) {
|
|
172
|
+
return deserializePendingPublish(readFileSync(scopedPath, 'utf-8'));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Fall back to legacy file
|
|
177
|
+
const legacyPath = getPendingPublishPath();
|
|
178
|
+
if (!existsSync(legacyPath)) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return deserializePendingPublish(readFileSync(legacyPath, 'utf-8'));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Delete the pending-publish file for a given network.
|
|
187
|
+
*
|
|
188
|
+
* Deletes both the network-scoped file and legacy file (cleanup).
|
|
189
|
+
* No-op if files don't exist. Best-effort — never throws.
|
|
190
|
+
*/
|
|
191
|
+
export function deletePendingPublish(network?: string): void {
|
|
192
|
+
try {
|
|
193
|
+
// Delete network-scoped file
|
|
194
|
+
if (network) {
|
|
195
|
+
const scopedPath = getPendingPublishPath(network);
|
|
196
|
+
if (existsSync(scopedPath)) {
|
|
197
|
+
unlinkSync(scopedPath);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Also delete legacy file if it exists (cleanup)
|
|
202
|
+
const legacyPath = getPendingPublishPath();
|
|
203
|
+
if (existsSync(legacyPath)) {
|
|
204
|
+
unlinkSync(legacyPath);
|
|
205
|
+
}
|
|
206
|
+
} catch {
|
|
207
|
+
// Best-effort: file deletion should never crash post-payment UX
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ============================================================================
|
|
212
|
+
// Internal Helpers
|
|
213
|
+
// ============================================================================
|
|
214
|
+
|
|
215
|
+
function deserializePendingPublish(raw: string): PendingPublish {
|
|
216
|
+
const serialized: SerializedPendingPublish = JSON.parse(raw);
|
|
217
|
+
return {
|
|
218
|
+
version: serialized.version,
|
|
219
|
+
configHash: serialized.configHash,
|
|
220
|
+
cid: serialized.cid,
|
|
221
|
+
endpoint: serialized.endpoint,
|
|
222
|
+
serviceDescriptors: serialized.serviceDescriptors.map(deserializeDescriptor),
|
|
223
|
+
createdAt: serialized.createdAt,
|
|
224
|
+
...(serialized.network ? { network: serialized.network } : {}),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
@@ -176,7 +176,88 @@ export function extractRegistrationParams(
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
// ============================================================================
|
|
179
|
-
//
|
|
179
|
+
// Prepare Publish (offline — no on-chain calls)
|
|
180
|
+
// ============================================================================
|
|
181
|
+
|
|
182
|
+
export interface PreparePublishOptions {
|
|
183
|
+
/** Path to AGIRAILS.md file */
|
|
184
|
+
path: string;
|
|
185
|
+
/** Filebase client for IPFS upload */
|
|
186
|
+
filebaseClient: FilebaseClient;
|
|
187
|
+
/** Arweave client for permanent storage (optional) */
|
|
188
|
+
arweaveClient?: ArweaveClient;
|
|
189
|
+
/** Skip Arweave upload */
|
|
190
|
+
skipArweave?: boolean;
|
|
191
|
+
/** Dry run — compute and show but don't execute */
|
|
192
|
+
dryRun?: boolean;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export interface PreparePublishResult {
|
|
196
|
+
/** IPFS CID of uploaded AGIRAILS.md */
|
|
197
|
+
cid: string;
|
|
198
|
+
/** Canonical config hash (bytes32) */
|
|
199
|
+
configHash: string;
|
|
200
|
+
/** Arweave transaction ID (if uploaded) */
|
|
201
|
+
arweaveTxId?: string;
|
|
202
|
+
/** Parsed frontmatter */
|
|
203
|
+
frontmatter: Record<string, unknown>;
|
|
204
|
+
/** Parsed body */
|
|
205
|
+
body: string;
|
|
206
|
+
/** Whether this was a dry run */
|
|
207
|
+
dryRun: boolean;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Prepare publish — IPFS upload + hash computation only.
|
|
212
|
+
*
|
|
213
|
+
* No on-chain calls. Returns the CID and configHash for
|
|
214
|
+
* saving to pending-publish.json (lazy publish flow).
|
|
215
|
+
*/
|
|
216
|
+
export async function preparePublish(options: PreparePublishOptions): Promise<PreparePublishResult> {
|
|
217
|
+
const {
|
|
218
|
+
path,
|
|
219
|
+
filebaseClient,
|
|
220
|
+
arweaveClient,
|
|
221
|
+
skipArweave = false,
|
|
222
|
+
dryRun = false,
|
|
223
|
+
} = options;
|
|
224
|
+
|
|
225
|
+
// Read and parse
|
|
226
|
+
const content = readFileSync(path, 'utf-8');
|
|
227
|
+
const { frontmatter, body } = parseAgirailsMd(content);
|
|
228
|
+
const { configHash } = computeConfigHash(content);
|
|
229
|
+
|
|
230
|
+
if (dryRun) {
|
|
231
|
+
return { cid: '(dry-run)', configHash, frontmatter, body, dryRun: true };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Upload to IPFS
|
|
235
|
+
const ipfsResult = await filebaseClient.uploadBinary(
|
|
236
|
+
Buffer.from(content, 'utf-8'),
|
|
237
|
+
'text/markdown',
|
|
238
|
+
{ metadata: { type: 'agirails-config', version: '1.0' } }
|
|
239
|
+
);
|
|
240
|
+
const cid = ipfsResult.cid;
|
|
241
|
+
|
|
242
|
+
// Arweave (optional)
|
|
243
|
+
let arweaveTxId: string | undefined;
|
|
244
|
+
if (!skipArweave && arweaveClient) {
|
|
245
|
+
const arweaveResult = await arweaveClient.uploadJSON(
|
|
246
|
+
{ frontmatter, body, _format: 'agirails.md.v1' },
|
|
247
|
+
[
|
|
248
|
+
{ name: 'Type', value: 'agent-config' },
|
|
249
|
+
{ name: 'ConfigHash', value: configHash },
|
|
250
|
+
{ name: 'IPFS-CID', value: cid },
|
|
251
|
+
]
|
|
252
|
+
);
|
|
253
|
+
arweaveTxId = arweaveResult.txId;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { cid, configHash, arweaveTxId, frontmatter, body, dryRun: false };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ============================================================================
|
|
260
|
+
// Pipeline (legacy — makes on-chain calls)
|
|
180
261
|
// ============================================================================
|
|
181
262
|
|
|
182
263
|
/**
|
package/src/index.ts
CHANGED
|
@@ -64,6 +64,12 @@ export {
|
|
|
64
64
|
StandardTransactionParams,
|
|
65
65
|
} from './adapters/StandardAdapter';
|
|
66
66
|
|
|
67
|
+
export {
|
|
68
|
+
X402Adapter,
|
|
69
|
+
X402AdapterConfig,
|
|
70
|
+
X402PayParams,
|
|
71
|
+
} from './adapters/X402Adapter';
|
|
72
|
+
|
|
67
73
|
export { AdapterRegistry } from './adapters/AdapterRegistry';
|
|
68
74
|
|
|
69
75
|
export {
|
|
@@ -353,4 +359,6 @@ export {
|
|
|
353
359
|
ArweaveTimeoutError,
|
|
354
360
|
InvalidArweaveTxIdError,
|
|
355
361
|
InsufficientBalanceError as StorageInsufficientBalanceError,
|
|
362
|
+
SwapExecutionError,
|
|
363
|
+
QueryCapExceededError,
|
|
356
364
|
} from './errors';
|
|
@@ -191,7 +191,7 @@ export class AutoWalletProvider implements IWalletProvider {
|
|
|
191
191
|
* Builds approve + createTransaction + linkEscrow as a single UserOp.
|
|
192
192
|
* Manages ACTP nonce inside the mutex queue for concurrent safety.
|
|
193
193
|
*/
|
|
194
|
-
async payACTPBatched(params: BatchedPayParams): Promise<BatchedPayResult> {
|
|
194
|
+
async payACTPBatched(params: BatchedPayParams, prependCalls?: SmartWalletCall[]): Promise<BatchedPayResult> {
|
|
195
195
|
return this.nonceManager.enqueue(
|
|
196
196
|
async ({ entryPointNonce, actpNonce }) => {
|
|
197
197
|
const batch = buildACTPPayBatch({
|
|
@@ -199,7 +199,12 @@ export class AutoWalletProvider implements IWalletProvider {
|
|
|
199
199
|
actpNonce,
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
-
|
|
202
|
+
// Combine activation calls (if any) with payment calls
|
|
203
|
+
const allCalls = prependCalls && prependCalls.length > 0
|
|
204
|
+
? [...prependCalls, ...batch.calls]
|
|
205
|
+
: batch.calls;
|
|
206
|
+
|
|
207
|
+
const receipt = await this.submitUserOp(allCalls, entryPointNonce);
|
|
203
208
|
|
|
204
209
|
return {
|
|
205
210
|
result: {
|
|
@@ -127,7 +127,9 @@ export interface IWalletProvider {
|
|
|
127
127
|
* Only available on AA wallets (supportsBatching: true).
|
|
128
128
|
* Handles ACTP nonce management internally via the DualNonceManager mutex.
|
|
129
129
|
*
|
|
130
|
-
*
|
|
130
|
+
* @param params - Payment batch parameters
|
|
131
|
+
* @param prependCalls - Optional calls to prepend (e.g., lazy publish activation)
|
|
132
|
+
* @returns undefined if batched payments are not supported.
|
|
131
133
|
*/
|
|
132
|
-
payACTPBatched?(params: BatchedPayParams): Promise<BatchedPayResult>;
|
|
134
|
+
payACTPBatched?(params: BatchedPayParams, prependCalls?: import('./aa/constants').SmartWalletCall[]): Promise<BatchedPayResult>;
|
|
133
135
|
}
|
|
@@ -233,8 +233,9 @@ export class BundlerClient {
|
|
|
233
233
|
const json = (await response.json()) as JsonRpcResponse<T>;
|
|
234
234
|
|
|
235
235
|
if (json.error) {
|
|
236
|
+
const dataStr = json.error.data ? ` | data: ${JSON.stringify(json.error.data)}` : '';
|
|
236
237
|
const err = new Error(
|
|
237
|
-
`Bundler RPC error ${json.error.code}: ${json.error.message}`
|
|
238
|
+
`Bundler RPC error ${json.error.code}: ${json.error.message}${dataStr}`
|
|
238
239
|
);
|
|
239
240
|
(err as any).code = json.error.code;
|
|
240
241
|
(err as any).data = json.error.data;
|
|
@@ -141,17 +141,27 @@ export class DualNonceManager {
|
|
|
141
141
|
|
|
142
142
|
/**
|
|
143
143
|
* Read current ACTP nonce for the requester.
|
|
144
|
-
* requesterNonces is public on ACTPKernel.
|
|
144
|
+
* requesterNonces is public on ACTPKernel (added in v2).
|
|
145
|
+
* Older deployments may not expose this — fall back to 0n.
|
|
145
146
|
*/
|
|
146
147
|
private async readActpNonce(): Promise<bigint> {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
try {
|
|
149
|
+
const kernel = new ethers.Contract(
|
|
150
|
+
this.actpKernelAddress,
|
|
151
|
+
ACTP_KERNEL_NONCE_ABI,
|
|
152
|
+
this.provider
|
|
153
|
+
);
|
|
154
|
+
const nonce = await kernel.requesterNonces(this.senderAddress);
|
|
155
|
+
this.cachedActpNonce = nonce;
|
|
156
|
+
return nonce;
|
|
157
|
+
} catch {
|
|
158
|
+
// Older ACTPKernel deployments don't expose requesterNonces.
|
|
159
|
+
// Return 0n — registration doesn't need it, and payment batches
|
|
160
|
+
// will fail at the contract level anyway if nonces are wrong.
|
|
161
|
+
sdkLogger.warn('requesterNonces not available on ACTPKernel — using 0 (older deployment?)');
|
|
162
|
+
this.cachedActpNonce = 0n;
|
|
163
|
+
return 0n;
|
|
164
|
+
}
|
|
155
165
|
}
|
|
156
166
|
|
|
157
167
|
/**
|
|
@@ -160,8 +160,9 @@ export class PaymasterClient {
|
|
|
160
160
|
const json = (await response.json()) as JsonRpcResponse<T>;
|
|
161
161
|
|
|
162
162
|
if (json.error) {
|
|
163
|
+
const dataStr = json.error.data ? ` | data: ${JSON.stringify(json.error.data)}` : '';
|
|
163
164
|
throw new Error(
|
|
164
|
-
`Paymaster RPC error ${json.error.code}: ${json.error.message}`
|
|
165
|
+
`Paymaster RPC error ${json.error.code}: ${json.error.message}${dataStr}`
|
|
165
166
|
);
|
|
166
167
|
}
|
|
167
168
|
|
|
@@ -154,6 +154,8 @@ export function buildACTPPayBatch(params: ACTPBatchParams): ACTPBatchResult {
|
|
|
154
154
|
*/
|
|
155
155
|
const AGENT_REGISTRY_ABI = [
|
|
156
156
|
'function registerAgent(string endpoint, (bytes32 serviceTypeHash, string serviceType, string schemaURI, uint256 minPrice, uint256 maxPrice, uint256 avgCompletionTime, string metadataCID)[] serviceDescriptors)',
|
|
157
|
+
'function publishConfig(string cid, bytes32 configHash)',
|
|
158
|
+
'function setListed(bool listed)',
|
|
157
159
|
];
|
|
158
160
|
|
|
159
161
|
/**
|
|
@@ -238,3 +240,114 @@ export function buildTestnetInitBatch(params: {
|
|
|
238
240
|
);
|
|
239
241
|
return [...registerCalls, ...mintCalls];
|
|
240
242
|
}
|
|
243
|
+
|
|
244
|
+
// ============================================================================
|
|
245
|
+
// Lazy Publish — Activation Batch Builders
|
|
246
|
+
// ============================================================================
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Lazy publish activation scenario.
|
|
250
|
+
*
|
|
251
|
+
* - 'A': First activation — registerAgent + publishConfig + setListed (3 calls)
|
|
252
|
+
* - 'B1': Re-publish with listing change — publishConfig + setListed (2 calls)
|
|
253
|
+
* - 'B2': Re-publish config only — publishConfig (1 call)
|
|
254
|
+
* - 'C': Stale pending — delete pending-publish.json, no calls
|
|
255
|
+
* - 'none': No pending publish, normal flow
|
|
256
|
+
*/
|
|
257
|
+
export type ActivationScenario = 'A' | 'B1' | 'B2' | 'C' | 'none';
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Parameters for building an activation batch.
|
|
261
|
+
*/
|
|
262
|
+
export interface ActivationBatchParams {
|
|
263
|
+
/** Activation scenario */
|
|
264
|
+
scenario: ActivationScenario;
|
|
265
|
+
/** AgentRegistry contract address */
|
|
266
|
+
agentRegistryAddress: string;
|
|
267
|
+
/** IPFS CID of the published AGIRAILS.md */
|
|
268
|
+
cid: string;
|
|
269
|
+
/** Canonical config hash (bytes32) */
|
|
270
|
+
configHash: string;
|
|
271
|
+
/** Agent endpoint URL (for scenario A registration) */
|
|
272
|
+
endpoint?: string;
|
|
273
|
+
/** Service descriptors (for scenario A registration) */
|
|
274
|
+
serviceDescriptors?: ServiceDescriptor[];
|
|
275
|
+
/** Whether to set listed=true (for scenarios A, B1) */
|
|
276
|
+
listed?: boolean;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Build a publishConfig batch call for AgentRegistry.
|
|
281
|
+
*
|
|
282
|
+
* @param agentRegistryAddress - AgentRegistry contract address
|
|
283
|
+
* @param cid - IPFS CID of the uploaded AGIRAILS.md
|
|
284
|
+
* @param configHash - Canonical config hash (bytes32)
|
|
285
|
+
*/
|
|
286
|
+
export function buildPublishConfigBatch(
|
|
287
|
+
agentRegistryAddress: string,
|
|
288
|
+
cid: string,
|
|
289
|
+
configHash: string
|
|
290
|
+
): SmartWalletCall[] {
|
|
291
|
+
const iface = new ethers.Interface(AGENT_REGISTRY_ABI);
|
|
292
|
+
const data = iface.encodeFunctionData('publishConfig', [cid, configHash]);
|
|
293
|
+
return [{ target: agentRegistryAddress, value: 0n, data }];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Build a setListed batch call for AgentRegistry.
|
|
298
|
+
*
|
|
299
|
+
* @param agentRegistryAddress - AgentRegistry contract address
|
|
300
|
+
* @param listed - Whether to list the agent
|
|
301
|
+
*/
|
|
302
|
+
export function buildSetListedBatch(
|
|
303
|
+
agentRegistryAddress: string,
|
|
304
|
+
listed: boolean
|
|
305
|
+
): SmartWalletCall[] {
|
|
306
|
+
const iface = new ethers.Interface(AGENT_REGISTRY_ABI);
|
|
307
|
+
const data = iface.encodeFunctionData('setListed', [listed]);
|
|
308
|
+
return [{ target: agentRegistryAddress, value: 0n, data }];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Build the full activation batch based on scenario.
|
|
313
|
+
*
|
|
314
|
+
* Scenario call counts:
|
|
315
|
+
* - A: registerAgent + publishConfig + setListed = 3 calls
|
|
316
|
+
* - B1: publishConfig + setListed = 2 calls
|
|
317
|
+
* - B2: publishConfig = 1 call
|
|
318
|
+
* - C/none: empty (0 calls)
|
|
319
|
+
*/
|
|
320
|
+
export function buildActivationBatch(params: ActivationBatchParams): SmartWalletCall[] {
|
|
321
|
+
const { scenario, agentRegistryAddress, cid, configHash } = params;
|
|
322
|
+
|
|
323
|
+
switch (scenario) {
|
|
324
|
+
case 'A': {
|
|
325
|
+
// First activation: register + publish + list
|
|
326
|
+
if (!params.endpoint || !params.serviceDescriptors || params.serviceDescriptors.length === 0) {
|
|
327
|
+
throw new Error('Scenario A requires endpoint and serviceDescriptors');
|
|
328
|
+
}
|
|
329
|
+
const registerCalls = buildRegisterAgentBatch(
|
|
330
|
+
agentRegistryAddress,
|
|
331
|
+
params.endpoint,
|
|
332
|
+
params.serviceDescriptors
|
|
333
|
+
);
|
|
334
|
+
const publishCalls = buildPublishConfigBatch(agentRegistryAddress, cid, configHash);
|
|
335
|
+
const listCalls = buildSetListedBatch(agentRegistryAddress, params.listed ?? true);
|
|
336
|
+
return [...registerCalls, ...publishCalls, ...listCalls];
|
|
337
|
+
}
|
|
338
|
+
case 'B1': {
|
|
339
|
+
// Re-publish with listing: publish + list
|
|
340
|
+
const publishCalls = buildPublishConfigBatch(agentRegistryAddress, cid, configHash);
|
|
341
|
+
const listCalls = buildSetListedBatch(agentRegistryAddress, params.listed ?? true);
|
|
342
|
+
return [...publishCalls, ...listCalls];
|
|
343
|
+
}
|
|
344
|
+
case 'B2': {
|
|
345
|
+
// Re-publish config only
|
|
346
|
+
return buildPublishConfigBatch(agentRegistryAddress, cid, configHash);
|
|
347
|
+
}
|
|
348
|
+
case 'C':
|
|
349
|
+
case 'none':
|
|
350
|
+
// Stale or no pending — no activation calls
|
|
351
|
+
return [];
|
|
352
|
+
}
|
|
353
|
+
}
|