@unlink-xyz/cli 0.1.0

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.
Files changed (50) hide show
  1. package/README.md +9 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +46 -0
  4. package/dist/commands/account.d.ts +2 -0
  5. package/dist/commands/account.js +83 -0
  6. package/dist/commands/balance.d.ts +2 -0
  7. package/dist/commands/balance.js +75 -0
  8. package/dist/commands/burner.d.ts +2 -0
  9. package/dist/commands/burner.js +114 -0
  10. package/dist/commands/config.d.ts +2 -0
  11. package/dist/commands/config.js +140 -0
  12. package/dist/commands/deposit.d.ts +2 -0
  13. package/dist/commands/deposit.js +58 -0
  14. package/dist/commands/history.d.ts +2 -0
  15. package/dist/commands/history.js +30 -0
  16. package/dist/commands/multisig.d.ts +2 -0
  17. package/dist/commands/multisig.js +343 -0
  18. package/dist/commands/notes.d.ts +2 -0
  19. package/dist/commands/notes.js +28 -0
  20. package/dist/commands/sync.d.ts +2 -0
  21. package/dist/commands/sync.js +51 -0
  22. package/dist/commands/transfer.d.ts +2 -0
  23. package/dist/commands/transfer.js +47 -0
  24. package/dist/commands/tx-status.d.ts +2 -0
  25. package/dist/commands/tx-status.js +31 -0
  26. package/dist/commands/wallet.d.ts +2 -0
  27. package/dist/commands/wallet.js +98 -0
  28. package/dist/commands/withdraw.d.ts +2 -0
  29. package/dist/commands/withdraw.js +47 -0
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.js +6 -0
  32. package/dist/lib/context.d.ts +14 -0
  33. package/dist/lib/context.js +42 -0
  34. package/dist/lib/errors.d.ts +1 -0
  35. package/dist/lib/errors.js +24 -0
  36. package/dist/lib/multisig-store.d.ts +8 -0
  37. package/dist/lib/multisig-store.js +121 -0
  38. package/dist/lib/options.d.ts +40 -0
  39. package/dist/lib/options.js +109 -0
  40. package/dist/lib/output.d.ts +6 -0
  41. package/dist/lib/output.js +34 -0
  42. package/dist/lib/relay.d.ts +18 -0
  43. package/dist/lib/relay.js +35 -0
  44. package/dist/lib/tokens.d.ts +14 -0
  45. package/dist/lib/tokens.js +142 -0
  46. package/dist/storage/sqlite.d.ts +1 -0
  47. package/dist/storage/sqlite.js +1 -0
  48. package/dist/test-utils.d.ts +56 -0
  49. package/dist/test-utils.js +73 -0
  50. package/package.json +45 -0
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @unlink-xyz/cli
2
+
3
+ Command-line interface for the Unlink privacy protocol.
4
+
5
+ ## Overview
6
+
7
+ CLI tool for managing wallets, accounts, and performing private deposits, transfers, and withdrawals on EVM blockchains. Uses `@unlink-xyz/core` under the hood with SQLite for local state storage.
8
+
9
+ > **Documentation:** [CLI guide](../../docs/sdk/cli.mdx)
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createProgram(): Command;
package/dist/cli.js ADDED
@@ -0,0 +1,46 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { Command, Option } from "commander";
4
+ import { registerAccountCommands } from "./commands/account.js";
5
+ import { registerBalanceCommands } from "./commands/balance.js";
6
+ import { registerBurnerCommands } from "./commands/burner.js";
7
+ import { registerConfigCommands } from "./commands/config.js";
8
+ import { registerDepositCommands } from "./commands/deposit.js";
9
+ import { registerHistoryCommands } from "./commands/history.js";
10
+ import { registerMultisigCommands } from "./commands/multisig.js";
11
+ import { registerNotesCommands } from "./commands/notes.js";
12
+ import { registerSyncCommands } from "./commands/sync.js";
13
+ import { registerTransferCommands } from "./commands/transfer.js";
14
+ import { registerTxStatusCommands } from "./commands/tx-status.js";
15
+ import { registerWalletCommands } from "./commands/wallet.js";
16
+ import { registerWithdrawCommands } from "./commands/withdraw.js";
17
+ export function createProgram() {
18
+ const program = new Command("unlink")
19
+ .description("Unlink Protocol CLI — privacy-as-a-service for EVM blockchains")
20
+ .version("0.1.0");
21
+ program
22
+ .addOption(new Option("--gateway-url <url>", "Gateway endpoint URL").env("UNLINK_GATEWAY_URL"))
23
+ .addOption(new Option("--chain-id <id>", "Chain ID").env("UNLINK_CHAIN_ID"))
24
+ .addOption(new Option("--pool-address <addr>", "Pool contract address").env("UNLINK_POOL_ADDRESS"))
25
+ .addOption(new Option("--data-dir <path>", "Data directory")
26
+ .env("UNLINK_DATA_DIR")
27
+ .default(path.join(os.homedir(), ".unlink")))
28
+ .addOption(new Option("--node-url <url>", "Ethereum JSON-RPC URL for on-chain transactions (defaults to --gateway-url)").env("UNLINK_RPC_HTTP_URL"))
29
+ .addOption(new Option("--json", "Output JSON").default(false))
30
+ .addOption(new Option("--private-key <key>", "Private key for signing").env("UNLINK_PRIVATE_KEY"))
31
+ .addOption(new Option("--artifact-version <version>", "ZK artifact version for proof generation").env("UNLINK_ARTIFACT_VERSION"));
32
+ registerWalletCommands(program);
33
+ registerAccountCommands(program);
34
+ registerBalanceCommands(program);
35
+ registerBurnerCommands(program);
36
+ registerDepositCommands(program);
37
+ registerTransferCommands(program);
38
+ registerWithdrawCommands(program);
39
+ registerSyncCommands(program);
40
+ registerTxStatusCommands(program);
41
+ registerHistoryCommands(program);
42
+ registerNotesCommands(program);
43
+ registerConfigCommands(program);
44
+ registerMultisigCommands(program);
45
+ return program;
46
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerAccountCommands(program: Command): void;
@@ -0,0 +1,83 @@
1
+ import { createContext } from "../lib/context.js";
2
+ import { withErrorHandler } from "../lib/errors.js";
3
+ import { parseIndex, resolveOptions } from "../lib/options.js";
4
+ import { output } from "../lib/output.js";
5
+ export function registerAccountCommands(program) {
6
+ const account = program.command("account").description("Account management");
7
+ account
8
+ .command("list")
9
+ .description("List all derived accounts")
10
+ .action(withErrorHandler(async (_opts, cmd) => {
11
+ const options = resolveOptions(cmd.optsWithGlobals());
12
+ const ctx = await createContext(options, { local: true });
13
+ const accounts = await ctx.wallet.accounts.list();
14
+ const activeIndex = await ctx.wallet.accounts.getActiveIndex();
15
+ if (options.json) {
16
+ output({
17
+ accounts: accounts.map((a) => ({
18
+ index: a.index,
19
+ address: a.address,
20
+ masterPublicKey: a.masterPublicKey.toString(),
21
+ })),
22
+ activeIndex,
23
+ }, options);
24
+ }
25
+ else {
26
+ if (accounts.length === 0) {
27
+ process.stdout.write("No accounts. Create one with: unlink account create\n");
28
+ return;
29
+ }
30
+ for (const a of accounts) {
31
+ const marker = a.index === activeIndex ? " (active)" : "";
32
+ process.stdout.write(`#${a.index}${marker} ${a.address}\n`);
33
+ }
34
+ }
35
+ }));
36
+ account
37
+ .command("create")
38
+ .description("Derive and persist a new account")
39
+ .action(withErrorHandler(async (_opts, cmd) => {
40
+ const options = resolveOptions(cmd.optsWithGlobals());
41
+ const ctx = await createContext(options, { local: true });
42
+ const acct = await ctx.wallet.accounts.create();
43
+ const list = await ctx.wallet.accounts.list();
44
+ const info = list.find((a) => a.masterPublicKey === acct.masterPublicKey);
45
+ output(options.json
46
+ ? {
47
+ index: info?.index,
48
+ address: acct.address,
49
+ masterPublicKey: acct.masterPublicKey.toString(),
50
+ }
51
+ : `Account #${info?.index} created: ${acct.address}`, options);
52
+ }));
53
+ account
54
+ .command("active")
55
+ .description("Show active account")
56
+ .action(withErrorHandler(async (_opts, cmd) => {
57
+ const options = resolveOptions(cmd.optsWithGlobals());
58
+ const ctx = await createContext(options, { local: true });
59
+ const idx = await ctx.wallet.accounts.getActiveIndex();
60
+ if (idx === null) {
61
+ output(options.json ? { active: null } : "No active account", options);
62
+ return;
63
+ }
64
+ const list = await ctx.wallet.accounts.list();
65
+ const active = list.find((a) => a.index === idx);
66
+ output(options.json
67
+ ? { index: idx, address: active?.address }
68
+ : `#${idx} ${active?.address}`, options);
69
+ }));
70
+ account
71
+ .command("switch")
72
+ .argument("<index>", "Account index")
73
+ .description("Switch active account")
74
+ .action(withErrorHandler(async (indexStr, _opts, cmd) => {
75
+ const options = resolveOptions(cmd.optsWithGlobals());
76
+ const index = parseIndex(indexStr);
77
+ const ctx = await createContext(options, { local: true });
78
+ await ctx.wallet.accounts.setActive(index);
79
+ output(options.json
80
+ ? { switched: true, index }
81
+ : `Switched to account #${index}`, options);
82
+ }));
83
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerBalanceCommands(program: Command): void;
@@ -0,0 +1,75 @@
1
+ import { createContext } from "../lib/context.js";
2
+ import { withErrorHandler } from "../lib/errors.js";
3
+ import { mergeConfigDefaults, requireChainId, requireGatewayUrl, resolveOptions, } from "../lib/options.js";
4
+ import { output } from "../lib/output.js";
5
+ import { formatTokenAmount, resolveTokenDecimals, resolveTokenSymbol, } from "../lib/tokens.js";
6
+ export function registerBalanceCommands(program) {
7
+ program
8
+ .command("balance")
9
+ .argument("[token]", "Token address (omit for all)")
10
+ .description("Show token balances")
11
+ .action(withErrorHandler(async (token, _opts, cmd) => {
12
+ const options = mergeConfigDefaults(resolveOptions(cmd.optsWithGlobals()));
13
+ requireGatewayUrl(options);
14
+ requireChainId(options);
15
+ const ctx = await createContext(options);
16
+ // Show active account header in human-readable mode
17
+ const activeIdx = await ctx.wallet.accounts.getActiveIndex();
18
+ const activeAcct = await ctx.wallet.accounts.getActive();
19
+ const addressTag = activeAcct
20
+ ? ` (${truncateZkAddress(activeAcct.address)})`
21
+ : "";
22
+ if (token) {
23
+ const bal = await ctx.wallet.getBalance(token);
24
+ if (options.json) {
25
+ output({ token, balance: bal.toString() }, options);
26
+ }
27
+ else {
28
+ const label = options.nodeUrl
29
+ ? await resolveTokenSymbol(options.nodeUrl, token)
30
+ : token;
31
+ const decimals = options.nodeUrl
32
+ ? await resolveTokenDecimals(options.nodeUrl, token)
33
+ : undefined;
34
+ process.stdout.write(`Account #${activeIdx}${addressTag}\n`);
35
+ process.stdout.write(`${label}: ${formatTokenAmount(bal, decimals)}\n`);
36
+ }
37
+ }
38
+ else {
39
+ const balances = await ctx.wallet.getBalances();
40
+ const balMap = balances;
41
+ const entries = Object.entries(balMap).map(([t, b]) => ({
42
+ token: t,
43
+ balance: b,
44
+ }));
45
+ if (options.json) {
46
+ output({
47
+ balances: entries.map((e) => ({
48
+ token: e.token,
49
+ balance: e.balance.toString(),
50
+ })),
51
+ }, options);
52
+ }
53
+ else if (entries.length === 0) {
54
+ process.stdout.write("No balances. Run 'unlink sync' first.\n");
55
+ }
56
+ else {
57
+ process.stdout.write(`Account #${activeIdx}${addressTag}\n`);
58
+ for (const { token: t, balance: b } of entries) {
59
+ const label = options.nodeUrl
60
+ ? await resolveTokenSymbol(options.nodeUrl, t)
61
+ : t;
62
+ const decimals = options.nodeUrl
63
+ ? await resolveTokenDecimals(options.nodeUrl, t)
64
+ : undefined;
65
+ process.stdout.write(`${label}: ${formatTokenAmount(b, decimals)}\n`);
66
+ }
67
+ }
68
+ }
69
+ }));
70
+ }
71
+ function truncateZkAddress(addr) {
72
+ if (addr.length <= 16)
73
+ return addr;
74
+ return `${addr.slice(0, 8)}...${addr.slice(-4)}`;
75
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerBurnerCommands(program: Command): void;
@@ -0,0 +1,114 @@
1
+ import { createContext } from "../lib/context.js";
2
+ import { withErrorHandler } from "../lib/errors.js";
3
+ import { mergeConfigDefaults, parseIndex, requireChainId, requireGatewayUrl, requirePoolAddress, resolveOptions, } from "../lib/options.js";
4
+ import { log, output } from "../lib/output.js";
5
+ export function registerBurnerCommands(program) {
6
+ const burner = program
7
+ .command("burner")
8
+ .description("Manage burner accounts for DeFi interactions");
9
+ burner
10
+ .command("address")
11
+ .argument("<index>", "BIP-44 derivation index")
12
+ .description("Derive burner address at a given index")
13
+ .action(withErrorHandler(async (indexArg, _opts, cmd) => {
14
+ const options = resolveOptions(cmd.optsWithGlobals());
15
+ const ctx = await createContext(options, { local: true });
16
+ const index = parseIndex(indexArg);
17
+ const account = await ctx.wallet.burner.addressOf(index);
18
+ output(account, options);
19
+ }));
20
+ burner
21
+ .command("fund")
22
+ .argument("<index>", "BIP-44 derivation index")
23
+ .requiredOption("--token <address>", "Token contract address")
24
+ .requiredOption("--amount <value>", "Amount (raw atomic units)")
25
+ .description("Withdraw from shielded pool to fund a burner")
26
+ .action(withErrorHandler(async (indexArg, cmdOpts, cmd) => {
27
+ const options = mergeConfigDefaults(resolveOptions(cmd.optsWithGlobals()));
28
+ requireGatewayUrl(options);
29
+ requireChainId(options);
30
+ requirePoolAddress(options);
31
+ const ctx = await createContext(options);
32
+ const index = parseIndex(indexArg);
33
+ log("Funding burner from shielded pool...", options);
34
+ const result = await ctx.wallet.burner.fund(index, {
35
+ token: cmdOpts["token"],
36
+ amount: BigInt(cmdOpts["amount"]),
37
+ });
38
+ output({ relayId: result.relayId, status: "submitted" }, options);
39
+ }));
40
+ burner
41
+ .command("send")
42
+ .argument("<index>", "BIP-44 derivation index")
43
+ .requiredOption("--to <address>", "Destination address")
44
+ .option("--data <hex>", "Transaction calldata (hex)")
45
+ .option("--value <amount>", "ETH value in wei")
46
+ .option("--gas-limit <limit>", "Gas limit")
47
+ .description("Send a transaction from a burner account")
48
+ .action(withErrorHandler(async (indexArg, cmdOpts, cmd) => {
49
+ const options = resolveOptions(cmd.optsWithGlobals());
50
+ const ctx = await createContext(options, { local: true });
51
+ const index = parseIndex(indexArg);
52
+ log("Sending transaction from burner...", options);
53
+ const result = await ctx.wallet.burner.send(index, {
54
+ to: cmdOpts["to"],
55
+ data: cmdOpts["data"] ?? undefined,
56
+ value: cmdOpts["value"]
57
+ ? BigInt(cmdOpts["value"])
58
+ : undefined,
59
+ gasLimit: cmdOpts["gasLimit"]
60
+ ? BigInt(cmdOpts["gasLimit"])
61
+ : undefined,
62
+ });
63
+ output(result, options);
64
+ }));
65
+ burner
66
+ .command("sweep")
67
+ .argument("<index>", "BIP-44 derivation index")
68
+ .requiredOption("--token <address>", "Token contract address")
69
+ .option("--amount <value>", "Amount to sweep (omit to sweep full balance)")
70
+ .description("Sweep burner balance back to shielded pool")
71
+ .action(withErrorHandler(async (indexArg, cmdOpts, cmd) => {
72
+ const options = mergeConfigDefaults(resolveOptions(cmd.optsWithGlobals()));
73
+ requireGatewayUrl(options);
74
+ requireChainId(options);
75
+ requirePoolAddress(options);
76
+ const ctx = await createContext(options);
77
+ const index = parseIndex(indexArg);
78
+ log("Sweeping burner balance to pool...", options);
79
+ const result = await ctx.wallet.burner.sweepToPool(index, {
80
+ token: cmdOpts["token"],
81
+ amount: cmdOpts["amount"]
82
+ ? BigInt(cmdOpts["amount"])
83
+ : undefined,
84
+ });
85
+ output(result, options);
86
+ }));
87
+ burner
88
+ .command("export-key")
89
+ .argument("<index>", "BIP-44 derivation index")
90
+ .description("Export burner private key for wallet import")
91
+ .action(withErrorHandler(async (indexArg, _opts, cmd) => {
92
+ const options = resolveOptions(cmd.optsWithGlobals());
93
+ const ctx = await createContext(options, { local: true });
94
+ const index = parseIndex(indexArg);
95
+ const key = await ctx.wallet.burner.exportKey(index);
96
+ output(options.json ? { privateKey: key } : key, options);
97
+ }));
98
+ burner
99
+ .command("balance")
100
+ .argument("<address>", "Burner or EOA address")
101
+ .option("--token <address>", "ERC-20 token address (omit for native ETH)")
102
+ .description("Check burner ETH or token balance")
103
+ .action(withErrorHandler(async (address, cmdOpts, cmd) => {
104
+ const options = resolveOptions(cmd.optsWithGlobals());
105
+ const ctx = await createContext(options, { local: true });
106
+ const token = cmdOpts["token"];
107
+ const balance = token
108
+ ? await ctx.wallet.burner.getTokenBalance(address, token)
109
+ : await ctx.wallet.burner.getBalance(address);
110
+ output(options.json
111
+ ? { address, token: token ?? "ETH", balance: balance.toString() }
112
+ : `${token ?? "ETH"}: ${balance.toString()}`, options);
113
+ }));
114
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerConfigCommands(program: Command): void;
@@ -0,0 +1,140 @@
1
+ import readline from "node:readline/promises";
2
+ import { withErrorHandler } from "../lib/errors.js";
3
+ import { loadConfigFile, mergeConfigDefaults, resolveOptions, saveConfigFile, } from "../lib/options.js";
4
+ import { log, output } from "../lib/output.js";
5
+ const VALID_CONFIG_KEYS = [
6
+ "gatewayUrl",
7
+ "chainId",
8
+ "poolAddress",
9
+ "nodeUrl",
10
+ "artifactVersion",
11
+ ];
12
+ export function registerConfigCommands(program) {
13
+ const config = program.command("config").description("Configuration");
14
+ config
15
+ .command("show")
16
+ .description("Show current configuration (with value sources)")
17
+ .action(withErrorHandler(async (_opts, cmd) => {
18
+ const options = resolveOptions(cmd.optsWithGlobals());
19
+ const fileConfig = loadConfigFile(options.dataDir);
20
+ const merged = mergeConfigDefaults(options);
21
+ function source(key, cliValue) {
22
+ if (cliValue !== undefined)
23
+ return "flag/env";
24
+ if (fileConfig[key] !== undefined)
25
+ return "config";
26
+ return "";
27
+ }
28
+ const rows = [
29
+ {
30
+ key: "Gateway URL",
31
+ value: merged.gatewayUrl ?? "(not set)",
32
+ src: source("gatewayUrl", options.gatewayUrl),
33
+ },
34
+ {
35
+ key: "Chain ID",
36
+ value: merged.chainId ?? "(not set)",
37
+ src: source("chainId", options.chainId),
38
+ },
39
+ {
40
+ key: "Pool Address",
41
+ value: merged.poolAddress ?? "(not set)",
42
+ src: source("poolAddress", options.poolAddress),
43
+ },
44
+ {
45
+ key: "Node URL",
46
+ value: merged.nodeUrl ?? "(not set)",
47
+ src: source("nodeUrl", options.nodeUrl),
48
+ },
49
+ {
50
+ key: "Artifact Ver",
51
+ value: merged.artifactVersion ?? "(not set)",
52
+ src: source("artifactVersion", options.artifactVersion),
53
+ },
54
+ { key: "Data Dir", value: merged.dataDir, src: "" },
55
+ ];
56
+ if (options.json) {
57
+ output({
58
+ gatewayUrl: merged.gatewayUrl ?? null,
59
+ chainId: merged.chainId ?? null,
60
+ poolAddress: merged.poolAddress ?? null,
61
+ nodeUrl: merged.nodeUrl ?? null,
62
+ artifactVersion: merged.artifactVersion ?? null,
63
+ dataDir: merged.dataDir,
64
+ }, options);
65
+ }
66
+ else {
67
+ for (const row of rows) {
68
+ const srcTag = row.src ? ` [${row.src}]` : "";
69
+ process.stdout.write(`${row.key.padEnd(14)} ${row.value}${srcTag}\n`);
70
+ }
71
+ }
72
+ }));
73
+ config
74
+ .command("init")
75
+ .description("Initialize connection config (interactive)")
76
+ .action(withErrorHandler(async (_opts, cmd) => {
77
+ const options = resolveOptions(cmd.optsWithGlobals());
78
+ const existing = loadConfigFile(options.dataDir);
79
+ const rl = readline.createInterface({
80
+ input: process.stdin,
81
+ output: process.stderr,
82
+ });
83
+ try {
84
+ const gatewayUrl = (await rl.question(`Gateway URL [${existing.gatewayUrl ?? ""}]: `)) || existing.gatewayUrl;
85
+ const chainIdStr = (await rl.question(`Chain ID [${existing.chainId ?? ""}]: `)) ||
86
+ String(existing.chainId ?? "");
87
+ const poolAddress = (await rl.question(`Pool Address [${existing.poolAddress ?? ""}]: `)) || existing.poolAddress;
88
+ const nodeUrl = (await rl.question(`Node URL [${existing.nodeUrl ?? ""}]: `)) ||
89
+ existing.nodeUrl;
90
+ const artifactVersion = (await rl.question(`Artifact Version [${existing.artifactVersion ?? ""}]: `)) || existing.artifactVersion;
91
+ const newConfig = {};
92
+ if (gatewayUrl)
93
+ newConfig.gatewayUrl = gatewayUrl;
94
+ if (chainIdStr)
95
+ newConfig.chainId = Number(chainIdStr);
96
+ if (poolAddress)
97
+ newConfig.poolAddress = poolAddress;
98
+ if (nodeUrl)
99
+ newConfig.nodeUrl = nodeUrl;
100
+ if (artifactVersion)
101
+ newConfig.artifactVersion = artifactVersion;
102
+ saveConfigFile(options.dataDir, newConfig);
103
+ log(`Config saved to ${options.dataDir}/config.json`, options);
104
+ if (options.json) {
105
+ output(newConfig, options);
106
+ }
107
+ }
108
+ finally {
109
+ rl.close();
110
+ }
111
+ }));
112
+ config
113
+ .command("set")
114
+ .argument("<key>", `Config key (${VALID_CONFIG_KEYS.join(", ")})`)
115
+ .argument("<value>", "Config value")
116
+ .description("Set a single config value")
117
+ .action(withErrorHandler(async (key, value, _opts, cmd) => {
118
+ const options = resolveOptions(cmd.optsWithGlobals());
119
+ if (!VALID_CONFIG_KEYS.includes(key)) {
120
+ throw new Error(`Invalid config key "${key}". Valid keys: ${VALID_CONFIG_KEYS.join(", ")}`);
121
+ }
122
+ const existing = loadConfigFile(options.dataDir);
123
+ const updated = { ...existing };
124
+ if (key === "chainId") {
125
+ const num = Number(value);
126
+ if (Number.isNaN(num) || !Number.isInteger(num) || num < 0) {
127
+ throw new Error("chainId must be a non-negative integer");
128
+ }
129
+ updated.chainId = num;
130
+ }
131
+ else {
132
+ updated[key] = value;
133
+ }
134
+ saveConfigFile(options.dataDir, updated);
135
+ log(`Set ${key} = ${value}`, options);
136
+ if (options.json) {
137
+ output(updated, options);
138
+ }
139
+ }));
140
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerDepositCommands(program: Command): void;
@@ -0,0 +1,58 @@
1
+ import { ethers } from "ethers";
2
+ import { createContext } from "../lib/context.js";
3
+ import { withErrorHandler } from "../lib/errors.js";
4
+ import { mergeConfigDefaults, requireChainId, requireGatewayUrl, requirePoolAddress, requirePrivateKey, resolveOptions, } from "../lib/options.js";
5
+ import { log, output } from "../lib/output.js";
6
+ export function registerDepositCommands(program) {
7
+ program
8
+ .command("deposit")
9
+ .requiredOption("--token <address>", "Token contract address")
10
+ .requiredOption("--amount <value>", "Amount (raw atomic units)")
11
+ .description("Deposit tokens into the privacy pool")
12
+ .action(withErrorHandler(async (cmdOpts, cmd) => {
13
+ const options = mergeConfigDefaults(resolveOptions(cmd.optsWithGlobals()));
14
+ requireGatewayUrl(options);
15
+ requireChainId(options);
16
+ requirePoolAddress(options);
17
+ requirePrivateKey(options);
18
+ const ctx = await createContext(options);
19
+ const amount = BigInt(cmdOpts["amount"]);
20
+ const token = cmdOpts["token"];
21
+ const ethRpcUrl = options.nodeUrl ?? ctx.gatewayUrl;
22
+ const provider = new ethers.JsonRpcProvider(ethRpcUrl, ctx.wallet.chainId, { staticNetwork: true });
23
+ const signer = new ethers.Wallet(options.privateKey, provider);
24
+ const depositor = await signer.getAddress();
25
+ const erc20 = new ethers.Contract(token, [
26
+ "function allowance(address,address) view returns (uint256)",
27
+ "function approve(address,uint256) returns (bool)",
28
+ ], signer);
29
+ const allowance = (await erc20.getFunction("allowance")(depositor, options.poolAddress));
30
+ if (allowance < amount) {
31
+ log("Approving token spend...", options);
32
+ const approveTx = (await erc20.getFunction("approve")(options.poolAddress, amount));
33
+ await approveTx.wait();
34
+ }
35
+ log("Preparing deposit...", options);
36
+ const relay = await ctx.wallet.deposit({
37
+ depositor,
38
+ deposits: [{ token, amount }],
39
+ });
40
+ log(`Relay ID: ${relay.relayId}\nSubmitting transaction...`, options);
41
+ const nonce = await provider.getTransactionCount(depositor, "pending");
42
+ const tx = await signer.sendTransaction({
43
+ to: relay.to,
44
+ data: relay.calldata,
45
+ nonce,
46
+ });
47
+ log(`Tx hash: ${tx.hash}\nWaiting for confirmation...`, options);
48
+ await tx.wait();
49
+ log("Reconciling deposit...", options);
50
+ const result = await ctx.wallet.confirmDeposit(relay.relayId);
51
+ output({
52
+ relayId: relay.relayId,
53
+ txHash: tx.hash,
54
+ status: "confirmed",
55
+ commitments: result.commitments,
56
+ }, options);
57
+ }));
58
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerHistoryCommands(program: Command): void;
@@ -0,0 +1,30 @@
1
+ import { createContext } from "../lib/context.js";
2
+ import { withErrorHandler } from "../lib/errors.js";
3
+ import { mergeConfigDefaults, requireChainId, requireGatewayUrl, resolveOptions, } from "../lib/options.js";
4
+ import { output } from "../lib/output.js";
5
+ export function registerHistoryCommands(program) {
6
+ program
7
+ .command("history")
8
+ .description("Show transaction history")
9
+ .option("--include-self-sends", "Include self-sends", false)
10
+ .action(withErrorHandler(async (cmdOpts, cmd) => {
11
+ const options = mergeConfigDefaults(resolveOptions(cmd.optsWithGlobals()));
12
+ requireGatewayUrl(options);
13
+ requireChainId(options);
14
+ const ctx = await createContext(options);
15
+ const entries = await ctx.wallet.getHistory({
16
+ includeSelfSends: Boolean(cmdOpts["includeSelfSends"]),
17
+ });
18
+ if (options.json) {
19
+ output({ entries }, options);
20
+ }
21
+ else if (entries.length === 0) {
22
+ process.stdout.write("No transaction history.\n");
23
+ }
24
+ else {
25
+ for (const e of entries) {
26
+ process.stdout.write(`${e.kind} ${e.txHash ?? "pending"}\n`);
27
+ }
28
+ }
29
+ }));
30
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerMultisigCommands(program: Command): void;