@provablehq/shield-swap-cli 0.7.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Provable Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # @provablehq/shield-swap-cli
2
+
3
+ The `shield-swap` command: trade on Shield Swap from a terminal. Each subcommand
4
+ does one job against a live deployment — builds a session, plans the work,
5
+ prints what happened.
6
+
7
+ This is a separate install from `@provablehq/shield-swap-sdk`, so a project that
8
+ only needs the client never pulls the command line in. Install it, then call it by
9
+ name:
10
+
11
+ ```sh
12
+ npm install -g @provablehq/shield-swap-cli
13
+
14
+ shield-swap pools
15
+ shield-swap swap --from USDCx --to ETH --amount 1.5 --execute
16
+ ```
17
+
18
+ Or keep it in the project and go through the local bin:
19
+
20
+ ```sh
21
+ npm install --save-dev @provablehq/shield-swap-cli
22
+
23
+ npx shield-swap setup --new
24
+ npx shield-swap pools
25
+ ```
26
+
27
+ `npx shield-swap` runs the binary that is installed. `npx @provablehq/shield-swap-cli`
28
+ names the package instead, which sends npx to the registry: the version can change
29
+ between two commands in the same session, and it does not work offline. Prefer the
30
+ install.
31
+
32
+ `shield-swap` on its own lists the subcommands, and every subcommand takes
33
+ `--help` for its own flags.
34
+
35
+ ## Running it from the Veil repo
36
+
37
+ No build step. `tsx` honours the root tsconfig's `paths`, so the SDK resolves
38
+ straight to source and every run picks up edits immediately:
39
+
40
+ ```sh
41
+ pnpm install
42
+ pnpm shield-swap pools
43
+ pnpm shield-swap swap --from USDCx --to ETH --amount 1.5
44
+ ```
45
+
46
+ Use `pnpm -s` when parsing the output. `pnpm run` prints a two-line banner on
47
+ stdout, which is harmless for a person and fatal for `--json`:
48
+
49
+ ```sh
50
+ pnpm -s shield-swap balances --json
51
+ ```
52
+
53
+ Or skip the wrapper and call it directly, which has no banner:
54
+
55
+ ```sh
56
+ npx tsx packages/shield-swap-cli/src/index.ts balances --json
57
+ ```
58
+
59
+ To exercise the built binary as a user would get it — the shebang, the lazy
60
+ command loading, the `bin` wiring — build the package and run `dist`:
61
+
62
+ ```sh
63
+ pnpm --filter @provablehq/shield-swap-cli build
64
+ node packages/shield-swap-cli/dist/index.js pools
65
+ ```
66
+
67
+ State lands in `./.shield-swap/<network>/` relative to the working directory, so
68
+ running from the repo root keeps a session there. Set `SHIELD_SWAP_STATE_DIR` to
69
+ point somewhere else — worth doing if you want a scratch account separate from
70
+ the one you normally trade with.
71
+
72
+ To drive the SDK directly while sharing the same state file the command line
73
+ writes, import the session helpers:
74
+
75
+ ```ts
76
+ import { loadSession, formatAmount } from '@provablehq/shield-swap-cli/session'
77
+ ```
78
+
79
+ ## Two rules that hold everywhere
80
+
81
+ **Nothing spends until you pass `--execute`.** Every write command plans the
82
+ transaction against live chain state, prints exactly what it would do, and stops.
83
+ The dry run and the real run differ only in whether a transaction follows, so a
84
+ first run is always safe and always worth doing.
85
+
86
+ **Mainnet is never implicit.** The default network is testnet. Mainnet needs
87
+ `--network mainnet` on every invocation (or `SHIELD_SWAP_NETWORK=mainnet` in the
88
+ environment), because everything downstream is per-network — the DEX API host, the
89
+ prover, the record scanner, the token registry, and the blinded identity store —
90
+ and the mainnet ones move real value. When a plan is for mainnet, the banner above
91
+ it says so.
92
+
93
+ Amounts you type are human units (`--amount 1.5`), amounts you read are rendered
94
+ with each token's decimals, and raw base units stay inside the SDK where they
95
+ belong. `--json` prints one machine-readable object and silences everything else,
96
+ so an agent or a pipeline can drive the same command a person uses.
97
+
98
+ ## What each command needs
99
+
100
+ Every command loads the session from `./.shield-swap/<network>/state.json`, which
101
+ means all of them need `setup` to have run once. "Funds" below means a private
102
+ balance: private records are what pay for trades and deposits, and a public
103
+ balance cannot be traded from. Transaction fees are paid by the delegated prover's
104
+ FeeMaster account by default, so a faucet-funded account needs no public credits
105
+ (set `SHIELD_SWAP_FEE_MASTER=0` when the account should pay its own).
106
+
107
+ | Command | What it does | Needs |
108
+ | --- | --- | --- |
109
+ | `setup` | Sets up all credentials Shield Swap requires, idempotently: key material, DEX authentication, Provable API credentials, invite-code redemption, API token, testnet airdrop. | Nothing (an invite code when access is locked; a key file for a returning account) |
110
+ | `pools` | Lists pools from the API and joins each with chain state, so the tradeable flag and the depth come from the mappings rather than the index. | Session |
111
+ | `balances` | Private and public holdings per token, reconciled against the registry. | Session, record access |
112
+ | `positions` | Every liquidity position the account holds, with its range, its backing amounts, and the fees earned that a collect would pay. | Session, record access |
113
+ | `swap` | Sells one token for another, single hop or routed, then claims the output. | Funds in the token being sold |
114
+ | `swap-concurrent` | Makes multiple swaps concurrently, one per token sold, planned before any is submitted. | Funds in each token being sold |
115
+ | `history` | Swap history and the status of each swap, claiming what is still waiting; rebuilds a lost identity store from chain history. | Session (claiming needs the prover) |
116
+ | `mint` | Opens a position: aligns a percentage range to the pool's tick spacing and deposits what the range consumes. | Funds on **both** sides of the pool |
117
+ | `liquidity` | Adds to an open position, or removes liquidity and books it as owed. | A position; funds on both sides to add |
118
+ | `collect` | Sweeps what a position is owed into records, and with `--close` burns the drained position. | A position with something owed |
119
+ | `liquidity-e2e` | The whole lifecycle in one run — mint, increase, decrease, collect, burn — with the waits each step needs. | Funds on both sides of the pool |
120
+
121
+ "Record access" means the hosted record scanner, which `setup` configures with
122
+ the Provable API credentials it registers. Reads that touch only mappings
123
+ (`pools`) work without it.
124
+
125
+ ## A suggested order
126
+
127
+ 1. **`shield-swap setup --new`** — once. It ends by telling you the account is
128
+ ready, and on testnet it draws funds and waits for the records to land.
129
+ 2. **`shield-swap balances`** — confirm what arrived. Nothing below works until
130
+ this shows a private balance.
131
+ 3. **`shield-swap pools`** — see what can be traded and how deep it is. Note the
132
+ pool keys and symbols you care about.
133
+ 4. **`shield-swap swap --from … --to … --amount …`** — no `--execute` first, then
134
+ with it. A swap is two transactions and this does both, so the proceeds arrive
135
+ in the same run.
136
+ 5. **`shield-swap history`** — after any trading session. It reads the chain
137
+ rather than local bookkeeping, so an entry appears exactly when a claim would
138
+ succeed.
139
+ 6. **`shield-swap mint --pair … --percent …`** — become the market instead of
140
+ trading against it. Read the plan carefully: the range, and how much of each
141
+ side the range actually consumes.
142
+ 7. **`shield-swap positions`** — watch what the position holds and earns.
143
+ 8. **`shield-swap liquidity --position … --increase|--decrease`** — top it up, or
144
+ take part of it back out. A decrease books the proceeds; it does not pay them
145
+ out.
146
+ 9. **`shield-swap collect`** — sweep the earnings, and `--close` to burn a
147
+ position you are finished with.
148
+
149
+ `shield-swap liquidity-e2e` walks steps 6 through 9 in one go. It is the fastest
150
+ way to prove a funded account works end to end against a live deployment, and the
151
+ place to look for how the waiting between dependent transactions has to be done.
152
+
153
+ ## Two kinds of lag worth knowing about
154
+
155
+ Both bite when one transaction is built from the result of the last one, which is
156
+ most of the liquidity flow.
157
+
158
+ Mapping writes propagate to reads asynchronously, so a read taken straight after a
159
+ confirmed transaction can still show the previous state. Every command that needs
160
+ its own write back polls for it.
161
+
162
+ The record scanner lags further, and its failure mode is quieter. Each liquidity
163
+ write spends the position record and issues a new one; a transaction built on the
164
+ spent record carries a serial number the chain has already consumed, so the node
165
+ drops it at verification. It never reaches a block, and the only symptom is a
166
+ confirmation wait against a transaction nothing has heard of. Checking that a
167
+ record exists is not enough — the spent one satisfies that too — so the commands
168
+ wait for the record's tag to change.
169
+
170
+ ## Keeping state safe
171
+
172
+ `./.shield-swap/<network>/state.json` holds the private key and the DEX
173
+ credentials, written with mode 0600. Add `.shield-swap/` to `.gitignore` and treat
174
+ it like a wallet file. Set `SHIELD_SWAP_STATE_DIR` to keep it somewhere else.
175
+
176
+ Nothing is shared between testnet and mainnet — not the key, not the API grant,
177
+ and above all not the blinded identity store, whose reservations are only
178
+ meaningful against the chain they were checked on.
@@ -0,0 +1,66 @@
1
+ import {
2
+ done,
3
+ flags,
4
+ output,
5
+ run,
6
+ step,
7
+ table
8
+ } from "./chunk-IBVZHLUT.js";
9
+ import {
10
+ dim,
11
+ green,
12
+ greenBright,
13
+ greenDim
14
+ } from "./chunk-IHYFMX5A.js";
15
+ import {
16
+ formatAmount,
17
+ loadSession
18
+ } from "./chunk-2OT6LZPW.js";
19
+
20
+ // src/commands/balances.ts
21
+ var USAGE = `shield-swap balances \u2014 private and public holdings per token
22
+
23
+ --network <testnet|mainnet> default testnet
24
+ --token <symbol|id> only this token
25
+ --all include tokens with a zero balance
26
+ --json machine-readable output`;
27
+ async function main(argv) {
28
+ const args = flags({ token: { type: "string" }, all: { type: "boolean" } }, USAGE, argv);
29
+ await run(async () => {
30
+ const { client, account, network } = await loadSession({ network: args.network });
31
+ done(`session on ${network} for ${account.address}`);
32
+ const only = args.token ? await client.tokenData(args.token) : void 0;
33
+ step("scanning records and reading public balances");
34
+ const scope = only ? [only.id] : args.all ? (await client.listTokens()).map((token) => token.id) : void 0;
35
+ const balances = await client.getBalances(scope ? { tokens: scope } : {});
36
+ const rows = Object.entries(balances).map(([id, entry]) => ({ id, ...entry })).filter((row) => args.all || row.total > 0n).sort((a, b) => a.symbol.localeCompare(b.symbol));
37
+ output({ network, address: account.address, tokens: rows }, (data) => {
38
+ if (!data.tokens.length) {
39
+ console.log("\nNo balances. Fund the account (testnet: `shield-swap setup`) and try again.");
40
+ return;
41
+ }
42
+ table(
43
+ ["TOKEN", "PRIVATE", "PUBLIC", "TOTAL", "TOKEN ID"],
44
+ // Green throughout, because every figure here is money the account holds.
45
+ // The private side is brightest: it is the only one a swap can spend, so a
46
+ // reader deciding whether a trade is possible wants it to stand out from a
47
+ // public balance that has to be wrapped or transferred first.
48
+ data.tokens.map((row) => [
49
+ row.symbol,
50
+ greenBright(formatAmount(row.private, row.decimals)),
51
+ greenDim(formatAmount(row.public, row.decimals)),
52
+ green(formatAmount(row.total, row.decimals)),
53
+ dim(row.id)
54
+ ]),
55
+ // Left throughout: the amounts already line up on their decimal points, and
56
+ // ragging them against the right edge of a wide header only pushed them away
57
+ // from the token they belong to.
58
+ ["left", "left", "left", "left", "left"]
59
+ );
60
+ });
61
+ });
62
+ }
63
+ export {
64
+ main
65
+ };
66
+ //# sourceMappingURL=balances-6SU4DCKM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/balances.ts"],"sourcesContent":["/**\n * Balances — what the account holds, private and public, per token.\n *\n * The private side is what funds trades: swaps and mints spend private records,\n * and a freshly funded account shows zero public balance because the faucet\n * delivers records. Both sides come from `getBalances`, which reconciles the\n * token registry against the record scanner and the API.\n *\n * Reads only. Spends nothing.\n *\n * Usage:\n * shield-swap balances # testnet\n * shield-swap balances --network mainnet\n * shield-swap balances --token USDCx # one token\n * shield-swap balances --all # include zero balances\n * shield-swap balances --json\n */\nimport { loadSession, formatAmount } from '../session.js'\nimport { flags, step, done, output, run, table } from '../shared.js'\nimport { dim, green, greenBright, greenDim } from '../color.js'\n\nconst USAGE = `shield-swap balances — private and public holdings per token\n\n --network <testnet|mainnet> default testnet\n --token <symbol|id> only this token\n --all include tokens with a zero balance\n --json machine-readable output`\n\n/**\n * Runs the `balances` subcommand.\n *\n * @param argv Arguments after the subcommand name, as the dispatcher supplies them.\n */\nexport async function main(argv: string[]): Promise<void> {\n const args = flags({ token: { type: 'string' }, all: { type: 'boolean' } }, USAGE, argv)\n\n await run(async () => {\n const { client, account, network } = await loadSession({ network: args.network as string | undefined })\n done(`session on ${network} for ${account.address}`)\n\n const only = args.token ? await client.tokenData(args.token as string) : undefined\n step('scanning records and reading public balances')\n // getBalances returns only what the account holds unless asked for specific\n // tokens, so `--all` has to name the whole registry — filtering a map that\n // never contained the zero rows would drop them silently.\n const scope = only\n ? [only.id]\n : args.all\n ? (await client.listTokens()).map((token) => token.id)\n : undefined\n const balances = await client.getBalances(scope ? { tokens: scope } : {})\n\n const rows = Object.entries(balances)\n .map(([id, entry]) => ({ id, ...entry }))\n .filter((row) => args.all || row.total > 0n)\n .sort((a, b) => a.symbol.localeCompare(b.symbol))\n\n output({ network, address: account.address, tokens: rows }, (data) => {\n if (!data.tokens.length) {\n console.log('\\nNo balances. Fund the account (testnet: `shield-swap setup`) and try again.')\n return\n }\n // Printed as formatted, with no padding of their own. Aligning on the decimal\n // point instead would pad each integer part out to the widest one, and in a\n // left-aligned column that pad becomes a visible indent — the points would\n // line up while the numbers started at three different places.\n table(\n ['TOKEN', 'PRIVATE', 'PUBLIC', 'TOTAL', 'TOKEN ID'],\n // Green throughout, because every figure here is money the account holds.\n // The private side is brightest: it is the only one a swap can spend, so a\n // reader deciding whether a trade is possible wants it to stand out from a\n // public balance that has to be wrapped or transferred first.\n data.tokens.map((row) => [\n row.symbol,\n greenBright(formatAmount(row.private, row.decimals)),\n greenDim(formatAmount(row.public, row.decimals)),\n green(formatAmount(row.total, row.decimals)),\n dim(row.id),\n ]),\n // Left throughout: the amounts already line up on their decimal points, and\n // ragging them against the right edge of a wide header only pushed them away\n // from the token they belong to.\n ['left', 'left', 'left', 'left', 'left'],\n )\n })\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAqBA,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAYd,eAAsB,KAAK,MAA+B;AACxD,QAAM,OAAO,MAAM,EAAE,OAAO,EAAE,MAAM,SAAS,GAAG,KAAK,EAAE,MAAM,UAAU,EAAE,GAAG,OAAO,IAAI;AAEvF,QAAM,IAAI,YAAY;AACpB,UAAM,EAAE,QAAQ,SAAS,QAAQ,IAAI,MAAM,YAAY,EAAE,SAAS,KAAK,QAA8B,CAAC;AACtG,SAAK,cAAc,OAAO,QAAQ,QAAQ,OAAO,EAAE;AAEnD,UAAM,OAAO,KAAK,QAAQ,MAAM,OAAO,UAAU,KAAK,KAAe,IAAI;AACzE,SAAK,8CAA8C;AAInD,UAAM,QAAQ,OACV,CAAC,KAAK,EAAE,IACR,KAAK,OACF,MAAM,OAAO,WAAW,GAAG,IAAI,CAAC,UAAU,MAAM,EAAE,IACnD;AACN,UAAM,WAAW,MAAM,OAAO,YAAY,QAAQ,EAAE,QAAQ,MAAM,IAAI,CAAC,CAAC;AAExE,UAAM,OAAO,OAAO,QAAQ,QAAQ,EACjC,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,IAAI,GAAG,MAAM,EAAE,EACvC,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,QAAQ,EAAE,EAC1C,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,cAAc,EAAE,MAAM,CAAC;AAElD,WAAO,EAAE,SAAS,SAAS,QAAQ,SAAS,QAAQ,KAAK,GAAG,CAAC,SAAS;AACpE,UAAI,CAAC,KAAK,OAAO,QAAQ;AACvB,gBAAQ,IAAI,+EAA+E;AAC3F;AAAA,MACF;AAKA;AAAA,QACE,CAAC,SAAS,WAAW,UAAU,SAAS,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,QAKlD,KAAK,OAAO,IAAI,CAAC,QAAQ;AAAA,UACvB,IAAI;AAAA,UACJ,YAAY,aAAa,IAAI,SAAS,IAAI,QAAQ,CAAC;AAAA,UACnD,SAAS,aAAa,IAAI,QAAQ,IAAI,QAAQ,CAAC;AAAA,UAC/C,MAAM,aAAa,IAAI,OAAO,IAAI,QAAQ,CAAC;AAAA,UAC3C,IAAI,IAAI,EAAE;AAAA,QACZ,CAAC;AAAA;AAAA;AAAA;AAAA,QAID,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,MAAM;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;","names":[]}
@@ -0,0 +1,178 @@
1
+ // src/session.ts
2
+ import { mkdirSync, readFileSync, writeFileSync, existsSync, chmodSync, renameSync } from "fs";
3
+ import { join } from "path";
4
+ import {
5
+ loadNetwork,
6
+ generateAccount
7
+ } from "@provablehq/veil-aleo-sdk";
8
+ import { fileCredentialStore } from "@provablehq/veil-aleo-sdk/node";
9
+ import { shieldSwapActions, parseUnits } from "@provablehq/shield-swap-sdk";
10
+ import { fileBlindedIdentityStore } from "@provablehq/shield-swap-sdk/node";
11
+ function resolveNetwork(explicit) {
12
+ const value = explicit ?? process.env.SHIELD_SWAP_NETWORK ?? "testnet";
13
+ if (value !== "testnet" && value !== "mainnet") {
14
+ throw new Error(`Unknown network "${value}" \u2014 use testnet or mainnet.`);
15
+ }
16
+ return value;
17
+ }
18
+ var NETWORK_URL = "https://api.provable.com/v2";
19
+ var STATE_ROOT = process.env.SHIELD_SWAP_STATE_DIR ?? join(process.cwd(), ".shield-swap");
20
+ function stateDir(network) {
21
+ return join(STATE_ROOT, network);
22
+ }
23
+ function blindedStorePath(network) {
24
+ return join(stateDir(network), "blinded.json");
25
+ }
26
+ function credentialsPath(network) {
27
+ return join(stateDir(network), "provable-credentials.json");
28
+ }
29
+ function statePath(network) {
30
+ return join(stateDir(network), "state.json");
31
+ }
32
+ var LEGACY_STATE_PATH = join(STATE_ROOT, "state.json");
33
+ function loadState(network) {
34
+ const path = existsSync(statePath(network)) ? statePath(network) : network === "testnet" && existsSync(LEGACY_STATE_PATH) ? LEGACY_STATE_PATH : void 0;
35
+ if (!path) return { network };
36
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
37
+ parsed.network = network;
38
+ return parsed;
39
+ }
40
+ function saveState(state) {
41
+ const network = resolveNetwork(state.network);
42
+ mkdirSync(stateDir(network), { recursive: true });
43
+ const target = statePath(network);
44
+ const tmp = `${target}.tmp`;
45
+ writeFileSync(tmp, JSON.stringify(state, null, 2));
46
+ chmodSync(tmp, 384);
47
+ renameSync(tmp, target);
48
+ }
49
+ function formatAmount(amount, decimals, symbol) {
50
+ const scale = 10n ** BigInt(decimals);
51
+ const whole = amount / scale;
52
+ const frac = amount % scale;
53
+ let s = whole.toLocaleString("en-US");
54
+ if (frac > 0n) {
55
+ const fracStr = frac.toString().padStart(decimals, "0").replace(/0+$/, "");
56
+ if (fracStr) s += `.${fracStr}`;
57
+ }
58
+ return symbol ? `${s} ${symbol}` : s;
59
+ }
60
+ function namedAmounts(params) {
61
+ const [token0, token1] = params.tokens;
62
+ const amounts = [
63
+ params.indexed[0] ? parseUnits(params.indexed[0], token0.decimals) : void 0,
64
+ params.indexed[1] ? parseUnits(params.indexed[1], token1.decimals) : void 0
65
+ ];
66
+ const namedBy = [params.indexed[0] ? "--amount0" : void 0, params.indexed[1] ? "--amount1" : void 0];
67
+ for (const entry of params.entries) {
68
+ const parts = entry.split(":");
69
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
70
+ throw new Error(`--amount takes <symbol>:<amount>, e.g. --amount ${token0.symbol}:0.5 \u2014 got "${entry}"`);
71
+ }
72
+ const [name, value] = parts;
73
+ const wanted = name.trim();
74
+ const side = [token0, token1].findIndex(
75
+ (token2) => token2.symbol.toLowerCase() === wanted.toLowerCase() || token2.id === wanted
76
+ );
77
+ if (side === -1) {
78
+ throw new Error(`"${wanted}" is not in this pool's pair \u2014 it holds ${token0.symbol} and ${token1.symbol}.`);
79
+ }
80
+ const token = side === 0 ? token0 : token1;
81
+ if (amounts[side] !== void 0) {
82
+ throw new Error(`${token.symbol} is named twice, by ${namedBy[side]} and --amount ${entry}.`);
83
+ }
84
+ amounts[side] = parseUnits(value.trim(), token.decimals);
85
+ namedBy[side] = `--amount ${entry}`;
86
+ }
87
+ return { amount0: amounts[0], amount1: amounts[1] };
88
+ }
89
+ async function loadSession(options = {}) {
90
+ const network = resolveNetwork(options.network);
91
+ const state = loadState(network);
92
+ if (!state.privateKey) {
93
+ throw new Error(
94
+ `No shield-swap session found for ${network} \u2014 run \`shield-swap setup --network ${network}\` first (see startup.md).`
95
+ );
96
+ }
97
+ const aleo = await loadNetwork(network);
98
+ const scanner = aleo.createRemoteScanner();
99
+ const { walletClient, account } = aleo.createAleoClient({
100
+ privateKey: state.privateKey,
101
+ networkUrl: NETWORK_URL,
102
+ provingMode: "delegated",
103
+ credentialStore: fileCredentialStore(credentialsPath(network)),
104
+ // Faucet-funded accounts hold no public credits; the delegated prover
105
+ // pays fees from its FeeMaster account. Opt out with
106
+ // SHIELD_SWAP_FEE_MASTER=0 when the account funds its own fees.
107
+ useFeeMaster: process.env.SHIELD_SWAP_FEE_MASTER !== "0",
108
+ records: scanner
109
+ });
110
+ const apiUrl = process.env.SHIELD_SWAP_API_URL ?? state.apiUrl;
111
+ const blindedIdentities = fileBlindedIdentityStore(blindedStorePath(network));
112
+ const client = walletClient.extend(
113
+ shieldSwapActions({ api: { baseUrl: apiUrl }, blindedIdentities })
114
+ );
115
+ try {
116
+ await client.authenticateShieldSwap();
117
+ } catch (error) {
118
+ if (!apiUrl) throw error;
119
+ const source = process.env.SHIELD_SWAP_API_URL === apiUrl ? "SHIELD_SWAP_API_URL" : `apiUrl in ${statePath(network)}`;
120
+ throw new Error(
121
+ `could not authenticate with the DEX API at ${apiUrl}, which is pinned by ${source} rather than derived from the network. If that deployment is gone, re-pin with \`shield-swap setup --api-url <origin>\` or clear the field to fall back to the default for ${network}.`,
122
+ { cause: error }
123
+ );
124
+ }
125
+ return { client, account, scanner, state, aleo, network, blindedIdentities };
126
+ }
127
+ async function pollUntil(fn, attempts, intervalMs) {
128
+ for (let i = 0; i < attempts; i++) {
129
+ if (await fn()) return true;
130
+ await new Promise((r) => setTimeout(r, intervalMs));
131
+ }
132
+ return false;
133
+ }
134
+ async function ensureKeyMaterial(state, options = {}) {
135
+ const network = resolveNetwork(state.network);
136
+ await loadNetwork(network);
137
+ if (state.privateKey && options.importKey && options.importKey !== state.privateKey) {
138
+ throw new Error(
139
+ `a DIFFERENT account is already configured here (${state.address ?? "address unknown"}). Refusing to switch silently \u2014 its funds and access live on that key. To use the imported key instead, move or delete the state directory first, then re-run with --private-key-file.`
140
+ );
141
+ }
142
+ if (!state.privateKey) {
143
+ if (options.importKey) {
144
+ state.privateKey = options.importKey;
145
+ } else if (options.allowGenerate) {
146
+ state.privateKey = generateAccount().privateKey;
147
+ } else {
148
+ throw new NeedsConfigDecisionError();
149
+ }
150
+ }
151
+ const aleo = await loadNetwork(network);
152
+ state.address = aleo.privateKeyToAccount(state.privateKey).address;
153
+ saveState(state);
154
+ return state;
155
+ }
156
+ var NeedsConfigDecisionError = class extends Error {
157
+ constructor() {
158
+ super("no key material found and none provided");
159
+ this.name = "NeedsConfigDecisionError";
160
+ }
161
+ };
162
+
163
+ export {
164
+ resolveNetwork,
165
+ NETWORK_URL,
166
+ stateDir,
167
+ blindedStorePath,
168
+ credentialsPath,
169
+ loadState,
170
+ saveState,
171
+ formatAmount,
172
+ namedAmounts,
173
+ loadSession,
174
+ pollUntil,
175
+ ensureKeyMaterial,
176
+ NeedsConfigDecisionError
177
+ };
178
+ //# sourceMappingURL=chunk-2OT6LZPW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/session.ts"],"sourcesContent":["/**\n * Shared session plumbing for the `shield-swap` command.\n *\n * Owns the state file (`./.shield-swap/<network>/state.json` by default) and\n * the client wiring, so every command starts from `loadSession()` and gets a\n * fully authenticated client plus persistent storage for the one thing that\n * must survive a crash and cannot be rediscovered: the private key, plus the\n * DEX grants tied to it.\n *\n * Exported as `@provablehq/shield-swap-cli/session` so a script driving the SDK\n * directly can share the same state file the command line writes.\n *\n * Nothing else is stored. Swap handles belong to the SDK's blinded identity\n * store, and positions are discovered from records with\n * `client.getOwnedPositions()` — a local list of either could only ever be a\n * stale copy of what the chain already knows.\n *\n * Everything is scoped by network. Nothing is shared between testnet and\n * mainnet: not the key, not the API grant, and above all not the identity\n * store, whose reservations are only meaningful against one chain.\n *\n * The state file holds a private key and API credentials — keep it out of\n * version control (`.shield-swap/` belongs in .gitignore) and treat it like\n * a wallet file.\n */\nimport { mkdirSync, readFileSync, writeFileSync, existsSync, chmodSync, renameSync } from 'node:fs'\nimport { join } from 'node:path'\nimport {\n loadNetwork,\n generateAccount,\n} from '@provablehq/veil-aleo-sdk'\nimport { fileCredentialStore } from '@provablehq/veil-aleo-sdk/node'\nimport { shieldSwapActions, getPrivateBalances, parseUnits, DEFAULT_PROGRAM } from '@provablehq/shield-swap-sdk'\nimport { fileBlindedIdentityStore } from '@provablehq/shield-swap-sdk/node'\n\n/** The networks these scripts run against. */\nexport type Network = 'testnet' | 'mainnet'\n\n/**\n * Resolves the network from an explicit choice, the environment, or the default.\n *\n * Testnet is the default and mainnet is never reached by omission: a script has\n * to be told, because everything downstream — the DEX API host, the prover, the\n * scanner, the token registry, and the identity store — is per-network, and the\n * mainnet ones move real value.\n *\n * @param explicit A `--network` value, when a script parsed one.\n * @throws When the value is neither network, rather than silently using testnet.\n */\nexport function resolveNetwork(explicit?: string): Network {\n const value = explicit ?? process.env.SHIELD_SWAP_NETWORK ?? 'testnet'\n if (value !== 'testnet' && value !== 'mainnet') {\n throw new Error(`Unknown network \"${value}\" — use testnet or mainnet.`)\n }\n return value\n}\n\nexport const NETWORK_URL = 'https://api.provable.com/v2'\n\n/** Everything that must survive between agent sessions. */\nexport type ShieldSwapState = {\n network: string\n /**\n * DEX API origin this session targets. Unset means the SDK's default\n * hosted deployment. Set by `shield-swap setup` (`--api-url` / SHIELD_SWAP_API_URL);\n * the access grant, API token, and airdrop job are scoped to one\n * deployment, so setup clears them when this changes.\n */\n apiUrl?: string\n privateKey?: string\n address?: string\n dexApiToken?: string\n accessRedeemed?: boolean\n /** Faucet job already requested for this account — prevents double-drawing on re-runs. */\n airdropJobId?: string\n}\n\nconst STATE_ROOT = process.env.SHIELD_SWAP_STATE_DIR ?? join(process.cwd(), '.shield-swap')\n\n/** Per-network state directory. Nothing is shared between networks. */\nexport function stateDir(network: Network): string {\n return join(STATE_ROOT, network)\n}\n\n/**\n * Where the blinded identity store lives for a network.\n *\n * Scoped by network because a reservation is only meaningful against the chain\n * it was checked on: counters reserved against testnet's\n * `used_blinded_addresses` say nothing about mainnet's, and one shared file\n * would hand out identities the other chain has already consumed.\n */\nexport function blindedStorePath(network: Network): string {\n return join(stateDir(network), 'blinded.json')\n}\n\n/**\n * Where Provable API credentials live for a network.\n *\n * Separate from the state file because the SDK owns the format: `shield-swap setup`\n * hands `fileCredentialStore` this path and the client reads and writes it\n * directly, including registering a consumer when the file is absent.\n */\nexport function credentialsPath(network: Network): string {\n return join(stateDir(network), 'provable-credentials.json')\n}\n\nfunction statePath(network: Network): string {\n return join(stateDir(network), 'state.json')\n}\n\n/** The pre-network layout, still read for testnet so existing keys survive. */\nconst LEGACY_STATE_PATH = join(STATE_ROOT, 'state.json')\n\n/**\n * Reads a network's state file, or returns a fresh empty state.\n *\n * Falls back to the pre-network layout for testnet only, so an existing\n * `.shield-swap/state.json` keeps working; the next save writes it to the\n * network-scoped path.\n */\nexport function loadState(network: Network): ShieldSwapState {\n const path = existsSync(statePath(network))\n ? statePath(network)\n : network === 'testnet' && existsSync(LEGACY_STATE_PATH)\n ? LEGACY_STATE_PATH\n : undefined\n if (!path) return { network }\n const parsed = JSON.parse(readFileSync(path, 'utf8')) as ShieldSwapState\n // A state file that names a different network is a wrong-chain hazard: its\n // key is fine but its access grant, API token, and airdrop job are not.\n parsed.network = network\n return parsed\n}\n\n/**\n * Writes the state file atomically (temp file + rename, 0600 — it holds the\n * private key). A crash mid-write can never truncate the only copy of the\n * key and the open swap handles.\n */\nexport function saveState(state: ShieldSwapState): void {\n const network = resolveNetwork(state.network)\n mkdirSync(stateDir(network), { recursive: true })\n const target = statePath(network)\n const tmp = `${target}.tmp`\n writeFileSync(tmp, JSON.stringify(state, null, 2))\n chmodSync(tmp, 0o600)\n renameSync(tmp, target)\n}\n\n\n/**\n * Renders a raw base-unit amount in human units (\"0.0534 ETH\"), the ONLY\n * format that should ever reach the user. Raw units (wei-style integers)\n * are SDK-facing; showing them to a person misstates their balances by\n * orders of magnitude.\n */\nexport function formatAmount(amount: bigint, decimals: number, symbol?: string): string {\n const scale = 10n ** BigInt(decimals)\n const whole = amount / scale\n const frac = amount % scale\n let s = whole.toLocaleString('en-US')\n if (frac > 0n) {\n // Full precision minus trailing zeros — tiny testnet amounts live many\n // places below the decimal point and must not round away to nothing.\n const fracStr = frac.toString().padStart(decimals, '0').replace(/0+$/, '')\n if (fracStr) s += `.${fracStr}`\n }\n return symbol ? `${s} ${symbol}` : s\n}\n\n/** What {@link namedAmounts} needs of a pool's token to place an amount. */\nexport type AmountToken = { id: string; symbol: string; decimals: number }\n\n/**\n * Places caller-named amounts into a pool's own token order.\n *\n * A pool orders its tokens by id, not by anything a caller types, so `--amount0`\n * is unknowable without reading the pool first: naming a pair `USDCx:ETH` does\n * not make USDCx side 0. `--amount USDCx:0.5` names the token instead and is\n * matched here, while `--amount0`/`--amount1` stay available for callers who know\n * the order. Parsing is the inverse of {@link formatAmount}: human decimals in,\n * raw base units out. Pure and local.\n *\n * @param params.entries `--amount` values, each `<symbol|id>:<decimal>`.\n * @param params.indexed The raw `--amount0` and `--amount1` strings, in that\n * order, `undefined` where the flag was absent.\n * @param params.tokens The pool's tokens in ITS order — `[token0, token1]`.\n * @returns Raw base units per side, `undefined` where nothing named that side.\n * @throws When an entry is malformed, names a token outside the pair, or names a\n * side twice — including once by symbol and once by index, where preferring\n * either would commit an amount the caller did not ask for.\n *\n * @example\n * const { amount0, amount1 } = namedAmounts({\n * entries: ['USDCx:0.5'],\n * indexed: [undefined, undefined],\n * tokens: [token0, token1],\n * })\n */\nexport function namedAmounts(params: {\n entries: string[]\n indexed: readonly [string | undefined, string | undefined]\n tokens: readonly [AmountToken, AmountToken]\n}): { amount0: bigint | undefined; amount1: bigint | undefined } {\n const [token0, token1] = params.tokens\n const amounts: Array<bigint | undefined> = [\n params.indexed[0] ? parseUnits(params.indexed[0], token0.decimals) : undefined,\n params.indexed[1] ? parseUnits(params.indexed[1], token1.decimals) : undefined,\n ]\n // Tracked alongside so a collision can name both flags rather than just the token.\n const namedBy = [params.indexed[0] ? '--amount0' : undefined, params.indexed[1] ? '--amount1' : undefined]\n\n for (const entry of params.entries) {\n const parts = entry.split(':')\n if (parts.length !== 2 || !parts[0] || !parts[1]) {\n throw new Error(`--amount takes <symbol>:<amount>, e.g. --amount ${token0.symbol}:0.5 — got \"${entry}\"`)\n }\n const [name, value] = parts as [string, string]\n const wanted = name.trim()\n const side = [token0, token1].findIndex(\n (token) => token.symbol.toLowerCase() === wanted.toLowerCase() || token.id === wanted,\n )\n if (side === -1) {\n throw new Error(`\"${wanted}\" is not in this pool's pair — it holds ${token0.symbol} and ${token1.symbol}.`)\n }\n const token = side === 0 ? token0 : token1\n if (amounts[side] !== undefined) {\n throw new Error(`${token.symbol} is named twice, by ${namedBy[side]} and --amount ${entry}.`)\n }\n amounts[side] = parseUnits(value.trim(), token.decimals)\n namedBy[side] = `--amount ${entry}`\n }\n return { amount0: amounts[0], amount1: amounts[1] }\n}\n\n/**\n * Builds the fully wired, authenticated session from the state file.\n *\n * Requires `shield-swap setup` to have run (key material in the state file).\n * Authenticates with the DEX API on every call — the session JWT covers\n * everything including access/token management, and auto-renews on expiry.\n *\n * Provable API credentials are not required up front: the client registers a\n * consumer through the credential file on first prove or scan when it holds\n * none — though `shield-swap setup` registers and verifies eagerly, so a session built\n * after setup has working credentials rather than untested ones.\n */\nexport async function loadSession(options: { network?: string } = {}) {\n const network = resolveNetwork(options.network)\n const state = loadState(network)\n if (!state.privateKey) {\n throw new Error(\n `No shield-swap session found for ${network} — run \\`shield-swap setup --network ${network}\\` first (see startup.md).`,\n )\n }\n\n const aleo = await loadNetwork(network)\n // Credentials reach both the prover and the scanner through one session the\n // client builds from the store, so a single JWT serves both.\n // No prover or scanner URL: both default to the Provable API and take the\n // network from the client, so naming them here would only risk drift.\n const scanner = aleo.createRemoteScanner()\n const { walletClient, account } = aleo.createAleoClient({\n privateKey: state.privateKey,\n networkUrl: NETWORK_URL,\n provingMode: 'delegated',\n credentialStore: fileCredentialStore(credentialsPath(network)),\n // Faucet-funded accounts hold no public credits; the delegated prover\n // pays fees from its FeeMaster account. Opt out with\n // SHIELD_SWAP_FEE_MASTER=0 when the account funds its own fees.\n useFeeMaster: process.env.SHIELD_SWAP_FEE_MASTER !== '0',\n records: scanner,\n })\n // SHIELD_SWAP_API_URL overrides for one-off runs; the persistent choice\n // lives in the state file (`shield-swap setup --api-url`).\n const apiUrl = process.env.SHIELD_SWAP_API_URL ?? state.apiUrl\n // The identity store is what makes concurrent swaps safe and unclaimed swaps\n // recoverable, and it is scoped by network because a reservation is only\n // meaningful against the chain it was checked on. Every swap through this\n // client reserves and records automatically.\n const blindedIdentities = fileBlindedIdentityStore(blindedStorePath(network))\n const client = walletClient.extend(\n shieldSwapActions({ api: { baseUrl: apiUrl }, blindedIdentities }),\n )\n try {\n await client.authenticateShieldSwap()\n } catch (error) {\n // A pinned host is invisible state: it lives in a file nobody re-reads, so\n // when the deployment behind it is retired every call fails with a bare 404\n // and nothing points at the pin. Name it, and say how to drop it.\n if (!apiUrl) throw error\n const source =\n process.env.SHIELD_SWAP_API_URL === apiUrl ? 'SHIELD_SWAP_API_URL' : `apiUrl in ${statePath(network)}`\n throw new Error(\n `could not authenticate with the DEX API at ${apiUrl}, which is pinned by ${source} rather than ` +\n `derived from the network. If that deployment is gone, re-pin with \\`shield-swap setup --api-url ` +\n `<origin>\\` or clear the field to fall back to the default for ${network}.`,\n { cause: error },\n )\n }\n\n return { client, account, scanner, state, aleo, network, blindedIdentities }\n}\n\n\n\n/** Polls a predicate until it returns true or attempts run out. */\nexport async function pollUntil(fn: () => Promise<boolean>, attempts: number, intervalMs: number): Promise<boolean> {\n for (let i = 0; i < attempts; i++) {\n if (await fn()) return true\n await new Promise((r) => setTimeout(r, intervalMs))\n }\n return false\n}\n\n/**\n * Resolves key material and stores it. Priority: existing state → imported\n * key (`importKey`) → fresh generation, but only when `allowGenerate` is\n * true. Returning users keep their account; a fresh key is never created\n * silently.\n */\nexport async function ensureKeyMaterial(\n state: ShieldSwapState,\n options: { importKey?: string; allowGenerate?: boolean } = {},\n): Promise<ShieldSwapState> {\n const network = resolveNetwork(state.network)\n await loadNetwork(network) // initializes the WASM the account helpers use\n if (state.privateKey && options.importKey && options.importKey !== state.privateKey) {\n throw new Error(\n `a DIFFERENT account is already configured here (${state.address ?? 'address unknown'}). ` +\n 'Refusing to switch silently — its funds and access live on that key. To use the imported ' +\n `key instead, move or delete the state directory first, then re-run with --private-key-file.`,\n )\n }\n if (!state.privateKey) {\n if (options.importKey) {\n state.privateKey = options.importKey\n } else if (options.allowGenerate) {\n state.privateKey = generateAccount().privateKey\n } else {\n throw new NeedsConfigDecisionError()\n }\n }\n // Derive the address from the key so imported/seeded states are complete.\n const aleo = await loadNetwork(network)\n state.address = aleo.privateKeyToAccount(state.privateKey).address\n saveState(state)\n return state\n}\n\n/** Signals that setup must ask the user about existing config before creating anything. */\nexport class NeedsConfigDecisionError extends Error {\n constructor() {\n super('no key material found and none provided')\n this.name = 'NeedsConfigDecisionError'\n }\n}\n\n\n"],"mappings":"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","names":["token"]}
@@ -0,0 +1,152 @@
1
+ import {
2
+ alarm,
3
+ bold,
4
+ dim,
5
+ green,
6
+ help,
7
+ red,
8
+ yellow
9
+ } from "./chunk-IHYFMX5A.js";
10
+
11
+ // src/shared.ts
12
+ import { parseArgs } from "util";
13
+ var COMMON = {
14
+ network: { type: "string" },
15
+ execute: { type: "boolean" },
16
+ json: { type: "boolean" },
17
+ // `-h` is declared here rather than only on the dispatcher: `parseArgs` rejects
18
+ // undeclared short options, so `shield-swap pools -h` failed with a usage error
19
+ // while `shield-swap -h` printed the help. The two must not disagree.
20
+ help: { type: "boolean", short: "h" },
21
+ "no-color": { type: "boolean" }
22
+ };
23
+ function flags(spec, usage, argv) {
24
+ let values;
25
+ try {
26
+ ;
27
+ ({ values } = parseArgs({
28
+ options: { ...COMMON, ...spec },
29
+ args: argv,
30
+ allowPositionals: false
31
+ }));
32
+ } catch (error) {
33
+ reportUsage(error.message, usage, argv);
34
+ }
35
+ if (values.help) {
36
+ console.log(help(usage));
37
+ process.exit(0);
38
+ }
39
+ return values;
40
+ }
41
+ function basisPoints(value, flag) {
42
+ if (value === void 0) return void 0;
43
+ const bps = Number(value);
44
+ if (!Number.isInteger(bps) || bps < 0 || bps > 1e4) {
45
+ fail(`${flag} takes a whole number of basis points between 0 and 10000, got "${value}". 50 is 0.5%.`);
46
+ }
47
+ return bps;
48
+ }
49
+ function reportUsage(message, usage, argv) {
50
+ if (argv.includes("--json")) {
51
+ console.log(JSON.stringify({ error: { message, usage } }, null, 2));
52
+ } else {
53
+ console.error(`${red(message, "stderr")}
54
+
55
+ ${help(usage)}`);
56
+ }
57
+ process.exit(64);
58
+ }
59
+ var quiet = false;
60
+ function setJsonMode(on) {
61
+ quiet = on;
62
+ }
63
+ function step(message) {
64
+ if (!quiet) console.log(dim(`\xB7 ${message}`));
65
+ }
66
+ function done(message) {
67
+ if (!quiet) console.log(`${green("\u2713")} ${message}`);
68
+ }
69
+ function warn(message) {
70
+ if (!quiet) console.warn(yellow(`! ${message}`, "stderr"));
71
+ }
72
+ function visibleWidth(text) {
73
+ return text.replace(/\u001B\[[0-9;]*m/g, "").length;
74
+ }
75
+ function output(data, human) {
76
+ if (quiet) {
77
+ console.log(JSON.stringify(data, (_key, value) => typeof value === "bigint" ? value.toString() : value, 2));
78
+ return;
79
+ }
80
+ human(data);
81
+ }
82
+ function table(headers, rows, align) {
83
+ const cells = rows.map((row) => headers.map((_, i) => row[i] ?? ""));
84
+ const widths = headers.map((header, i) => Math.max(header.length, ...cells.map((row) => visibleWidth(row[i]))));
85
+ const side = (i) => align?.[i] ?? (i === 0 ? "left" : "right");
86
+ const pad = (cell, i) => {
87
+ const fill = " ".repeat(Math.max(0, widths[i] - visibleWidth(cell)));
88
+ return side(i) === "left" ? cell + fill : fill + cell;
89
+ };
90
+ const line = (row) => ` ${row.map(pad).join(" ")}`.trimEnd();
91
+ console.log("");
92
+ console.log(bold(line(headers)));
93
+ console.log(dim(` ${widths.map((w) => "\u2500".repeat(w)).join(" ")}`));
94
+ for (const row of cells) console.log(line(row));
95
+ }
96
+ function confirmed(options) {
97
+ const banner = options.network === "mainnet" ? alarm("MAINNET \u2014 real funds") : dim(options.network);
98
+ if (!quiet) {
99
+ table(
100
+ ["PLAN", banner],
101
+ options.plan.map(([label, value]) => [label, value]),
102
+ ["left", "left"]
103
+ );
104
+ }
105
+ if (options.execute) return true;
106
+ if (!quiet) console.log(dim("\nnothing submitted. re-run with --execute to send it.\n"));
107
+ return false;
108
+ }
109
+ async function run(main) {
110
+ try {
111
+ await main();
112
+ } catch (error) {
113
+ const err = error;
114
+ reportError(err.message, err.cause);
115
+ process.exitCode = 1;
116
+ }
117
+ }
118
+ function reportError(message, cause) {
119
+ if (quiet) {
120
+ console.log(
121
+ JSON.stringify(
122
+ { error: { message, ...cause instanceof Error ? { cause: cause.message } : {} } },
123
+ null,
124
+ 2
125
+ )
126
+ );
127
+ return;
128
+ }
129
+ console.error(`
130
+ ${red("\u2717", "stderr")} ${message}`);
131
+ if (cause instanceof Error) console.error(dim(` caused by: ${cause.message}`));
132
+ }
133
+ function fail(message, cause) {
134
+ reportError(message, cause);
135
+ process.exit(1);
136
+ }
137
+
138
+ export {
139
+ flags,
140
+ basisPoints,
141
+ reportUsage,
142
+ setJsonMode,
143
+ step,
144
+ done,
145
+ warn,
146
+ output,
147
+ table,
148
+ confirmed,
149
+ run,
150
+ fail
151
+ };
152
+ //# sourceMappingURL=chunk-IBVZHLUT.js.map