@tokamak-private-dapps/private-state-cli 0.1.8 → 0.1.9
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/CHANGELOG.md +5 -0
- package/README.md +24 -3
- package/cli-assistant.html +7 -2
- package/package.json +4 -4
- package/private-state-bridge-cli.mjs +364 -31
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.9 - 2026-05-03
|
|
4
|
+
|
|
5
|
+
- Used the bundled Groth16 package version as the default `private-state-cli --install` Groth16 runtime version.
|
|
6
|
+
- Treated stale local Groth16 CRS metadata without `compatibleBackendVersion` as a cache miss so the matching public CRS can be reinstalled.
|
|
7
|
+
|
|
3
8
|
## 0.1.8 - 2026-04-30
|
|
4
9
|
|
|
5
10
|
- Reused common proof backend version helpers for Tokamak and Groth16 compatibility checks.
|
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Command-line client for the Tokamak private-state DApp.
|
|
4
4
|
|
|
5
|
+
The full private-state DApp documentation is published with the repository:
|
|
6
|
+
|
|
7
|
+
- https://github.com/tokamak-network/Tokamak-zk-EVM-contracts/tree/main/packages/apps/private-state/docs
|
|
8
|
+
|
|
5
9
|
## Install
|
|
6
10
|
|
|
7
11
|
```bash
|
|
@@ -15,11 +19,12 @@ artifacts:
|
|
|
15
19
|
private-state-cli --install
|
|
16
20
|
```
|
|
17
21
|
|
|
18
|
-
By default, `--install` resolves the latest `@tokamak-zk-evm/cli` and
|
|
19
|
-
|
|
22
|
+
By default, `--install` resolves the latest `@tokamak-zk-evm/cli` from the npm registry and uses the bundled
|
|
23
|
+
`@tokamak-private-dapps/groth16` dependency version selected by the installed private-state CLI package. To pin exact
|
|
24
|
+
proof backend versions for a channel, pass explicit versions:
|
|
20
25
|
|
|
21
26
|
```bash
|
|
22
|
-
private-state-cli --install --tokamak-zk-evm-cli-version 2.0
|
|
27
|
+
private-state-cli --install --tokamak-zk-evm-cli-version 2.1.0 --groth16-cli-version 0.2.0
|
|
23
28
|
```
|
|
24
29
|
|
|
25
30
|
The Groth16 installer downloads the public Google Drive CRS archive whose major.minor compatibility version matches the
|
|
@@ -63,6 +68,19 @@ A common private-state flow is:
|
|
|
63
68
|
|
|
64
69
|
Use `private-state-cli --help` for the full command list and required options.
|
|
65
70
|
|
|
71
|
+
Channel policy warning:
|
|
72
|
+
|
|
73
|
+
- `create-channel` commits to an immutable channel policy: verifier bindings, DApp execution metadata, function layout,
|
|
74
|
+
managed storage vector, and refund policy are fixed for that channel.
|
|
75
|
+
- `join-channel` means the user accepts the channel's current policy. Later policy-level fixes require a new channel or
|
|
76
|
+
migration; the existing channel is intentionally not mutated in place without renewed user consent.
|
|
77
|
+
- Before sending a channel-creation transaction or a first channel-registration transaction, the CLI prints the policy
|
|
78
|
+
snapshot that will be accepted: DApp metadata digest, digest schema, Groth16 verifier address, Groth16 compatible
|
|
79
|
+
backend version, Tokamak verifier address, and Tokamak compatible backend version.
|
|
80
|
+
- Users and operators must review this snapshot before signing. If any digest, schema, verifier address, or compatible
|
|
81
|
+
backend version is unexpected or has not been reviewed, do not create or join the channel. A later correction creates
|
|
82
|
+
a new channel; it does not rewrite the policy of an already-created channel.
|
|
83
|
+
|
|
66
84
|
`private-state-cli --doctor` reports the CLI package version, dependency versions recorded by the last
|
|
67
85
|
`private-state-cli --install`, selected proof backend runtime versions, current dependency versions through `tokamak-l2js`, and Tokamak zk-EVM runtime
|
|
68
86
|
install mode, Docker mode, CUDA runtime metadata, live `nvidia-smi` and Docker GPU probe results, and Groth16
|
|
@@ -117,6 +135,9 @@ Operating rules:
|
|
|
117
135
|
telling the user to move funds.
|
|
118
136
|
- Explain that wallet names are local CLI identifiers, while private transfers use notes owned by L2 addresses
|
|
119
137
|
registered in the channel.
|
|
138
|
+
- Before guiding a user through `create-channel` or `join-channel`, explain that channel policy is immutable after
|
|
139
|
+
creation and that joining a channel means accepting its current verifier, DApp metadata, function layout, managed
|
|
140
|
+
storage vector, and refund policy.
|
|
120
141
|
- Do not present one fixed command sequence as universally correct. Some flows start from an existing channel or wallet,
|
|
121
142
|
while others require creating or joining a channel first.
|
|
122
143
|
- When the user asks for a transfer, first determine whether the sender has minted notes available. If not, guide them
|
package/cli-assistant.html
CHANGED
|
@@ -400,6 +400,11 @@
|
|
|
400
400
|
case, you lose ownership of all notes because you can no longer use them, and that ownership cannot be
|
|
401
401
|
recovered.
|
|
402
402
|
</li>
|
|
403
|
+
<li>
|
|
404
|
+
Channel policy is immutable after creation. Joining a channel means accepting its verifier bindings,
|
|
405
|
+
DApp metadata, function layout, managed storage vector, and refund policy; later policy-level fixes
|
|
406
|
+
require a new channel or migration.
|
|
407
|
+
</li>
|
|
403
408
|
</ul>
|
|
404
409
|
</div>
|
|
405
410
|
</section>
|
|
@@ -521,12 +526,12 @@
|
|
|
521
526
|
const commands = [
|
|
522
527
|
{ id: "install-zk-evm", description: "Install the local Tokamak zk-EVM toolchain. Optionally forward --docker for Linux-host Docker installs.", fields: ["docker"] },
|
|
523
528
|
{ id: "uninstall-zk-evm", description: "Remove the Tokamak zk-EVM CLI runtime workspace.", fields: [] },
|
|
524
|
-
{ id: "create-channel", description: "Create
|
|
529
|
+
{ id: "create-channel", description: "Create a channel with an immutable operating policy and initialize its workspace.", fields: ["channelName", "network", "privateKey", "alchemyApiKey"] },
|
|
525
530
|
{ id: "recover-workspace", description: "Rebuild the saved channel workspace from bridge state.", fields: ["channelName", "network", "alchemyApiKey"] },
|
|
526
531
|
{ id: "deposit-bridge", description: "Deposit canonical tokens into the shared bridge vault.", fields: ["amount", "network", "privateKey", "alchemyApiKey"] },
|
|
527
532
|
{ id: "withdraw-bridge", description: "Withdraw shared bridge-vault funds back to the L1 wallet.", fields: ["amount", "network", "privateKey", "alchemyApiKey"] },
|
|
528
533
|
{ id: "get-my-bridge-fund", description: "Read the current bridge-vault balance.", fields: ["network", "privateKey", "alchemyApiKey"] },
|
|
529
|
-
{ id: "join-channel", description: "
|
|
534
|
+
{ id: "join-channel", description: "Accept the channel policy and bind the caller to a channel-specific L2 identity.", fields: ["channelName", "password", "network", "privateKey", "alchemyApiKey"] },
|
|
530
535
|
{ id: "recover-wallet", description: "Rebuild the recoverable portion of a wallet.", fields: ["channelName", "password", "network", "privateKey", "alchemyApiKey"] },
|
|
531
536
|
{ id: "get-my-wallet-meta", description: "Check whether a saved wallet matches on-chain registration.", fields: ["wallet", "password", "network"] },
|
|
532
537
|
{ id: "get-my-l1-address", description: "Derive the L1 address for a private key.", fields: ["privateKey"] },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokamak-private-dapps/private-state-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Command-line client for the Tokamak private-state DApp.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"author": "Tokamak Network",
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
"@ethereumjs/util": "^10.1.1",
|
|
44
44
|
"@noble/curves": "1.9.7",
|
|
45
45
|
"@tokamak-private-dapps/common-library": "^0.1.1",
|
|
46
|
-
"@tokamak-private-dapps/groth16": "^0.
|
|
47
|
-
"@tokamak-zk-evm/cli": "^2.0
|
|
46
|
+
"@tokamak-private-dapps/groth16": "^0.2.0",
|
|
47
|
+
"@tokamak-zk-evm/cli": "^2.1.0",
|
|
48
48
|
"ethers": "^6.14.1",
|
|
49
|
-
"tokamak-l2js": "^0.1.
|
|
49
|
+
"tokamak-l2js": "^0.1.4"
|
|
50
50
|
},
|
|
51
51
|
"engines": {
|
|
52
52
|
"node": ">=18"
|
|
@@ -166,6 +166,68 @@ const DEFAULT_LOG_REQUESTS_PER_SECOND = 5;
|
|
|
166
166
|
const LOG_REQUEST_INTERVAL_MS = Math.ceil(1000 / DEFAULT_LOG_REQUESTS_PER_SECOND);
|
|
167
167
|
let lastLogRequestStartedAtMs = 0;
|
|
168
168
|
|
|
169
|
+
function printImmutableChannelPolicyWarning({
|
|
170
|
+
action,
|
|
171
|
+
channelName,
|
|
172
|
+
channelId,
|
|
173
|
+
channelManager = null,
|
|
174
|
+
policySnapshot = null,
|
|
175
|
+
}) {
|
|
176
|
+
const details = [
|
|
177
|
+
`WARNING: ${action} commits to an immutable channel policy.`,
|
|
178
|
+
`Channel: ${channelName} (${channelId.toString()})`,
|
|
179
|
+
];
|
|
180
|
+
if (channelManager) {
|
|
181
|
+
details.push(`ChannelManager: ${channelManager}`);
|
|
182
|
+
}
|
|
183
|
+
details.push(
|
|
184
|
+
"The channel verifier bindings, DApp execution metadata, function layout, managed storage vector, and refund policy are fixed for this channel.",
|
|
185
|
+
"Those policy fields are intentionally not upgraded in place without channel-user consent.",
|
|
186
|
+
"If a policy bug is discovered later, the expected mitigation is creating or joining a new channel, not mutating this channel.",
|
|
187
|
+
"Review the DApp digest, digest schema, verifier addresses, and compatible backend versions before signing.",
|
|
188
|
+
);
|
|
189
|
+
if (policySnapshot) {
|
|
190
|
+
details.push(
|
|
191
|
+
"Channel policy snapshot:",
|
|
192
|
+
` DApp id: ${policySnapshot.dappId}`,
|
|
193
|
+
` DApp metadata digest schema: ${policySnapshot.dappMetadataDigestSchema}`,
|
|
194
|
+
` DApp metadata digest: ${policySnapshot.dappMetadataDigest}`,
|
|
195
|
+
` DApp function root: ${policySnapshot.functionRoot}`,
|
|
196
|
+
` Groth16 verifier: ${policySnapshot.grothVerifier}`,
|
|
197
|
+
` Groth16 compatible backend version: ${policySnapshot.grothVerifierCompatibleBackendVersion}`,
|
|
198
|
+
` Tokamak verifier: ${policySnapshot.tokamakVerifier}`,
|
|
199
|
+
` Tokamak compatible backend version: ${policySnapshot.tokamakVerifierCompatibleBackendVersion}`,
|
|
200
|
+
"Do not sign if any snapshot value is unexpected or has not been reviewed.",
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
console.error(details.join("\n"));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function normalizeDAppPolicySnapshot({
|
|
207
|
+
dappId,
|
|
208
|
+
metadataDigest,
|
|
209
|
+
metadataDigestSchema,
|
|
210
|
+
functionRoot,
|
|
211
|
+
verifierSnapshot,
|
|
212
|
+
}) {
|
|
213
|
+
return {
|
|
214
|
+
dappId: Number(dappId),
|
|
215
|
+
dappMetadataDigestSchema: normalizeBytes32Hex(metadataDigestSchema),
|
|
216
|
+
dappMetadataDigest: normalizeBytes32Hex(metadataDigest),
|
|
217
|
+
functionRoot: normalizeBytes32Hex(functionRoot),
|
|
218
|
+
grothVerifier: getAddress(verifierSnapshot.grothVerifier),
|
|
219
|
+
grothVerifierCompatibleBackendVersion: requireVersionString(
|
|
220
|
+
verifierSnapshot.grothVerifierCompatibleBackendVersion,
|
|
221
|
+
"registered DApp Groth16 verifier compatibleBackendVersion",
|
|
222
|
+
),
|
|
223
|
+
tokamakVerifier: getAddress(verifierSnapshot.tokamakVerifier),
|
|
224
|
+
tokamakVerifierCompatibleBackendVersion: requireVersionString(
|
|
225
|
+
verifierSnapshot.tokamakVerifierCompatibleBackendVersion,
|
|
226
|
+
"registered DApp Tokamak verifier compatibleBackendVersion",
|
|
227
|
+
),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
169
231
|
async function prepareDeploymentArtifacts(chainId) {
|
|
170
232
|
const normalizedChainId = Number(chainId);
|
|
171
233
|
const existingPaths = flatDeploymentArtifactPathsByChainId.get(normalizedChainId);
|
|
@@ -373,16 +435,25 @@ async function handleChannelCreate({ args, network, provider }) {
|
|
|
373
435
|
);
|
|
374
436
|
const canonicalAsset = getAddress(await bridgeCore.canonicalAsset());
|
|
375
437
|
const canonicalAssetDecimals = await fetchTokenDecimals(provider, canonicalAsset);
|
|
376
|
-
const
|
|
377
|
-
const
|
|
438
|
+
const joinTollInput = requireArg(args.joinToll, "--join-toll");
|
|
439
|
+
const joinToll = parseTokenAmount(joinTollInput, canonicalAssetDecimals);
|
|
378
440
|
const channelId = deriveChannelIdFromName(channelName);
|
|
379
|
-
const
|
|
441
|
+
const dapp = await resolveDAppIdByLabel({
|
|
380
442
|
provider,
|
|
381
443
|
bridgeResources,
|
|
382
444
|
dappLabel: PRIVATE_STATE_DAPP_LABEL,
|
|
383
445
|
});
|
|
446
|
+
const dappId = dapp.dappId;
|
|
447
|
+
const policySnapshot = dapp.policySnapshot;
|
|
384
448
|
|
|
385
|
-
|
|
449
|
+
printImmutableChannelPolicyWarning({
|
|
450
|
+
action: "create-channel",
|
|
451
|
+
channelName,
|
|
452
|
+
channelId,
|
|
453
|
+
policySnapshot,
|
|
454
|
+
});
|
|
455
|
+
const receipt =
|
|
456
|
+
await waitForReceipt(await bridgeCore.createChannel(channelId, dappId, joinToll, dapp.metadataDigest));
|
|
386
457
|
const channelInfo = await bridgeCore.getChannel(channelId);
|
|
387
458
|
|
|
388
459
|
const workspaceResult = await initializeChannelWorkspace({
|
|
@@ -399,9 +470,12 @@ async function handleChannelCreate({ args, network, provider }) {
|
|
|
399
470
|
channelName,
|
|
400
471
|
channelId: channelId.toString(),
|
|
401
472
|
dappId,
|
|
473
|
+
dappMetadataDigest: dapp.metadataDigest,
|
|
474
|
+
dappMetadataDigestSchema: dapp.metadataDigestSchema,
|
|
475
|
+
policySnapshot,
|
|
402
476
|
leader,
|
|
403
|
-
|
|
404
|
-
|
|
477
|
+
joinTollBaseUnits: joinToll.toString(),
|
|
478
|
+
joinTollTokens: ethers.formatUnits(joinToll, canonicalAssetDecimals),
|
|
405
479
|
canonicalAsset,
|
|
406
480
|
canonicalAssetDecimals,
|
|
407
481
|
asset: channelInfo.asset,
|
|
@@ -426,9 +500,21 @@ async function resolveDAppIdByLabel({ provider, bridgeResources, dappLabel }) {
|
|
|
426
500
|
const manifestLabel = typeof manifest.dappLabel === "string" ? manifest.dappLabel : null;
|
|
427
501
|
const manifestDappId = manifest.dappId;
|
|
428
502
|
const manifestManager = typeof manifest.dAppManager === "string" ? getAddress(manifest.dAppManager) : null;
|
|
503
|
+
const manifestMetadataDigest = normalizeBytes32Hex(manifest.registration?.metadataDigest);
|
|
504
|
+
const manifestMetadataDigestSchema = normalizeBytes32Hex(manifest.registration?.metadataDigestSchema);
|
|
505
|
+
const manifestFunctionRoot = normalizeBytes32Hex(manifest.registration?.functionRoot);
|
|
429
506
|
|
|
430
507
|
expect(manifestLabel === dappLabel, `DApp registration manifest label mismatch in ${manifestPath}.`);
|
|
431
508
|
expect(Number.isInteger(manifestDappId), `DApp registration manifest is missing an integer dappId: ${manifestPath}.`);
|
|
509
|
+
expect(manifestMetadataDigest !== null, `DApp registration manifest is missing registration.metadataDigest: ${manifestPath}.`);
|
|
510
|
+
expect(
|
|
511
|
+
manifestMetadataDigestSchema !== null,
|
|
512
|
+
`DApp registration manifest is missing registration.metadataDigestSchema: ${manifestPath}.`,
|
|
513
|
+
);
|
|
514
|
+
expect(
|
|
515
|
+
manifestFunctionRoot !== null,
|
|
516
|
+
`DApp registration manifest is missing registration.functionRoot: ${manifestPath}.`,
|
|
517
|
+
);
|
|
432
518
|
expect(
|
|
433
519
|
manifestManager !== null
|
|
434
520
|
&& ethers.toBigInt(manifestManager) === ethers.toBigInt(getAddress(bridgeResources.bridgeDeployment.dAppManager)),
|
|
@@ -441,7 +527,36 @@ async function resolveDAppIdByLabel({ provider, bridgeResources, dappLabel }) {
|
|
|
441
527
|
ethers.toBigInt(normalizeBytes32Hex(info.labelHash)) === ethers.toBigInt(expectedLabelHash),
|
|
442
528
|
`DApp id ${manifestDappId} from ${manifestPath} does not match label ${dappLabel} on-chain.`,
|
|
443
529
|
);
|
|
444
|
-
|
|
530
|
+
const onchainMetadataDigest = normalizeBytes32Hex(info.metadataDigest);
|
|
531
|
+
const onchainMetadataDigestSchema = normalizeBytes32Hex(info.metadataDigestSchema);
|
|
532
|
+
const onchainFunctionRoot = normalizeBytes32Hex(info.functionRoot);
|
|
533
|
+
const verifierSnapshot = await dAppManager.getDAppVerifierSnapshot(manifestDappId);
|
|
534
|
+
const policySnapshot = normalizeDAppPolicySnapshot({
|
|
535
|
+
dappId: manifestDappId,
|
|
536
|
+
metadataDigest: onchainMetadataDigest,
|
|
537
|
+
metadataDigestSchema: onchainMetadataDigestSchema,
|
|
538
|
+
functionRoot: onchainFunctionRoot,
|
|
539
|
+
verifierSnapshot,
|
|
540
|
+
});
|
|
541
|
+
expect(
|
|
542
|
+
ethers.toBigInt(onchainMetadataDigest) === ethers.toBigInt(manifestMetadataDigest),
|
|
543
|
+
`DApp id ${manifestDappId} metadata digest ${onchainMetadataDigest} does not match ${manifestMetadataDigest} from ${manifestPath}.`,
|
|
544
|
+
);
|
|
545
|
+
expect(
|
|
546
|
+
ethers.toBigInt(onchainMetadataDigestSchema) === ethers.toBigInt(manifestMetadataDigestSchema),
|
|
547
|
+
`DApp id ${manifestDappId} metadata digest schema ${onchainMetadataDigestSchema} does not match ${manifestMetadataDigestSchema} from ${manifestPath}.`,
|
|
548
|
+
);
|
|
549
|
+
expect(
|
|
550
|
+
ethers.toBigInt(onchainFunctionRoot) === ethers.toBigInt(manifestFunctionRoot),
|
|
551
|
+
`DApp id ${manifestDappId} function root ${onchainFunctionRoot} does not match ${manifestFunctionRoot} from ${manifestPath}.`,
|
|
552
|
+
);
|
|
553
|
+
return {
|
|
554
|
+
dappId: Number(manifestDappId),
|
|
555
|
+
metadataDigest: onchainMetadataDigest,
|
|
556
|
+
metadataDigestSchema: onchainMetadataDigestSchema,
|
|
557
|
+
functionRoot: onchainFunctionRoot,
|
|
558
|
+
policySnapshot,
|
|
559
|
+
};
|
|
445
560
|
}
|
|
446
561
|
|
|
447
562
|
async function handleWorkspaceInit({ args, network, provider }) {
|
|
@@ -514,6 +629,10 @@ async function initializeChannelWorkspace({
|
|
|
514
629
|
const currentRootVectorHash = normalizeBytes32Hex(await channelManager.currentRootVectorHash());
|
|
515
630
|
const genesisBlockNumber = Number(await channelManager.genesisBlockNumber());
|
|
516
631
|
const managedStorageAddresses = normalizedAddressVector(await channelManager.getManagedStorageAddresses());
|
|
632
|
+
const policySnapshot = await readChannelPolicySnapshot({
|
|
633
|
+
channelManager,
|
|
634
|
+
dappId: Number(channelInfo.dappId),
|
|
635
|
+
});
|
|
517
636
|
const deploymentManifestPath = dappDeploymentManifestPath(network.chainId);
|
|
518
637
|
const storageLayoutManifestPath = dappStorageLayoutManifestPath(network.chainId);
|
|
519
638
|
const deploymentManifest = readJson(deploymentManifestPath);
|
|
@@ -580,6 +699,10 @@ async function initializeChannelWorkspace({
|
|
|
580
699
|
controller: controllerAddress,
|
|
581
700
|
l2AccountingVault: l2AccountingVaultAddress,
|
|
582
701
|
aPubBlockHash: normalizeBytes32Hex(channelInfo.aPubBlockHash),
|
|
702
|
+
dappMetadataDigestSchema: policySnapshot.dappMetadataDigestSchema,
|
|
703
|
+
dappMetadataDigest: policySnapshot.dappMetadataDigest,
|
|
704
|
+
functionRoot: policySnapshot.functionRoot,
|
|
705
|
+
policySnapshot,
|
|
583
706
|
managedStorageAddresses,
|
|
584
707
|
liquidBalancesSlot: liquidBalancesSlot.toString(),
|
|
585
708
|
};
|
|
@@ -1194,20 +1317,27 @@ async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
|
1194
1317
|
let resolvedLeafIndex = leafIndex;
|
|
1195
1318
|
let approveReceipt = null;
|
|
1196
1319
|
let receipt = null;
|
|
1197
|
-
let
|
|
1320
|
+
let joinToll = 0n;
|
|
1198
1321
|
let status = null;
|
|
1199
1322
|
|
|
1200
1323
|
if (!existingRegistration.exists) {
|
|
1201
|
-
|
|
1324
|
+
joinToll = ethers.toBigInt(await context.channelManager.joinToll());
|
|
1202
1325
|
const asset = new Contract(
|
|
1203
1326
|
context.workspace.canonicalAsset,
|
|
1204
1327
|
context.bridgeAbiManifest.contracts.erc20.abi,
|
|
1205
1328
|
signer,
|
|
1206
1329
|
);
|
|
1207
1330
|
let nextNonce = await provider.getTransactionCount(signer.address, "pending");
|
|
1208
|
-
|
|
1331
|
+
printImmutableChannelPolicyWarning({
|
|
1332
|
+
action: "join-channel",
|
|
1333
|
+
channelName: context.workspace.channelName,
|
|
1334
|
+
channelId: ethers.toBigInt(context.workspace.channelId),
|
|
1335
|
+
channelManager: context.workspace.channelManager,
|
|
1336
|
+
policySnapshot: context.workspace.policySnapshot,
|
|
1337
|
+
});
|
|
1338
|
+
if (joinToll !== 0n) {
|
|
1209
1339
|
approveReceipt = await waitForReceipt(
|
|
1210
|
-
await asset.approve(context.workspace.bridgeTokenVault,
|
|
1340
|
+
await asset.approve(context.workspace.bridgeTokenVault, joinToll, { nonce: nextNonce++ }),
|
|
1211
1341
|
);
|
|
1212
1342
|
}
|
|
1213
1343
|
receipt = await waitForReceipt(
|
|
@@ -1241,7 +1371,7 @@ async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
|
1241
1371
|
"The existing note-receive public key parity does not match the derived note-receive public key.",
|
|
1242
1372
|
);
|
|
1243
1373
|
resolvedLeafIndex = existingRegistration.leafIndex;
|
|
1244
|
-
|
|
1374
|
+
joinToll = ethers.toBigInt(existingRegistration.joinTollPaid);
|
|
1245
1375
|
status = "already-registered";
|
|
1246
1376
|
}
|
|
1247
1377
|
|
|
@@ -1267,9 +1397,10 @@ async function handleJoinChannel({ args, network, provider, rpcUrl }) {
|
|
|
1267
1397
|
l2Address: l2Identity.l2Address,
|
|
1268
1398
|
l2StorageKey: storageKey,
|
|
1269
1399
|
leafIndex: resolvedLeafIndex.toString(),
|
|
1270
|
-
|
|
1271
|
-
|
|
1400
|
+
joinTollBaseUnits: joinToll.toString(),
|
|
1401
|
+
joinTollTokens: ethers.formatUnits(joinToll, Number(context.workspace.canonicalAssetDecimals)),
|
|
1272
1402
|
noteReceivePubKey: noteReceiveKeyMaterial.noteReceivePubKey,
|
|
1403
|
+
policySnapshot: context.workspace.policySnapshot,
|
|
1273
1404
|
approveGasUsed: approveReceipt ? receiptGasUsed(approveReceipt) : null,
|
|
1274
1405
|
gasUsed: receipt ? receiptGasUsed(receipt) : null,
|
|
1275
1406
|
approveTxUrl: approveReceipt ? explorerTxUrl(network, approveReceipt.hash) : null,
|
|
@@ -1296,7 +1427,7 @@ async function handleExitChannel({ args, provider }) {
|
|
|
1296
1427
|
"Run withdraw-channel first, or rerun exit-channel with --force to bypass this CLI check.",
|
|
1297
1428
|
].join(" "),
|
|
1298
1429
|
);
|
|
1299
|
-
const [refundAmount, refundBps] = await context.channelManager.
|
|
1430
|
+
const [refundAmount, refundBps] = await context.channelManager.getExitTollRefundQuote(signer.address);
|
|
1300
1431
|
const receipt = await waitForReceipt(
|
|
1301
1432
|
await context.bridgeTokenVault.connect(signer).exitChannel(ethers.toBigInt(context.workspace.channelId)),
|
|
1302
1433
|
);
|
|
@@ -1402,8 +1533,9 @@ async function handleGrothVaultMove({ args, provider, direction }) {
|
|
|
1402
1533
|
nextValue,
|
|
1403
1534
|
});
|
|
1404
1535
|
|
|
1536
|
+
const methodName = direction === "deposit" ? "depositToChannelVault" : "withdrawFromChannelVault";
|
|
1405
1537
|
const receipt = await waitForReceipt(
|
|
1406
|
-
await bridgeTokenVault[
|
|
1538
|
+
await bridgeTokenVault[methodName](ethers.toBigInt(context.workspace.channelId), transition.proof, transition.update),
|
|
1407
1539
|
);
|
|
1408
1540
|
const onchainRootVectorHash = normalizeBytes32Hex(await context.channelManager.currentRootVectorHash());
|
|
1409
1541
|
expect(
|
|
@@ -1462,6 +1594,63 @@ async function handleWithdrawBridge({ args, network, provider }) {
|
|
|
1462
1594
|
});
|
|
1463
1595
|
}
|
|
1464
1596
|
|
|
1597
|
+
function resolveFunctionMetadataProofForExecution({
|
|
1598
|
+
chainId,
|
|
1599
|
+
controllerAddress,
|
|
1600
|
+
functionSelector,
|
|
1601
|
+
preprocessInputHash,
|
|
1602
|
+
expectedFunctionRoot,
|
|
1603
|
+
}) {
|
|
1604
|
+
const manifestPath = requireFlatDeploymentArtifactPathsForChainId(chainId).dappRegistrationPath;
|
|
1605
|
+
const manifest = readJson(manifestPath);
|
|
1606
|
+
const proofRoot = normalizeBytes32Hex(manifest.functionMetadataProofs?.root);
|
|
1607
|
+
const expectedRoot = normalizeBytes32Hex(expectedFunctionRoot);
|
|
1608
|
+
expect(
|
|
1609
|
+
ethers.toBigInt(proofRoot) === ethers.toBigInt(expectedRoot),
|
|
1610
|
+
`DApp function proof root ${proofRoot} does not match channel function root ${expectedRoot}.`,
|
|
1611
|
+
);
|
|
1612
|
+
|
|
1613
|
+
const functions = manifest.functionMetadataProofs?.functions;
|
|
1614
|
+
expect(Array.isArray(functions), `DApp registration manifest is missing functionMetadataProofs.functions: ${manifestPath}.`);
|
|
1615
|
+
const normalizedController = getAddress(controllerAddress);
|
|
1616
|
+
const normalizedSelector = normalizeBytesHex(functionSelector, 4);
|
|
1617
|
+
const normalizedPreprocessInputHash = normalizeBytes32Hex(preprocessInputHash);
|
|
1618
|
+
const entry = functions.find((candidate) => {
|
|
1619
|
+
const metadata = candidate?.metadata;
|
|
1620
|
+
return metadata
|
|
1621
|
+
&& getAddress(metadata.entryContract) === normalizedController
|
|
1622
|
+
&& normalizeBytesHex(metadata.functionSig, 4) === normalizedSelector
|
|
1623
|
+
&& normalizeBytes32Hex(metadata.preprocessInputHash) === normalizedPreprocessInputHash;
|
|
1624
|
+
});
|
|
1625
|
+
expect(
|
|
1626
|
+
entry !== undefined,
|
|
1627
|
+
[
|
|
1628
|
+
`No DApp function metadata proof found for ${normalizedController} ${normalizedSelector}.`,
|
|
1629
|
+
`Expected preprocess input hash: ${normalizedPreprocessInputHash}.`,
|
|
1630
|
+
`Manifest: ${manifestPath}.`,
|
|
1631
|
+
].join(" "),
|
|
1632
|
+
);
|
|
1633
|
+
|
|
1634
|
+
return {
|
|
1635
|
+
metadata: {
|
|
1636
|
+
entryContract: getAddress(entry.metadata.entryContract),
|
|
1637
|
+
functionSig: normalizeBytesHex(entry.metadata.functionSig, 4),
|
|
1638
|
+
preprocessInputHash: normalizeBytes32Hex(entry.metadata.preprocessInputHash),
|
|
1639
|
+
instanceLayout: {
|
|
1640
|
+
entryContractOffsetWords: Number(entry.metadata.instanceLayout.entryContractOffsetWords),
|
|
1641
|
+
functionSigOffsetWords: Number(entry.metadata.instanceLayout.functionSigOffsetWords),
|
|
1642
|
+
currentRootVectorOffsetWords: Number(entry.metadata.instanceLayout.currentRootVectorOffsetWords),
|
|
1643
|
+
updatedRootVectorOffsetWords: Number(entry.metadata.instanceLayout.updatedRootVectorOffsetWords),
|
|
1644
|
+
eventLogs: entry.metadata.instanceLayout.eventLogs.map((eventLog) => ({
|
|
1645
|
+
startOffsetWords: Number(eventLog.startOffsetWords),
|
|
1646
|
+
topicCount: Number(eventLog.topicCount),
|
|
1647
|
+
})),
|
|
1648
|
+
},
|
|
1649
|
+
},
|
|
1650
|
+
siblings: entry.merkleProof.map((sibling) => normalizeBytes32Hex(sibling)),
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1465
1654
|
async function handleMintNotes({ args, provider }) {
|
|
1466
1655
|
const { wallet } = loadUnlockedWalletWithMetadata(args);
|
|
1467
1656
|
const canonicalAssetDecimals = Number(wallet.wallet.canonicalAssetDecimals);
|
|
@@ -3118,6 +3307,16 @@ async function executeWalletTemplateSend({
|
|
|
3118
3307
|
writeJson(path.join(operationDir, "resource", "synthesizer", "output", "state_snapshot.normalized.json"), nextSnapshot);
|
|
3119
3308
|
|
|
3120
3309
|
const payload = loadTokamakPayloadFromStep(operationDir);
|
|
3310
|
+
const functionProof = resolveFunctionMetadataProofForExecution({
|
|
3311
|
+
chainId: context.workspace.chainId,
|
|
3312
|
+
controllerAddress: context.workspace.controller,
|
|
3313
|
+
functionSelector: calldata.slice(0, 10),
|
|
3314
|
+
preprocessInputHash: hashTokamakPointEncoding(
|
|
3315
|
+
payload.functionPreprocessPart1,
|
|
3316
|
+
payload.functionPreprocessPart2,
|
|
3317
|
+
),
|
|
3318
|
+
expectedFunctionRoot: context.workspace.functionRoot ?? context.workspace.policySnapshot?.functionRoot,
|
|
3319
|
+
});
|
|
3121
3320
|
const noteLifecycle = extractControllerStorageDelta({
|
|
3122
3321
|
previousSnapshot: context.currentSnapshot,
|
|
3123
3322
|
nextSnapshot,
|
|
@@ -3132,7 +3331,9 @@ async function executeWalletTemplateSend({
|
|
|
3132
3331
|
);
|
|
3133
3332
|
|
|
3134
3333
|
const receipt =
|
|
3135
|
-
await waitForReceipt(
|
|
3334
|
+
await waitForReceipt(
|
|
3335
|
+
await context.channelManager.connect(signer).executeChannelTransaction(payload, functionProof),
|
|
3336
|
+
);
|
|
3136
3337
|
|
|
3137
3338
|
const onchainRootVectorHash = normalizeBytes32Hex(await context.channelManager.currentRootVectorHash());
|
|
3138
3339
|
expect(
|
|
@@ -3517,6 +3718,53 @@ async function readChannelVerifierCompatibleBackendVersions(context) {
|
|
|
3517
3718
|
}
|
|
3518
3719
|
}
|
|
3519
3720
|
|
|
3721
|
+
async function readChannelPolicySnapshot({ channelManager, dappId }) {
|
|
3722
|
+
const channelManagerAddress = getAddress(await channelManager.getAddress());
|
|
3723
|
+
try {
|
|
3724
|
+
const [
|
|
3725
|
+
dappMetadataDigestSchema,
|
|
3726
|
+
dappMetadataDigest,
|
|
3727
|
+
functionRoot,
|
|
3728
|
+
grothVerifier,
|
|
3729
|
+
grothVerifierCompatibleBackendVersion,
|
|
3730
|
+
tokamakVerifier,
|
|
3731
|
+
tokamakVerifierCompatibleBackendVersion,
|
|
3732
|
+
] = await Promise.all([
|
|
3733
|
+
channelManager.dappMetadataDigestSchema(),
|
|
3734
|
+
channelManager.dappMetadataDigest(),
|
|
3735
|
+
channelManager.functionRoot(),
|
|
3736
|
+
channelManager.grothVerifier(),
|
|
3737
|
+
channelManager.grothVerifierCompatibleBackendVersion(),
|
|
3738
|
+
channelManager.tokamakVerifier(),
|
|
3739
|
+
channelManager.tokamakVerifierCompatibleBackendVersion(),
|
|
3740
|
+
]);
|
|
3741
|
+
return {
|
|
3742
|
+
dappId: Number(dappId),
|
|
3743
|
+
dappMetadataDigestSchema: normalizeBytes32Hex(dappMetadataDigestSchema),
|
|
3744
|
+
dappMetadataDigest: normalizeBytes32Hex(dappMetadataDigest),
|
|
3745
|
+
functionRoot: normalizeBytes32Hex(functionRoot),
|
|
3746
|
+
grothVerifier: getAddress(grothVerifier),
|
|
3747
|
+
grothVerifierCompatibleBackendVersion: requireVersionString(
|
|
3748
|
+
grothVerifierCompatibleBackendVersion,
|
|
3749
|
+
"channel Groth16 verifier compatibleBackendVersion",
|
|
3750
|
+
),
|
|
3751
|
+
tokamakVerifier: getAddress(tokamakVerifier),
|
|
3752
|
+
tokamakVerifierCompatibleBackendVersion: requireVersionString(
|
|
3753
|
+
tokamakVerifierCompatibleBackendVersion,
|
|
3754
|
+
"channel Tokamak verifier compatibleBackendVersion",
|
|
3755
|
+
),
|
|
3756
|
+
};
|
|
3757
|
+
} catch (error) {
|
|
3758
|
+
throw new Error(
|
|
3759
|
+
[
|
|
3760
|
+
`Unable to read immutable policy snapshot from channel manager ${channelManagerAddress}.`,
|
|
3761
|
+
"The target channel must expose DApp digest, verifier address, and compatibleBackendVersion getters.",
|
|
3762
|
+
].join(" "),
|
|
3763
|
+
{ cause: error },
|
|
3764
|
+
);
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3520
3768
|
function readLocalProofBackendPackageVersions() {
|
|
3521
3769
|
const groth16Runtime = inspectGroth16Runtime();
|
|
3522
3770
|
const tokamakPackageReport = readTokamakCliPackageReport();
|
|
@@ -3927,6 +4175,10 @@ function hashTokamakPublicInputs(values) {
|
|
|
3927
4175
|
return keccak256(abiCoder.encode(["uint256[]"], [values]));
|
|
3928
4176
|
}
|
|
3929
4177
|
|
|
4178
|
+
function hashTokamakPointEncoding(part1, part2) {
|
|
4179
|
+
return keccak256(abiCoder.encode(["uint128[]", "uint256[]"], [part1, part2]));
|
|
4180
|
+
}
|
|
4181
|
+
|
|
3930
4182
|
function encodeTokamakBlockInfo(blockInfo) {
|
|
3931
4183
|
const values = new Array(TOKAMAK_APUB_BLOCK_LENGTH).fill(0n);
|
|
3932
4184
|
appendSplitWord(values, 0, ethers.toBigInt(blockInfo.coinBase));
|
|
@@ -4813,15 +5065,15 @@ function assertGetMyNotesArgs(args) {
|
|
|
4813
5065
|
|
|
4814
5066
|
function assertCreateChannelArgs(args) {
|
|
4815
5067
|
requireArg(args.channelName, "--channel-name");
|
|
4816
|
-
requireArg(args.
|
|
5068
|
+
requireArg(args.joinToll, "--join-toll");
|
|
4817
5069
|
requireNetworkName(args);
|
|
4818
5070
|
requireAlchemyApiKeyForPublicNetwork(args, "create-channel");
|
|
4819
5071
|
requireArg(args.privateKey, "--private-key");
|
|
4820
5072
|
assertAllowedCommandKeys(
|
|
4821
5073
|
args,
|
|
4822
5074
|
"create-channel",
|
|
4823
|
-
new Set(["command", "positional", "channelName", "
|
|
4824
|
-
"--channel-name, --join-
|
|
5075
|
+
new Set(["command", "positional", "channelName", "joinToll", "network", "alchemyApiKey", "privateKey"]),
|
|
5076
|
+
"--channel-name, --join-toll, --network, --private-key, and --alchemy-api-key on public networks",
|
|
4825
5077
|
);
|
|
4826
5078
|
}
|
|
4827
5079
|
|
|
@@ -4983,8 +5235,9 @@ Commands:
|
|
|
4983
5235
|
--doctor
|
|
4984
5236
|
Check private-state CLI package versions, runtime install state, Docker mode, CUDA mode, and deployment artifacts
|
|
4985
5237
|
|
|
4986
|
-
create-channel --channel-name <NAME> --join-
|
|
5238
|
+
create-channel --channel-name <NAME> --join-toll <TOKENS> --network <NAME> --private-key <HEX> --alchemy-api-key <KEY>
|
|
4987
5239
|
Create a bridge channel and initialize its workspace
|
|
5240
|
+
Prints the immutable policy snapshot before sending the transaction
|
|
4988
5241
|
|
|
4989
5242
|
recover-workspace --channel-name <NAME> --network <NAME> --alchemy-api-key <KEY>
|
|
4990
5243
|
Rebuild the local channel workspace from bridge state
|
|
@@ -5002,7 +5255,8 @@ Commands:
|
|
|
5002
5255
|
Rebuild a recoverable local wallet from on-chain channel state
|
|
5003
5256
|
|
|
5004
5257
|
join-channel --channel-name <NAME> --password <PASSWORD> --network <NAME> --private-key <HEX> --alchemy-api-key <KEY>
|
|
5005
|
-
Pay the channel join
|
|
5258
|
+
Pay the channel join toll and bind a wallet to a channel-specific L2 identity
|
|
5259
|
+
Prints the immutable policy snapshot before first registration
|
|
5006
5260
|
|
|
5007
5261
|
get-my-wallet-meta --wallet <NAME> --password <PASSWORD> --network <NAME>
|
|
5008
5262
|
Check whether a wallet matches the on-chain channel registration
|
|
@@ -5468,11 +5722,7 @@ function buildSelectedRuntimeVersionCheck({ installManifest, tokamakCli, groth16
|
|
|
5468
5722
|
|
|
5469
5723
|
async function resolvePrivateStateInstallRuntimeVersions(args) {
|
|
5470
5724
|
const [groth16, tokamak] = await Promise.all([
|
|
5471
|
-
|
|
5472
|
-
packageName: GROTH16_PACKAGE_NAME,
|
|
5473
|
-
requestedVersion: args.groth16CliVersion,
|
|
5474
|
-
optionName: "--groth16-cli-version",
|
|
5475
|
-
}),
|
|
5725
|
+
resolveRequestedGroth16PackageVersion(args.groth16CliVersion),
|
|
5476
5726
|
resolveRequestedNpmPackageVersion({
|
|
5477
5727
|
packageName: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
|
|
5478
5728
|
requestedVersion: args.tokamakZkEvmCliVersion,
|
|
@@ -5482,6 +5732,19 @@ async function resolvePrivateStateInstallRuntimeVersions(args) {
|
|
|
5482
5732
|
return { groth16, tokamak };
|
|
5483
5733
|
}
|
|
5484
5734
|
|
|
5735
|
+
async function resolveRequestedGroth16PackageVersion(requestedVersion) {
|
|
5736
|
+
if (requestedVersion !== undefined && requestedVersion !== null) {
|
|
5737
|
+
return resolveRequestedNpmPackageVersion({
|
|
5738
|
+
packageName: GROTH16_PACKAGE_NAME,
|
|
5739
|
+
requestedVersion,
|
|
5740
|
+
optionName: "--groth16-cli-version",
|
|
5741
|
+
});
|
|
5742
|
+
}
|
|
5743
|
+
|
|
5744
|
+
const bundledPackageJson = readJson(path.join(resolveGroth16PackageRoot(), "package.json"));
|
|
5745
|
+
return requireSemverVersion(bundledPackageJson.version, `${GROTH16_PACKAGE_NAME} bundled package version`);
|
|
5746
|
+
}
|
|
5747
|
+
|
|
5485
5748
|
async function resolveRequestedNpmPackageVersion({ packageName, requestedVersion, optionName }) {
|
|
5486
5749
|
const metadata = await fetchNpmPackageMetadata(packageName);
|
|
5487
5750
|
if (requestedVersion === undefined || requestedVersion === null) {
|
|
@@ -5532,10 +5795,7 @@ async function installTokamakCliRuntimeForPrivateState({ version, docker }) {
|
|
|
5532
5795
|
}
|
|
5533
5796
|
|
|
5534
5797
|
async function installGroth16RuntimeForPrivateState({ version, docker }) {
|
|
5535
|
-
const packageInstall =
|
|
5536
|
-
packageName: GROTH16_PACKAGE_NAME,
|
|
5537
|
-
version,
|
|
5538
|
-
});
|
|
5798
|
+
const packageInstall = resolveGroth16RuntimePackageInstall(version);
|
|
5539
5799
|
const packageRoot = packageInstall.packageRoot;
|
|
5540
5800
|
const entryPath = resolveGroth16CliEntryPath(packageRoot);
|
|
5541
5801
|
const args = [entryPath, "--install", "--no-setup"];
|
|
@@ -5561,9 +5821,32 @@ async function installGroth16RuntimeForPrivateState({ version, docker }) {
|
|
|
5561
5821
|
};
|
|
5562
5822
|
}
|
|
5563
5823
|
|
|
5824
|
+
function resolveGroth16RuntimePackageInstall(version) {
|
|
5825
|
+
const normalizedVersion = requireSemverVersion(version, `${GROTH16_PACKAGE_NAME} version`);
|
|
5826
|
+
const bundledPackageRoot = resolveGroth16PackageRoot();
|
|
5827
|
+
const bundledPackageJson = readJson(path.join(bundledPackageRoot, "package.json"));
|
|
5828
|
+
if (bundledPackageJson.name === GROTH16_PACKAGE_NAME && bundledPackageJson.version === normalizedVersion) {
|
|
5829
|
+
return {
|
|
5830
|
+
packageName: GROTH16_PACKAGE_NAME,
|
|
5831
|
+
version: normalizedVersion,
|
|
5832
|
+
installPrefix: null,
|
|
5833
|
+
packageRoot: bundledPackageRoot,
|
|
5834
|
+
};
|
|
5835
|
+
}
|
|
5836
|
+
|
|
5837
|
+
return installManagedNpmPackage({
|
|
5838
|
+
packageName: GROTH16_PACKAGE_NAME,
|
|
5839
|
+
version: normalizedVersion,
|
|
5840
|
+
});
|
|
5841
|
+
}
|
|
5842
|
+
|
|
5564
5843
|
async function installGroth16CrsForPrivateStateVersion(version) {
|
|
5565
5844
|
const workspaceRoot = defaultGroth16WorkspaceRoot();
|
|
5566
5845
|
const crsDir = path.join(workspaceRoot, "crs");
|
|
5846
|
+
const existingInstall = readExistingGroth16CrsInstall({ version, crsDir });
|
|
5847
|
+
if (existingInstall) {
|
|
5848
|
+
return existingInstall;
|
|
5849
|
+
}
|
|
5567
5850
|
const crsInstall = await downloadPublicGroth16MpcArtifactsByVersion({
|
|
5568
5851
|
version,
|
|
5569
5852
|
outputDir: crsDir,
|
|
@@ -5585,6 +5868,56 @@ async function installGroth16CrsForPrivateStateVersion(version) {
|
|
|
5585
5868
|
return crsInstall;
|
|
5586
5869
|
}
|
|
5587
5870
|
|
|
5871
|
+
function readExistingGroth16CrsInstall({ version, crsDir }) {
|
|
5872
|
+
const normalizedVersion = requireCanonicalGroth16CompatibleBackendVersion(version, "Groth16 MPC CRS version");
|
|
5873
|
+
const selectedFiles = [
|
|
5874
|
+
"circuit_final.zkey",
|
|
5875
|
+
"verification_key.json",
|
|
5876
|
+
"metadata.json",
|
|
5877
|
+
"zkey_provenance.json",
|
|
5878
|
+
];
|
|
5879
|
+
const targetPaths = selectedFiles.map((fileName) => path.join(crsDir, fileName));
|
|
5880
|
+
if (!targetPaths.every((targetPath) => fs.existsSync(targetPath))) {
|
|
5881
|
+
return null;
|
|
5882
|
+
}
|
|
5883
|
+
const metadata = readJson(path.join(crsDir, "metadata.json"));
|
|
5884
|
+
let metadataVersion;
|
|
5885
|
+
try {
|
|
5886
|
+
metadataVersion = requireCanonicalGroth16CompatibleBackendVersion(
|
|
5887
|
+
metadata.compatibleBackendVersion,
|
|
5888
|
+
"installed Groth16 MPC CRS version",
|
|
5889
|
+
);
|
|
5890
|
+
} catch {
|
|
5891
|
+
return null;
|
|
5892
|
+
}
|
|
5893
|
+
if (metadataVersion !== normalizedVersion) {
|
|
5894
|
+
return null;
|
|
5895
|
+
}
|
|
5896
|
+
const provenance = readJson(path.join(crsDir, "zkey_provenance.json"));
|
|
5897
|
+
return {
|
|
5898
|
+
source: "local-cache",
|
|
5899
|
+
archiveName: provenance.published_archive_name ?? null,
|
|
5900
|
+
archiveFileId: parseDriveFileIdFromDownloadUrl(provenance.zkey_download_url),
|
|
5901
|
+
folderUrl: provenance.published_folder_url ?? null,
|
|
5902
|
+
version: normalizedVersion,
|
|
5903
|
+
installedFiles: selectedFiles.map((archivePath, index) => ({
|
|
5904
|
+
archivePath,
|
|
5905
|
+
targetPath: targetPaths[index],
|
|
5906
|
+
})),
|
|
5907
|
+
};
|
|
5908
|
+
}
|
|
5909
|
+
|
|
5910
|
+
function parseDriveFileIdFromDownloadUrl(value) {
|
|
5911
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
5912
|
+
return null;
|
|
5913
|
+
}
|
|
5914
|
+
try {
|
|
5915
|
+
return new URL(value).searchParams.get("id");
|
|
5916
|
+
} catch {
|
|
5917
|
+
return null;
|
|
5918
|
+
}
|
|
5919
|
+
}
|
|
5920
|
+
|
|
5588
5921
|
function installManagedNpmPackage({ packageName, version, cacheBaseRoot = resolveArtifactCacheBaseRoot() }) {
|
|
5589
5922
|
const normalizedPackageName = requireNonEmptyString(packageName, "packageName");
|
|
5590
5923
|
const normalizedVersion = requireSemverVersion(version, `${normalizedPackageName} version`);
|