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 =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
|
2233
|
-
reason: "
|
|
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
|
|
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
|
|
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 || !
|
|
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
|
|
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
|
|
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 (!
|
|
4399
|
+
if (!isHexPrivateKey2(privateKey)) {
|
|
4181
4400
|
throw new AgentError(
|
|
4182
4401
|
"WRITABLE_SESSION_INVALID",
|
|
4183
|
-
"Stored
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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 || !
|
|
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
|
|
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
|
|
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
|
|
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 (!
|
|
7012
|
-
blockers.push("Stored
|
|
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
|
|
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
|
|
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
|
|
7054
|
-
if (!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
|
|
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
|
|
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
|
|
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
|
|
7163
|
+
const privateKey = resolveLocalExecutionPrivateKey(wallet);
|
|
7100
7164
|
if (!privateKey) {
|
|
7101
7165
|
throw new Error(
|
|
7102
|
-
"Writable session requires
|
|
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 (!
|
|
7106
|
-
throw new Error("Stored
|
|
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
|
|
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
|
|
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 ||
|
|
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",
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
11828
|
-
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
|
|
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
|
|
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 =
|
|
12068
|
+
const currentDir2 = path7.dirname(currentFile);
|
|
11842
12069
|
const candidates = [
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
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(
|
|
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(
|
|
11865
|
-
return publicOrigin?.trim() ? normalizeRelayBaseUrl(new URL(publicOrigin.trim()).toString()) : normalizeRelayBaseUrl(
|
|
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(
|
|
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(
|
|
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
|
|
11901
|
-
|
|
11902
|
-
|
|
11903
|
-
|
|
11904
|
-
|
|
11905
|
-
|
|
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
|
|
12141
|
+
return response.json;
|
|
11911
12142
|
}
|
|
11912
12143
|
async function fetchRelayStatus(baseUrl, requestId) {
|
|
11913
|
-
const response = await
|
|
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
|
|
12148
|
+
return response.json;
|
|
11918
12149
|
}
|
|
11919
12150
|
async function fetchRelayHealth(baseUrl) {
|
|
11920
|
-
const response = await
|
|
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
|
|
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
|
|
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
|
|
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 || "/",
|
|
12192
|
+
const requestUrl = new URL(request.url || "/", "http://localhost");
|
|
11960
12193
|
const pathname = requestUrl.pathname;
|
|
11961
12194
|
const method = request.method || "GET";
|
|
11962
|
-
const
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
12284
|
+
writeBody(response, 200, contentTypeFor(filePath), fs7.readFileSync(filePath));
|
|
12048
12285
|
return;
|
|
12049
12286
|
}
|
|
12050
|
-
const indexPath =
|
|
12287
|
+
const indexPath = path7.join(uiDistRoot, "index.html");
|
|
12051
12288
|
if (fs7.existsSync(indexPath)) {
|
|
12052
|
-
|
|
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:
|
|
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
|
|
12169
|
-
|
|
12170
|
-
|
|
12171
|
-
|
|
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
|
-
}
|
|
12316
|
-
|
|
12317
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
12681
|
+
path8.join(moduleRoot, "dist", "builtin-account-profiles"),
|
|
12351
12682
|
moduleRoot,
|
|
12352
|
-
|
|
12353
|
-
|
|
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 =
|
|
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 ?
|
|
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 ?
|
|
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:
|
|
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
|
|
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
|
|
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 =
|
|
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([
|
|
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
|
-
|
|
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
|
-
...!
|
|
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",
|
|
14055
|
-
...recommendedCommands.
|
|
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
|
-
"
|
|
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
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
|
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"
|
|
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
|
-
` ${
|
|
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
|
}
|