@tokamak-private-dapps/private-state-cli 0.1.9 → 1.0.1
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 +101 -0
- package/README.md +138 -25
- package/assets/tx-fees.json +170 -0
- package/cli-assistant.html +59 -413
- package/lib/private-state-cli-command-registry.mjs +376 -0
- package/lib/private-state-cli-shared.mjs +7 -7
- package/lib/private-state-note-delivery.mjs +415 -0
- package/lib/private-state-runtime-management.mjs +1311 -0
- package/lib/private-state-tokamak-helpers.mjs +184 -0
- package/package.json +2 -1
- package/private-state-bridge-cli.mjs +2675 -2253
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
export const PRIVATE_STATE_CLI_FIELD_CATALOG = Object.freeze({
|
|
2
|
+
channelName: {
|
|
3
|
+
label: "Channel Name",
|
|
4
|
+
type: "text",
|
|
5
|
+
placeholder: "demo-channel",
|
|
6
|
+
valueLabel: "<NAME>",
|
|
7
|
+
option: "--channel-name",
|
|
8
|
+
},
|
|
9
|
+
network: {
|
|
10
|
+
label: "Network",
|
|
11
|
+
type: "select",
|
|
12
|
+
options: ["sepolia", "mainnet", "anvil"],
|
|
13
|
+
valueLabel: "<NAME>",
|
|
14
|
+
option: "--network",
|
|
15
|
+
},
|
|
16
|
+
rpcUrl: {
|
|
17
|
+
label: "RPC URL",
|
|
18
|
+
type: "password",
|
|
19
|
+
placeholder: "https://example-rpc",
|
|
20
|
+
valueLabel: "<URL>",
|
|
21
|
+
hint: "Optional. When omitted, the CLI reads RPC_URL from ~/tokamak-private-channels/secrets/<network>/.env.",
|
|
22
|
+
option: "--rpc-url",
|
|
23
|
+
optional: true,
|
|
24
|
+
},
|
|
25
|
+
account: {
|
|
26
|
+
label: "Account",
|
|
27
|
+
type: "text",
|
|
28
|
+
placeholder: "my-account",
|
|
29
|
+
valueLabel: "<NAME>",
|
|
30
|
+
option: "--account",
|
|
31
|
+
},
|
|
32
|
+
txSubmitter: {
|
|
33
|
+
label: "Transaction Submitter",
|
|
34
|
+
type: "text",
|
|
35
|
+
placeholder: "relayer-account",
|
|
36
|
+
valueLabel: "<ACCOUNT>",
|
|
37
|
+
hint: "Optional for proof-backed note commands. Uses a separate local L1 account to submit executeChannelTransaction.",
|
|
38
|
+
option: "--tx-submitter",
|
|
39
|
+
optional: true,
|
|
40
|
+
},
|
|
41
|
+
privateKeyFile: {
|
|
42
|
+
label: "Private Key File",
|
|
43
|
+
type: "text",
|
|
44
|
+
placeholder: "/path/to/private-key",
|
|
45
|
+
valueLabel: "<PATH>",
|
|
46
|
+
hint: "Source file permissions are not enforced; the imported canonical account secret is protected.",
|
|
47
|
+
option: "--private-key-file",
|
|
48
|
+
},
|
|
49
|
+
joinToll: {
|
|
50
|
+
label: "Join Toll",
|
|
51
|
+
type: "text",
|
|
52
|
+
placeholder: "1",
|
|
53
|
+
valueLabel: "<TOKENS>",
|
|
54
|
+
option: "--join-toll",
|
|
55
|
+
},
|
|
56
|
+
walletSecretPath: {
|
|
57
|
+
label: "Wallet Secret File",
|
|
58
|
+
type: "text",
|
|
59
|
+
placeholder: "/path/to/wallet-secret",
|
|
60
|
+
valueLabel: "<PATH>",
|
|
61
|
+
hint: "Source file permissions are not enforced; the imported wallet-local secret is protected.",
|
|
62
|
+
option: "--wallet-secret-path",
|
|
63
|
+
},
|
|
64
|
+
wallet: {
|
|
65
|
+
label: "Wallet Name",
|
|
66
|
+
type: "text",
|
|
67
|
+
placeholder: "channel-0xYourL1Address",
|
|
68
|
+
valueLabel: "<NAME>",
|
|
69
|
+
option: "--wallet",
|
|
70
|
+
},
|
|
71
|
+
amount: {
|
|
72
|
+
label: "Amount",
|
|
73
|
+
type: "text",
|
|
74
|
+
placeholder: "3",
|
|
75
|
+
valueLabel: "<TOKENS>",
|
|
76
|
+
option: "--amount",
|
|
77
|
+
},
|
|
78
|
+
amounts: {
|
|
79
|
+
label: "Amounts",
|
|
80
|
+
type: "textarea",
|
|
81
|
+
placeholder: "[1,2,3]",
|
|
82
|
+
valueLabel: "<A,B,...>",
|
|
83
|
+
option: "--amounts",
|
|
84
|
+
},
|
|
85
|
+
noteIds: {
|
|
86
|
+
label: "Note IDs",
|
|
87
|
+
type: "textarea",
|
|
88
|
+
placeholder: "[\"0x...\"]",
|
|
89
|
+
valueLabel: "<ID,ID,...>",
|
|
90
|
+
option: "--note-ids",
|
|
91
|
+
},
|
|
92
|
+
recipients: {
|
|
93
|
+
label: "Recipients JSON",
|
|
94
|
+
type: "textarea",
|
|
95
|
+
placeholder: "[\"0xRecipientL2Address\"]",
|
|
96
|
+
valueLabel: "<ADDR,ADDR,...>",
|
|
97
|
+
option: "--recipients",
|
|
98
|
+
},
|
|
99
|
+
docker: {
|
|
100
|
+
label: "Docker Install Mode",
|
|
101
|
+
type: "checkbox",
|
|
102
|
+
hint: "Forward --docker to install. This mode is supported only on Linux hosts by the Tokamak CLI.",
|
|
103
|
+
option: "--docker",
|
|
104
|
+
optional: true,
|
|
105
|
+
},
|
|
106
|
+
includeLocalArtifacts: {
|
|
107
|
+
label: "Include Local Artifacts",
|
|
108
|
+
type: "checkbox",
|
|
109
|
+
hint: "Also install local deployment/ artifacts from the current working directory.",
|
|
110
|
+
option: "--include-local-artifacts",
|
|
111
|
+
optional: true,
|
|
112
|
+
},
|
|
113
|
+
groth16CliVersion: {
|
|
114
|
+
label: "Groth16 CLI Version",
|
|
115
|
+
type: "text",
|
|
116
|
+
placeholder: "0.2.0",
|
|
117
|
+
option: "--groth16-cli-version",
|
|
118
|
+
valueLabel: "<VERSION>",
|
|
119
|
+
optional: true,
|
|
120
|
+
},
|
|
121
|
+
tokamakZkEvmCliVersion: {
|
|
122
|
+
label: "Tokamak zk-EVM CLI Version",
|
|
123
|
+
type: "text",
|
|
124
|
+
placeholder: "2.0.16",
|
|
125
|
+
option: "--tokamak-zk-evm-cli-version",
|
|
126
|
+
valueLabel: "<VERSION>",
|
|
127
|
+
optional: true,
|
|
128
|
+
},
|
|
129
|
+
fromGenesis: {
|
|
130
|
+
label: "Scan From Genesis",
|
|
131
|
+
type: "checkbox",
|
|
132
|
+
hint: "Ignore the local recovery index and replay channel logs from genesis.",
|
|
133
|
+
option: "--from-genesis",
|
|
134
|
+
optional: true,
|
|
135
|
+
},
|
|
136
|
+
json: {
|
|
137
|
+
label: "JSON Output",
|
|
138
|
+
type: "checkbox",
|
|
139
|
+
hint: "Print the command result as JSON.",
|
|
140
|
+
option: "--json",
|
|
141
|
+
optional: true,
|
|
142
|
+
},
|
|
143
|
+
gpu: {
|
|
144
|
+
label: "Live GPU Probe",
|
|
145
|
+
type: "checkbox",
|
|
146
|
+
hint: "Run live NVIDIA and Docker GPU probes during doctor.",
|
|
147
|
+
option: "--gpu",
|
|
148
|
+
optional: true,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
153
|
+
{
|
|
154
|
+
id: "install",
|
|
155
|
+
description: "Install the Tokamak zk-EVM CLI runtime, Groth16 runtime, and private-state deployment artifacts.",
|
|
156
|
+
fields: ["docker", "includeLocalArtifacts", "groth16CliVersion", "tokamakZkEvmCliVersion"],
|
|
157
|
+
usage: "optional --docker, --include-local-artifacts, --groth16-cli-version, and --tokamak-zk-evm-cli-version",
|
|
158
|
+
help: [
|
|
159
|
+
"Version options install exact CLI package versions; omitted versions resolve to npm registry latest",
|
|
160
|
+
"Use --docker on Linux to forward Docker mode to the Tokamak zk-EVM and Groth16 runtimes",
|
|
161
|
+
"Use --include-local-artifacts to also install local deployment/ artifacts from the current working directory",
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "uninstall",
|
|
166
|
+
description: "Interactively remove local private-state workspaces, wallet secrets, proof artifacts, Tokamak zk-EVM runtime data, and the global CLI package when installed.",
|
|
167
|
+
fields: [],
|
|
168
|
+
usage: "no options",
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: "doctor",
|
|
172
|
+
description: "Check private-state CLI package versions, runtime install state, Docker mode, CUDA mode, and deployment artifacts.",
|
|
173
|
+
fields: ["gpu", "json"],
|
|
174
|
+
usage: "optional --gpu and optional --json",
|
|
175
|
+
help: [
|
|
176
|
+
"Prints a concise human-readable table by default; use --json for the full machine-readable report",
|
|
177
|
+
"Use --gpu to run live NVIDIA/Docker GPU probes",
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: "guide",
|
|
182
|
+
description: "Inspect local CLI state and available on-chain state, then print the next safe command.",
|
|
183
|
+
fields: ["network", "channelName", "account", "wallet"],
|
|
184
|
+
optionalFields: ["network", "channelName", "account", "wallet"],
|
|
185
|
+
usage: "optional --network, --channel-name, --account, and --wallet",
|
|
186
|
+
help: ["Does not accept --rpc-url and never writes RPC configuration"],
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: "transaction-fees",
|
|
190
|
+
description: "Estimate ETH and USD fees for transaction-sending commands from packaged measured gas data and live network fee data.",
|
|
191
|
+
fields: ["network", "rpcUrl", "json"],
|
|
192
|
+
usage: "--network, optional --rpc-url, and optional --json",
|
|
193
|
+
help: [
|
|
194
|
+
"Uses packages/apps/private-state/cli/assets/tx-fees.json as the measured gas source packaged with the CLI",
|
|
195
|
+
"Reads live fee data from the selected network RPC and live ETH/USD from CoinGecko",
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: "account-import",
|
|
200
|
+
display: "account import",
|
|
201
|
+
description: "Import a private-key source file into a protected local L1 account secret for later --account use.",
|
|
202
|
+
fields: ["account", "network", "privateKeyFile"],
|
|
203
|
+
usage: "--account, --network, and --private-key-file",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: "create-channel",
|
|
207
|
+
description: "Create a bridge channel and initialize its workspace.",
|
|
208
|
+
fields: ["channelName", "joinToll", "network", "account", "rpcUrl"],
|
|
209
|
+
usage: "--channel-name, --join-toll, --network, --account, and optional --rpc-url",
|
|
210
|
+
help: ["Prints the immutable policy snapshot before sending the transaction"],
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: "recover-workspace",
|
|
214
|
+
description: "Rebuild the local channel workspace from bridge state.",
|
|
215
|
+
fields: ["channelName", "network", "fromGenesis", "rpcUrl"],
|
|
216
|
+
usage: "--channel-name, --network, optional --from-genesis, and optional --rpc-url",
|
|
217
|
+
help: [
|
|
218
|
+
"By default, resumes RPC log scanning from the workspace recovery index when available",
|
|
219
|
+
"Use --from-genesis to ignore the recovery index and replay logs from channel genesis",
|
|
220
|
+
"Prints RPC log scan progress while rebuilding the workspace",
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
id: "get-channel",
|
|
225
|
+
description: "Read channel existence, manager, vault, toll, refund schedule, and immutable policy snapshot.",
|
|
226
|
+
fields: ["channelName", "network", "rpcUrl"],
|
|
227
|
+
usage: "--channel-name, --network, and optional --rpc-url",
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
id: "deposit-bridge",
|
|
231
|
+
description: "Deposit canonical tokens into the shared bridge vault.",
|
|
232
|
+
fields: ["amount", "network", "account", "rpcUrl"],
|
|
233
|
+
usage: "--amount, --network, --account, and optional --rpc-url",
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: "withdraw-bridge",
|
|
237
|
+
description: "Withdraw tokens from the shared bridge vault back to the wallet.",
|
|
238
|
+
fields: ["amount", "network", "account", "rpcUrl"],
|
|
239
|
+
usage: "--amount, --network, --account, and optional --rpc-url",
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: "get-my-bridge-fund",
|
|
243
|
+
description: "Read the current shared bridge vault balance.",
|
|
244
|
+
fields: ["network", "account", "rpcUrl"],
|
|
245
|
+
usage: "--network, --account, and optional --rpc-url",
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
id: "recover-wallet",
|
|
249
|
+
description: "Rebuild a recoverable local wallet from on-chain channel state.",
|
|
250
|
+
fields: ["channelName", "network", "account", "rpcUrl"],
|
|
251
|
+
usage: "--channel-name, --network, --account, and optional --rpc-url",
|
|
252
|
+
help: [
|
|
253
|
+
"Requires the protected wallet-local secret imported during join-channel to exist at the canonical secret path",
|
|
254
|
+
"Does not create or recover the wallet secret itself",
|
|
255
|
+
"Prints RPC log scan progress while rebuilding channel state and received-note state",
|
|
256
|
+
],
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: "join-channel",
|
|
260
|
+
description: "Pay the channel join toll and bind a wallet to a channel-specific L2 identity.",
|
|
261
|
+
fields: ["channelName", "network", "account", "walletSecretPath", "rpcUrl"],
|
|
262
|
+
usage: "--channel-name, --network, --account, --wallet-secret-path, and optional --rpc-url",
|
|
263
|
+
help: [
|
|
264
|
+
"--wallet-secret-path imports an existing source secret file into the protected wallet-local secret file",
|
|
265
|
+
"Prints the immutable policy snapshot before first registration",
|
|
266
|
+
],
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
id: "get-my-wallet-meta",
|
|
270
|
+
description: "Check whether a wallet matches the on-chain channel registration.",
|
|
271
|
+
fields: ["wallet", "network"],
|
|
272
|
+
usage: "--wallet and --network",
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
id: "get-my-l1-address",
|
|
276
|
+
description: "Derive the L1 address for a private key.",
|
|
277
|
+
fields: ["account", "network"],
|
|
278
|
+
usage: "--network and --account",
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: "list-local-wallets",
|
|
282
|
+
description: "List saved local wallet names that can be reused with --wallet.",
|
|
283
|
+
fields: ["network", "channelName"],
|
|
284
|
+
optionalFields: ["network", "channelName"],
|
|
285
|
+
usage: "optional --network and --channel-name",
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
id: "deposit-channel",
|
|
289
|
+
description: "Move bridged funds into the channel L2 accounting balance.",
|
|
290
|
+
fields: ["wallet", "network", "amount"],
|
|
291
|
+
usage: "--wallet, --network, and --amount",
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "withdraw-channel",
|
|
295
|
+
description: "Move channel L2 balance back into the shared bridge vault.",
|
|
296
|
+
fields: ["wallet", "network", "amount"],
|
|
297
|
+
usage: "--wallet, --network, and --amount",
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
id: "get-my-channel-fund",
|
|
301
|
+
description: "Read the current channel L2 accounting balance.",
|
|
302
|
+
fields: ["wallet", "network"],
|
|
303
|
+
usage: "--wallet and --network",
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: "exit-channel",
|
|
307
|
+
description: "Exit a channel. Both the CLI and bridge contract require a zero channel balance.",
|
|
308
|
+
fields: ["wallet", "network"],
|
|
309
|
+
usage: "--wallet and --network",
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: "mint-notes",
|
|
313
|
+
description: "Mint one or two private-state notes from the wallet's channel balance.",
|
|
314
|
+
fields: ["wallet", "network", "amounts", "txSubmitter"],
|
|
315
|
+
usage: "--wallet, --network, --amounts, and optional --tx-submitter",
|
|
316
|
+
help: ["Use --tx-submitter <ACCOUNT> to let a separate local L1 account pay gas for stronger transaction privacy"],
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: "transfer-notes",
|
|
320
|
+
description: "Spend input notes into the registered 1->1, 1->2, or 2->1 private transfer shapes.",
|
|
321
|
+
fields: ["wallet", "network", "noteIds", "recipients", "amounts", "txSubmitter"],
|
|
322
|
+
usage: "--wallet, --network, --note-ids, --recipients, --amounts, and optional --tx-submitter",
|
|
323
|
+
help: ["Use --tx-submitter <ACCOUNT> to let a separate local L1 account pay gas for stronger transaction privacy"],
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
id: "redeem-notes",
|
|
327
|
+
description: "Redeem one tracked note back into the wallet's channel balance.",
|
|
328
|
+
fields: ["wallet", "network", "noteIds", "txSubmitter"],
|
|
329
|
+
usage: "--wallet, --network, --note-ids, and optional --tx-submitter",
|
|
330
|
+
help: ["Use --tx-submitter <ACCOUNT> to let a separate local L1 account pay gas for stronger transaction privacy"],
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
id: "get-my-notes",
|
|
334
|
+
description: "Show the wallet's tracked note state and refresh received notes.",
|
|
335
|
+
fields: ["wallet", "network"],
|
|
336
|
+
usage: "--wallet and --network",
|
|
337
|
+
},
|
|
338
|
+
]);
|
|
339
|
+
|
|
340
|
+
export function privateStateCliCommandDisplay(command) {
|
|
341
|
+
return command.display ?? command.id;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function privateStateCliCommandOptionKeys(command) {
|
|
345
|
+
return ["command", "positional", ...command.fields];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export function privateStateCliCommandRequiredOptionKeys(command) {
|
|
349
|
+
const optionalFields = new Set(command.optionalFields ?? []);
|
|
350
|
+
return command.fields.filter((fieldKey) => {
|
|
351
|
+
const field = PRIVATE_STATE_CLI_FIELD_CATALOG[fieldKey];
|
|
352
|
+
return field?.optional !== true
|
|
353
|
+
&& field?.type !== "checkbox"
|
|
354
|
+
&& !optionalFields.has(fieldKey);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function privateStateCliCommandSynopsis(command) {
|
|
359
|
+
const display = privateStateCliCommandDisplay(command);
|
|
360
|
+
const optionalFields = new Set(command.optionalFields ?? []);
|
|
361
|
+
const options = command.fields
|
|
362
|
+
.filter((fieldKey) => fieldKey !== "json")
|
|
363
|
+
.map((fieldKey) => {
|
|
364
|
+
const field = PRIVATE_STATE_CLI_FIELD_CATALOG[fieldKey];
|
|
365
|
+
if (!field?.option) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
const valueLabel = field.valueLabel ?? field.placeholderLabel ?? `<${field.label?.toUpperCase().replace(/\s+/g, "_") ?? "VALUE"}>`;
|
|
369
|
+
const option = field.type === "checkbox" || fieldKey === "fromGenesis"
|
|
370
|
+
? field.option
|
|
371
|
+
: `${field.option} ${valueLabel}`;
|
|
372
|
+
return field.optional || optionalFields.has(fieldKey) ? `[${option}]` : option;
|
|
373
|
+
})
|
|
374
|
+
.filter(Boolean);
|
|
375
|
+
return [display, ...options].join(" ");
|
|
376
|
+
}
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
fromEdwardsToAddress,
|
|
10
10
|
} from "tokamak-l2js";
|
|
11
11
|
|
|
12
|
-
export const
|
|
13
|
-
export const CHANNEL_BOUND_L2_DERIVATION_MODE = "channel-name-plus-
|
|
12
|
+
export const L2_WALLET_SECRET_SIGNING_DOMAIN = "Tokamak private-state L2 wallet secret binding";
|
|
13
|
+
export const CHANNEL_BOUND_L2_DERIVATION_MODE = "channel-name-plus-wallet-secret-v1";
|
|
14
14
|
|
|
15
15
|
export function slugifyPathComponent(value) {
|
|
16
16
|
return String(value)
|
|
@@ -23,19 +23,19 @@ export function deriveChannelIdFromName(channelName) {
|
|
|
23
23
|
return ethers.toBigInt(keccak256(ethers.toUtf8Bytes(channelName)));
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export function
|
|
26
|
+
export function buildL2WalletSecretSigningMessage({ channelName, walletSecret }) {
|
|
27
27
|
if (typeof channelName !== "string" || channelName.length === 0) {
|
|
28
28
|
throw new Error("Missing channel name for L2 identity derivation.");
|
|
29
29
|
}
|
|
30
30
|
return [
|
|
31
|
-
|
|
31
|
+
L2_WALLET_SECRET_SIGNING_DOMAIN,
|
|
32
32
|
`channel:${channelName}`,
|
|
33
|
-
`
|
|
33
|
+
`walletSecret:${String(walletSecret)}`,
|
|
34
34
|
].join("\n");
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
export async function deriveParticipantIdentityFromSigner({ channelName,
|
|
38
|
-
const seedSignature = await signer.signMessage(
|
|
37
|
+
export async function deriveParticipantIdentityFromSigner({ channelName, walletSecret, signer }) {
|
|
38
|
+
const seedSignature = await signer.signMessage(buildL2WalletSecretSigningMessage({ channelName, walletSecret }));
|
|
39
39
|
const keySet = deriveL2KeysFromSignature(seedSignature);
|
|
40
40
|
const l2Address = getAddress(fromEdwardsToAddress(keySet.publicKey).toString());
|
|
41
41
|
return {
|