zk-agent-cli 0.1.0-beta.5 → 0.1.0-beta.7

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,
@@ -6039,188 +6258,31 @@ var ZkSyncDefiProvider = class {
6039
6258
  cause: formatCause(error),
6040
6259
  finalizationStatus: pendingFailure.kind,
6041
6260
  reason: pendingFailure.reason,
6042
- note: pendingFailure.note,
6043
- retryable: true,
6044
- suggestedAction: pendingFailure.suggestedAction
6045
- }
6046
- );
6047
- }
6048
- const code = input.broadcast ? "WITHDRAW_FINALIZE_BROADCAST_FAILED" : "WITHDRAW_FINALIZE_PREVIEW_FAILED";
6049
- const message = input.broadcast ? "Failed to broadcast the L1 withdraw finalization transaction." : "Failed to derive withdraw finalization parameters from the L2 transaction.";
6050
- throw new AgentError(
6051
- code,
6052
- message,
6053
- {
6054
- chain: chain.key,
6055
- l1ChainId,
6056
- txHash,
6057
- index,
6058
- cause: formatCause(error)
6059
- }
6060
- );
6061
- }
6062
- }
6063
- };
6064
-
6065
- // ../provider-zksync-wallet/src/provider.ts
6066
- 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
- );
6261
+ note: pendingFailure.note,
6262
+ retryable: true,
6263
+ suggestedAction: pendingFailure.suggestedAction
6264
+ }
6265
+ );
6266
+ }
6267
+ const code = input.broadcast ? "WITHDRAW_FINALIZE_BROADCAST_FAILED" : "WITHDRAW_FINALIZE_PREVIEW_FAILED";
6268
+ const message = input.broadcast ? "Failed to broadcast the L1 withdraw finalization transaction." : "Failed to derive withdraw finalization parameters from the L2 transaction.";
6269
+ throw new AgentError(
6270
+ code,
6271
+ message,
6272
+ {
6273
+ chain: chain.key,
6274
+ l1ChainId,
6275
+ txHash,
6276
+ index,
6277
+ cause: formatCause(error)
6278
+ }
6279
+ );
6280
+ }
6189
6281
  }
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
- }
6282
+ };
6222
6283
 
6223
6284
  // ../provider-zksync-wallet/src/provider.ts
6285
+ import { randomBytes as randomBytes3 } from "node:crypto";
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
  }
@@ -10330,7 +10409,10 @@ function buildNextHelpText() {
10330
10409
  return [
10331
10410
  "",
10332
10411
  "Use `next` as the product entrypoint:",
10333
- " Fresh operator routing:",
10412
+ " Fresh local-first routing:",
10413
+ " zk-agent setup",
10414
+ " zk-agent next",
10415
+ " zk-agent wallet create --await-local",
10334
10416
  " zk-agent next",
10335
10417
  "",
10336
10418
  " Continue a stored workflow checkpoint:",
@@ -10339,6 +10421,9 @@ function buildNextHelpText() {
10339
10421
  " Stay on the wallet layer only when you need wallet-specific remediation:",
10340
10422
  " zk-agent wallet next --name main",
10341
10423
  "",
10424
+ " Switch to the hosted remote-approval path only when the browser is not colocated:",
10425
+ " zk-agent wallet --help",
10426
+ "",
10342
10427
  " Stay on the workflow layer only when you already have an explicit workflow or checkpoint:",
10343
10428
  " zk-agent workflow next --request-id <id>"
10344
10429
  ].join("\n");
@@ -10505,11 +10590,15 @@ function createNextCommand(deps) {
10505
10590
  nativeSymbol: nativeBalance?.symbol,
10506
10591
  funding
10507
10592
  });
10593
+ const workflowPay = buildWorkflowPayRecommendedCommand(
10594
+ wallet.walletName,
10595
+ paymasterMode
10596
+ );
10508
10597
  const workflowAuto = appendPaymasterMode2(
10509
10598
  buildWorkflowAutoRecommendedCommand(wallet.walletName),
10510
10599
  paymasterMode
10511
10600
  );
10512
- const nextCommand = summary.recommendedCommand || workflowAuto;
10601
+ const nextCommand = summary.recommendedCommand || workflowPay;
10513
10602
  const agentFollowup = buildAgentFollowup(agentProfile, {
10514
10603
  walletName: wallet.walletName,
10515
10604
  walletExists: true
@@ -10521,6 +10610,7 @@ function createNextCommand(deps) {
10521
10610
  discoverOwnedTokens: buildOwnedTokensRecommendedCommand(wallet.walletName),
10522
10611
  discoverTokens: buildTokensRecommendedCommand(wallet.chain),
10523
10612
  inspectToken: buildResolveTokenRecommendedCommand(wallet.chain),
10613
+ workflowPay,
10524
10614
  workflowAuto,
10525
10615
  nextAction: nextCommand,
10526
10616
  inspectDefaults: buildDefaultsRecommendedCommand()
@@ -10530,7 +10620,7 @@ function createNextCommand(deps) {
10530
10620
  ...walletNextLines(summary),
10531
10621
  ...agentProfileLines(agentProfile),
10532
10622
  ...agentFollowupLines(agentFollowup),
10533
- ...summary.recommendedCommand ? [] : [["next", workflowAuto]],
10623
+ ...summary.recommendedCommand ? [] : [["next", workflowPay]],
10534
10624
  ["discover assets", recommendedCommands.discoverAssets],
10535
10625
  ["discover owned tokens", recommendedCommands.discoverOwnedTokens],
10536
10626
  ["discover tokens", recommendedCommands.discoverTokens],
@@ -11745,21 +11835,155 @@ import { Command as Command8 } from "commander";
11745
11835
  // src/lib/relay.ts
11746
11836
  import { createServer } from "node:http";
11747
11837
  import fs7 from "node:fs";
11748
- import path6 from "node:path";
11838
+ import path7 from "node:path";
11749
11839
  import { fileURLToPath as fileURLToPath3 } from "node:url";
11840
+
11841
+ // src/lib/http.ts
11842
+ import { spawn } from "node:child_process";
11843
+ import { mkdtemp, readFile as readFile2, rm as rm2 } from "node:fs/promises";
11844
+ import os2 from "node:os";
11845
+ import path6 from "node:path";
11846
+ function isDnsResolutionFailure(error) {
11847
+ if (!error || typeof error !== "object") {
11848
+ return false;
11849
+ }
11850
+ const cause = "cause" in error ? error.cause : void 0;
11851
+ if (!cause || typeof cause !== "object") {
11852
+ return false;
11853
+ }
11854
+ const code = "code" in cause ? cause.code : void 0;
11855
+ return code === "ENOTFOUND" || code === "EAI_AGAIN";
11856
+ }
11857
+ function isHttpUrl(value) {
11858
+ return value.startsWith("http://") || value.startsWith("https://");
11859
+ }
11860
+ function parseCurlHeaders(rawHeaders) {
11861
+ const blocks = rawHeaders.replace(/\r\n/g, "\n").split(/\n{2,}/).map((block) => block.trim()).filter((block) => block.startsWith("HTTP/"));
11862
+ const lastBlock = blocks.at(-1);
11863
+ if (!lastBlock) {
11864
+ throw new Error("curl fallback did not emit an HTTP response header block.");
11865
+ }
11866
+ const [statusLine, ...headerLines] = lastBlock.split("\n");
11867
+ const status = Number.parseInt(statusLine.split(/\s+/)[1] || "", 10);
11868
+ if (!Number.isInteger(status)) {
11869
+ throw new Error(`curl fallback emitted an invalid status line: ${statusLine}`);
11870
+ }
11871
+ const headers = new Headers();
11872
+ for (const line of headerLines) {
11873
+ const separator = line.indexOf(":");
11874
+ if (separator <= 0) continue;
11875
+ const key = line.slice(0, separator).trim();
11876
+ const value = line.slice(separator + 1).trim();
11877
+ if (key) {
11878
+ headers.append(key, value);
11879
+ }
11880
+ }
11881
+ return { status, headers };
11882
+ }
11883
+ async function runCurlRequest(url, options) {
11884
+ const tempDir = await mkdtemp(path6.join(os2.tmpdir(), "zk-agent-http-"));
11885
+ const headersPath = path6.join(tempDir, "headers.txt");
11886
+ const bodyPath = path6.join(tempDir, "body.txt");
11887
+ try {
11888
+ const args = [
11889
+ "--silent",
11890
+ "--show-error",
11891
+ "--output",
11892
+ bodyPath,
11893
+ "--dump-header",
11894
+ headersPath,
11895
+ "--request",
11896
+ options.method || "GET"
11897
+ ];
11898
+ if (options.redirect === "follow") {
11899
+ args.push("--location");
11900
+ }
11901
+ for (const [key, value] of Object.entries(options.headers || {})) {
11902
+ args.push("--header", `${key}: ${value}`);
11903
+ }
11904
+ if (typeof options.body === "string") {
11905
+ args.push("--data-raw", options.body);
11906
+ }
11907
+ args.push(url);
11908
+ const child = spawn("curl", args, {
11909
+ env: process.env,
11910
+ stdio: ["ignore", "pipe", "pipe"]
11911
+ });
11912
+ let stdout = "";
11913
+ let stderr = "";
11914
+ child.stdout.setEncoding("utf8");
11915
+ child.stderr.setEncoding("utf8");
11916
+ child.stdout.on("data", (chunk) => {
11917
+ stdout += chunk;
11918
+ });
11919
+ child.stderr.on("data", (chunk) => {
11920
+ stderr += chunk;
11921
+ });
11922
+ const exitCode = await new Promise((resolve, reject) => {
11923
+ child.once("error", reject);
11924
+ child.once("close", resolve);
11925
+ });
11926
+ if (exitCode !== 0) {
11927
+ throw new Error(stderr.trim() || stdout.trim() || `curl exited with code ${exitCode}`);
11928
+ }
11929
+ const [rawHeaders, body] = await Promise.all([
11930
+ readFile2(headersPath, "utf8"),
11931
+ readFile2(bodyPath, "utf8")
11932
+ ]);
11933
+ const { status, headers } = parseCurlHeaders(rawHeaders);
11934
+ return {
11935
+ ok: status >= 200 && status < 300,
11936
+ status,
11937
+ headers,
11938
+ body
11939
+ };
11940
+ } finally {
11941
+ await rm2(tempDir, { recursive: true, force: true });
11942
+ }
11943
+ }
11944
+ async function fetchTextWithFallback(url, options = {}) {
11945
+ try {
11946
+ const response = await fetch(url, {
11947
+ method: options.method,
11948
+ headers: options.headers,
11949
+ body: options.body,
11950
+ redirect: options.redirect
11951
+ });
11952
+ return {
11953
+ ok: response.ok,
11954
+ status: response.status,
11955
+ headers: response.headers,
11956
+ body: await response.text()
11957
+ };
11958
+ } catch (error) {
11959
+ if (!isHttpUrl(url) || !isDnsResolutionFailure(error)) {
11960
+ throw error;
11961
+ }
11962
+ return await runCurlRequest(url, options);
11963
+ }
11964
+ }
11965
+ async function fetchJsonWithFallback(url, options = {}) {
11966
+ const response = await fetchTextWithFallback(url, options);
11967
+ return {
11968
+ ...response,
11969
+ json: JSON.parse(response.body)
11970
+ };
11971
+ }
11972
+
11973
+ // src/lib/relay.ts
11750
11974
  var RELAY_BODY_LIMIT_BYTES = 1024 * 1024;
11751
11975
  var RELAY_SERVICE = "zk-agent-relay";
11752
11976
  var RELAY_PROTOCOL = "zk-agent-session-relay";
11753
11977
  var RELAY_SCHEMA_VERSION = 1;
11754
11978
  function relayDir() {
11755
- const directory = path6.join(storageDir(), "relay");
11979
+ const directory = path7.join(storageDir(), "relay");
11756
11980
  if (!fs7.existsSync(directory)) {
11757
11981
  fs7.mkdirSync(directory, { recursive: true, mode: 448 });
11758
11982
  }
11759
11983
  return directory;
11760
11984
  }
11761
11985
  function relayRecordPath(requestId) {
11762
- return path6.join(relayDir(), `${requestId}.json`);
11986
+ return path7.join(relayDir(), `${requestId}.json`);
11763
11987
  }
11764
11988
  function writeRelayRecord(record) {
11765
11989
  fs7.writeFileSync(relayRecordPath(record.request_id), JSON.stringify(record, null, 2), {
@@ -11824,28 +12048,31 @@ function readRequestBody(request, limitBytes = RELAY_BODY_LIMIT_BYTES) {
11824
12048
  });
11825
12049
  }
11826
12050
  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));
12051
+ writeBody(response, statusCode, "application/json; charset=utf-8", JSON.stringify(payload, null, 2), {
12052
+ "Access-Control-Allow-Origin": "*",
12053
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
12054
+ "Access-Control-Allow-Headers": "Content-Type"
12055
+ });
11833
12056
  }
11834
- function writeText(response, statusCode, contentType, value) {
12057
+ function writeBody(response, statusCode, contentType, value, extraHeaders = {}) {
11835
12058
  response.statusCode = statusCode;
11836
12059
  response.setHeader("Content-Type", contentType);
12060
+ response.setHeader("Content-Length", Buffer.isBuffer(value) ? String(value.byteLength) : String(Buffer.byteLength(value)));
12061
+ for (const [key, headerValue] of Object.entries(extraHeaders)) {
12062
+ response.setHeader(key, headerValue);
12063
+ }
11837
12064
  response.end(value);
11838
12065
  }
11839
12066
  function resolveConnectorUiDistRoot() {
11840
12067
  const currentFile = fileURLToPath3(import.meta.url);
11841
- const currentDir2 = path6.dirname(currentFile);
12068
+ const currentDir2 = path7.dirname(currentFile);
11842
12069
  const candidates = [
11843
- path6.resolve(currentDir2, "./connector-ui"),
11844
- path6.resolve(currentDir2, "../../../zk-connector-ui/dist"),
11845
- path6.resolve(currentDir2, "../../zk-connector-ui/dist")
12070
+ path7.resolve(currentDir2, "./connector-ui"),
12071
+ path7.resolve(currentDir2, "../../../zk-connector-ui/dist"),
12072
+ path7.resolve(currentDir2, "../../zk-connector-ui/dist")
11846
12073
  ];
11847
12074
  for (const candidate of candidates) {
11848
- if (fs7.existsSync(path6.join(candidate, "index.html"))) {
12075
+ if (fs7.existsSync(path7.join(candidate, "index.html"))) {
11849
12076
  return candidate;
11850
12077
  }
11851
12078
  }
@@ -11861,8 +12088,8 @@ function contentTypeFor(filePath) {
11861
12088
  function normalizeRelayBaseUrl(baseUrl) {
11862
12089
  return baseUrl.replace(/\/+$/, "");
11863
12090
  }
11864
- function resolveRelayPublicBaseUrl(actualBaseUrl, publicOrigin) {
11865
- return publicOrigin?.trim() ? normalizeRelayBaseUrl(new URL(publicOrigin.trim()).toString()) : normalizeRelayBaseUrl(actualBaseUrl);
12091
+ function resolveRelayPublicBaseUrl(bindBaseUrl, publicOrigin) {
12092
+ return publicOrigin?.trim() ? normalizeRelayBaseUrl(new URL(publicOrigin.trim()).toString()) : normalizeRelayBaseUrl(bindBaseUrl);
11866
12093
  }
11867
12094
  function relayCapabilities(connectorUiAvailable) {
11868
12095
  return [
@@ -11874,15 +12101,16 @@ function relayCapabilities(connectorUiAvailable) {
11874
12101
  ...connectorUiAvailable ? ["connector-ui"] : []
11875
12102
  ];
11876
12103
  }
11877
- function relayHealthResponse(actualBaseUrl, publicBaseUrl, connectorUiAvailable) {
12104
+ function relayHealthResponse(bindBaseUrl, publicBaseUrl, connectorUiAvailable, publicOriginSource) {
11878
12105
  return {
11879
12106
  ok: true,
11880
12107
  service: RELAY_SERVICE,
11881
12108
  protocol: RELAY_PROTOCOL,
11882
12109
  schema_version: RELAY_SCHEMA_VERSION,
11883
12110
  relay_mode: "local-file",
11884
- origin: normalizeRelayBaseUrl(actualBaseUrl),
12111
+ origin: normalizeRelayBaseUrl(bindBaseUrl),
11885
12112
  public_origin: normalizeRelayBaseUrl(publicBaseUrl),
12113
+ public_origin_source: publicOriginSource,
11886
12114
  connector_ui_available: connectorUiAvailable,
11887
12115
  capabilities: relayCapabilities(connectorUiAvailable)
11888
12116
  };
@@ -11897,31 +12125,34 @@ function relayApprovalUrl(baseUrl, requestId) {
11897
12125
  return `${relayStatusUrl(baseUrl, requestId)}/approval`;
11898
12126
  }
11899
12127
  async function publishRelayRequest(baseUrl, body) {
11900
- const response = await fetch(`${normalizeRelayBaseUrl(baseUrl)}/api/requests`, {
11901
- method: "POST",
11902
- headers: {
11903
- "Content-Type": "application/json"
11904
- },
11905
- body: JSON.stringify(body)
11906
- });
12128
+ const response = await fetchJsonWithFallback(
12129
+ `${normalizeRelayBaseUrl(baseUrl)}/api/requests`,
12130
+ {
12131
+ method: "POST",
12132
+ headers: {
12133
+ "Content-Type": "application/json"
12134
+ },
12135
+ body: JSON.stringify(body)
12136
+ }
12137
+ );
11907
12138
  if (!response.ok) {
11908
12139
  throw new Error(`Relay publish failed with status ${response.status}`);
11909
12140
  }
11910
- return await response.json();
12141
+ return response.json;
11911
12142
  }
11912
12143
  async function fetchRelayStatus(baseUrl, requestId) {
11913
- const response = await fetch(relayStatusUrl(baseUrl, requestId));
12144
+ const response = await fetchJsonWithFallback(relayStatusUrl(baseUrl, requestId));
11914
12145
  if (!response.ok) {
11915
12146
  throw new Error(`Relay status fetch failed with status ${response.status}`);
11916
12147
  }
11917
- return await response.json();
12148
+ return response.json;
11918
12149
  }
11919
12150
  async function fetchRelayHealth(baseUrl) {
11920
- const response = await fetch(`${normalizeRelayBaseUrl(baseUrl)}/health`);
12151
+ const response = await fetchJsonWithFallback(`${normalizeRelayBaseUrl(baseUrl)}/health`);
11921
12152
  if (!response.ok) {
11922
12153
  throw new Error(`Relay health fetch failed with status ${response.status}`);
11923
12154
  }
11924
- return await response.json();
12155
+ return response.json;
11925
12156
  }
11926
12157
  function sleep(ms) {
11927
12158
  return new Promise((resolve) => {
@@ -11946,21 +12177,22 @@ async function waitForRelayApprovalReady(baseUrl, requestId, options) {
11946
12177
  );
11947
12178
  }
11948
12179
  async function fetchRelayApproval(baseUrl, requestId) {
11949
- const response = await fetch(relayApprovalUrl(baseUrl, requestId));
12180
+ const response = await fetchJsonWithFallback(relayApprovalUrl(baseUrl, requestId));
11950
12181
  if (!response.ok) {
11951
12182
  throw new Error(`Relay approval fetch failed with status ${response.status}`);
11952
12183
  }
11953
- return await response.json();
12184
+ return response.json;
11954
12185
  }
11955
12186
  async function startRelayServer(options) {
11956
12187
  const uiDistRoot = resolveConnectorUiDistRoot();
12188
+ let bindBaseUrl = "";
12189
+ const publicOriginSource = options.publicOrigin?.trim() ? "configured" : "bind-origin-default";
11957
12190
  const server = createServer(async (request, response) => {
11958
12191
  try {
11959
- const requestUrl = new URL(request.url || "/", `http://${request.headers.host || "localhost"}`);
12192
+ const requestUrl = new URL(request.url || "/", "http://localhost");
11960
12193
  const pathname = requestUrl.pathname;
11961
12194
  const method = request.method || "GET";
11962
- const actualBaseUrl = `${requestUrl.protocol}//${requestUrl.host}`;
11963
- const publicBaseUrl = resolveRelayPublicBaseUrl(actualBaseUrl, options.publicOrigin);
12195
+ const publicBaseUrl = resolveRelayPublicBaseUrl(bindBaseUrl, options.publicOrigin);
11964
12196
  if (method === "OPTIONS") {
11965
12197
  writeJson2(response, 204, {});
11966
12198
  return;
@@ -11969,7 +12201,12 @@ async function startRelayServer(options) {
11969
12201
  writeJson2(
11970
12202
  response,
11971
12203
  200,
11972
- relayHealthResponse(actualBaseUrl, publicBaseUrl, Boolean(uiDistRoot))
12204
+ relayHealthResponse(
12205
+ bindBaseUrl,
12206
+ publicBaseUrl,
12207
+ Boolean(uiDistRoot),
12208
+ publicOriginSource
12209
+ )
11973
12210
  );
11974
12211
  return;
11975
12212
  }
@@ -12038,18 +12275,18 @@ async function startRelayServer(options) {
12038
12275
  }
12039
12276
  if (method === "GET" && uiDistRoot) {
12040
12277
  const relativePath = pathname === "/" ? "/index.html" : pathname;
12041
- const filePath = path6.resolve(uiDistRoot, `.${relativePath}`);
12278
+ const filePath = path7.resolve(uiDistRoot, `.${relativePath}`);
12042
12279
  if (!filePath.startsWith(uiDistRoot)) {
12043
12280
  writeJson2(response, 403, { error: "Forbidden path" });
12044
12281
  return;
12045
12282
  }
12046
12283
  if (fs7.existsSync(filePath) && fs7.statSync(filePath).isFile()) {
12047
- writeText(response, 200, contentTypeFor(filePath), fs7.readFileSync(filePath, "utf8"));
12284
+ writeBody(response, 200, contentTypeFor(filePath), fs7.readFileSync(filePath));
12048
12285
  return;
12049
12286
  }
12050
- const indexPath = path6.join(uiDistRoot, "index.html");
12287
+ const indexPath = path7.join(uiDistRoot, "index.html");
12051
12288
  if (fs7.existsSync(indexPath)) {
12052
- writeText(response, 200, "text/html; charset=utf-8", fs7.readFileSync(indexPath, "utf8"));
12289
+ writeBody(response, 200, "text/html; charset=utf-8", fs7.readFileSync(indexPath));
12053
12290
  return;
12054
12291
  }
12055
12292
  }
@@ -12074,9 +12311,10 @@ async function startRelayServer(options) {
12074
12311
  });
12075
12312
  });
12076
12313
  });
12314
+ bindBaseUrl = `http://${address.address}:${address.port}`;
12077
12315
  return {
12078
12316
  connectorUiAvailable: Boolean(uiDistRoot),
12079
- origin: `http://${address.address}:${address.port}`,
12317
+ origin: bindBaseUrl,
12080
12318
  port: address.port,
12081
12319
  close: async () => await new Promise((resolve, reject) => {
12082
12320
  server.close((error) => {
@@ -12093,8 +12331,14 @@ async function startRelayServer(options) {
12093
12331
  // src/commands/relay.ts
12094
12332
  function buildRelayServeRecommendedCommands(relayUrl) {
12095
12333
  return {
12096
- createWallet: `zk-agent wallet create --relay-url ${relayUrl}`,
12097
- reapproveWallet: `zk-agent wallet reapprove --name main --relay-url ${relayUrl}`
12334
+ createWallet: `zk-agent wallet create --relay-url ${relayUrl} --wait-relay --prompt-code`,
12335
+ reapproveWallet: `zk-agent wallet reapprove --name main --relay-url ${relayUrl} --wait-relay --prompt-code`
12336
+ };
12337
+ }
12338
+ function buildAdvertisedRelayBases(publicOrigin) {
12339
+ return {
12340
+ shareLinkBaseUrl: `${publicOrigin}/r`,
12341
+ statusApiBaseUrl: `${publicOrigin}/api/requests`
12098
12342
  };
12099
12343
  }
12100
12344
  function isRecord3(value) {
@@ -12110,6 +12354,9 @@ function isRelayCapability(value) {
12110
12354
  "connector-ui"
12111
12355
  ].includes(String(value));
12112
12356
  }
12357
+ function isRelayPublicOriginSource(value) {
12358
+ return value === "configured" || value === "bind-origin-default";
12359
+ }
12113
12360
  function asRelayHealthResponse(value) {
12114
12361
  if (!isRecord3(value)) return null;
12115
12362
  if (value.ok !== true) return null;
@@ -12119,6 +12366,9 @@ function asRelayHealthResponse(value) {
12119
12366
  if (value.relay_mode !== "local-file") return null;
12120
12367
  if (typeof value.origin !== "string") return null;
12121
12368
  if (typeof value.public_origin !== "string") return null;
12369
+ if (typeof value.public_origin_source !== "undefined" && !isRelayPublicOriginSource(value.public_origin_source)) {
12370
+ return null;
12371
+ }
12122
12372
  if (typeof value.connector_ui_available !== "boolean") return null;
12123
12373
  if (!Array.isArray(value.capabilities) || !value.capabilities.every(isRelayCapability)) {
12124
12374
  return null;
@@ -12137,6 +12387,29 @@ function relayPublicOriginLooksLocal(origin) {
12137
12387
  return false;
12138
12388
  }
12139
12389
  }
12390
+ function normalizeComparableRelayUrl(value) {
12391
+ try {
12392
+ return new URL(value).toString().replace(/\/+$/, "");
12393
+ } catch {
12394
+ return null;
12395
+ }
12396
+ }
12397
+ function relayUrlMatches(left, right) {
12398
+ const normalizedLeft = normalizeComparableRelayUrl(left);
12399
+ const normalizedRight = right ? normalizeComparableRelayUrl(right) : null;
12400
+ if (normalizedLeft === null || normalizedRight === null) {
12401
+ return null;
12402
+ }
12403
+ return normalizedLeft === normalizedRight;
12404
+ }
12405
+ function inferRelayPublicOriginSource(options) {
12406
+ const normalizedOrigin = options.origin ? normalizeComparableRelayUrl(options.origin) : null;
12407
+ const normalizedPublicOrigin = normalizeComparableRelayUrl(options.publicOrigin);
12408
+ if (!normalizedOrigin || !normalizedPublicOrigin) {
12409
+ return null;
12410
+ }
12411
+ return normalizedOrigin === normalizedPublicOrigin ? "bind-origin-default" : "configured";
12412
+ }
12140
12413
  function relayHostedReadinessNotes(options) {
12141
12414
  const notes = [];
12142
12415
  if (!options.compatible) {
@@ -12147,7 +12420,7 @@ function relayHostedReadinessNotes(options) {
12147
12420
  }
12148
12421
  if (relayPublicOriginLooksLocal(options.publicOrigin)) {
12149
12422
  notes.push(
12150
- "Relay compatibility is present, but the advertised public origin still points at a local-only address. Set --public-origin to the externally reachable URL before using this as a hosted approval path."
12423
+ options.publicOriginSource === "bind-origin-default" ? "Relay compatibility is present, but the relay is still advertising its bind origin as the public origin. Set --public-origin to the externally reachable URL before using this as a hosted approval path." : "Relay compatibility is present, but the advertised public origin still points at a local-only address. Set --public-origin to the externally reachable URL before using this as a hosted approval path."
12151
12424
  );
12152
12425
  }
12153
12426
  if (options.connectorUiAvailable === false) {
@@ -12157,19 +12430,50 @@ function relayHostedReadinessNotes(options) {
12157
12430
  }
12158
12431
  return notes;
12159
12432
  }
12433
+ function relayOriginRelationshipNotes(options) {
12434
+ const notes = [];
12435
+ const relayUrlMatchesOrigin = relayUrlMatches(options.relayUrl, options.origin);
12436
+ const relayUrlMatchesPublicOrigin = relayUrlMatches(options.relayUrl, options.publicOrigin);
12437
+ if (relayUrlMatchesOrigin === false) {
12438
+ notes.push(
12439
+ "The inspected relay URL differs from the bind origin reported by /health. That is expected when you inspect a relay through a reverse proxy or tunnel instead of the local bind address."
12440
+ );
12441
+ }
12442
+ if (relayUrlMatchesPublicOrigin === false) {
12443
+ notes.push(
12444
+ "The inspected relay URL differs from the advertised public origin. Share links and wallet approval commands will use the public origin, not the inspected relay URL."
12445
+ );
12446
+ }
12447
+ return notes;
12448
+ }
12160
12449
  function buildRelayInspectPayload(relayUrl, rawHealth) {
12161
12450
  const health = asRelayHealthResponse(rawHealth);
12162
12451
  const fallbackPublicOrigin = isRecord3(rawHealth) && typeof rawHealth.public_origin === "string" ? rawHealth.public_origin : relayUrl;
12163
12452
  const publicOrigin = health?.public_origin || fallbackPublicOrigin;
12453
+ const publicOriginSource = health?.public_origin_source || inferRelayPublicOriginSource({
12454
+ origin: health?.origin || null,
12455
+ publicOrigin
12456
+ });
12164
12457
  const compatible = Boolean(health && hasCoreRelayCapabilities(health.capabilities));
12165
12458
  const connectorUiAvailable = health?.connector_ui_available ?? null;
12459
+ const relayUrlMatchesOrigin = relayUrlMatches(relayUrl, health?.origin || null);
12460
+ const relayUrlMatchesPublicOrigin = relayUrlMatches(relayUrl, publicOrigin);
12166
12461
  const publicOriginLooksLocal = relayPublicOriginLooksLocal(publicOrigin);
12167
12462
  const hostedShareRedirectReady = compatible && connectorUiAvailable === true && !publicOriginLooksLocal;
12168
- const notes = relayHostedReadinessNotes({
12169
- compatible,
12170
- publicOrigin,
12171
- connectorUiAvailable
12172
- });
12463
+ const { shareLinkBaseUrl, statusApiBaseUrl } = buildAdvertisedRelayBases(publicOrigin);
12464
+ const notes = [
12465
+ ...relayHostedReadinessNotes({
12466
+ compatible,
12467
+ publicOrigin,
12468
+ publicOriginSource,
12469
+ connectorUiAvailable
12470
+ }),
12471
+ ...relayOriginRelationshipNotes({
12472
+ relayUrl,
12473
+ origin: health?.origin || null,
12474
+ publicOrigin
12475
+ })
12476
+ ];
12173
12477
  return {
12174
12478
  ok: true,
12175
12479
  status: "relay-inspected",
@@ -12181,6 +12485,11 @@ function buildRelayInspectPayload(relayUrl, rawHealth) {
12181
12485
  relayMode: health?.relay_mode || null,
12182
12486
  origin: health?.origin || null,
12183
12487
  publicOrigin,
12488
+ publicOriginSource,
12489
+ shareLinkBaseUrl,
12490
+ statusApiBaseUrl,
12491
+ relayUrlMatchesOrigin,
12492
+ relayUrlMatchesPublicOrigin,
12184
12493
  publicOriginLooksLocal,
12185
12494
  connectorUiAvailable,
12186
12495
  hostedShareRedirectReady,
@@ -12206,13 +12515,16 @@ function createRelayCommand() {
12206
12515
  publicOrigin: options.publicOrigin?.trim()
12207
12516
  });
12208
12517
  const publicOrigin = options.publicOrigin?.trim() || server.origin;
12518
+ const publicOriginSource = options.publicOrigin?.trim() ? "configured" : "bind-origin-default";
12209
12519
  const publicOriginLooksLocal = relayPublicOriginLooksLocal(publicOrigin);
12210
12520
  const connectorUiAvailable = server.connectorUiAvailable;
12211
12521
  const hostedShareRedirectReady = connectorUiAvailable && !publicOriginLooksLocal;
12522
+ const { shareLinkBaseUrl, statusApiBaseUrl } = buildAdvertisedRelayBases(publicOrigin);
12212
12523
  const recommendedCommands = buildRelayServeRecommendedCommands(publicOrigin);
12213
12524
  const notes = relayHostedReadinessNotes({
12214
12525
  compatible: true,
12215
12526
  publicOrigin,
12527
+ publicOriginSource,
12216
12528
  connectorUiAvailable
12217
12529
  });
12218
12530
  const payload = {
@@ -12220,6 +12532,9 @@ function createRelayCommand() {
12220
12532
  status: "relay-serving",
12221
12533
  origin: server.origin,
12222
12534
  publicOrigin,
12535
+ publicOriginSource,
12536
+ shareLinkBaseUrl,
12537
+ statusApiBaseUrl,
12223
12538
  publicOriginLooksLocal,
12224
12539
  port: server.port,
12225
12540
  healthUrl: `${server.origin}/health`,
@@ -12246,6 +12561,9 @@ function createRelayCommand() {
12246
12561
  if (publicOrigin !== server.origin) {
12247
12562
  humanLine("public origin", publicOrigin);
12248
12563
  }
12564
+ humanLine("public origin source", publicOriginSource);
12565
+ humanLine("share-link base", shareLinkBaseUrl);
12566
+ humanLine("status api base", statusApiBaseUrl);
12249
12567
  humanLine("health", `${server.origin}/health`);
12250
12568
  humanLine("hosted ready", hostedShareRedirectReady ? "yes" : "no");
12251
12569
  if (connectorUiAvailable !== null) {
@@ -12297,6 +12615,20 @@ function createRelayCommand() {
12297
12615
  if (payload.publicOrigin) {
12298
12616
  humanLine("public origin", payload.publicOrigin);
12299
12617
  }
12618
+ if (payload.publicOriginSource) {
12619
+ humanLine("public origin source", payload.publicOriginSource);
12620
+ }
12621
+ humanLine("share-link base", payload.shareLinkBaseUrl);
12622
+ humanLine("status api base", payload.statusApiBaseUrl);
12623
+ if (payload.relayUrlMatchesOrigin !== null) {
12624
+ humanLine("relay url matches origin", payload.relayUrlMatchesOrigin ? "yes" : "no");
12625
+ }
12626
+ if (payload.relayUrlMatchesPublicOrigin !== null) {
12627
+ humanLine(
12628
+ "relay url matches public origin",
12629
+ payload.relayUrlMatchesPublicOrigin ? "yes" : "no"
12630
+ );
12631
+ }
12300
12632
  humanLine("public origin local", payload.publicOriginLooksLocal ? "yes" : "no");
12301
12633
  if (payload.connectorUiAvailable !== null) {
12302
12634
  humanLine("connector ui", payload.connectorUiAvailable ? "available" : "missing");
@@ -12312,10 +12644,9 @@ function createRelayCommand() {
12312
12644
  if (payload.recommendedCommands.reapproveWallet) {
12313
12645
  humanLine("reapprove wallet", payload.recommendedCommands.reapproveWallet);
12314
12646
  }
12315
- } else {
12316
- for (const note of payload.notes) {
12317
- humanLine("note", note);
12318
- }
12647
+ }
12648
+ for (const note of payload.notes) {
12649
+ humanLine("note", note);
12319
12650
  }
12320
12651
  });
12321
12652
  return relay;
@@ -12328,12 +12659,12 @@ import { Command as Command9 } from "commander";
12328
12659
 
12329
12660
  // ../account-profiles/src/profiles.ts
12330
12661
  import fs8 from "node:fs";
12331
- import path7 from "node:path";
12662
+ import path8 from "node:path";
12332
12663
  import { fileURLToPath as fileURLToPath4 } from "node:url";
12333
12664
  var PACKAGE_NAME = "@zk-agent/account-profiles";
12334
12665
  var PACKAGE_ROOT_FALLBACK = `<${PACKAGE_NAME} package root unavailable>`;
12335
12666
  function isExpectedPackageRoot(candidate) {
12336
- const manifestPath = path7.join(candidate, "package.json");
12667
+ const manifestPath = path8.join(candidate, "package.json");
12337
12668
  if (!fs8.existsSync(manifestPath)) return false;
12338
12669
  try {
12339
12670
  const manifest = JSON.parse(fs8.readFileSync(manifestPath, "utf8"));
@@ -12343,19 +12674,19 @@ function isExpectedPackageRoot(candidate) {
12343
12674
  }
12344
12675
  }
12345
12676
  function resolvePackageRoot() {
12346
- const moduleRoot = path7.resolve(path7.dirname(fileURLToPath4(import.meta.url)), "..");
12677
+ const moduleRoot = path8.resolve(path8.dirname(fileURLToPath4(import.meta.url)), "..");
12347
12678
  const cwd = process.cwd();
12348
12679
  const candidates = [
12349
12680
  process.env.ZK_AGENT_ACCOUNT_PROFILES_ROOT,
12350
- path7.join(moduleRoot, "dist", "builtin-account-profiles"),
12681
+ path8.join(moduleRoot, "dist", "builtin-account-profiles"),
12351
12682
  moduleRoot,
12352
- path7.join(cwd, "packages", "account-profiles"),
12353
- path7.join(cwd, "account-profiles"),
12683
+ path8.join(cwd, "packages", "account-profiles"),
12684
+ path8.join(cwd, "account-profiles"),
12354
12685
  cwd
12355
12686
  ].filter((value) => typeof value === "string" && value.trim().length > 0);
12356
12687
  const seen = /* @__PURE__ */ new Set();
12357
12688
  for (const candidate of candidates) {
12358
- const normalized = path7.resolve(candidate);
12689
+ const normalized = path8.resolve(candidate);
12359
12690
  if (seen.has(normalized)) continue;
12360
12691
  seen.add(normalized);
12361
12692
  if (isExpectedPackageRoot(normalized)) {
@@ -12427,11 +12758,11 @@ function missingArtifactError(profileId, artifactPath2) {
12427
12758
  }
12428
12759
  function contractPath(...segments) {
12429
12760
  const packageRoot = tryResolvePackageRoot();
12430
- return packageRoot ? path7.join(packageRoot, "contracts", ...segments) : path7.join(PACKAGE_ROOT_FALLBACK, "contracts", ...segments);
12761
+ return packageRoot ? path8.join(packageRoot, "contracts", ...segments) : path8.join(PACKAGE_ROOT_FALLBACK, "contracts", ...segments);
12431
12762
  }
12432
12763
  function artifactPath(...segments) {
12433
12764
  const packageRoot = tryResolvePackageRoot();
12434
- return packageRoot ? path7.join(packageRoot, "artifacts", ...segments) : path7.join(PACKAGE_ROOT_FALLBACK, "artifacts", ...segments);
12765
+ return packageRoot ? path8.join(packageRoot, "artifacts", ...segments) : path8.join(PACKAGE_ROOT_FALLBACK, "artifacts", ...segments);
12435
12766
  }
12436
12767
  function createDailySpendLimitProfile() {
12437
12768
  const resolvedArtifactPath = artifactPath("daily-spend-limit", "Account.json");
@@ -12853,6 +13184,7 @@ var WALLET_HELP_COMMAND_ORDER = [
12853
13184
  "rename",
12854
13185
  "remove",
12855
13186
  "request",
13187
+ "signer",
12856
13188
  "paymaster",
12857
13189
  "smart-account"
12858
13190
  ];
@@ -12872,6 +13204,11 @@ var WALLET_SMART_ACCOUNT_HELP_COMMAND_ORDER = [
12872
13204
  "sed-lite",
12873
13205
  "daily-spend-limit"
12874
13206
  ];
13207
+ var WALLET_SIGNER_HELP_COMMAND_ORDER = [
13208
+ "show",
13209
+ "attach",
13210
+ "remove"
13211
+ ];
12875
13212
  function applyCommandOrder(command, orderedNames) {
12876
13213
  const order = new Map(orderedNames.map((name, index) => [name, index]));
12877
13214
  const sortedCommands = [...command.commands].sort((left, right) => {
@@ -12892,6 +13229,10 @@ function sanitizeSessionPayload(payload) {
12892
13229
  function stripSensitiveWalletRecord(wallet) {
12893
13230
  return {
12894
13231
  ...wallet,
13232
+ localExecutionAuthority: wallet.localExecutionAuthority ? {
13233
+ ...wallet.localExecutionAuthority,
13234
+ privateKey: void 0
13235
+ } : wallet.localExecutionAuthority,
12895
13236
  sessionPayload: wallet.sessionPayload ? {
12896
13237
  ...wallet.sessionPayload,
12897
13238
  sessionPrivateKey: void 0
@@ -12907,11 +13248,16 @@ function sanitizeWalletRecord(wallet) {
12907
13248
  function relayOutputAliases(relay) {
12908
13249
  const shareUrl = relay && "share_url" in relay ? relay.share_url : void 0;
12909
13250
  const statusUrl = relay && "status_url" in relay ? relay.status_url : void 0;
13251
+ const approvalUrl = relay?.approval_url;
13252
+ const shareLinkBaseUrl = shareUrl ? shareUrl.replace(/\/[^/]+$/, "") : approvalUrl ? approvalUrl.replace(/\/[^/]+$/, "") : void 0;
13253
+ const statusApiBaseUrl = statusUrl ? statusUrl.replace(/\/[^/]+$/, "") : approvalUrl && relay?.request_id ? `${approvalUrl.replace(/\/r\/[^/]+$/, "")}/api/requests` : void 0;
12910
13254
  return {
12911
13255
  relayRequestId: relay?.request_id,
12912
13256
  relayShareUrl: shareUrl,
12913
13257
  relayStatusUrl: statusUrl,
12914
- relayApprovalUrl: relay?.approval_url
13258
+ relayApprovalUrl: approvalUrl,
13259
+ relayShareLinkBaseUrl: shareLinkBaseUrl,
13260
+ relayStatusApiBaseUrl: statusApiBaseUrl
12915
13261
  };
12916
13262
  }
12917
13263
  function sanitizeWalletRequestRecord(request) {
@@ -12982,6 +13328,7 @@ function isRecord5(value) {
12982
13328
  function cloneWalletSessionRecord(wallet) {
12983
13329
  return {
12984
13330
  ...wallet,
13331
+ localExecutionAuthority: wallet.localExecutionAuthority ? { ...wallet.localExecutionAuthority } : wallet.localExecutionAuthority,
12985
13332
  validationHookAddresses: wallet.validationHookAddresses ? [...wallet.validationHookAddresses] : wallet.validationHookAddresses,
12986
13333
  sessionScope: wallet.sessionScope ? {
12987
13334
  ...wallet.sessionScope,
@@ -13032,6 +13379,12 @@ function parseWalletExportRecord(value) {
13032
13379
  if (wallet.validatorAddress && !isAddress3(wallet.validatorAddress)) {
13033
13380
  throw new Error("Restore payload validatorAddress must be a valid 20-byte hex address.");
13034
13381
  }
13382
+ if (wallet.localExecutionAuthority?.signerAddress && !isAddress3(wallet.localExecutionAuthority.signerAddress)) {
13383
+ throw new Error("Restore payload localExecutionAuthority.signerAddress must be a valid 20-byte hex address.");
13384
+ }
13385
+ if (wallet.localExecutionAuthority?.privateKey && !/^0x[a-fA-F0-9]{64}$/.test(wallet.localExecutionAuthority.privateKey)) {
13386
+ throw new Error("Restore payload localExecutionAuthority.privateKey must be a valid 32-byte hex string.");
13387
+ }
13035
13388
  if (wallet.validationHookAddresses && wallet.validationHookAddresses.some((hookAddress) => !isAddress3(hookAddress))) {
13036
13389
  throw new Error("Restore payload validationHookAddresses must contain valid 20-byte hex addresses.");
13037
13390
  }
@@ -13064,7 +13417,7 @@ function parseWalletExportRecord(value) {
13064
13417
  }
13065
13418
  return {
13066
13419
  ...candidate,
13067
- wallet: cloneWalletSessionRecord(wallet)
13420
+ wallet: migrateWalletSessionRecord(cloneWalletSessionRecord(wallet))
13068
13421
  };
13069
13422
  }
13070
13423
  function normalizeHexString2(value, label) {
@@ -13075,6 +13428,13 @@ function normalizeHexString2(value, label) {
13075
13428
  }
13076
13429
  return prefixed;
13077
13430
  }
13431
+ function normalizePrivateKey(value, label) {
13432
+ const normalized = normalizeHexString2(value, label);
13433
+ if (!/^0x[a-fA-F0-9]{64}$/.test(normalized)) {
13434
+ throw new Error(`${label} must be a valid 32-byte hex string.`);
13435
+ }
13436
+ return normalized;
13437
+ }
13078
13438
  function parseArtifactInput(value) {
13079
13439
  const raw = parseJsonInput(value);
13080
13440
  if (!isRecord5(raw)) throw new Error("Artifact must be a JSON object");
@@ -13783,6 +14143,12 @@ function inspectionLines(inspection) {
13783
14143
  inspection.signerMatchesStoredIdentity ? "yes" : "no"
13784
14144
  ]);
13785
14145
  }
14146
+ if (typeof inspection.approvalReady === "boolean") {
14147
+ lines.push(["approval", inspection.approvalReady ? "present" : "missing"]);
14148
+ }
14149
+ if (typeof inspection.localExecutionKeyStored === "boolean") {
14150
+ lines.push(["local signer", inspection.localExecutionKeyStored ? "stored" : "missing"]);
14151
+ }
13786
14152
  lines.push(["session key", inspection.sessionPrivateKeyStored ? "stored" : "missing"]);
13787
14153
  if (inspection.paymasterMode) {
13788
14154
  lines.push(["paymaster", inspection.paymasterMode]);
@@ -13881,6 +14247,7 @@ function walletExportLines(wallet, bundle) {
13881
14247
  }
13882
14248
  function walletRestoreLines(wallet, restoredFrom, syncResult) {
13883
14249
  let nextCommand = buildWalletNextRecommendedCommand(wallet.walletName);
14250
+ let afterNextCommandLabel;
13884
14251
  const lines = [
13885
14252
  ["wallet", wallet.walletName],
13886
14253
  ["address", wallet.walletAddress],
@@ -13891,12 +14258,15 @@ function walletRestoreLines(wallet, restoredFrom, syncResult) {
13891
14258
  ["source export", restoredFrom.exportedAt],
13892
14259
  ["sensitive data", restoredFrom.sensitiveDataIncluded ? "included in backup" : "not included in backup"]
13893
14260
  ];
13894
- if (!wallet.sessionPayload?.sessionPrivateKey) {
14261
+ if (!resolveLocalExecutionPrivateKey(wallet)) {
14262
+ const attachSignerCommand = buildWalletSignerAttachRecommendedCommand(wallet.walletName);
14263
+ const reapproveCommand = buildWalletReapproveRecommendedCommand(wallet.walletName);
13895
14264
  lines.push([
13896
14265
  "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)}`
14266
+ 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
14267
  ]);
13899
- nextCommand = buildWalletReapproveRecommendedCommand(wallet.walletName);
14268
+ nextCommand = wallet.sessionPayload ? attachSignerCommand : reapproveCommand;
14269
+ afterNextCommandLabel = wallet.sessionPayload ? "after signer attach" : "after reapprove";
13900
14270
  }
13901
14271
  if (syncResult) {
13902
14272
  lines.push(["sync", "completed"]);
@@ -13920,7 +14290,10 @@ function walletRestoreLines(wallet, restoredFrom, syncResult) {
13920
14290
  }
13921
14291
  lines.push(["next", nextCommand]);
13922
14292
  if (nextCommand !== buildWalletNextRecommendedCommand(wallet.walletName)) {
13923
- lines.push(["after reapprove", buildWalletNextRecommendedCommand(wallet.walletName)]);
14293
+ lines.push([
14294
+ afterNextCommandLabel || "after reapprove",
14295
+ buildWalletNextRecommendedCommand(wallet.walletName)
14296
+ ]);
13924
14297
  }
13925
14298
  lines.push(["status command", buildWalletStatusRecommendedCommand(wallet.walletName)]);
13926
14299
  return lines;
@@ -13932,6 +14305,17 @@ function preserveExistingWalletMetadata(importedWallet, existingWallet) {
13932
14305
  if (existingWallet.chain !== importedWallet.chain || existingWallet.chainId !== importedWallet.chainId || existingWallet.walletAddress.toLowerCase() !== importedWallet.walletAddress.toLowerCase()) {
13933
14306
  return importedWallet;
13934
14307
  }
14308
+ const sameAccountKind = existingWallet.accountKind === importedWallet.accountKind;
14309
+ const sameSmartAccountOwner = existingWallet.accountKind !== "smart-account" || existingWallet.ownerAddress && importedWallet.ownerAddress && existingWallet.ownerAddress.toLowerCase() === importedWallet.ownerAddress.toLowerCase();
14310
+ const existingSessionPrivateKey = resolveLocalExecutionPrivateKey(existingWallet);
14311
+ const existingLocalExecutionAuthority = existingWallet.localExecutionAuthority || buildLocalExecutionAuthority({
14312
+ privateKey: existingSessionPrivateKey,
14313
+ signerType: existingWallet.sessionPayload?.account?.signerType,
14314
+ source: existingSessionPrivateKey ? "legacy-session-payload" : void 0,
14315
+ attachedAt: existingWallet.createdAt
14316
+ });
14317
+ const importedSessionPrivateKey = resolveLocalExecutionPrivateKey(importedWallet);
14318
+ const shouldPreserveWritableSession = sameAccountKind && sameSmartAccountOwner && Boolean(existingSessionPrivateKey) && !importedSessionPrivateKey;
13935
14319
  const metadataUpdates = {};
13936
14320
  if (existingWallet.smartAccountProfileId) {
13937
14321
  const profileId = tryResolveBuiltinProfileId(existingWallet.smartAccountProfileId);
@@ -13948,7 +14332,13 @@ function preserveExistingWalletMetadata(importedWallet, existingWallet) {
13948
14332
  if (existingWallet.validatorAddress) {
13949
14333
  metadataUpdates.validatorAddress = existingWallet.validatorAddress;
13950
14334
  }
13951
- return applyWalletSyncMetadata(importedWallet, metadataUpdates);
14335
+ const mergedWallet = shouldPreserveWritableSession && existingLocalExecutionAuthority ? {
14336
+ ...importedWallet,
14337
+ localExecutionAuthority: {
14338
+ ...existingLocalExecutionAuthority
14339
+ }
14340
+ } : importedWallet;
14341
+ return migrateWalletSessionRecord(applyWalletSyncMetadata(mergedWallet, metadataUpdates));
13952
14342
  }
13953
14343
  function linesForWriteResult2(result, nextCommand) {
13954
14344
  const lines = [
@@ -14042,23 +14432,85 @@ function buildWalletApprovalLines(status, requestId, walletRecord) {
14042
14432
  ];
14043
14433
  }
14044
14434
  function buildWalletFollowUpRecommendedCommands(walletRecord) {
14435
+ const localExecutionReady = Boolean(resolveLocalExecutionPrivateKey(walletRecord));
14436
+ const approvalReady = Boolean(walletRecord.sessionPayload);
14045
14437
  return {
14046
14438
  next: buildWalletNextRecommendedCommand(walletRecord.walletName),
14047
14439
  status: buildWalletStatusRecommendedCommand(walletRecord.walletName),
14048
- ...!walletRecord.sessionPayload?.sessionPrivateKey ? { reapprove: buildWalletReapproveRecommendedCommand(walletRecord.walletName) } : {}
14440
+ ...!localExecutionReady && approvalReady ? {
14441
+ attachSigner: buildWalletSignerAttachRecommendedCommand(walletRecord.walletName),
14442
+ reapprove: buildWalletReapproveRecommendedCommand(walletRecord.walletName)
14443
+ } : !localExecutionReady ? { reapprove: buildWalletReapproveRecommendedCommand(walletRecord.walletName) } : {}
14444
+ };
14445
+ }
14446
+ function walletSignerSummary(walletRecord) {
14447
+ const authority = walletRecord.localExecutionAuthority;
14448
+ const localExecutionPrivateKey = resolveLocalExecutionPrivateKey(walletRecord);
14449
+ return {
14450
+ approvalReady: Boolean(walletRecord.sessionPayload),
14451
+ localExecutionKeyStored: Boolean(localExecutionPrivateKey),
14452
+ legacySessionKeyStored: Boolean(walletRecord.sessionPayload?.sessionPrivateKey),
14453
+ signerAddress: authority?.signerAddress || deriveAddressFromPrivateKey(localExecutionPrivateKey),
14454
+ signerType: authority?.signerType || walletRecord.sessionPayload?.account?.signerType,
14455
+ source: authority?.source || (walletRecord.sessionPayload?.sessionPrivateKey ? "legacy-session-payload" : void 0),
14456
+ attachedAt: authority?.attachedAt
14457
+ };
14458
+ }
14459
+ function walletSignerLines(walletRecord) {
14460
+ const signer = walletSignerSummary(walletRecord);
14461
+ return [
14462
+ ["wallet", walletRecord.walletName],
14463
+ ["address", walletRecord.walletAddress],
14464
+ ...displayOwnerAddress(walletRecord) ? [["owner", displayOwnerAddress(walletRecord)]] : [],
14465
+ ["account", displayAccountKind(walletRecord)],
14466
+ ["chain", `${walletRecord.chain} (${walletRecord.chainId})`],
14467
+ ["approval", signer.approvalReady ? "present" : "missing"],
14468
+ ["local signer", signer.localExecutionKeyStored ? "stored" : "missing"],
14469
+ ["legacy payload mirror", signer.legacySessionKeyStored ? "present" : "missing"],
14470
+ ...signer.signerType ? [["signer type", signer.signerType]] : [],
14471
+ ...signer.signerAddress ? [["signer address", signer.signerAddress]] : [],
14472
+ ...signer.source ? [["source", signer.source]] : [],
14473
+ ...signer.attachedAt ? [["attached", signer.attachedAt]] : [],
14474
+ ...walletFollowUpLines(walletRecord)
14475
+ ];
14476
+ }
14477
+ function walletSignerAttachResult(walletRecord, note) {
14478
+ return {
14479
+ ok: true,
14480
+ wallet: sanitizeWalletRecord(walletRecord),
14481
+ signer: walletSignerSummary(walletRecord),
14482
+ ...note ? { note } : {},
14483
+ nextAction: buildWalletFollowUpNextAction(walletRecord),
14484
+ recommendedCommands: buildWalletFollowUpRecommendedCommands(walletRecord)
14049
14485
  };
14050
14486
  }
14487
+ function normalizeSignerType(value) {
14488
+ if (value === "local" || value === "connector" || value === "external") {
14489
+ return value;
14490
+ }
14491
+ throw new Error(`Unsupported signer type: ${value}`);
14492
+ }
14493
+ function clearLegacySessionPrivateKey(payload) {
14494
+ return payload ? {
14495
+ ...payload,
14496
+ sessionPrivateKey: void 0
14497
+ } : payload;
14498
+ }
14051
14499
  function walletFollowUpLines(walletRecord) {
14052
14500
  const recommendedCommands = buildWalletFollowUpRecommendedCommands(walletRecord);
14501
+ const nextCommand = recommendedCommands.attachSigner ?? recommendedCommands.reapprove ?? recommendedCommands.next;
14053
14502
  return [
14054
- ["next", recommendedCommands.reapprove ?? recommendedCommands.next],
14055
- ...recommendedCommands.reapprove ? [["after reapprove", recommendedCommands.next]] : [],
14503
+ ["next", nextCommand],
14504
+ ...recommendedCommands.attachSigner ? [
14505
+ ["after signer attach", recommendedCommands.next],
14506
+ ["or reapprove", recommendedCommands.reapprove]
14507
+ ] : recommendedCommands.reapprove ? [["after reapprove", recommendedCommands.next]] : [],
14056
14508
  ["status command", recommendedCommands.status]
14057
14509
  ];
14058
14510
  }
14059
14511
  function buildWalletFollowUpNextAction(walletRecord) {
14060
14512
  const recommendedCommands = buildWalletFollowUpRecommendedCommands(walletRecord);
14061
- return recommendedCommands.reapprove ?? recommendedCommands.next;
14513
+ return recommendedCommands.attachSigner ?? recommendedCommands.reapprove ?? recommendedCommands.next;
14062
14514
  }
14063
14515
  function buildPendingRequestRecommendedCommands(walletName, requestId, relayUrl, paymasterMode) {
14064
14516
  return {
@@ -14136,7 +14588,7 @@ async function createWalletReapprovalRequest(options) {
14136
14588
  chain: options.walletRecord.chain,
14137
14589
  connectorUrl,
14138
14590
  accountKind: displayAccountKind(options.walletRecord),
14139
- paymasterMode: displayPaymasterMode(options.walletRecord),
14591
+ paymasterMode: options.paymasterMode || displayPaymasterMode(options.walletRecord),
14140
14592
  policies
14141
14593
  });
14142
14594
  await saveWalletRequest(request);
@@ -14604,6 +15056,9 @@ function createWalletCommand(deps) {
14604
15056
  const resolvedDeps = resolveWalletCommandDeps(deps);
14605
15057
  const wallet = new Command9("wallet").description("Manage wallet sessions");
14606
15058
  const request = new Command9("request").description("Inspect and finalize pending wallet requests");
15059
+ const signer = new Command9("signer").description(
15060
+ "Inspect and manage the stored local execution signer for a wallet"
15061
+ );
14607
15062
  const smartAccount = new Command9("smart-account").description(
14608
15063
  "Predict and deploy zkSync smart-account contracts from a supplied artifact or built-in profile"
14609
15064
  );
@@ -14629,20 +15084,24 @@ function createWalletCommand(deps) {
14629
15084
  "after",
14630
15085
  [
14631
15086
  "",
14632
- "Default wallet path:",
15087
+ "Local-first wallet path:",
14633
15088
  " First bootstrap:",
14634
15089
  " zk-agent wallet create --await-local",
14635
15090
  " zk-agent next",
14636
15091
  "",
14637
- " Restore a writable session for an existing wallet:",
15092
+ " Restore approval metadata for an existing wallet:",
14638
15093
  " zk-agent wallet reapprove --name main --await-local",
14639
15094
  " zk-agent next",
14640
15095
  "",
15096
+ " Attach a local signer when approval is still present:",
15097
+ " zk-agent wallet signer attach --name main --private-key <hex>",
15098
+ " zk-agent next",
15099
+ "",
14641
15100
  " Wallet-layer inspection:",
14642
15101
  " zk-agent wallet status --name main",
14643
15102
  " zk-agent wallet next --name main",
14644
15103
  "",
14645
- " Remote approval path:",
15104
+ " Hosted remote approval path:",
14646
15105
  " zk-agent relay inspect --relay-url <url>",
14647
15106
  " zk-agent wallet create --relay-url <url> --wait-relay --prompt-code",
14648
15107
  " zk-agent wallet reapprove --name main --relay-url <url> --wait-relay --prompt-code"
@@ -14662,6 +15121,21 @@ function createWalletCommand(deps) {
14662
15121
  " zk-agent wallet request approve --request-id <id> --relay-url <url> --code <code> --wait"
14663
15122
  ].join("\n")
14664
15123
  );
15124
+ signer.addHelpText(
15125
+ "after",
15126
+ [
15127
+ "",
15128
+ "Wallet signer path:",
15129
+ " Inspect the stored local execution signer state:",
15130
+ " zk-agent wallet signer show --name main",
15131
+ "",
15132
+ " Attach a local execution signer without rebuilding approval metadata:",
15133
+ " zk-agent wallet signer attach --name main --private-key <hex>",
15134
+ "",
15135
+ " Remove the stored local execution signer:",
15136
+ " zk-agent wallet signer remove --name main"
15137
+ ].join("\n")
15138
+ );
14665
15139
  smartAccount.addHelpText(
14666
15140
  "after",
14667
15141
  [
@@ -14785,7 +15259,9 @@ function createWalletCommand(deps) {
14785
15259
  ["approval url", request2.approvalUrl],
14786
15260
  ...relay ? [
14787
15261
  ["share url", relay.share_url],
14788
- ["status url", relay.status_url]
15262
+ ["status url", relay.status_url],
15263
+ ["share-link base", relay.share_url.replace(/\/[^/]+$/, "")],
15264
+ ["status api base", relay.status_url.replace(/\/[^/]+$/, "")]
14789
15265
  ] : [],
14790
15266
  ["expires", request2.expiresAt],
14791
15267
  ["next local", buildWalletRequestAwaitLocalRecommendedCommand(request2.requestId)],
@@ -14932,7 +15408,9 @@ function createWalletCommand(deps) {
14932
15408
  ["approval url", request2.approvalUrl],
14933
15409
  ...relay ? [
14934
15410
  ["share url", relay.share_url],
14935
- ["status url", relay.status_url]
15411
+ ["status url", relay.status_url],
15412
+ ["share-link base", relay.share_url.replace(/\/[^/]+$/, "")],
15413
+ ["status api base", relay.status_url.replace(/\/[^/]+$/, "")]
14936
15414
  ] : [],
14937
15415
  ["expires", request2.expiresAt],
14938
15416
  ["next local", buildWalletRequestAwaitLocalRecommendedCommand(request2.requestId)],
@@ -15030,7 +15508,56 @@ function createWalletCommand(deps) {
15030
15508
  }
15031
15509
  );
15032
15510
  });
15033
- 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) => {
15511
+ signer.command("show").description("Show the stored local execution signer state for a wallet").option("--name <name>", "Wallet name", "main").action(async (options) => {
15512
+ const walletRecord = await requireWalletRecord(options.name);
15513
+ printResult(walletSignerLines(walletRecord), {
15514
+ ok: true,
15515
+ wallet: sanitizeWalletRecord(walletRecord),
15516
+ signer: walletSignerSummary(walletRecord),
15517
+ nextAction: buildWalletFollowUpNextAction(walletRecord),
15518
+ recommendedCommands: buildWalletFollowUpRecommendedCommands(walletRecord)
15519
+ });
15520
+ });
15521
+ 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) => {
15522
+ const walletRecord = await requireWalletRecord(options.name);
15523
+ const privateKey = normalizePrivateKey(options.privateKey, "--private-key");
15524
+ const nextWallet = migrateWalletSessionRecord({
15525
+ ...walletRecord,
15526
+ localExecutionAuthority: buildLocalExecutionAuthority({
15527
+ privateKey,
15528
+ signerType: "local",
15529
+ source: "explicit-local-approval",
15530
+ attachedAt: (/* @__PURE__ */ new Date()).toISOString()
15531
+ }),
15532
+ sessionPayload: clearLegacySessionPrivateKey(walletRecord.sessionPayload)
15533
+ });
15534
+ 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.";
15535
+ await saveWalletSession(nextWallet);
15536
+ printResult(
15537
+ walletSignerLines(nextWallet),
15538
+ walletSignerAttachResult(nextWallet, note)
15539
+ );
15540
+ });
15541
+ signer.command("remove").description("Remove the stored local execution signer from a wallet record").option("--name <name>", "Wallet name", "main").action(async (options) => {
15542
+ const walletRecord = await requireWalletRecord(options.name);
15543
+ const hadLocalExecutionKey = Boolean(resolveLocalExecutionPrivateKey(walletRecord));
15544
+ const nextWallet = migrateWalletSessionRecord({
15545
+ ...walletRecord,
15546
+ localExecutionAuthority: void 0,
15547
+ sessionPayload: clearLegacySessionPrivateKey(walletRecord.sessionPayload)
15548
+ });
15549
+ 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.";
15550
+ await saveWalletSession(nextWallet);
15551
+ printResult(
15552
+ walletSignerLines(nextWallet),
15553
+ walletSignerAttachResult(nextWallet, note)
15554
+ );
15555
+ });
15556
+ wallet.command("export").description("Export one stored wallet as a portable backup bundle for later restore").option("--name <name>", "Wallet name", "main").option(
15557
+ "--include-sensitive-data",
15558
+ "Include the stored local execution key and any legacy sessionPrivateKey mirror in the exported bundle",
15559
+ false
15560
+ ).action(async (options) => {
15034
15561
  const walletRecord = await requireWalletRecord(options.name);
15035
15562
  const bundle = exportWalletRecord(walletRecord, Boolean(options.includeSensitiveData));
15036
15563
  const recommendedCommands = {
@@ -15237,6 +15764,8 @@ function createWalletCommand(deps) {
15237
15764
  ["request", relay.request_id],
15238
15765
  ["share url", relay.share_url],
15239
15766
  ["status url", relay.status_url],
15767
+ ["share-link base", relay.share_url.replace(/\/[^/]+$/, "")],
15768
+ ["status api base", relay.status_url.replace(/\/[^/]+$/, "")],
15240
15769
  ["next status", buildWalletRequestRelayStatusRecommendedCommand(relay.request_id, options.relayUrl)],
15241
15770
  ["next approve", buildWalletRequestRelayApproveRecommendedCommand(relay.request_id, options.relayUrl)]
15242
15771
  ],
@@ -15265,6 +15794,8 @@ function createWalletCommand(deps) {
15265
15794
  ["request", relay.request_id],
15266
15795
  ["approval ready", relay.approval_ready ? "yes" : "no"],
15267
15796
  ["share url", relay.approval_url],
15797
+ ["share-link base", relay.approval_url.replace(/\/[^/]+$/, "")],
15798
+ ["status api base", `${relay.approval_url.replace(/\/r\/[^/]+$/, "")}/api/requests`],
15268
15799
  ["expires", relay.expires_at],
15269
15800
  ["next status", buildWalletRequestRelayStatusRecommendedCommand(relay.request_id, options.relayUrl)],
15270
15801
  ...relay.approval_ready ? [[
@@ -15335,7 +15866,7 @@ function createWalletCommand(deps) {
15335
15866
  }
15336
15867
  );
15337
15868
  });
15338
- 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(
15869
+ 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(
15339
15870
  async (options) => {
15340
15871
  const walletRequest = await requireActiveWalletRequest(options.requestId);
15341
15872
  assertRequestActive(walletRequest.expiresAt);
@@ -15355,7 +15886,7 @@ function createWalletCommand(deps) {
15355
15886
  validatorAddress: options.validatorAddress,
15356
15887
  paymasterAddress: options.paymasterAddress,
15357
15888
  paymasterToken: options.paymasterToken,
15358
- signerType: options.signerType,
15889
+ signerType: options.signerType ? normalizeSignerType(options.signerType) : void 0,
15359
15890
  connectorOrigin: connectorOriginFromUrl(walletRequest.connectorUrl),
15360
15891
  connectorUrl: walletRequest.connectorUrl
15361
15892
  });
@@ -16941,10 +17472,12 @@ function createWalletCommand(deps) {
16941
17472
  sedLite.addCommand(selectorAllowlistHook);
16942
17473
  smartAccount.addCommand(sedLite);
16943
17474
  smartAccount.addCommand(dailySpendLimit);
16944
- wallet.addCommand(smartAccount);
16945
- wallet.addCommand(paymaster);
16946
17475
  wallet.addCommand(request);
17476
+ wallet.addCommand(signer);
17477
+ wallet.addCommand(paymaster);
17478
+ wallet.addCommand(smartAccount);
16947
17479
  applyCommandOrder(request, WALLET_REQUEST_HELP_COMMAND_ORDER);
17480
+ applyCommandOrder(signer, WALLET_SIGNER_HELP_COMMAND_ORDER);
16948
17481
  applyCommandOrder(smartAccount, WALLET_SMART_ACCOUNT_HELP_COMMAND_ORDER);
16949
17482
  applyCommandOrder(wallet, WALLET_HELP_COMMAND_ORDER);
16950
17483
  return wallet;
@@ -17376,7 +17909,7 @@ function prependWorkflowRequestId(requestId, lines) {
17376
17909
  return [["workflow request", requestId], ...lines];
17377
17910
  }
17378
17911
  function workflowHasSessionApprovalBlocker(status) {
17379
- return status.blockingActionIds.some((actionId) => actionId === "reapprove" || actionId === "signer-mismatch");
17912
+ return status.blockingActionIds.some((actionId) => actionId === "reapprove");
17380
17913
  }
17381
17914
  function workflowShouldEnsureWalletSession(options) {
17382
17915
  return Boolean(options.ensureWalletSession || options.awaitLocal);
@@ -17450,10 +17983,14 @@ async function ensureWorkflowWalletSession(input, deps) {
17450
17983
  goal: input.goal,
17451
17984
  options: input.options
17452
17985
  });
17986
+ const paymasterInput = resolveWorkflowPaymasterInput(input.options);
17453
17987
  const reusableRequest = await deps.findReusableWalletRequest(input.wallet.walletName);
17454
17988
  const walletRequest = reusableRequest || await deps.createWalletReapprovalRequest({
17455
17989
  walletRecord: input.wallet,
17456
17990
  connectorUrl: input.options.connectorUrl,
17991
+ ...paymasterInput?.mode ? {
17992
+ paymasterMode: paymasterInput.mode
17993
+ } : {},
17457
17994
  sessionPreset: policyRequestOptions.sessionPreset,
17458
17995
  sessionHours: policyRequestOptions.sessionHours,
17459
17996
  allowTransferTo: policyRequestOptions.allowTransferTo,
@@ -17538,6 +18075,7 @@ async function ensureWorkflowWalletSession(input, deps) {
17538
18075
  }
17539
18076
  function workflowWalletApprovalLines(walletApproval) {
17540
18077
  if (!walletApproval) return [];
18078
+ const relayAliases = workflowWalletApprovalRelayAliases(walletApproval.relay);
17541
18079
  const lines = [
17542
18080
  ["wallet approval", walletApproval.stage],
17543
18081
  ["wallet request", walletApproval.request.requestId]
@@ -17557,6 +18095,12 @@ function workflowWalletApprovalLines(walletApproval) {
17557
18095
  if (walletApproval.relay?.status_url) {
17558
18096
  lines.push(["status url", walletApproval.relay.status_url]);
17559
18097
  }
18098
+ if (relayAliases.walletApprovalRelayShareLinkBaseUrl) {
18099
+ lines.push(["share-link base", relayAliases.walletApprovalRelayShareLinkBaseUrl]);
18100
+ }
18101
+ if (relayAliases.walletApprovalRelayStatusApiBaseUrl) {
18102
+ lines.push(["status api base", relayAliases.walletApprovalRelayStatusApiBaseUrl]);
18103
+ }
17560
18104
  if (walletApproval.stage === "request-created" && walletApproval.recommendedCommands) {
17561
18105
  lines.push(["next local", walletApproval.recommendedCommands.awaitLocal]);
17562
18106
  lines.push(["next remote", walletApproval.recommendedCommands.approve]);
@@ -17616,6 +18160,7 @@ async function printWorkflowRunCommandResult(execution) {
17616
18160
  result: execution.result,
17617
18161
  walletRequestId: execution.walletApproval?.request.requestId,
17618
18162
  walletApprovalRelay: execution.walletApproval?.relay,
18163
+ ...workflowWalletApprovalRelayAliases(execution.walletApproval?.relay),
17619
18164
  walletApprovalRecommendedCommands: execution.walletApproval?.recommendedCommands,
17620
18165
  walletApproval: serializeWalletApproval(execution.walletApproval),
17621
18166
  recommendedCommands: recommendedCommands2
@@ -17657,6 +18202,7 @@ async function printWorkflowRunCommandResult(execution) {
17657
18202
  checkpoint: execution.checkpoint,
17658
18203
  walletRequestId: execution.walletApproval?.request.requestId,
17659
18204
  walletApprovalRelay: execution.walletApproval?.relay,
18205
+ ...workflowWalletApprovalRelayAliases(execution.walletApproval?.relay),
17660
18206
  walletApprovalRecommendedCommands: execution.walletApproval?.recommendedCommands,
17661
18207
  walletApproval: serializeWalletApproval(execution.walletApproval),
17662
18208
  recommendedCommands
@@ -17806,6 +18352,17 @@ function buildWorkflowAutoCheckpoint(context, status) {
17806
18352
  status
17807
18353
  });
17808
18354
  }
18355
+ function applyWorkflowPayDefaults(options) {
18356
+ return {
18357
+ ...options,
18358
+ intent: "send-native",
18359
+ createCheckpoint: true,
18360
+ executeWhenReady: true,
18361
+ ensureWalletSession: true,
18362
+ sessionPreset: options.sessionPreset?.trim() || "intent",
18363
+ paymasterMode: options.paymasterMode?.trim() || "approval-based"
18364
+ };
18365
+ }
17809
18366
  async function executeWorkflowAutoCommand(options, deps = resolveWorkflowCommandDeps(void 0)) {
17810
18367
  const { provider: provider4, defiProvider: defiProvider2 } = deps;
17811
18368
  const context = await resolveWorkflowAutoExecutionContext(options);
@@ -17946,6 +18503,7 @@ async function printWorkflowAutoCommandResult(execution) {
17946
18503
  checkpoint: execution.checkpoint,
17947
18504
  walletRequestId: execution.walletApproval?.request.requestId,
17948
18505
  walletApprovalRelay: execution.walletApproval?.relay,
18506
+ ...workflowWalletApprovalRelayAliases(execution.walletApproval?.relay),
17949
18507
  walletApprovalRecommendedCommands: execution.walletApproval?.recommendedCommands,
17950
18508
  walletApproval: serializeWalletApproval(execution.walletApproval),
17951
18509
  recommendedCommands
@@ -18076,11 +18634,29 @@ function serializeWalletApproval(walletApproval) {
18076
18634
  if (!walletApproval) return void 0;
18077
18635
  return {
18078
18636
  ...walletApproval,
18637
+ ...workflowWalletApprovalRelayAliases(walletApproval.relay, {
18638
+ shareLinkBaseField: "relayShareLinkBaseUrl",
18639
+ statusApiBaseField: "relayStatusApiBaseUrl"
18640
+ }),
18079
18641
  walletRequestId: walletApproval.request.requestId,
18080
18642
  request: sanitizeWalletRequestRecord(walletApproval.request),
18081
18643
  wallet: walletApproval.wallet ? sanitizeWalletRecord(walletApproval.wallet) : void 0
18082
18644
  };
18083
18645
  }
18646
+ function workflowWalletApprovalRelayAliases(relay, fieldNames = {
18647
+ shareLinkBaseField: "walletApprovalRelayShareLinkBaseUrl",
18648
+ statusApiBaseField: "walletApprovalRelayStatusApiBaseUrl"
18649
+ }) {
18650
+ const shareUrl = relay?.share_url;
18651
+ const statusUrl = relay?.status_url;
18652
+ const approvalUrl = relay?.approval_url;
18653
+ const shareLinkBaseUrl = shareUrl ? shareUrl.replace(/\/[^/]+$/, "") : approvalUrl ? approvalUrl.replace(/\/[^/]+$/, "") : void 0;
18654
+ const statusApiBaseUrl = statusUrl ? statusUrl.replace(/\/[^/]+$/, "") : approvalUrl && relay?.request_id ? `${approvalUrl.replace(/\/r\/[^/]+$/, "")}/api/requests` : void 0;
18655
+ return {
18656
+ [fieldNames.shareLinkBaseField]: shareLinkBaseUrl,
18657
+ [fieldNames.statusApiBaseField]: statusApiBaseUrl
18658
+ };
18659
+ }
18084
18660
  function addWorkflowGoalOptions(command, config = {}) {
18085
18661
  if (config.includeExecutionFlags) {
18086
18662
  command.option("--broadcast", "Broadcast the underlying transaction(s) instead of returning a preview", false).option(
@@ -18360,6 +18936,9 @@ function buildWorkflowHelpText() {
18360
18936
  " Guided default:",
18361
18937
  " zk-agent workflow auto --wallet main --intent <intent> [goal flags] --create-checkpoint --execute-when-ready",
18362
18938
  "",
18939
+ " Flagship native pay path:",
18940
+ " zk-agent workflow pay --wallet main --to <address> --amount <amount>",
18941
+ "",
18363
18942
  " Checkpointed execution:",
18364
18943
  " zk-agent workflow start --wallet main --intent <intent> [goal flags]",
18365
18944
  " zk-agent workflow status --request-id <id>",
@@ -18375,6 +18954,7 @@ function buildWorkflowHelpText() {
18375
18954
  }
18376
18955
  var WORKFLOW_HELP_COMMAND_ORDER = [
18377
18956
  "auto",
18957
+ "pay",
18378
18958
  "start",
18379
18959
  "status",
18380
18960
  "next",
@@ -18598,6 +19178,23 @@ function createWorkflowCommand(deps) {
18598
19178
  const execution = await executeWorkflowAutoCommand(options, resolvedDeps);
18599
19179
  await printWorkflowAutoCommandResult(execution);
18600
19180
  }));
19181
+ const pay = workflow.command("pay").description(
19182
+ "Guided flagship AA native-send path with checkpoint persistence, intent-scoped session recovery, and paymaster-aware defaults"
19183
+ ).option("--wallet <name>", "Wallet name", "main").option(
19184
+ "--request-id <id>",
19185
+ "Load the workflow definition from a stored checkpoint, or reserve this id for the flagship pay path"
19186
+ );
19187
+ addWorkflowGoalOptions(pay, {
19188
+ includeExecutionFlags: true,
19189
+ includeFundingDispatch: true,
19190
+ includeLocalApproval: true
19191
+ }).action(withWorkflowInputErrorHandling(async (options) => {
19192
+ const execution = await executeWorkflowAutoCommand(
19193
+ applyWorkflowPayDefaults(options),
19194
+ resolvedDeps
19195
+ );
19196
+ await printWorkflowAutoCommandResult(execution);
19197
+ }));
18601
19198
  const run = workflow.command("run").description("Run the requested workflow, or stop on the next required prerequisite or funding step first").option(
18602
19199
  "--intent <intent>",
18603
19200
  "send-native, send-token, call-write, swap, bridge, deposit, or withdraw"
@@ -18653,6 +19250,7 @@ function createWorkflowCommand(deps) {
18653
19250
  checkpoint: inspection.checkpoint,
18654
19251
  walletRequestId: inspection.walletApproval?.request.requestId,
18655
19252
  walletApprovalRelay: inspection.walletApproval?.relay,
19253
+ ...workflowWalletApprovalRelayAliases(inspection.walletApproval?.relay),
18656
19254
  walletApprovalRecommendedCommands: inspection.walletApproval?.recommendedCommands,
18657
19255
  walletApproval: serializeWalletApproval(inspection.walletApproval),
18658
19256
  recommendedCommands
@@ -18716,6 +19314,7 @@ function createWorkflowCommand(deps) {
18716
19314
  checkpoint: inspection.checkpoint,
18717
19315
  walletRequestId: inspection.walletApproval?.request.requestId,
18718
19316
  walletApprovalRelay: inspection.walletApproval?.relay,
19317
+ ...workflowWalletApprovalRelayAliases(inspection.walletApproval?.relay),
18719
19318
  walletApprovalRecommendedCommands: inspection.walletApproval?.recommendedCommands,
18720
19319
  walletApproval: serializeWalletApproval(inspection.walletApproval),
18721
19320
  recommendedCommands
@@ -18767,6 +19366,7 @@ function createWorkflowCommand(deps) {
18767
19366
  checkpoint: inspection.checkpoint,
18768
19367
  walletRequestId: inspection.walletApproval.request.requestId,
18769
19368
  walletApprovalRelay: inspection.walletApproval.relay,
19369
+ ...workflowWalletApprovalRelayAliases(inspection.walletApproval.relay),
18770
19370
  walletApprovalRecommendedCommands: inspection.walletApproval.recommendedCommands,
18771
19371
  walletApproval: serializeWalletApproval(inspection.walletApproval),
18772
19372
  recommendedCommands: recommendedCommands2
@@ -18817,6 +19417,7 @@ function createWorkflowCommand(deps) {
18817
19417
  checkpoint: execution.checkpoint,
18818
19418
  walletRequestId: execution.walletApproval?.request.requestId,
18819
19419
  walletApprovalRelay: execution.walletApproval?.relay,
19420
+ ...workflowWalletApprovalRelayAliases(execution.walletApproval?.relay),
18820
19421
  walletApprovalRecommendedCommands: execution.walletApproval?.recommendedCommands,
18821
19422
  walletApproval: serializeWalletApproval(execution.walletApproval),
18822
19423
  recommendedCommands: recommendedCommands2
@@ -18858,6 +19459,7 @@ function createWorkflowCommand(deps) {
18858
19459
  result: execution.result,
18859
19460
  walletRequestId: inspection.walletApproval?.request.requestId,
18860
19461
  walletApprovalRelay: inspection.walletApproval?.relay,
19462
+ ...workflowWalletApprovalRelayAliases(inspection.walletApproval?.relay),
18861
19463
  walletApprovalRecommendedCommands: inspection.walletApproval?.recommendedCommands,
18862
19464
  walletApproval: serializeWalletApproval(inspection.walletApproval),
18863
19465
  recommendedCommands
@@ -18895,14 +19497,15 @@ function createWorkflowCommand(deps) {
18895
19497
  function buildDefaultOperatorPathHelpText() {
18896
19498
  return [
18897
19499
  "",
18898
- "Default operator path:",
19500
+ "Default local-first operator path:",
18899
19501
  " zk-agent setup",
18900
19502
  " zk-agent next",
18901
19503
  " zk-agent wallet create --await-local",
18902
19504
  " zk-agent next",
18903
- ` ${buildWorkflowAutoRecommendedCommand("main")}`,
19505
+ ` ${buildWorkflowPayRecommendedCommand("main")}`,
18904
19506
  "",
18905
19507
  "Use `zk-agent next --request-id <id>` to continue a stored workflow checkpoint.",
19508
+ "Use `zk-agent relay inspect --relay-url <url>` and `zk-agent wallet --help` for the hosted remote-approval path.",
18906
19509
  "Use `zk-agent wallet --help` for bootstrap/reapproval details and `zk-agent workflow --help` once the intent is known."
18907
19510
  ].join("\n");
18908
19511
  }