zk-agent-cli 0.1.0-beta.4 → 0.1.0-beta.6

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/index.js CHANGED
@@ -74,10 +74,215 @@ function isAgentError(error) {
74
74
  }
75
75
 
76
76
  // ../agent-core/src/storage.ts
77
- import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
77
+ import { createCipheriv, createDecipheriv, randomBytes as randomBytes2 } from "node:crypto";
78
78
  import fs from "node:fs";
79
79
  import os from "node:os";
80
80
  import path from "node:path";
81
+
82
+ // ../agent-session-protocol/src/constants.ts
83
+ var PROTOCOL_VERSION = "zk-agent-session-v1";
84
+
85
+ // ../agent-session-protocol/src/encoding.ts
86
+ function bytesToHex(bytes) {
87
+ return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
88
+ }
89
+ function hexToBytes(hex) {
90
+ if (hex.length % 2 !== 0) throw new Error("Invalid hex string");
91
+ const bytes = new Uint8Array(hex.length / 2);
92
+ for (let index = 0; index < bytes.length; index += 1) {
93
+ bytes[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
94
+ }
95
+ return bytes;
96
+ }
97
+ function b64urlEncode(bytes) {
98
+ let binary = "";
99
+ for (const byte of bytes) binary += String.fromCharCode(byte);
100
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
101
+ }
102
+ function b64urlDecode(value) {
103
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
104
+ const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - normalized.length % 4);
105
+ const binary = atob(normalized + padding);
106
+ const bytes = new Uint8Array(binary.length);
107
+ for (let index = 0; index < binary.length; index += 1) {
108
+ bytes[index] = binary.charCodeAt(index);
109
+ }
110
+ return bytes;
111
+ }
112
+
113
+ // ../agent-session-protocol/src/crypto.ts
114
+ import { xchacha20poly1305 } from "@noble/ciphers/chacha";
115
+ import { x25519 } from "@noble/curves/ed25519";
116
+ import { secp256k1 } from "@noble/curves/secp256k1";
117
+ import { hkdf } from "@noble/hashes/hkdf";
118
+ import { sha256 } from "@noble/hashes/sha2";
119
+ import { keccak_256 } from "@noble/hashes/sha3";
120
+ import { randomBytes } from "@noble/hashes/utils";
121
+ function deriveEthereumAddressFromPrivateKey(privateKeyHex) {
122
+ const normalized = privateKeyHex.startsWith("0x") ? privateKeyHex.slice(2) : privateKeyHex;
123
+ if (!/^[0-9a-fA-F]{64}$/.test(normalized)) {
124
+ throw new Error("sessionPrivateKey must be a 32-byte hex string");
125
+ }
126
+ const publicKey = secp256k1.getPublicKey(hexToBytes(normalized), false);
127
+ const digest = keccak_256(publicKey.slice(1));
128
+ return `0x${bytesToHex(digest.slice(-20))}`;
129
+ }
130
+ function generateX25519Keypair() {
131
+ const secretKey = randomBytes(32);
132
+ const publicKey = x25519.getPublicKey(secretKey);
133
+ return { secretKey, publicKey };
134
+ }
135
+ function computeCodeHash(requestId, code) {
136
+ if (!requestId) throw new Error("requestId must not be empty");
137
+ return sha256(new TextEncoder().encode(requestId + code));
138
+ }
139
+ function deriveEncryptionKey(sharedSecret, code, cliPublicKeyHex, walletPublicKeyHex) {
140
+ const salt = sha256(new TextEncoder().encode(code));
141
+ const info = new TextEncoder().encode(cliPublicKeyHex + walletPublicKeyHex + PROTOCOL_VERSION);
142
+ return hkdf(sha256, sharedSecret, salt, info, 32);
143
+ }
144
+ function decryptSession(encrypted, cliSecretKey, code, requestId) {
145
+ if (!requestId) throw new Error("requestId must not be empty");
146
+ const cliPublicKey = x25519.getPublicKey(cliSecretKey);
147
+ const walletPublicKey = hexToBytes(encrypted.wallet_pk_hex);
148
+ const sharedSecret = x25519.getSharedSecret(cliSecretKey, walletPublicKey);
149
+ const cliPublicKeyHex = bytesToHex(cliPublicKey);
150
+ const encryptionKey = deriveEncryptionKey(
151
+ sharedSecret,
152
+ code,
153
+ cliPublicKeyHex,
154
+ encrypted.wallet_pk_hex
155
+ );
156
+ const expectedHash = bytesToHex(computeCodeHash(requestId, code));
157
+ if (expectedHash !== encrypted.code_hash_hex) {
158
+ throw new Error("Invalid code: hash mismatch");
159
+ }
160
+ const nonce = hexToBytes(encrypted.nonce_hex);
161
+ const aad = new Uint8Array([...cliPublicKey, ...walletPublicKey]);
162
+ const ciphertext = b64urlDecode(encrypted.ciphertext_b64url);
163
+ const cipher = xchacha20poly1305(encryptionKey, nonce, aad);
164
+ const plaintext = cipher.decrypt(ciphertext);
165
+ return JSON.parse(new TextDecoder().decode(plaintext));
166
+ }
167
+
168
+ // ../agent-session-protocol/src/connector.ts
169
+ var textEncoder = new TextEncoder();
170
+ var textDecoder = new TextDecoder();
171
+ function isAddress(value) {
172
+ return /^0x[a-fA-F0-9]{40}$/.test(value);
173
+ }
174
+ function encodeSessionApprovalRequest(request) {
175
+ return b64urlEncode(textEncoder.encode(JSON.stringify(request)));
176
+ }
177
+ function buildApprovedSessionPayload(input) {
178
+ if (!isAddress(input.walletAddress)) throw new Error("walletAddress must be a valid address");
179
+ const derivedOwnerAddress = input.sessionPrivateKey ? deriveEthereumAddressFromPrivateKey(input.sessionPrivateKey) : void 0;
180
+ const ownerAddress = input.ownerAddress || derivedOwnerAddress;
181
+ if (ownerAddress && !isAddress(ownerAddress)) {
182
+ throw new Error("ownerAddress must be a valid address");
183
+ }
184
+ if (input.sessionAddress && !isAddress(input.sessionAddress)) {
185
+ throw new Error("sessionAddress must be a valid address");
186
+ }
187
+ if (input.sessionPrivateKey && !/^0x[0-9a-fA-F]{64}$/.test(input.sessionPrivateKey)) {
188
+ throw new Error("sessionPrivateKey must be a 32-byte hex string");
189
+ }
190
+ if (input.validatorAddress && !isAddress(input.validatorAddress)) {
191
+ throw new Error("validatorAddress must be a valid address");
192
+ }
193
+ if (input.paymasterAddress && !isAddress(input.paymasterAddress)) {
194
+ throw new Error("paymasterAddress must be a valid address");
195
+ }
196
+ if (input.paymasterToken && !isAddress(input.paymasterToken)) {
197
+ throw new Error("paymasterToken must be a valid address");
198
+ }
199
+ if (input.request.requestedAccountKind === "smart-account" && !ownerAddress) {
200
+ throw new Error(
201
+ "Smart-account approval requires ownerAddress or a sessionPrivateKey that can be used to derive it"
202
+ );
203
+ }
204
+ const signerType = input.signerType || (input.sessionPrivateKey ? "local" : "connector");
205
+ return {
206
+ version: 1,
207
+ provider: input.request.provider,
208
+ chain: input.request.chain,
209
+ chainId: input.request.chainId,
210
+ walletAddress: input.walletAddress,
211
+ account: {
212
+ kind: input.request.requestedAccountKind,
213
+ address: input.walletAddress,
214
+ ownerAddress,
215
+ sessionAddress: input.sessionAddress,
216
+ validatorAddress: input.validatorAddress,
217
+ signerType
218
+ },
219
+ sessionScope: input.request.requestedSessionScope,
220
+ capabilities: input.request.requestedCapabilities,
221
+ sessionExpiresAt: input.request.expiresAt,
222
+ paymaster: {
223
+ mode: input.request.requestedPaymasterMode,
224
+ address: input.paymasterAddress || null,
225
+ token: input.paymasterToken
226
+ },
227
+ sessionPublicKey: input.request.sessionPublicKey,
228
+ sessionPrivateKey: input.sessionPrivateKey,
229
+ sessionAddress: input.sessionAddress,
230
+ permissions: input.request.policies,
231
+ connectorUrl: input.connectorUrl || input.request.connectorUrl,
232
+ connectorOrigin: input.connectorOrigin,
233
+ paymasterAddress: input.paymasterAddress || null,
234
+ metadata: input.metadata
235
+ };
236
+ }
237
+
238
+ // ../agent-core/src/wallet-session.ts
239
+ function isHexPrivateKey(value) {
240
+ return /^0x[a-fA-F0-9]{64}$/.test(value);
241
+ }
242
+ function deriveLocalExecutionSignerAddress(privateKey) {
243
+ if (!privateKey || !isHexPrivateKey(privateKey)) return void 0;
244
+ try {
245
+ return deriveEthereumAddressFromPrivateKey(privateKey);
246
+ } catch {
247
+ return void 0;
248
+ }
249
+ }
250
+ function buildLocalExecutionAuthority(input) {
251
+ if (!input.privateKey) return void 0;
252
+ return {
253
+ privateKey: input.privateKey,
254
+ signerAddress: deriveLocalExecutionSignerAddress(input.privateKey),
255
+ signerType: input.signerType || "local",
256
+ source: input.source,
257
+ attachedAt: input.attachedAt
258
+ };
259
+ }
260
+ function resolveLocalExecutionPrivateKey(wallet) {
261
+ return wallet.localExecutionAuthority?.privateKey || wallet.sessionPayload?.sessionPrivateKey;
262
+ }
263
+ function migrateWalletSessionRecord(wallet) {
264
+ const legacyPrivateKey = wallet.sessionPayload?.sessionPrivateKey;
265
+ const existingAuthority = wallet.localExecutionAuthority;
266
+ if (!existingAuthority && !legacyPrivateKey) {
267
+ return wallet;
268
+ }
269
+ const privateKey = existingAuthority?.privateKey || legacyPrivateKey;
270
+ if (!privateKey) {
271
+ return wallet;
272
+ }
273
+ return {
274
+ ...wallet,
275
+ localExecutionAuthority: {
276
+ privateKey,
277
+ signerAddress: existingAuthority?.signerAddress || deriveLocalExecutionSignerAddress(privateKey),
278
+ signerType: existingAuthority?.signerType || wallet.sessionPayload?.account?.signerType || "local",
279
+ source: existingAuthority?.source || (legacyPrivateKey ? "legacy-session-payload" : void 0),
280
+ attachedAt: existingAuthority?.attachedAt || wallet.createdAt
281
+ }
282
+ };
283
+ }
284
+
285
+ // ../agent-core/src/storage.ts
81
286
  function normalizeOptionalPath(value) {
82
287
  const trimmed = value?.trim();
83
288
  return trimmed ? path.resolve(trimmed) : null;
@@ -126,13 +331,13 @@ function getEncryptionKey() {
126
331
  const storageDirectory = ensureStorageDir();
127
332
  const encryptionKeyFile = path.join(storageDirectory, ".encryption-key");
128
333
  if (fs.existsSync(encryptionKeyFile)) return fs.readFileSync(encryptionKeyFile);
129
- const key = randomBytes(32);
334
+ const key = randomBytes2(32);
130
335
  fs.writeFileSync(encryptionKeyFile, key, { mode: 384 });
131
336
  return key;
132
337
  }
133
338
  function encrypt(plaintext) {
134
339
  const key = getEncryptionKey();
135
- const iv = randomBytes(16);
340
+ const iv = randomBytes2(16);
136
341
  const cipher = createCipheriv("aes-256-gcm", key, iv);
137
342
  let encrypted = cipher.update(plaintext, "utf8", "hex");
138
343
  encrypted += cipher.final("hex");
@@ -181,12 +386,15 @@ async function loadProjectConfig() {
181
386
  }
182
387
  async function saveWalletSession(record) {
183
388
  const storageDirectory = ensureStorageDir();
184
- writeEncryptedJson(path.join(storageDirectory, "wallets", `${record.walletName}.json`), record);
389
+ writeEncryptedJson(
390
+ path.join(storageDirectory, "wallets", `${record.walletName}.json`),
391
+ migrateWalletSessionRecord(record)
392
+ );
185
393
  }
186
394
  async function loadWalletSession(walletName) {
187
395
  const filePath = storagePath("wallets", `${walletName}.json`);
188
396
  if (!fs.existsSync(filePath)) return null;
189
- return readEncryptedJson(filePath);
397
+ return migrateWalletSessionRecord(readEncryptedJson(filePath));
190
398
  }
191
399
  async function listWalletNames() {
192
400
  const storageDirectory = ensureStorageDir();
@@ -2225,22 +2433,33 @@ function buildWalletPreparationActions(input) {
2225
2433
  const { wallet, inspection, funding } = input;
2226
2434
  const exclude = new Set(input.excludeActionIds || []);
2227
2435
  const actions = [];
2228
- if (!exclude.has("reapprove") && !inspection.sessionPrivateKeyStored) {
2436
+ const approvalReady = inspection.approvalReady ?? Boolean(wallet.sessionPayload);
2437
+ const localExecutionReady = inspection.localExecutionKeyStored ?? inspection.sessionPrivateKeyStored;
2438
+ if (!exclude.has("reapprove") && !approvalReady) {
2229
2439
  actions.push({
2230
2440
  id: "reapprove",
2231
2441
  priority: "required",
2232
- title: "Restore a writable local session",
2233
- reason: "No local session key is stored, so this wallet cannot execute local write actions yet.",
2442
+ title: "Restore approved session metadata",
2443
+ reason: localExecutionReady ? "A local execution signer is already present, but approved session metadata is missing or not usable yet." : "Approved session metadata is missing, so this wallet cannot execute local write actions yet.",
2234
2444
  command: `zk-agent wallet reapprove --name ${wallet.walletName} --await-local`
2235
2445
  });
2236
2446
  }
2447
+ if (!exclude.has("attach-signer") && approvalReady && !localExecutionReady) {
2448
+ actions.push({
2449
+ id: "attach-signer",
2450
+ priority: "required",
2451
+ title: "Attach a local execution signer",
2452
+ reason: "Approved session metadata exists, but no local execution signer is stored for write actions yet.",
2453
+ command: `zk-agent wallet signer attach --name ${wallet.walletName} --private-key <hex>`
2454
+ });
2455
+ }
2237
2456
  if (!exclude.has("signer-mismatch") && inspection.signerMatchesStoredIdentity === false) {
2238
2457
  actions.push({
2239
2458
  id: "signer-mismatch",
2240
2459
  priority: "required",
2241
2460
  title: "Repair the signer/address mismatch",
2242
2461
  reason: "The stored local signer does not match the wallet identity currently recorded for this session.",
2243
- command: `zk-agent wallet reapprove --name ${wallet.walletName} --await-local`
2462
+ command: `zk-agent wallet signer attach --name ${wallet.walletName} --private-key <hex>`
2244
2463
  });
2245
2464
  }
2246
2465
  if (!exclude.has("deploy") && inspection.accountKind === "smart-account" && inspection.deploymentStatus === "not-deployed") {
@@ -2784,7 +3003,7 @@ async function loadWorkflowRuntimeState(wallet, intent, provider4, protocol, toC
2784
3003
  }
2785
3004
  function manualBlockingActionIds(plan) {
2786
3005
  return plan.steps.filter(
2787
- (step) => step.kind === "prerequisite" && step.priority === "required" && (step.id === "reapprove" || step.id === "signer-mismatch" || step.id === "deploy")
3006
+ (step) => step.kind === "prerequisite" && step.priority === "required" && (step.id === "reapprove" || step.id === "attach-signer" || step.id === "signer-mismatch" || step.id === "deploy")
2788
3007
  ).map((step) => step.id);
2789
3008
  }
2790
3009
  function buildWorkflowGoalCommand(goal, wallet) {
@@ -3120,7 +3339,7 @@ function mergeNotes2(...groups) {
3120
3339
  }
3121
3340
  function manualBlockingActionIds2(plan) {
3122
3341
  return plan.steps.filter(
3123
- (step) => step.kind === "prerequisite" && step.priority === "required" && (step.id === "reapprove" || step.id === "signer-mismatch" || step.id === "deploy")
3342
+ (step) => step.kind === "prerequisite" && step.priority === "required" && (step.id === "reapprove" || step.id === "attach-signer" || step.id === "signer-mismatch" || step.id === "deploy")
3124
3343
  ).map((step) => step.id);
3125
3344
  }
3126
3345
  async function inspectWorkflowStatus(input, deps) {
@@ -4159,28 +4378,28 @@ function getL1ExplorerUrl(l1ChainId, txHash) {
4159
4378
  return void 0;
4160
4379
  }
4161
4380
  }
4162
- function isHexPrivateKey(value) {
4381
+ function isHexPrivateKey2(value) {
4163
4382
  return /^0x[a-fA-F0-9]{64}$/.test(value);
4164
4383
  }
4165
4384
  function deriveSignerAddress(privateKey) {
4166
- if (!privateKey || !isHexPrivateKey(privateKey)) return void 0;
4385
+ if (!privateKey || !isHexPrivateKey2(privateKey)) return void 0;
4167
4386
  return new Wallet(privateKey).address;
4168
4387
  }
4169
4388
  function requireWritableSession(wallet) {
4170
- const privateKey = wallet.sessionPayload?.sessionPrivateKey;
4389
+ const privateKey = resolveLocalExecutionPrivateKey(wallet);
4171
4390
  if (!privateKey) {
4172
4391
  throw new AgentError(
4173
4392
  "WRITABLE_SESSION_REQUIRED",
4174
- "Writable local execution requires a stored sessionPrivateKey.",
4393
+ "Writable local execution requires a stored local execution key.",
4175
4394
  {
4176
4395
  walletName: wallet.walletName
4177
4396
  }
4178
4397
  );
4179
4398
  }
4180
- if (!isHexPrivateKey(privateKey)) {
4399
+ if (!isHexPrivateKey2(privateKey)) {
4181
4400
  throw new AgentError(
4182
4401
  "WRITABLE_SESSION_INVALID",
4183
- "Stored sessionPrivateKey is not a valid 32-byte hex key.",
4402
+ "Stored local execution key is not a valid 32-byte hex key.",
4184
4403
  {
4185
4404
  walletName: wallet.walletName
4186
4405
  }
@@ -4193,7 +4412,7 @@ function resolveWritableSignerAddress(wallet, privateKey) {
4193
4412
  if (!derivedSignerAddress) {
4194
4413
  throw new AgentError(
4195
4414
  "WRITABLE_SESSION_INVALID",
4196
- "Stored sessionPrivateKey is not a valid 32-byte hex key.",
4415
+ "Stored local execution key is not a valid 32-byte hex key.",
4197
4416
  {
4198
4417
  walletName: wallet.walletName
4199
4418
  }
@@ -4204,7 +4423,7 @@ function resolveWritableSignerAddress(wallet, privateKey) {
4204
4423
  if (derivedSignerAddress.toLowerCase() !== executionAddress.toLowerCase()) {
4205
4424
  throw new AgentError(
4206
4425
  "EOA_SIGNER_MISMATCH",
4207
- "Stored sessionPrivateKey does not match the EOA execution address.",
4426
+ "Stored local execution key does not match the EOA execution address.",
4208
4427
  {
4209
4428
  walletName: wallet.walletName,
4210
4429
  executionAddress,
@@ -4228,7 +4447,7 @@ function resolveWritableSignerAddress(wallet, privateKey) {
4228
4447
  if (derivedSignerAddress.toLowerCase() !== ownerAddress.toLowerCase()) {
4229
4448
  throw new AgentError(
4230
4449
  "SMART_ACCOUNT_SIGNER_MISMATCH",
4231
- "Stored sessionPrivateKey does not match the smart-account ownerAddress.",
4450
+ "Stored local execution key does not match the smart-account ownerAddress.",
4232
4451
  {
4233
4452
  walletName: wallet.walletName,
4234
4453
  ownerAddress,
@@ -4255,7 +4474,7 @@ async function buildSigner(wallet, provider4) {
4255
4474
  if (derivedSignerAddress && derivedSignerAddress.toLowerCase() !== executionAddress.toLowerCase()) {
4256
4475
  throw new AgentError(
4257
4476
  "EOA_SIGNER_MISMATCH",
4258
- "Stored sessionPrivateKey does not match the EOA execution address.",
4477
+ "Stored local execution key does not match the EOA execution address.",
4259
4478
  {
4260
4479
  walletName: wallet.walletName,
4261
4480
  executionAddress,
@@ -4279,7 +4498,7 @@ async function buildSigner(wallet, provider4) {
4279
4498
  if (derivedSignerAddress && derivedSignerAddress.toLowerCase() !== ownerAddress.toLowerCase()) {
4280
4499
  throw new AgentError(
4281
4500
  "SMART_ACCOUNT_SIGNER_MISMATCH",
4282
- "Stored sessionPrivateKey does not match the smart-account ownerAddress.",
4501
+ "Stored local execution key does not match the smart-account ownerAddress.",
4283
4502
  {
4284
4503
  walletName: wallet.walletName,
4285
4504
  ownerAddress,
@@ -6064,163 +6283,6 @@ var ZkSyncDefiProvider = class {
6064
6283
 
6065
6284
  // ../provider-zksync-wallet/src/provider.ts
6066
6285
  import { randomBytes as randomBytes3 } from "node:crypto";
6067
-
6068
- // ../agent-session-protocol/src/constants.ts
6069
- var PROTOCOL_VERSION = "zk-agent-session-v1";
6070
-
6071
- // ../agent-session-protocol/src/encoding.ts
6072
- function bytesToHex(bytes) {
6073
- return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
6074
- }
6075
- function hexToBytes(hex) {
6076
- if (hex.length % 2 !== 0) throw new Error("Invalid hex string");
6077
- const bytes = new Uint8Array(hex.length / 2);
6078
- for (let index = 0; index < bytes.length; index += 1) {
6079
- bytes[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
6080
- }
6081
- return bytes;
6082
- }
6083
- function b64urlEncode(bytes) {
6084
- let binary = "";
6085
- for (const byte of bytes) binary += String.fromCharCode(byte);
6086
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
6087
- }
6088
- function b64urlDecode(value) {
6089
- const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
6090
- const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - normalized.length % 4);
6091
- const binary = atob(normalized + padding);
6092
- const bytes = new Uint8Array(binary.length);
6093
- for (let index = 0; index < binary.length; index += 1) {
6094
- bytes[index] = binary.charCodeAt(index);
6095
- }
6096
- return bytes;
6097
- }
6098
-
6099
- // ../agent-session-protocol/src/crypto.ts
6100
- import { xchacha20poly1305 } from "@noble/ciphers/chacha";
6101
- import { x25519 } from "@noble/curves/ed25519";
6102
- import { secp256k1 } from "@noble/curves/secp256k1";
6103
- import { hkdf } from "@noble/hashes/hkdf";
6104
- import { sha256 } from "@noble/hashes/sha2";
6105
- import { keccak_256 } from "@noble/hashes/sha3";
6106
- import { randomBytes as randomBytes2 } from "@noble/hashes/utils";
6107
- function deriveEthereumAddressFromPrivateKey(privateKeyHex) {
6108
- const normalized = privateKeyHex.startsWith("0x") ? privateKeyHex.slice(2) : privateKeyHex;
6109
- if (!/^[0-9a-fA-F]{64}$/.test(normalized)) {
6110
- throw new Error("sessionPrivateKey must be a 32-byte hex string");
6111
- }
6112
- const publicKey = secp256k1.getPublicKey(hexToBytes(normalized), false);
6113
- const digest = keccak_256(publicKey.slice(1));
6114
- return `0x${bytesToHex(digest.slice(-20))}`;
6115
- }
6116
- function generateX25519Keypair() {
6117
- const secretKey = randomBytes2(32);
6118
- const publicKey = x25519.getPublicKey(secretKey);
6119
- return { secretKey, publicKey };
6120
- }
6121
- function computeCodeHash(requestId, code) {
6122
- if (!requestId) throw new Error("requestId must not be empty");
6123
- return sha256(new TextEncoder().encode(requestId + code));
6124
- }
6125
- function deriveEncryptionKey(sharedSecret, code, cliPublicKeyHex, walletPublicKeyHex) {
6126
- const salt = sha256(new TextEncoder().encode(code));
6127
- const info = new TextEncoder().encode(cliPublicKeyHex + walletPublicKeyHex + PROTOCOL_VERSION);
6128
- return hkdf(sha256, sharedSecret, salt, info, 32);
6129
- }
6130
- function decryptSession(encrypted, cliSecretKey, code, requestId) {
6131
- if (!requestId) throw new Error("requestId must not be empty");
6132
- const cliPublicKey = x25519.getPublicKey(cliSecretKey);
6133
- const walletPublicKey = hexToBytes(encrypted.wallet_pk_hex);
6134
- const sharedSecret = x25519.getSharedSecret(cliSecretKey, walletPublicKey);
6135
- const cliPublicKeyHex = bytesToHex(cliPublicKey);
6136
- const encryptionKey = deriveEncryptionKey(
6137
- sharedSecret,
6138
- code,
6139
- cliPublicKeyHex,
6140
- encrypted.wallet_pk_hex
6141
- );
6142
- const expectedHash = bytesToHex(computeCodeHash(requestId, code));
6143
- if (expectedHash !== encrypted.code_hash_hex) {
6144
- throw new Error("Invalid code: hash mismatch");
6145
- }
6146
- const nonce = hexToBytes(encrypted.nonce_hex);
6147
- const aad = new Uint8Array([...cliPublicKey, ...walletPublicKey]);
6148
- const ciphertext = b64urlDecode(encrypted.ciphertext_b64url);
6149
- const cipher = xchacha20poly1305(encryptionKey, nonce, aad);
6150
- const plaintext = cipher.decrypt(ciphertext);
6151
- return JSON.parse(new TextDecoder().decode(plaintext));
6152
- }
6153
-
6154
- // ../agent-session-protocol/src/connector.ts
6155
- var textEncoder = new TextEncoder();
6156
- var textDecoder = new TextDecoder();
6157
- function isAddress(value) {
6158
- return /^0x[a-fA-F0-9]{40}$/.test(value);
6159
- }
6160
- function encodeSessionApprovalRequest(request) {
6161
- return b64urlEncode(textEncoder.encode(JSON.stringify(request)));
6162
- }
6163
- function buildApprovedSessionPayload(input) {
6164
- if (!isAddress(input.walletAddress)) throw new Error("walletAddress must be a valid address");
6165
- const derivedOwnerAddress = input.sessionPrivateKey ? deriveEthereumAddressFromPrivateKey(input.sessionPrivateKey) : void 0;
6166
- const ownerAddress = input.ownerAddress || derivedOwnerAddress;
6167
- if (ownerAddress && !isAddress(ownerAddress)) {
6168
- throw new Error("ownerAddress must be a valid address");
6169
- }
6170
- if (input.sessionAddress && !isAddress(input.sessionAddress)) {
6171
- throw new Error("sessionAddress must be a valid address");
6172
- }
6173
- if (input.sessionPrivateKey && !/^0x[0-9a-fA-F]{64}$/.test(input.sessionPrivateKey)) {
6174
- throw new Error("sessionPrivateKey must be a 32-byte hex string");
6175
- }
6176
- if (input.validatorAddress && !isAddress(input.validatorAddress)) {
6177
- throw new Error("validatorAddress must be a valid address");
6178
- }
6179
- if (input.paymasterAddress && !isAddress(input.paymasterAddress)) {
6180
- throw new Error("paymasterAddress must be a valid address");
6181
- }
6182
- if (input.paymasterToken && !isAddress(input.paymasterToken)) {
6183
- throw new Error("paymasterToken must be a valid address");
6184
- }
6185
- if (input.request.requestedAccountKind === "smart-account" && !ownerAddress) {
6186
- throw new Error(
6187
- "Smart-account approval requires ownerAddress or a sessionPrivateKey that can be used to derive it"
6188
- );
6189
- }
6190
- return {
6191
- version: 1,
6192
- provider: input.request.provider,
6193
- chain: input.request.chain,
6194
- chainId: input.request.chainId,
6195
- walletAddress: input.walletAddress,
6196
- account: {
6197
- kind: input.request.requestedAccountKind,
6198
- address: input.walletAddress,
6199
- ownerAddress,
6200
- sessionAddress: input.sessionAddress,
6201
- validatorAddress: input.validatorAddress,
6202
- signerType: input.signerType || "connector"
6203
- },
6204
- sessionScope: input.request.requestedSessionScope,
6205
- capabilities: input.request.requestedCapabilities,
6206
- sessionExpiresAt: input.request.expiresAt,
6207
- paymaster: {
6208
- mode: input.request.requestedPaymasterMode,
6209
- address: input.paymasterAddress || null,
6210
- token: input.paymasterToken
6211
- },
6212
- sessionPublicKey: input.request.sessionPublicKey,
6213
- sessionPrivateKey: input.sessionPrivateKey,
6214
- sessionAddress: input.sessionAddress,
6215
- permissions: input.request.policies,
6216
- connectorUrl: input.connectorUrl || input.request.connectorUrl,
6217
- connectorOrigin: input.connectorOrigin,
6218
- paymasterAddress: input.paymasterAddress || null,
6219
- metadata: input.metadata
6220
- };
6221
- }
6222
-
6223
- // ../provider-zksync-wallet/src/provider.ts
6224
6286
  import { ethers as ethers2 } from "ethers";
6225
6287
  import { ContractFactory, ECDSASmartAccount as ECDSASmartAccount2, Provider as Provider2, Wallet as Wallet2, utils as utils2 } from "zksync-ethers";
6226
6288
 
@@ -6251,7 +6313,7 @@ function isAddress2(value) {
6251
6313
  function isHexData(value) {
6252
6314
  return /^0x([a-fA-F0-9]{2})*$/.test(value);
6253
6315
  }
6254
- function isHexPrivateKey2(value) {
6316
+ function isHexPrivateKey3(value) {
6255
6317
  return /^0x[a-fA-F0-9]{64}$/.test(value);
6256
6318
  }
6257
6319
  function padHex(value, length = 64) {
@@ -6428,6 +6490,8 @@ function buildInspectionResult(wallet, codeLength, derivedSignerAddress, blocker
6428
6490
  paymasterMode: wallet.paymasterMode,
6429
6491
  deploymentStatus,
6430
6492
  codeLength,
6493
+ approvalReady: Boolean(wallet.sessionPayload),
6494
+ localExecutionKeyStored: Boolean(resolveLocalExecutionPrivateKey(wallet)),
6431
6495
  sessionPrivateKeyStored: Boolean(wallet.sessionPayload?.sessionPrivateKey),
6432
6496
  derivedSignerAddress,
6433
6497
  signerMatchesStoredIdentity,
@@ -6837,7 +6901,7 @@ async function preparePaymasterTransaction(wallet, tx, paymaster) {
6837
6901
  }
6838
6902
  }
6839
6903
  function deriveSignerAddress2(privateKey) {
6840
- if (!privateKey || !isHexPrivateKey2(privateKey)) return void 0;
6904
+ if (!privateKey || !isHexPrivateKey3(privateKey)) return void 0;
6841
6905
  return new Wallet2(privateKey).address;
6842
6906
  }
6843
6907
  function normalizeSalt(value) {
@@ -6897,7 +6961,7 @@ async function resolveSmartAccountDeploymentContext(input) {
6897
6961
  if (deployerAddress.toLowerCase() !== ownerAddress.toLowerCase()) {
6898
6962
  throw new AgentError(
6899
6963
  "SMART_ACCOUNT_SIGNER_MISMATCH",
6900
- "Stored sessionPrivateKey does not match the smart-account ownerAddress.",
6964
+ "Stored local execution key does not match the smart-account ownerAddress.",
6901
6965
  {
6902
6966
  walletName: wallet.walletName,
6903
6967
  ownerAddress,
@@ -6994,7 +7058,7 @@ async function resolveSmartAccountDeploymentContext(input) {
6994
7058
  async function inspectWalletRecord(wallet) {
6995
7059
  const executionAddress = resolveExecutionAddress2(wallet);
6996
7060
  const ownerAddress = resolveOwnerAddress2(wallet);
6997
- const sessionPrivateKey = wallet.sessionPayload?.sessionPrivateKey;
7061
+ const sessionPrivateKey = resolveLocalExecutionPrivateKey(wallet);
6998
7062
  const derivedSignerAddress = deriveSignerAddress2(sessionPrivateKey);
6999
7063
  const provider4 = getProvider(wallet.chain);
7000
7064
  const code = await provider4.getCode(executionAddress);
@@ -7006,14 +7070,14 @@ async function inspectWalletRecord(wallet) {
7006
7070
  }
7007
7071
  if (!sessionPrivateKey) {
7008
7072
  blockers.push(
7009
- "Writable local execution requires a stored sessionPrivateKey. Re-approve locally with --session-private-key or import a writable session."
7073
+ "Writable local execution requires a stored local execution key. Attach one with wallet signer attach, re-approve locally with --session-private-key, or import a writable session."
7010
7074
  );
7011
- } else if (!isHexPrivateKey2(sessionPrivateKey)) {
7012
- blockers.push("Stored sessionPrivateKey is not a valid 32-byte hex key.");
7075
+ } else if (!isHexPrivateKey3(sessionPrivateKey)) {
7076
+ blockers.push("Stored local execution key is not a valid 32-byte hex key.");
7013
7077
  }
7014
7078
  if (wallet.accountKind === "eoa") {
7015
7079
  if (derivedSignerAddress && derivedSignerAddress.toLowerCase() !== executionAddress.toLowerCase()) {
7016
- blockers.push("Stored sessionPrivateKey does not match the EOA execution address.");
7080
+ blockers.push("Stored local execution key does not match the EOA execution address.");
7017
7081
  }
7018
7082
  if (codeLength > 0) {
7019
7083
  notes.push("EOA wallet record points to an address with deployed bytecode.");
@@ -7024,7 +7088,7 @@ async function inspectWalletRecord(wallet) {
7024
7088
  blockers.push("Smart-account session is missing ownerAddress metadata.");
7025
7089
  }
7026
7090
  if (ownerAddress && derivedSignerAddress && derivedSignerAddress.toLowerCase() !== ownerAddress.toLowerCase()) {
7027
- blockers.push("Stored sessionPrivateKey does not match the smart-account ownerAddress.");
7091
+ blockers.push("Stored local execution key does not match the smart-account ownerAddress.");
7028
7092
  }
7029
7093
  if (codeLength === 0) {
7030
7094
  blockers.push(
@@ -7050,18 +7114,18 @@ async function assertWalletReadyForWrite(wallet) {
7050
7114
  { inspection }
7051
7115
  );
7052
7116
  }
7053
- const sessionPrivateKey = wallet.sessionPayload?.sessionPrivateKey;
7054
- if (!sessionPrivateKey || !isHexPrivateKey2(sessionPrivateKey)) {
7117
+ const sessionPrivateKey = resolveLocalExecutionPrivateKey(wallet);
7118
+ if (!sessionPrivateKey || !isHexPrivateKey3(sessionPrivateKey)) {
7055
7119
  throw new AgentError(
7056
7120
  "WRITABLE_SESSION_REQUIRED",
7057
- "Writable local execution requires a valid stored sessionPrivateKey.",
7121
+ "Writable local execution requires a valid stored local execution key.",
7058
7122
  { inspection }
7059
7123
  );
7060
7124
  }
7061
7125
  if (wallet.accountKind === "eoa") {
7062
7126
  throw new AgentError(
7063
7127
  "EOA_SIGNER_MISMATCH",
7064
- "Stored sessionPrivateKey does not match the EOA execution address.",
7128
+ "Stored local execution key does not match the EOA execution address.",
7065
7129
  { inspection }
7066
7130
  );
7067
7131
  }
@@ -7075,7 +7139,7 @@ async function assertWalletReadyForWrite(wallet) {
7075
7139
  if (inspection.signerMatchesStoredIdentity === false) {
7076
7140
  throw new AgentError(
7077
7141
  "SMART_ACCOUNT_SIGNER_MISMATCH",
7078
- "Stored sessionPrivateKey does not match the smart-account ownerAddress.",
7142
+ "Stored local execution key does not match the smart-account ownerAddress.",
7079
7143
  { inspection }
7080
7144
  );
7081
7145
  }
@@ -7096,14 +7160,14 @@ async function assertWalletReadyForWrite(wallet) {
7096
7160
  );
7097
7161
  }
7098
7162
  function requireWritableSession2(wallet) {
7099
- const privateKey = wallet.sessionPayload?.sessionPrivateKey;
7163
+ const privateKey = resolveLocalExecutionPrivateKey(wallet);
7100
7164
  if (!privateKey) {
7101
7165
  throw new Error(
7102
- "Writable session requires sessionPrivateKey. Re-approve locally with --session-private-key or import a writable testnet session."
7166
+ "Writable session requires a stored local execution key. Re-approve locally with --session-private-key or import a writable testnet session."
7103
7167
  );
7104
7168
  }
7105
- if (!isHexPrivateKey2(privateKey)) {
7106
- throw new Error("Stored sessionPrivateKey is not a valid 32-byte hex key");
7169
+ if (!isHexPrivateKey3(privateKey)) {
7170
+ throw new Error("Stored local execution key is not a valid 32-byte hex key");
7107
7171
  }
7108
7172
  return privateKey;
7109
7173
  }
@@ -7354,6 +7418,13 @@ var ZkSyncWalletProvider = class {
7354
7418
  }
7355
7419
  const chain = resolveChain(payload.chainId);
7356
7420
  const walletAddress = payload.account?.address || payload.walletAddress;
7421
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
7422
+ const localExecutionAuthority = buildLocalExecutionAuthority({
7423
+ privateKey: payload.sessionPrivateKey,
7424
+ signerType: payload.account?.signerType,
7425
+ source: payload.sessionPrivateKey ? payload.account?.signerType === "local" ? "explicit-local-approval" : "approved-payload" : void 0,
7426
+ attachedAt: createdAt
7427
+ });
7357
7428
  return {
7358
7429
  walletName,
7359
7430
  walletAddress,
@@ -7367,7 +7438,8 @@ var ZkSyncWalletProvider = class {
7367
7438
  sessionScope: payload.sessionScope || summarizeScope(chain.key, chain.chainId),
7368
7439
  capabilities: payload.capabilities,
7369
7440
  paymasterMode: resolvePaymasterMode(payload),
7370
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7441
+ createdAt,
7442
+ localExecutionAuthority,
7371
7443
  sessionPayload: payload
7372
7444
  };
7373
7445
  }
@@ -7692,6 +7764,9 @@ function buildWalletListRecommendedCommand() {
7692
7764
  function buildWalletStatusRecommendedCommand(walletName) {
7693
7765
  return `zk-agent wallet status --name ${walletName}`;
7694
7766
  }
7767
+ function buildWalletSignerAttachRecommendedCommand(walletName, privateKeyRef = "<hex>") {
7768
+ return `zk-agent wallet signer attach --name ${walletName} --private-key ${privateKeyRef}`;
7769
+ }
7695
7770
  function buildAssetsRecommendedCommand(walletName) {
7696
7771
  return `zk-agent assets --wallet ${walletName}`;
7697
7772
  }
@@ -7760,6 +7835,10 @@ function buildWorkflowListRecommendedCommand() {
7760
7835
  function buildWorkflowAutoRecommendedCommand(walletName) {
7761
7836
  return `zk-agent workflow auto --wallet ${walletName} --intent <intent> [goal flags] --create-checkpoint --execute-when-ready`;
7762
7837
  }
7838
+ function buildWorkflowPayRecommendedCommand(walletName, paymasterMode) {
7839
+ const command = `zk-agent workflow pay --wallet ${walletName} --to <address> --amount <amount>`;
7840
+ return appendPaymasterMode(command, paymasterMode);
7841
+ }
7763
7842
  function buildWorkflowShowRecommendedCommand(requestId) {
7764
7843
  return `zk-agent workflow show --request-id ${requestId}`;
7765
7844
  }
@@ -10505,11 +10584,15 @@ function createNextCommand(deps) {
10505
10584
  nativeSymbol: nativeBalance?.symbol,
10506
10585
  funding
10507
10586
  });
10587
+ const workflowPay = buildWorkflowPayRecommendedCommand(
10588
+ wallet.walletName,
10589
+ paymasterMode
10590
+ );
10508
10591
  const workflowAuto = appendPaymasterMode2(
10509
10592
  buildWorkflowAutoRecommendedCommand(wallet.walletName),
10510
10593
  paymasterMode
10511
10594
  );
10512
- const nextCommand = summary.recommendedCommand || workflowAuto;
10595
+ const nextCommand = summary.recommendedCommand || workflowPay;
10513
10596
  const agentFollowup = buildAgentFollowup(agentProfile, {
10514
10597
  walletName: wallet.walletName,
10515
10598
  walletExists: true
@@ -10521,6 +10604,7 @@ function createNextCommand(deps) {
10521
10604
  discoverOwnedTokens: buildOwnedTokensRecommendedCommand(wallet.walletName),
10522
10605
  discoverTokens: buildTokensRecommendedCommand(wallet.chain),
10523
10606
  inspectToken: buildResolveTokenRecommendedCommand(wallet.chain),
10607
+ workflowPay,
10524
10608
  workflowAuto,
10525
10609
  nextAction: nextCommand,
10526
10610
  inspectDefaults: buildDefaultsRecommendedCommand()
@@ -10530,7 +10614,7 @@ function createNextCommand(deps) {
10530
10614
  ...walletNextLines(summary),
10531
10615
  ...agentProfileLines(agentProfile),
10532
10616
  ...agentFollowupLines(agentFollowup),
10533
- ...summary.recommendedCommand ? [] : [["next", workflowAuto]],
10617
+ ...summary.recommendedCommand ? [] : [["next", workflowPay]],
10534
10618
  ["discover assets", recommendedCommands.discoverAssets],
10535
10619
  ["discover owned tokens", recommendedCommands.discoverOwnedTokens],
10536
10620
  ["discover tokens", recommendedCommands.discoverTokens],
@@ -11824,16 +11908,19 @@ function readRequestBody(request, limitBytes = RELAY_BODY_LIMIT_BYTES) {
11824
11908
  });
11825
11909
  }
11826
11910
  function writeJson2(response, statusCode, payload) {
11827
- response.statusCode = statusCode;
11828
- response.setHeader("Content-Type", "application/json; charset=utf-8");
11829
- response.setHeader("Access-Control-Allow-Origin", "*");
11830
- response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
11831
- response.setHeader("Access-Control-Allow-Headers", "Content-Type");
11832
- response.end(JSON.stringify(payload, null, 2));
11911
+ writeBody(response, statusCode, "application/json; charset=utf-8", JSON.stringify(payload, null, 2), {
11912
+ "Access-Control-Allow-Origin": "*",
11913
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
11914
+ "Access-Control-Allow-Headers": "Content-Type"
11915
+ });
11833
11916
  }
11834
- function writeText(response, statusCode, contentType, value) {
11917
+ function writeBody(response, statusCode, contentType, value, extraHeaders = {}) {
11835
11918
  response.statusCode = statusCode;
11836
11919
  response.setHeader("Content-Type", contentType);
11920
+ response.setHeader("Content-Length", Buffer.isBuffer(value) ? String(value.byteLength) : String(Buffer.byteLength(value)));
11921
+ for (const [key, headerValue] of Object.entries(extraHeaders)) {
11922
+ response.setHeader(key, headerValue);
11923
+ }
11837
11924
  response.end(value);
11838
11925
  }
11839
11926
  function resolveConnectorUiDistRoot() {
@@ -11861,8 +11948,8 @@ function contentTypeFor(filePath) {
11861
11948
  function normalizeRelayBaseUrl(baseUrl) {
11862
11949
  return baseUrl.replace(/\/+$/, "");
11863
11950
  }
11864
- function resolveRelayPublicBaseUrl(actualBaseUrl, publicOrigin) {
11865
- return publicOrigin?.trim() ? normalizeRelayBaseUrl(new URL(publicOrigin.trim()).toString()) : normalizeRelayBaseUrl(actualBaseUrl);
11951
+ function resolveRelayPublicBaseUrl(bindBaseUrl, publicOrigin) {
11952
+ return publicOrigin?.trim() ? normalizeRelayBaseUrl(new URL(publicOrigin.trim()).toString()) : normalizeRelayBaseUrl(bindBaseUrl);
11866
11953
  }
11867
11954
  function relayCapabilities(connectorUiAvailable) {
11868
11955
  return [
@@ -11874,14 +11961,14 @@ function relayCapabilities(connectorUiAvailable) {
11874
11961
  ...connectorUiAvailable ? ["connector-ui"] : []
11875
11962
  ];
11876
11963
  }
11877
- function relayHealthResponse(actualBaseUrl, publicBaseUrl, connectorUiAvailable) {
11964
+ function relayHealthResponse(bindBaseUrl, publicBaseUrl, connectorUiAvailable) {
11878
11965
  return {
11879
11966
  ok: true,
11880
11967
  service: RELAY_SERVICE,
11881
11968
  protocol: RELAY_PROTOCOL,
11882
11969
  schema_version: RELAY_SCHEMA_VERSION,
11883
11970
  relay_mode: "local-file",
11884
- origin: normalizeRelayBaseUrl(actualBaseUrl),
11971
+ origin: normalizeRelayBaseUrl(bindBaseUrl),
11885
11972
  public_origin: normalizeRelayBaseUrl(publicBaseUrl),
11886
11973
  connector_ui_available: connectorUiAvailable,
11887
11974
  capabilities: relayCapabilities(connectorUiAvailable)
@@ -11954,13 +12041,13 @@ async function fetchRelayApproval(baseUrl, requestId) {
11954
12041
  }
11955
12042
  async function startRelayServer(options) {
11956
12043
  const uiDistRoot = resolveConnectorUiDistRoot();
12044
+ let bindBaseUrl = "";
11957
12045
  const server = createServer(async (request, response) => {
11958
12046
  try {
11959
- const requestUrl = new URL(request.url || "/", `http://${request.headers.host || "localhost"}`);
12047
+ const requestUrl = new URL(request.url || "/", "http://localhost");
11960
12048
  const pathname = requestUrl.pathname;
11961
12049
  const method = request.method || "GET";
11962
- const actualBaseUrl = `${requestUrl.protocol}//${requestUrl.host}`;
11963
- const publicBaseUrl = resolveRelayPublicBaseUrl(actualBaseUrl, options.publicOrigin);
12050
+ const publicBaseUrl = resolveRelayPublicBaseUrl(bindBaseUrl, options.publicOrigin);
11964
12051
  if (method === "OPTIONS") {
11965
12052
  writeJson2(response, 204, {});
11966
12053
  return;
@@ -11969,7 +12056,7 @@ async function startRelayServer(options) {
11969
12056
  writeJson2(
11970
12057
  response,
11971
12058
  200,
11972
- relayHealthResponse(actualBaseUrl, publicBaseUrl, Boolean(uiDistRoot))
12059
+ relayHealthResponse(bindBaseUrl, publicBaseUrl, Boolean(uiDistRoot))
11973
12060
  );
11974
12061
  return;
11975
12062
  }
@@ -12044,12 +12131,12 @@ async function startRelayServer(options) {
12044
12131
  return;
12045
12132
  }
12046
12133
  if (fs7.existsSync(filePath) && fs7.statSync(filePath).isFile()) {
12047
- writeText(response, 200, contentTypeFor(filePath), fs7.readFileSync(filePath, "utf8"));
12134
+ writeBody(response, 200, contentTypeFor(filePath), fs7.readFileSync(filePath));
12048
12135
  return;
12049
12136
  }
12050
12137
  const indexPath = path6.join(uiDistRoot, "index.html");
12051
12138
  if (fs7.existsSync(indexPath)) {
12052
- writeText(response, 200, "text/html; charset=utf-8", fs7.readFileSync(indexPath, "utf8"));
12139
+ writeBody(response, 200, "text/html; charset=utf-8", fs7.readFileSync(indexPath));
12053
12140
  return;
12054
12141
  }
12055
12142
  }
@@ -12074,9 +12161,10 @@ async function startRelayServer(options) {
12074
12161
  });
12075
12162
  });
12076
12163
  });
12164
+ bindBaseUrl = `http://${address.address}:${address.port}`;
12077
12165
  return {
12078
12166
  connectorUiAvailable: Boolean(uiDistRoot),
12079
- origin: `http://${address.address}:${address.port}`,
12167
+ origin: bindBaseUrl,
12080
12168
  port: address.port,
12081
12169
  close: async () => await new Promise((resolve, reject) => {
12082
12170
  server.close((error) => {
@@ -12853,6 +12941,7 @@ var WALLET_HELP_COMMAND_ORDER = [
12853
12941
  "rename",
12854
12942
  "remove",
12855
12943
  "request",
12944
+ "signer",
12856
12945
  "paymaster",
12857
12946
  "smart-account"
12858
12947
  ];
@@ -12872,6 +12961,11 @@ var WALLET_SMART_ACCOUNT_HELP_COMMAND_ORDER = [
12872
12961
  "sed-lite",
12873
12962
  "daily-spend-limit"
12874
12963
  ];
12964
+ var WALLET_SIGNER_HELP_COMMAND_ORDER = [
12965
+ "show",
12966
+ "attach",
12967
+ "remove"
12968
+ ];
12875
12969
  function applyCommandOrder(command, orderedNames) {
12876
12970
  const order = new Map(orderedNames.map((name, index) => [name, index]));
12877
12971
  const sortedCommands = [...command.commands].sort((left, right) => {
@@ -12892,6 +12986,10 @@ function sanitizeSessionPayload(payload) {
12892
12986
  function stripSensitiveWalletRecord(wallet) {
12893
12987
  return {
12894
12988
  ...wallet,
12989
+ localExecutionAuthority: wallet.localExecutionAuthority ? {
12990
+ ...wallet.localExecutionAuthority,
12991
+ privateKey: void 0
12992
+ } : wallet.localExecutionAuthority,
12895
12993
  sessionPayload: wallet.sessionPayload ? {
12896
12994
  ...wallet.sessionPayload,
12897
12995
  sessionPrivateKey: void 0
@@ -12982,6 +13080,7 @@ function isRecord5(value) {
12982
13080
  function cloneWalletSessionRecord(wallet) {
12983
13081
  return {
12984
13082
  ...wallet,
13083
+ localExecutionAuthority: wallet.localExecutionAuthority ? { ...wallet.localExecutionAuthority } : wallet.localExecutionAuthority,
12985
13084
  validationHookAddresses: wallet.validationHookAddresses ? [...wallet.validationHookAddresses] : wallet.validationHookAddresses,
12986
13085
  sessionScope: wallet.sessionScope ? {
12987
13086
  ...wallet.sessionScope,
@@ -13032,6 +13131,12 @@ function parseWalletExportRecord(value) {
13032
13131
  if (wallet.validatorAddress && !isAddress3(wallet.validatorAddress)) {
13033
13132
  throw new Error("Restore payload validatorAddress must be a valid 20-byte hex address.");
13034
13133
  }
13134
+ if (wallet.localExecutionAuthority?.signerAddress && !isAddress3(wallet.localExecutionAuthority.signerAddress)) {
13135
+ throw new Error("Restore payload localExecutionAuthority.signerAddress must be a valid 20-byte hex address.");
13136
+ }
13137
+ if (wallet.localExecutionAuthority?.privateKey && !/^0x[a-fA-F0-9]{64}$/.test(wallet.localExecutionAuthority.privateKey)) {
13138
+ throw new Error("Restore payload localExecutionAuthority.privateKey must be a valid 32-byte hex string.");
13139
+ }
13035
13140
  if (wallet.validationHookAddresses && wallet.validationHookAddresses.some((hookAddress) => !isAddress3(hookAddress))) {
13036
13141
  throw new Error("Restore payload validationHookAddresses must contain valid 20-byte hex addresses.");
13037
13142
  }
@@ -13064,7 +13169,7 @@ function parseWalletExportRecord(value) {
13064
13169
  }
13065
13170
  return {
13066
13171
  ...candidate,
13067
- wallet: cloneWalletSessionRecord(wallet)
13172
+ wallet: migrateWalletSessionRecord(cloneWalletSessionRecord(wallet))
13068
13173
  };
13069
13174
  }
13070
13175
  function normalizeHexString2(value, label) {
@@ -13075,6 +13180,13 @@ function normalizeHexString2(value, label) {
13075
13180
  }
13076
13181
  return prefixed;
13077
13182
  }
13183
+ function normalizePrivateKey(value, label) {
13184
+ const normalized = normalizeHexString2(value, label);
13185
+ if (!/^0x[a-fA-F0-9]{64}$/.test(normalized)) {
13186
+ throw new Error(`${label} must be a valid 32-byte hex string.`);
13187
+ }
13188
+ return normalized;
13189
+ }
13078
13190
  function parseArtifactInput(value) {
13079
13191
  const raw = parseJsonInput(value);
13080
13192
  if (!isRecord5(raw)) throw new Error("Artifact must be a JSON object");
@@ -13783,6 +13895,12 @@ function inspectionLines(inspection) {
13783
13895
  inspection.signerMatchesStoredIdentity ? "yes" : "no"
13784
13896
  ]);
13785
13897
  }
13898
+ if (typeof inspection.approvalReady === "boolean") {
13899
+ lines.push(["approval", inspection.approvalReady ? "present" : "missing"]);
13900
+ }
13901
+ if (typeof inspection.localExecutionKeyStored === "boolean") {
13902
+ lines.push(["local signer", inspection.localExecutionKeyStored ? "stored" : "missing"]);
13903
+ }
13786
13904
  lines.push(["session key", inspection.sessionPrivateKeyStored ? "stored" : "missing"]);
13787
13905
  if (inspection.paymasterMode) {
13788
13906
  lines.push(["paymaster", inspection.paymasterMode]);
@@ -13881,6 +13999,7 @@ function walletExportLines(wallet, bundle) {
13881
13999
  }
13882
14000
  function walletRestoreLines(wallet, restoredFrom, syncResult) {
13883
14001
  let nextCommand = buildWalletNextRecommendedCommand(wallet.walletName);
14002
+ let afterNextCommandLabel;
13884
14003
  const lines = [
13885
14004
  ["wallet", wallet.walletName],
13886
14005
  ["address", wallet.walletAddress],
@@ -13891,12 +14010,15 @@ function walletRestoreLines(wallet, restoredFrom, syncResult) {
13891
14010
  ["source export", restoredFrom.exportedAt],
13892
14011
  ["sensitive data", restoredFrom.sensitiveDataIncluded ? "included in backup" : "not included in backup"]
13893
14012
  ];
13894
- if (!wallet.sessionPayload?.sessionPrivateKey) {
14013
+ if (!resolveLocalExecutionPrivateKey(wallet)) {
14014
+ const attachSignerCommand = buildWalletSignerAttachRecommendedCommand(wallet.walletName);
14015
+ const reapproveCommand = buildWalletReapproveRecommendedCommand(wallet.walletName);
13895
14016
  lines.push([
13896
14017
  "note",
13897
- `No sessionPrivateKey was present in the backup. The restored wallet can be inspected and synced, but local write execution will stay blocked until you re-import or re-approve a writable session, for example: ${buildWalletReapproveRecommendedCommand(wallet.walletName)}`
14018
+ wallet.sessionPayload ? `No local execution signer was present in the backup. The restored wallet can be inspected and synced, but local write execution will stay blocked until you attach a local signer or re-approve a writable session, for example: ${attachSignerCommand}` : `No local execution signer was present in the backup. The restored wallet can be inspected and synced, but local write execution will stay blocked until you re-import, attach a local signer, or re-approve a writable session, for example: ${reapproveCommand}`
13898
14019
  ]);
13899
- nextCommand = buildWalletReapproveRecommendedCommand(wallet.walletName);
14020
+ nextCommand = wallet.sessionPayload ? attachSignerCommand : reapproveCommand;
14021
+ afterNextCommandLabel = wallet.sessionPayload ? "after signer attach" : "after reapprove";
13900
14022
  }
13901
14023
  if (syncResult) {
13902
14024
  lines.push(["sync", "completed"]);
@@ -13920,7 +14042,10 @@ function walletRestoreLines(wallet, restoredFrom, syncResult) {
13920
14042
  }
13921
14043
  lines.push(["next", nextCommand]);
13922
14044
  if (nextCommand !== buildWalletNextRecommendedCommand(wallet.walletName)) {
13923
- lines.push(["after reapprove", buildWalletNextRecommendedCommand(wallet.walletName)]);
14045
+ lines.push([
14046
+ afterNextCommandLabel || "after reapprove",
14047
+ buildWalletNextRecommendedCommand(wallet.walletName)
14048
+ ]);
13924
14049
  }
13925
14050
  lines.push(["status command", buildWalletStatusRecommendedCommand(wallet.walletName)]);
13926
14051
  return lines;
@@ -13932,6 +14057,17 @@ function preserveExistingWalletMetadata(importedWallet, existingWallet) {
13932
14057
  if (existingWallet.chain !== importedWallet.chain || existingWallet.chainId !== importedWallet.chainId || existingWallet.walletAddress.toLowerCase() !== importedWallet.walletAddress.toLowerCase()) {
13933
14058
  return importedWallet;
13934
14059
  }
14060
+ const sameAccountKind = existingWallet.accountKind === importedWallet.accountKind;
14061
+ const sameSmartAccountOwner = existingWallet.accountKind !== "smart-account" || existingWallet.ownerAddress && importedWallet.ownerAddress && existingWallet.ownerAddress.toLowerCase() === importedWallet.ownerAddress.toLowerCase();
14062
+ const existingSessionPrivateKey = resolveLocalExecutionPrivateKey(existingWallet);
14063
+ const existingLocalExecutionAuthority = existingWallet.localExecutionAuthority || buildLocalExecutionAuthority({
14064
+ privateKey: existingSessionPrivateKey,
14065
+ signerType: existingWallet.sessionPayload?.account?.signerType,
14066
+ source: existingSessionPrivateKey ? "legacy-session-payload" : void 0,
14067
+ attachedAt: existingWallet.createdAt
14068
+ });
14069
+ const importedSessionPrivateKey = resolveLocalExecutionPrivateKey(importedWallet);
14070
+ const shouldPreserveWritableSession = sameAccountKind && sameSmartAccountOwner && Boolean(existingSessionPrivateKey) && !importedSessionPrivateKey;
13935
14071
  const metadataUpdates = {};
13936
14072
  if (existingWallet.smartAccountProfileId) {
13937
14073
  const profileId = tryResolveBuiltinProfileId(existingWallet.smartAccountProfileId);
@@ -13948,7 +14084,13 @@ function preserveExistingWalletMetadata(importedWallet, existingWallet) {
13948
14084
  if (existingWallet.validatorAddress) {
13949
14085
  metadataUpdates.validatorAddress = existingWallet.validatorAddress;
13950
14086
  }
13951
- return applyWalletSyncMetadata(importedWallet, metadataUpdates);
14087
+ const mergedWallet = shouldPreserveWritableSession && existingLocalExecutionAuthority ? {
14088
+ ...importedWallet,
14089
+ localExecutionAuthority: {
14090
+ ...existingLocalExecutionAuthority
14091
+ }
14092
+ } : importedWallet;
14093
+ return migrateWalletSessionRecord(applyWalletSyncMetadata(mergedWallet, metadataUpdates));
13952
14094
  }
13953
14095
  function linesForWriteResult2(result, nextCommand) {
13954
14096
  const lines = [
@@ -14042,23 +14184,85 @@ function buildWalletApprovalLines(status, requestId, walletRecord) {
14042
14184
  ];
14043
14185
  }
14044
14186
  function buildWalletFollowUpRecommendedCommands(walletRecord) {
14187
+ const localExecutionReady = Boolean(resolveLocalExecutionPrivateKey(walletRecord));
14188
+ const approvalReady = Boolean(walletRecord.sessionPayload);
14045
14189
  return {
14046
14190
  next: buildWalletNextRecommendedCommand(walletRecord.walletName),
14047
14191
  status: buildWalletStatusRecommendedCommand(walletRecord.walletName),
14048
- ...!walletRecord.sessionPayload?.sessionPrivateKey ? { reapprove: buildWalletReapproveRecommendedCommand(walletRecord.walletName) } : {}
14192
+ ...!localExecutionReady && approvalReady ? {
14193
+ attachSigner: buildWalletSignerAttachRecommendedCommand(walletRecord.walletName),
14194
+ reapprove: buildWalletReapproveRecommendedCommand(walletRecord.walletName)
14195
+ } : !localExecutionReady ? { reapprove: buildWalletReapproveRecommendedCommand(walletRecord.walletName) } : {}
14196
+ };
14197
+ }
14198
+ function walletSignerSummary(walletRecord) {
14199
+ const authority = walletRecord.localExecutionAuthority;
14200
+ const localExecutionPrivateKey = resolveLocalExecutionPrivateKey(walletRecord);
14201
+ return {
14202
+ approvalReady: Boolean(walletRecord.sessionPayload),
14203
+ localExecutionKeyStored: Boolean(localExecutionPrivateKey),
14204
+ legacySessionKeyStored: Boolean(walletRecord.sessionPayload?.sessionPrivateKey),
14205
+ signerAddress: authority?.signerAddress || deriveAddressFromPrivateKey(localExecutionPrivateKey),
14206
+ signerType: authority?.signerType || walletRecord.sessionPayload?.account?.signerType,
14207
+ source: authority?.source || (walletRecord.sessionPayload?.sessionPrivateKey ? "legacy-session-payload" : void 0),
14208
+ attachedAt: authority?.attachedAt
14209
+ };
14210
+ }
14211
+ function walletSignerLines(walletRecord) {
14212
+ const signer = walletSignerSummary(walletRecord);
14213
+ return [
14214
+ ["wallet", walletRecord.walletName],
14215
+ ["address", walletRecord.walletAddress],
14216
+ ...displayOwnerAddress(walletRecord) ? [["owner", displayOwnerAddress(walletRecord)]] : [],
14217
+ ["account", displayAccountKind(walletRecord)],
14218
+ ["chain", `${walletRecord.chain} (${walletRecord.chainId})`],
14219
+ ["approval", signer.approvalReady ? "present" : "missing"],
14220
+ ["local signer", signer.localExecutionKeyStored ? "stored" : "missing"],
14221
+ ["legacy payload mirror", signer.legacySessionKeyStored ? "present" : "missing"],
14222
+ ...signer.signerType ? [["signer type", signer.signerType]] : [],
14223
+ ...signer.signerAddress ? [["signer address", signer.signerAddress]] : [],
14224
+ ...signer.source ? [["source", signer.source]] : [],
14225
+ ...signer.attachedAt ? [["attached", signer.attachedAt]] : [],
14226
+ ...walletFollowUpLines(walletRecord)
14227
+ ];
14228
+ }
14229
+ function walletSignerAttachResult(walletRecord, note) {
14230
+ return {
14231
+ ok: true,
14232
+ wallet: sanitizeWalletRecord(walletRecord),
14233
+ signer: walletSignerSummary(walletRecord),
14234
+ ...note ? { note } : {},
14235
+ nextAction: buildWalletFollowUpNextAction(walletRecord),
14236
+ recommendedCommands: buildWalletFollowUpRecommendedCommands(walletRecord)
14049
14237
  };
14050
14238
  }
14239
+ function normalizeSignerType(value) {
14240
+ if (value === "local" || value === "connector" || value === "external") {
14241
+ return value;
14242
+ }
14243
+ throw new Error(`Unsupported signer type: ${value}`);
14244
+ }
14245
+ function clearLegacySessionPrivateKey(payload) {
14246
+ return payload ? {
14247
+ ...payload,
14248
+ sessionPrivateKey: void 0
14249
+ } : payload;
14250
+ }
14051
14251
  function walletFollowUpLines(walletRecord) {
14052
14252
  const recommendedCommands = buildWalletFollowUpRecommendedCommands(walletRecord);
14253
+ const nextCommand = recommendedCommands.attachSigner ?? recommendedCommands.reapprove ?? recommendedCommands.next;
14053
14254
  return [
14054
- ["next", recommendedCommands.reapprove ?? recommendedCommands.next],
14055
- ...recommendedCommands.reapprove ? [["after reapprove", recommendedCommands.next]] : [],
14255
+ ["next", nextCommand],
14256
+ ...recommendedCommands.attachSigner ? [
14257
+ ["after signer attach", recommendedCommands.next],
14258
+ ["or reapprove", recommendedCommands.reapprove]
14259
+ ] : recommendedCommands.reapprove ? [["after reapprove", recommendedCommands.next]] : [],
14056
14260
  ["status command", recommendedCommands.status]
14057
14261
  ];
14058
14262
  }
14059
14263
  function buildWalletFollowUpNextAction(walletRecord) {
14060
14264
  const recommendedCommands = buildWalletFollowUpRecommendedCommands(walletRecord);
14061
- return recommendedCommands.reapprove ?? recommendedCommands.next;
14265
+ return recommendedCommands.attachSigner ?? recommendedCommands.reapprove ?? recommendedCommands.next;
14062
14266
  }
14063
14267
  function buildPendingRequestRecommendedCommands(walletName, requestId, relayUrl, paymasterMode) {
14064
14268
  return {
@@ -14136,7 +14340,7 @@ async function createWalletReapprovalRequest(options) {
14136
14340
  chain: options.walletRecord.chain,
14137
14341
  connectorUrl,
14138
14342
  accountKind: displayAccountKind(options.walletRecord),
14139
- paymasterMode: displayPaymasterMode(options.walletRecord),
14343
+ paymasterMode: options.paymasterMode || displayPaymasterMode(options.walletRecord),
14140
14344
  policies
14141
14345
  });
14142
14346
  await saveWalletRequest(request);
@@ -14604,6 +14808,9 @@ function createWalletCommand(deps) {
14604
14808
  const resolvedDeps = resolveWalletCommandDeps(deps);
14605
14809
  const wallet = new Command9("wallet").description("Manage wallet sessions");
14606
14810
  const request = new Command9("request").description("Inspect and finalize pending wallet requests");
14811
+ const signer = new Command9("signer").description(
14812
+ "Inspect and manage the stored local execution signer for a wallet"
14813
+ );
14607
14814
  const smartAccount = new Command9("smart-account").description(
14608
14815
  "Predict and deploy zkSync smart-account contracts from a supplied artifact or built-in profile"
14609
14816
  );
@@ -14634,8 +14841,11 @@ function createWalletCommand(deps) {
14634
14841
  " zk-agent wallet create --await-local",
14635
14842
  " zk-agent next",
14636
14843
  "",
14637
- " Restore a writable session for an existing wallet:",
14844
+ " Restore approval metadata for an existing wallet:",
14638
14845
  " zk-agent wallet reapprove --name main --await-local",
14846
+ "",
14847
+ " Attach a local signer when approval is still present:",
14848
+ " zk-agent wallet signer attach --name main --private-key <hex>",
14639
14849
  " zk-agent next",
14640
14850
  "",
14641
14851
  " Wallet-layer inspection:",
@@ -14643,8 +14853,9 @@ function createWalletCommand(deps) {
14643
14853
  " zk-agent wallet next --name main",
14644
14854
  "",
14645
14855
  " Remote approval path:",
14646
- " zk-agent wallet create --relay-url <url>",
14647
- " zk-agent wallet request approve --request-id <id> --relay-url <url> --code <code> --wait"
14856
+ " zk-agent relay inspect --relay-url <url>",
14857
+ " zk-agent wallet create --relay-url <url> --wait-relay --prompt-code",
14858
+ " zk-agent wallet reapprove --name main --relay-url <url> --wait-relay --prompt-code"
14648
14859
  ].join("\n")
14649
14860
  );
14650
14861
  request.addHelpText(
@@ -14661,6 +14872,21 @@ function createWalletCommand(deps) {
14661
14872
  " zk-agent wallet request approve --request-id <id> --relay-url <url> --code <code> --wait"
14662
14873
  ].join("\n")
14663
14874
  );
14875
+ signer.addHelpText(
14876
+ "after",
14877
+ [
14878
+ "",
14879
+ "Wallet signer path:",
14880
+ " Inspect the stored local execution signer state:",
14881
+ " zk-agent wallet signer show --name main",
14882
+ "",
14883
+ " Attach a local execution signer without rebuilding approval metadata:",
14884
+ " zk-agent wallet signer attach --name main --private-key <hex>",
14885
+ "",
14886
+ " Remove the stored local execution signer:",
14887
+ " zk-agent wallet signer remove --name main"
14888
+ ].join("\n")
14889
+ );
14664
14890
  smartAccount.addHelpText(
14665
14891
  "after",
14666
14892
  [
@@ -15029,7 +15255,56 @@ function createWalletCommand(deps) {
15029
15255
  }
15030
15256
  );
15031
15257
  });
15032
- wallet.command("export").description("Export one stored wallet as a portable backup bundle for later restore").option("--name <name>", "Wallet name", "main").option("--include-sensitive-data", "Include sessionPrivateKey in the exported bundle", false).action(async (options) => {
15258
+ signer.command("show").description("Show the stored local execution signer state for a wallet").option("--name <name>", "Wallet name", "main").action(async (options) => {
15259
+ const walletRecord = await requireWalletRecord(options.name);
15260
+ printResult(walletSignerLines(walletRecord), {
15261
+ ok: true,
15262
+ wallet: sanitizeWalletRecord(walletRecord),
15263
+ signer: walletSignerSummary(walletRecord),
15264
+ nextAction: buildWalletFollowUpNextAction(walletRecord),
15265
+ recommendedCommands: buildWalletFollowUpRecommendedCommands(walletRecord)
15266
+ });
15267
+ });
15268
+ signer.command("attach").description("Attach a stored local execution signer to an existing wallet record").option("--name <name>", "Wallet name", "main").requiredOption("--private-key <hex>", "Local execution private key to store").action(async (options) => {
15269
+ const walletRecord = await requireWalletRecord(options.name);
15270
+ const privateKey = normalizePrivateKey(options.privateKey, "--private-key");
15271
+ const nextWallet = migrateWalletSessionRecord({
15272
+ ...walletRecord,
15273
+ localExecutionAuthority: buildLocalExecutionAuthority({
15274
+ privateKey,
15275
+ signerType: "local",
15276
+ source: "explicit-local-approval",
15277
+ attachedAt: (/* @__PURE__ */ new Date()).toISOString()
15278
+ }),
15279
+ sessionPayload: clearLegacySessionPrivateKey(walletRecord.sessionPayload)
15280
+ });
15281
+ const note = walletRecord.sessionPayload ? void 0 : "No approved session metadata is stored yet. Attach succeeded, but wallet reapprove is still required before local write execution becomes usable.";
15282
+ await saveWalletSession(nextWallet);
15283
+ printResult(
15284
+ walletSignerLines(nextWallet),
15285
+ walletSignerAttachResult(nextWallet, note)
15286
+ );
15287
+ });
15288
+ signer.command("remove").description("Remove the stored local execution signer from a wallet record").option("--name <name>", "Wallet name", "main").action(async (options) => {
15289
+ const walletRecord = await requireWalletRecord(options.name);
15290
+ const hadLocalExecutionKey = Boolean(resolveLocalExecutionPrivateKey(walletRecord));
15291
+ const nextWallet = migrateWalletSessionRecord({
15292
+ ...walletRecord,
15293
+ localExecutionAuthority: void 0,
15294
+ sessionPayload: clearLegacySessionPrivateKey(walletRecord.sessionPayload)
15295
+ });
15296
+ const note = hadLocalExecutionKey ? "Removed the stored local execution signer. Local write execution now requires signer attach or reapproval." : "No stored local execution signer was present.";
15297
+ await saveWalletSession(nextWallet);
15298
+ printResult(
15299
+ walletSignerLines(nextWallet),
15300
+ walletSignerAttachResult(nextWallet, note)
15301
+ );
15302
+ });
15303
+ wallet.command("export").description("Export one stored wallet as a portable backup bundle for later restore").option("--name <name>", "Wallet name", "main").option(
15304
+ "--include-sensitive-data",
15305
+ "Include the stored local execution key and any legacy sessionPrivateKey mirror in the exported bundle",
15306
+ false
15307
+ ).action(async (options) => {
15033
15308
  const walletRecord = await requireWalletRecord(options.name);
15034
15309
  const bundle = exportWalletRecord(walletRecord, Boolean(options.includeSensitiveData));
15035
15310
  const recommendedCommands = {
@@ -15334,7 +15609,7 @@ function createWalletCommand(deps) {
15334
15609
  }
15335
15610
  );
15336
15611
  });
15337
- request.command("approve-local").description("Manually construct an approved session payload from CLI inputs and save the resulting wallet").requiredOption("--request-id <id>", "Wallet request id").requiredOption("--wallet-address <address>", "Approved execution address (EOA address or smart-account address)").option("--owner-address <address>", "Owner / signer address for smart-account sessions").option("--name <name>", "Override saved wallet name").option("--session-address <address>", "Optional session address").option("--session-private-key <hex>", "Optional local private key for writable testnet sessions").option("--validator-address <address>", "Optional validator address").option("--paymaster-address <address>", "Optional paymaster address").option("--paymaster-token <address>", "Optional ERC-20 token used by an approval-based paymaster").option("--signer-type <type>", "Signer type", "connector").action(
15612
+ request.command("approve-local").description("Manually construct an approved session payload from CLI inputs and save the resulting wallet").requiredOption("--request-id <id>", "Wallet request id").requiredOption("--wallet-address <address>", "Approved execution address (EOA address or smart-account address)").option("--owner-address <address>", "Owner / signer address for smart-account sessions").option("--name <name>", "Override saved wallet name").option("--session-address <address>", "Optional session address").option("--session-private-key <hex>", "Optional local execution private key for writable sessions").option("--validator-address <address>", "Optional validator address").option("--paymaster-address <address>", "Optional paymaster address").option("--paymaster-token <address>", "Optional ERC-20 token used by an approval-based paymaster").option("--signer-type <type>", "Optional signer type override: local, connector, or external").action(
15338
15613
  async (options) => {
15339
15614
  const walletRequest = await requireActiveWalletRequest(options.requestId);
15340
15615
  assertRequestActive(walletRequest.expiresAt);
@@ -15354,7 +15629,7 @@ function createWalletCommand(deps) {
15354
15629
  validatorAddress: options.validatorAddress,
15355
15630
  paymasterAddress: options.paymasterAddress,
15356
15631
  paymasterToken: options.paymasterToken,
15357
- signerType: options.signerType,
15632
+ signerType: options.signerType ? normalizeSignerType(options.signerType) : void 0,
15358
15633
  connectorOrigin: connectorOriginFromUrl(walletRequest.connectorUrl),
15359
15634
  connectorUrl: walletRequest.connectorUrl
15360
15635
  });
@@ -16940,10 +17215,12 @@ function createWalletCommand(deps) {
16940
17215
  sedLite.addCommand(selectorAllowlistHook);
16941
17216
  smartAccount.addCommand(sedLite);
16942
17217
  smartAccount.addCommand(dailySpendLimit);
16943
- wallet.addCommand(smartAccount);
16944
- wallet.addCommand(paymaster);
16945
17218
  wallet.addCommand(request);
17219
+ wallet.addCommand(signer);
17220
+ wallet.addCommand(paymaster);
17221
+ wallet.addCommand(smartAccount);
16946
17222
  applyCommandOrder(request, WALLET_REQUEST_HELP_COMMAND_ORDER);
17223
+ applyCommandOrder(signer, WALLET_SIGNER_HELP_COMMAND_ORDER);
16947
17224
  applyCommandOrder(smartAccount, WALLET_SMART_ACCOUNT_HELP_COMMAND_ORDER);
16948
17225
  applyCommandOrder(wallet, WALLET_HELP_COMMAND_ORDER);
16949
17226
  return wallet;
@@ -17375,7 +17652,7 @@ function prependWorkflowRequestId(requestId, lines) {
17375
17652
  return [["workflow request", requestId], ...lines];
17376
17653
  }
17377
17654
  function workflowHasSessionApprovalBlocker(status) {
17378
- return status.blockingActionIds.some((actionId) => actionId === "reapprove" || actionId === "signer-mismatch");
17655
+ return status.blockingActionIds.some((actionId) => actionId === "reapprove");
17379
17656
  }
17380
17657
  function workflowShouldEnsureWalletSession(options) {
17381
17658
  return Boolean(options.ensureWalletSession || options.awaitLocal);
@@ -17449,10 +17726,14 @@ async function ensureWorkflowWalletSession(input, deps) {
17449
17726
  goal: input.goal,
17450
17727
  options: input.options
17451
17728
  });
17729
+ const paymasterInput = resolveWorkflowPaymasterInput(input.options);
17452
17730
  const reusableRequest = await deps.findReusableWalletRequest(input.wallet.walletName);
17453
17731
  const walletRequest = reusableRequest || await deps.createWalletReapprovalRequest({
17454
17732
  walletRecord: input.wallet,
17455
17733
  connectorUrl: input.options.connectorUrl,
17734
+ ...paymasterInput?.mode ? {
17735
+ paymasterMode: paymasterInput.mode
17736
+ } : {},
17456
17737
  sessionPreset: policyRequestOptions.sessionPreset,
17457
17738
  sessionHours: policyRequestOptions.sessionHours,
17458
17739
  allowTransferTo: policyRequestOptions.allowTransferTo,
@@ -17805,6 +18086,17 @@ function buildWorkflowAutoCheckpoint(context, status) {
17805
18086
  status
17806
18087
  });
17807
18088
  }
18089
+ function applyWorkflowPayDefaults(options) {
18090
+ return {
18091
+ ...options,
18092
+ intent: "send-native",
18093
+ createCheckpoint: true,
18094
+ executeWhenReady: true,
18095
+ ensureWalletSession: true,
18096
+ sessionPreset: options.sessionPreset?.trim() || "intent",
18097
+ paymasterMode: options.paymasterMode?.trim() || "approval-based"
18098
+ };
18099
+ }
17808
18100
  async function executeWorkflowAutoCommand(options, deps = resolveWorkflowCommandDeps(void 0)) {
17809
18101
  const { provider: provider4, defiProvider: defiProvider2 } = deps;
17810
18102
  const context = await resolveWorkflowAutoExecutionContext(options);
@@ -18359,6 +18651,9 @@ function buildWorkflowHelpText() {
18359
18651
  " Guided default:",
18360
18652
  " zk-agent workflow auto --wallet main --intent <intent> [goal flags] --create-checkpoint --execute-when-ready",
18361
18653
  "",
18654
+ " Flagship native pay path:",
18655
+ " zk-agent workflow pay --wallet main --to <address> --amount <amount>",
18656
+ "",
18362
18657
  " Checkpointed execution:",
18363
18658
  " zk-agent workflow start --wallet main --intent <intent> [goal flags]",
18364
18659
  " zk-agent workflow status --request-id <id>",
@@ -18374,6 +18669,7 @@ function buildWorkflowHelpText() {
18374
18669
  }
18375
18670
  var WORKFLOW_HELP_COMMAND_ORDER = [
18376
18671
  "auto",
18672
+ "pay",
18377
18673
  "start",
18378
18674
  "status",
18379
18675
  "next",
@@ -18597,6 +18893,23 @@ function createWorkflowCommand(deps) {
18597
18893
  const execution = await executeWorkflowAutoCommand(options, resolvedDeps);
18598
18894
  await printWorkflowAutoCommandResult(execution);
18599
18895
  }));
18896
+ const pay = workflow.command("pay").description(
18897
+ "Guided flagship AA native-send path with checkpoint persistence, intent-scoped session recovery, and paymaster-aware defaults"
18898
+ ).option("--wallet <name>", "Wallet name", "main").option(
18899
+ "--request-id <id>",
18900
+ "Load the workflow definition from a stored checkpoint, or reserve this id for the flagship pay path"
18901
+ );
18902
+ addWorkflowGoalOptions(pay, {
18903
+ includeExecutionFlags: true,
18904
+ includeFundingDispatch: true,
18905
+ includeLocalApproval: true
18906
+ }).action(withWorkflowInputErrorHandling(async (options) => {
18907
+ const execution = await executeWorkflowAutoCommand(
18908
+ applyWorkflowPayDefaults(options),
18909
+ resolvedDeps
18910
+ );
18911
+ await printWorkflowAutoCommandResult(execution);
18912
+ }));
18600
18913
  const run = workflow.command("run").description("Run the requested workflow, or stop on the next required prerequisite or funding step first").option(
18601
18914
  "--intent <intent>",
18602
18915
  "send-native, send-token, call-write, swap, bridge, deposit, or withdraw"