@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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared.ts"],"sourcesContent":["/**\n * The conventions every trader script shares: flags, progress, output, and the\n * guard that stands between a plan and a transaction.\n *\n * Kept deliberately small. Each command is meant to be read top to bottom, so\n * the only thing hidden here is the plumbing that would otherwise be identical\n * in twelve files.\n *\n * Three rules hold everywhere:\n *\n * 1. Nothing spends money without `--execute`. Every script prints its plan\n * and stops, so a first run is always safe.\n * 2. `--network mainnet` must be explicit. Mainnet is never a default.\n * 3. `--json` prints one machine-readable object on stdout and nothing else,\n * so an agent or a pipeline can drive the same script a person uses.\n */\nimport { parseArgs } from 'node:util'\nimport { alarm, bold, dim, green, help as helpText, red, yellow } from './color.js'\n\n/** Flags every script accepts, whatever else it adds. */\nconst COMMON = {\n network: { type: 'string' },\n execute: { type: 'boolean' },\n json: { type: 'boolean' },\n // `-h` is declared here rather than only on the dispatcher: `parseArgs` rejects\n // undeclared short options, so `shield-swap pools -h` failed with a usage error\n // while `shield-swap -h` printed the help. The two must not disagree.\n help: { type: 'boolean', short: 'h' },\n 'no-color': { type: 'boolean' },\n} as const\n\nexport type CommonFlags = {\n network?: string\n execute?: boolean\n json?: boolean\n help?: boolean\n 'no-color'?: boolean\n}\n\n/**\n * Parses a command's arguments into the common flags plus its own.\n *\n * Unknown flags are an error rather than ignored: a mistyped `--amont` that\n * silently fell back to a default would submit a transaction the caller did not\n * describe.\n *\n * @param spec The command's own flags, in `parseArgs` option form.\n * @param usage Printed for `--help`, and on a parse error.\n * @param argv The arguments after the subcommand name, as the dispatcher passes\n * them to `main`. Never `process.argv`, which still carries the subcommand\n * itself and would fail `allowPositionals: false`.\n * @returns The parsed values, merged with the common flags.\n */\nexport function flags<T extends Record<string, { type: 'string' | 'boolean'; multiple?: boolean }>>(\n spec: T,\n usage: string,\n argv: string[],\n): CommonFlags & Record<keyof T, string | boolean | string[] | undefined> {\n // Widened deliberately: parseArgs' generics describe the option table, and\n // threading that through this helper buys nothing a command uses.\n let values: Record<string, string | boolean | string[] | undefined>\n // Only the parse is guarded. A wider try would report any later failure as a\n // usage error, and would swallow the `--help` exit below.\n try {\n ;({ values } = parseArgs({\n options: { ...COMMON, ...spec },\n args: argv,\n allowPositionals: false,\n }) as { values: Record<string, string | boolean | string[] | undefined> })\n } catch (error) {\n reportUsage((error as Error).message, usage, argv)\n }\n\n if (values.help) {\n console.log(helpText(usage))\n process.exit(0)\n }\n return values as CommonFlags & Record<keyof T, string | boolean | string[] | undefined>\n}\n\n/**\n * Reads a basis-points flag, rejecting what the arithmetic downstream cannot take.\n *\n * `Number(flag)` yields `NaN` for a typo and a fraction for `0.5`, and neither\n * survives the floor calculation in `planSwap`: it multiplies by\n * `BigInt(10_000 - bps)`, which throws on both. Above 10000 the multiplier goes\n * negative, and below 0 the floor rises above the quote so every swap reverts for\n * demanding more than the pool offers. Checked here so the failure names the flag\n * and costs no network calls, rather than surfacing from inside a plan.\n *\n * @param value The raw flag, or `undefined` when it was not passed.\n * @param flag The flag's name, for the error.\n * @returns The parsed basis points, or `undefined` to leave the default in place.\n */\nexport function basisPoints(value: string | undefined, flag: string): number | undefined {\n if (value === undefined) return undefined\n const bps = Number(value)\n if (!Number.isInteger(bps) || bps < 0 || bps > 10_000) {\n fail(`${flag} takes a whole number of basis points between 0 and 10000, got \"${value}\". 50 is 0.5%.`)\n }\n return bps\n}\n\n/**\n * Reports a malformed invocation and exits `64` (`EX_USAGE`).\n *\n * Used for failures that happen before {@link setJsonMode} can run — an unknown\n * subcommand, or a flag `parseArgs` rejects — so `--json` is read straight from\n * the arguments. A caller that asked for JSON must not get a usage block on\n * stderr and an empty stdout, which is indistinguishable from a crash.\n *\n * @param message What is wrong with the invocation.\n * @param usage The usage block to show alongside it.\n * @param argv The arguments as received, inspected only for `--json`.\n */\nexport function reportUsage(message: string, usage: string, argv: string[]): never {\n if (argv.includes('--json')) {\n console.log(JSON.stringify({ error: { message, usage } }, null, 2))\n } else {\n // The message is the failure; the usage below it is reference material, so\n // only the latter is styled — and never the JSON branch above.\n console.error(`${red(message, 'stderr')}\\n\\n${helpText(usage)}`)\n }\n process.exit(64)\n}\n\n/** True when the script should print machine-readable output only. */\nlet quiet = false\n\n/** Silences progress so `--json` emits exactly one object. */\nexport function setJsonMode(on: boolean): void {\n quiet = on\n}\n\n/**\n * Prints a progress line, unless `--json` asked for silence.\n *\n * These scripts wait on proving and confirmation for tens of seconds at a time.\n * Without narration a human cannot tell a slow step from a hung one, and an\n * agent has nothing to report back.\n */\nexport function step(message: string): void {\n // The whole line is dimmed: progress is scaffolding, and it should recede once\n // the result it was narrating arrives.\n if (!quiet) console.log(dim(`· ${message}`))\n}\n\n/** Prints a completed step. */\nexport function done(message: string): void {\n // Marker only. Colouring the message too would put half the output in green and\n // leave nothing for it to stand out against.\n if (!quiet) console.log(`${green('✓')} ${message}`)\n}\n\n/** Prints a warning that does not stop the script. */\nexport function warn(message: string): void {\n if (!quiet) console.warn(yellow(`! ${message}`, 'stderr'))\n}\n\n/**\n * Counts the columns text occupies, ignoring ANSI styling.\n *\n * A style is ESC [ … m and prints nothing, so measuring the raw string would\n * count bytes that take no space and shift every column right of a coloured cell.\n */\nfunction visibleWidth(text: string): number {\n // eslint-disable-next-line no-control-regex\n return text.replace(/\\u001B\\[[0-9;]*m/g, '').length\n}\n\n/**\n * Prints the result: the JSON object under `--json`, otherwise the human lines.\n *\n * @param data The machine-readable result.\n * @param human Called instead when a person is reading. Receives the same data.\n */\nexport function output<T>(data: T, human: (data: T) => void): void {\n if (quiet) {\n console.log(JSON.stringify(data, (_key, value) => (typeof value === 'bigint' ? value.toString() : value), 2))\n return\n }\n human(data)\n}\n\n/**\n * Prints rows under a header, each column sized to its widest cell.\n *\n * Every script that lists things prints the same shape, and the part worth\n * getting right once is the width: a column narrower than its own header lets\n * the header spill into the next one, which silently misaligns the whole table.\n *\n * Numbers are right-aligned and labels left-aligned by default, since that is\n * what makes magnitudes comparable down a column. Amounts that need their\n * decimal points aligned should be padded by the caller before they arrive here\n * — this pads cells, it does not parse them.\n *\n * @param headers Column headings, also the minimum width of each column.\n * @param rows One array of cells per row, in header order. Short rows are padded\n * with empty cells rather than throwing.\n * @param align Per-column alignment. Defaults to the first column left and the\n * rest right; pass explicitly when a trailing id or status reads better left.\n */\nexport function table(\n headers: string[],\n rows: string[][],\n align?: ReadonlyArray<'left' | 'right'>,\n): void {\n const cells = rows.map((row) => headers.map((_, i) => row[i] ?? ''))\n // Measured without styling: a coloured cell carries escape codes that occupy no\n // columns, so `padEnd` on the raw string would indent every later column by the\n // length of the codes.\n const widths = headers.map((header, i) => Math.max(header.length, ...cells.map((row) => visibleWidth(row[i]!))))\n const side = (i: number) => align?.[i] ?? (i === 0 ? 'left' : 'right')\n const pad = (cell: string, i: number) => {\n const fill = ' '.repeat(Math.max(0, widths[i]! - visibleWidth(cell)))\n return side(i) === 'left' ? cell + fill : fill + cell\n }\n const line = (row: string[]) => ` ${row.map(pad).join(' ')}`.trimEnd()\n\n console.log('')\n console.log(bold(line(headers)))\n console.log(dim(` ${widths.map((w) => '─'.repeat(w)).join(' ')}`))\n for (const row of cells) console.log(line(row))\n}\n\n/**\n * Stops before spending unless `--execute` was passed.\n *\n * The plan is printed either way, so the dry run and the real run differ only in\n * whether a transaction follows. On mainnet the network is named in the plan\n * because the same command against the wrong network is the expensive mistake.\n *\n * @param options.execute Whether `--execute` was passed.\n * @param options.network The resolved network.\n * @param options.plan Label/value pairs describing exactly what would happen,\n * rendered as a table. An empty label continues the row above it, which is how\n * a two-sided amount states its second side.\n * @returns `true` when the caller should proceed.\n */\nexport function confirmed(options: {\n execute?: boolean\n network: string\n plan: ReadonlyArray<readonly [string, string]>\n}): boolean {\n // The network heads the value column rather than sitting in a sentence above\n // it, so the one thing worth double-checking before spending is level with the\n // amounts being spent.\n const banner =\n options.network === 'mainnet' ? alarm('MAINNET — real funds') : dim(options.network)\n if (!quiet) {\n table(\n ['PLAN', banner],\n options.plan.map(([label, value]) => [label, value]),\n ['left', 'left'],\n )\n }\n if (options.execute) return true\n if (!quiet) console.log(dim('\\nnothing submitted. re-run with --execute to send it.\\n'))\n return false\n}\n\n/**\n * Runs a script's body, reporting a failure as a line rather than a stack.\n *\n * A trader reading a wall of frames learns less than they would from the\n * message, and every error these scripts surface is written to be actionable.\n * The cause chain is printed when there is one, because the SDK attaches the\n * underlying failure rather than replacing it.\n */\nexport async function run(main: () => Promise<void>): Promise<void> {\n try {\n await main()\n } catch (error) {\n const err = error as Error & { cause?: unknown }\n reportError(err.message, err.cause)\n process.exitCode = 1\n }\n}\n\n/**\n * Renders a failure on the channel the caller asked for.\n *\n * Under --json the failure has to arrive as an object too: a message on stderr\n * with nothing on stdout leaves a caller that parses stdout with an empty string\n * and no way to tell a failure from a script that found nothing.\n *\n * @param message What went wrong, already written to be actionable.\n * @param cause The underlying error when the SDK chained one.\n */\nfunction reportError(message: string, cause?: unknown): void {\n if (quiet) {\n console.log(\n JSON.stringify(\n { error: { message, ...(cause instanceof Error ? { cause: cause.message } : {}) } },\n null,\n 2,\n ),\n )\n return\n }\n console.error(`\\n${red('✗', 'stderr')} ${message}`)\n if (cause instanceof Error) console.error(dim(` caused by: ${cause.message}`))\n}\n\n/**\n * Reports a failure and exits, for checks that run before {@link run}.\n *\n * Flag validation happens at the top of `main`, outside `run`'s try — a bare\n * `throw` there reaches the dispatcher as an unhandled rejection and prints a\n * Node stack trace, which carries no JSON and buries the one line the caller\n * needs. Returns `never`, so TypeScript narrows the checked value afterwards.\n *\n * @param message What is wrong with the invocation.\n * @param cause The underlying error, when there is one.\n */\nexport function fail(message: string, cause?: unknown): never {\n reportError(message, cause)\n process.exit(1)\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,54 @@
1
+ // src/color.ts
2
+ import { styleText } from "util";
3
+ var disabled = false;
4
+ function setNoColor(on) {
5
+ disabled = on;
6
+ }
7
+ function paint(format, text, stream = "stdout") {
8
+ if (disabled) return text;
9
+ return styleText(format, text, { stream: stream === "stderr" ? process.stderr : process.stdout });
10
+ }
11
+ var dim = (text) => paint("dim", text);
12
+ var green = (text) => paint("green", text);
13
+ var yellow = (text, stream = "stdout") => paint("yellow", text, stream);
14
+ var red = (text, stream = "stdout") => paint("red", text, stream);
15
+ var bold = (text) => paint("bold", text);
16
+ var alarm = (text) => paint(["red", "bold"], text);
17
+ var cyan = (text) => paint("cyan", text);
18
+ var greenBright = (text) => paint("greenBright", text);
19
+ var greenDim = (text) => paint(["green", "dim"], text);
20
+ function help(text) {
21
+ if (disabled) return text;
22
+ return text.split("\n").map((line, index) => {
23
+ if (index === 0) {
24
+ const split = line.indexOf(" \u2014 ");
25
+ return split === -1 ? bold(line) : `${bold(line.slice(0, split))}${dim(line.slice(split))}`;
26
+ }
27
+ const row = /^( {2})(\S.*?)( {2,})(.*)$/.exec(line);
28
+ if (row) {
29
+ const [, indent, name, gap, description] = row;
30
+ const styled = name.startsWith("-") ? (
31
+ // Flag names cyan; their `<placeholders>` dim, since those are the part
32
+ // a reader substitutes rather than types.
33
+ name.replace(/(-{1,2}[\w-]+)/g, (flag) => cyan(flag)).replace(/(<[^>]+>)/g, (placeholder) => dim(placeholder))
34
+ ) : cyan(name);
35
+ return `${indent}${styled}${gap}${description}`;
36
+ }
37
+ if (/^\S.*:$/.test(line)) return bold(line);
38
+ return line;
39
+ }).join("\n");
40
+ }
41
+
42
+ export {
43
+ setNoColor,
44
+ dim,
45
+ green,
46
+ yellow,
47
+ red,
48
+ bold,
49
+ alarm,
50
+ greenBright,
51
+ greenDim,
52
+ help
53
+ };
54
+ //# sourceMappingURL=chunk-IHYFMX5A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/color.ts"],"sourcesContent":["/**\n * The CLI's colour palette, used to carry meaning rather than decoration.\n *\n * Colour is applied only where it says something a reader would otherwise have\n * to parse: which line is progress and which is a result, whether a pool is\n * tradeable, and above all whether a plan is about to spend real funds. Amounts\n * are never coloured — a green number invites a reading (\"good\", \"gain\") that the\n * figure does not carry.\n *\n * `styleText` handles the environment: it strips codes when the stream is not a\n * TTY, and honours `NO_COLOR` and `FORCE_COLOR`. So piping to a file, running in\n * CI, and `--json` all come out plain without a check here. `--no-color` is\n * additionally respected for a caller on a TTY who wants none.\n */\nimport { styleText } from 'node:util'\n\n/** Set from `--no-color`; `styleText` covers NO_COLOR and non-TTY streams itself. */\nlet disabled = false\n\n/** Turns colour off for the rest of the process. */\nexport function setNoColor(on: boolean): void {\n disabled = on\n}\n\ntype Format = Parameters<typeof styleText>[0]\n\n/**\n * Styles text, or returns it unchanged when colour is off.\n *\n * @param format A `styleText` format, or an array of them.\n * @param text The text to style.\n * @param stream Which stream the text is bound for — `styleText` decides whether\n * to emit codes by inspecting it, so a warning bound for stderr must say so or\n * it inherits stdout's answer.\n */\nfunction paint(format: Format, text: string, stream: 'stdout' | 'stderr' = 'stdout'): string {\n if (disabled) return text\n return styleText(format, text, { stream: stream === 'stderr' ? process.stderr : process.stdout })\n}\n\n/** Secondary text: progress lines, table rules, absent values. */\nexport const dim = (text: string) => paint('dim', text)\n/** A completed step or a healthy state. */\nexport const green = (text: string) => paint('green', text)\n/** Something that did not stop the run but changes what the reader should expect. */\nexport const yellow = (text: string, stream: 'stdout' | 'stderr' = 'stdout') => paint('yellow', text, stream)\n/** A failure, or a state that blocks every operation on the thing described. */\nexport const red = (text: string, stream: 'stdout' | 'stderr' = 'stdout') => paint('red', text, stream)\n/** Column headings, and the one banner that must not be skimmed past. */\nexport const bold = (text: string) => paint('bold', text)\n/** Reserved for the mainnet banner: the only place both weight and colour apply. */\nexport const alarm = (text: string) => paint(['red', 'bold'], text)\n/** A flag name, or a subcommand in the main listing. */\nexport const cyan = (text: string) => paint('cyan', text)\n/**\n * A balance that can fund a trade — the private side.\n *\n * Brighter than {@link green} so the two sides of a balance read apart at a\n * glance: a swap spends records, so this is the figure that decides whether a\n * trade is possible.\n */\nexport const greenBright = (text: string) => paint('greenBright', text)\n/** A balance that cannot fund a trade until it is wrapped or transferred. */\nexport const greenDim = (text: string) => paint(['green', 'dim'], text)\n\n/**\n * Colours a help screen by its structure, leaving the text itself plain.\n *\n * Applied at print time rather than written into the usage strings, so those stay\n * greppable, diffable, and safe to embed in a JSON error. Every rule keys off\n * shape a help screen already has:\n *\n * - the first line names the command, so its `shield-swap x` half is bold and\n * the description after the dash is dim;\n * - a two-column row beginning with a dash is a flag: the flag names are cyan\n * and their `<placeholders>` dim;\n * - a two-column row that does not is a subcommand in the main listing, so the\n * name is cyan;\n * - an unindented line ending in a colon heads a section, so it is bold.\n *\n * Prose paragraphs are left alone. Colour here is for scanning to the flag you\n * want, and prose is not something a reader scans for.\n *\n * @param text The plain usage block.\n * @returns The same text with styling applied, or unchanged when colour is off.\n */\nexport function help(text: string): string {\n if (disabled) return text\n return text\n .split('\\n')\n .map((line, index) => {\n if (index === 0) {\n // `shield-swap liquidity — add to or withdraw…`: name bold, summary dim.\n const split = line.indexOf(' — ')\n return split === -1\n ? bold(line)\n : `${bold(line.slice(0, split))}${dim(line.slice(split))}`\n }\n // Two-column rows: the left column is the name, the right its description.\n // Anchored to an indent of exactly two spaces, which is what separates a\n // flag or subcommand from the deeply indented continuation of a description\n // above it — those are prose and must not be read as names.\n const row = /^( {2})(\\S.*?)( {2,})(.*)$/.exec(line)\n if (row) {\n const [, indent, name, gap, description] = row as unknown as [string, string, string, string, string]\n const styled = name.startsWith('-')\n ? // Flag names cyan; their `<placeholders>` dim, since those are the part\n // a reader substitutes rather than types.\n name\n .replace(/(-{1,2}[\\w-]+)/g, (flag) => cyan(flag))\n .replace(/(<[^>]+>)/g, (placeholder) => dim(placeholder))\n : cyan(name)\n return `${indent}${styled}${gap}${description}`\n }\n // `Usage:` and `Common to every command below setup:` head their sections.\n if (/^\\S.*:$/.test(line)) return bold(line)\n return line\n })\n .join('\\n')\n}\n"],"mappings":";AAcA,SAAS,iBAAiB;AAG1B,IAAI,WAAW;AAGR,SAAS,WAAW,IAAmB;AAC5C,aAAW;AACb;AAaA,SAAS,MAAM,QAAgB,MAAc,SAA8B,UAAkB;AAC3F,MAAI,SAAU,QAAO;AACrB,SAAO,UAAU,QAAQ,MAAM,EAAE,QAAQ,WAAW,WAAW,QAAQ,SAAS,QAAQ,OAAO,CAAC;AAClG;AAGO,IAAM,MAAM,CAAC,SAAiB,MAAM,OAAO,IAAI;AAE/C,IAAM,QAAQ,CAAC,SAAiB,MAAM,SAAS,IAAI;AAEnD,IAAM,SAAS,CAAC,MAAc,SAA8B,aAAa,MAAM,UAAU,MAAM,MAAM;AAErG,IAAM,MAAM,CAAC,MAAc,SAA8B,aAAa,MAAM,OAAO,MAAM,MAAM;AAE/F,IAAM,OAAO,CAAC,SAAiB,MAAM,QAAQ,IAAI;AAEjD,IAAM,QAAQ,CAAC,SAAiB,MAAM,CAAC,OAAO,MAAM,GAAG,IAAI;AAE3D,IAAM,OAAO,CAAC,SAAiB,MAAM,QAAQ,IAAI;AAQjD,IAAM,cAAc,CAAC,SAAiB,MAAM,eAAe,IAAI;AAE/D,IAAM,WAAW,CAAC,SAAiB,MAAM,CAAC,SAAS,KAAK,GAAG,IAAI;AAuB/D,SAAS,KAAK,MAAsB;AACzC,MAAI,SAAU,QAAO;AACrB,SAAO,KACJ,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,UAAU;AACpB,QAAI,UAAU,GAAG;AAEf,YAAM,QAAQ,KAAK,QAAQ,UAAK;AAChC,aAAO,UAAU,KACb,KAAK,IAAI,IACT,GAAG,KAAK,KAAK,MAAM,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,KAAK,MAAM,KAAK,CAAC,CAAC;AAAA,IAC5D;AAKA,UAAM,MAAM,6BAA6B,KAAK,IAAI;AAClD,QAAI,KAAK;AACP,YAAM,CAAC,EAAE,QAAQ,MAAM,KAAK,WAAW,IAAI;AAC3C,YAAM,SAAS,KAAK,WAAW,GAAG;AAAA;AAAA;AAAA,QAG9B,KACG,QAAQ,mBAAmB,CAAC,SAAS,KAAK,IAAI,CAAC,EAC/C,QAAQ,cAAc,CAAC,gBAAgB,IAAI,WAAW,CAAC;AAAA,UAC1D,KAAK,IAAI;AACb,aAAO,GAAG,MAAM,GAAG,MAAM,GAAG,GAAG,GAAG,WAAW;AAAA,IAC/C;AAEA,QAAI,UAAU,KAAK,IAAI,EAAG,QAAO,KAAK,IAAI;AAC1C,WAAO;AAAA,EACT,CAAC,EACA,KAAK,IAAI;AACd;","names":[]}
@@ -0,0 +1,240 @@
1
+ import {
2
+ confirmed,
3
+ done,
4
+ flags,
5
+ output,
6
+ run,
7
+ step,
8
+ warn
9
+ } from "./chunk-IBVZHLUT.js";
10
+ import "./chunk-IHYFMX5A.js";
11
+ import {
12
+ formatAmount,
13
+ loadSession,
14
+ pollUntil
15
+ } from "./chunk-2OT6LZPW.js";
16
+
17
+ // src/commands/collect.ts
18
+ var USAGE = `shield-swap collect \u2014 withdraw a position's owed tokens, and optionally close it
19
+
20
+ --position <id> one position; omit for every owed position
21
+ --close burn each position left fully drained
22
+ --booked-only request only what the chain has already booked,
23
+ leaving fees accrued since the last operation
24
+ --network <testnet|mainnet> default testnet
25
+ --execute actually submit
26
+ --json machine-readable output
27
+
28
+ Payment goes to the withdrawal address fixed at mint, not to whoever runs this.`;
29
+ async function main(argv) {
30
+ const args = flags(
31
+ { position: { type: "string" }, close: { type: "boolean" }, "booked-only": { type: "boolean" } },
32
+ USAGE,
33
+ argv
34
+ );
35
+ await run(async () => {
36
+ const { client, network } = await loadSession({ network: args.network });
37
+ done(`session on ${network}`);
38
+ step("scanning position records and joining chain state");
39
+ const owned = args.position ? await client.getOwnedPosition({ positionTokenId: args.position }).then((position) => position ? [position] : []) : await client.getOwnedPositions();
40
+ if (args.position && !owned.length) {
41
+ throw new Error(
42
+ `this account holds no position record for ${args.position} on ${network}. List what it does hold with \`shield-swap positions\`.`
43
+ );
44
+ }
45
+ const tokens = await client.listTokens();
46
+ const infoOf = (id) => tokens.find((token) => token.id === id);
47
+ const candidates = [];
48
+ for (const position of owned) {
49
+ const token0 = infoOf(position.token0Id);
50
+ const token1 = infoOf(position.token1Id);
51
+ const label = `${token0?.symbol ?? "?"}/${token1?.symbol ?? "?"}`;
52
+ if (position.frozen) {
53
+ warn(`skipping ${position.positionTokenId}: frozen, so a collect reverts until an admin unfreezes it`);
54
+ continue;
55
+ }
56
+ if (!position.state) {
57
+ warn(
58
+ `skipping ${position.positionTokenId}: no entry in the positions mapping \u2014 a mint still finalizing, or one already burned whose record the scanner still serves`
59
+ );
60
+ continue;
61
+ }
62
+ if (!token0 || !token1) {
63
+ warn(`skipping ${position.positionTokenId}: the registry does not describe both of its tokens`);
64
+ continue;
65
+ }
66
+ const fresh = await client.getOwnedPosition({ positionTokenId: position.positionTokenId });
67
+ const onchain = fresh?.state;
68
+ if (!onchain) {
69
+ warn(`skipping ${position.positionTokenId}: its positions entry disappeared between the two reads`);
70
+ continue;
71
+ }
72
+ const request0 = args["booked-only"] ? onchain.tokensOwed0 : onchain.uncollectedFees0;
73
+ const request1 = args["booked-only"] ? onchain.tokensOwed1 : onchain.uncollectedFees1;
74
+ if (request0 === 0n && request1 === 0n) {
75
+ if (args.position) {
76
+ warn(
77
+ `position ${position.positionTokenId} (${label}) is owed nothing. ` + (position.state.liquidity > 0n ? "Withdraw some liquidity first with `shield-swap liquidity --position <id> --decrease --percent 100`." : "It is drained and swept \u2014 `--close` would burn it.")
78
+ );
79
+ }
80
+ continue;
81
+ }
82
+ candidates.push({
83
+ position,
84
+ label,
85
+ token0,
86
+ token1,
87
+ booked0: onchain.tokensOwed0,
88
+ booked1: onchain.tokensOwed1,
89
+ request0,
90
+ request1,
91
+ liquidity: onchain.liquidity
92
+ });
93
+ }
94
+ if (!candidates.length) {
95
+ output({ network, submitted: false, collected: [], burned: [], failed: [] }, () => {
96
+ console.log("\nNothing to collect. `shield-swap positions` shows what each position is owed.");
97
+ });
98
+ return;
99
+ }
100
+ const planLines = [];
101
+ for (const entry of candidates) {
102
+ planLines.push([entry.label, `${entry.position.positionTokenId.slice(0, 20)}\u2026`]);
103
+ planLines.push([
104
+ "take",
105
+ `${formatAmount(entry.request0, entry.token0.decimals, entry.token0.symbol)} + ${formatAmount(entry.request1, entry.token1.decimals, entry.token1.symbol)}`
106
+ ]);
107
+ const accrued0 = entry.request0 - entry.booked0;
108
+ const accrued1 = entry.request1 - entry.booked1;
109
+ if (accrued0 > 0n || accrued1 > 0n) {
110
+ planLines.push([
111
+ "of it",
112
+ `${formatAmount(accrued0, entry.token0.decimals, entry.token0.symbol)} + ${formatAmount(accrued1, entry.token1.decimals, entry.token1.symbol)} is fees earned since the last operation, which the finalize settles first`
113
+ ]);
114
+ }
115
+ planLines.push(["pays", entry.position.withdrawal]);
116
+ if (args.close) {
117
+ planLines.push([
118
+ "then",
119
+ entry.liquidity === 0n ? "burn the drained position" : `NOT burned \u2014 it still holds ${entry.liquidity} liquidity`
120
+ ]);
121
+ }
122
+ }
123
+ if (!confirmed({ execute: args.execute, network, plan: planLines })) {
124
+ output(
125
+ {
126
+ network,
127
+ submitted: false,
128
+ positions: candidates.map((entry) => ({
129
+ positionTokenId: entry.position.positionTokenId,
130
+ poolKey: entry.position.poolKey,
131
+ request0: entry.request0,
132
+ request1: entry.request1,
133
+ booked0: entry.booked0,
134
+ booked1: entry.booked1,
135
+ liquidity: entry.liquidity
136
+ }))
137
+ },
138
+ () => {
139
+ }
140
+ );
141
+ return;
142
+ }
143
+ const importsByPool = /* @__PURE__ */ new Map();
144
+ const importsFor = async (entry) => {
145
+ const cached = importsByPool.get(entry.position.poolKey);
146
+ if (cached) return cached;
147
+ const resolved = await client.resolveDexImports({
148
+ tokenPrograms: [entry.token0.ammTokenProgram, entry.token1.ammTokenProgram].filter(
149
+ (program) => !!program
150
+ )
151
+ });
152
+ importsByPool.set(entry.position.poolKey, resolved);
153
+ return resolved;
154
+ };
155
+ const collected = [];
156
+ const burned = [];
157
+ const failed = [];
158
+ for (const entry of candidates) {
159
+ const id = entry.position.positionTokenId;
160
+ try {
161
+ step(`collecting ${entry.label} from ${id.slice(0, 20)}\u2026 \u2014 this takes a minute or two`);
162
+ const result = await client.collect({
163
+ positionTokenId: id,
164
+ poolKey: entry.position.poolKey,
165
+ amount0Requested: entry.request0,
166
+ amount1Requested: entry.request1,
167
+ imports: await importsFor(entry)
168
+ });
169
+ done(
170
+ `collected ${formatAmount(entry.request0, entry.token0.decimals, entry.token0.symbol)} + ${formatAmount(entry.request1, entry.token1.decimals, entry.token1.symbol)} (tx ${result.transactionId})`
171
+ );
172
+ collected.push({ positionTokenId: id, transactionId: result.transactionId, amount0: entry.request0, amount1: entry.request1 });
173
+ const cleared = await pollUntil(
174
+ async () => {
175
+ const onchain = await client.getPosition({ positionTokenId: id });
176
+ return !!onchain && onchain.tokens_owed0 === 0n && onchain.tokens_owed1 === 0n;
177
+ },
178
+ 10,
179
+ 3e3
180
+ );
181
+ if (!cleared) {
182
+ warn(
183
+ "the position still shows an owed balance \u2014 either the mapping has not caught up, or fees accrued while this ran and are collectable on the next pass"
184
+ );
185
+ }
186
+ if (!args.close) continue;
187
+ if (entry.liquidity > 0n) {
188
+ warn(
189
+ `not burning ${id.slice(0, 20)}\u2026: a burn needs zero liquidity, and it holds ${entry.liquidity}. Drain it with \`shield-swap liquidity --position <id> --decrease --percent 100\` first.`
190
+ );
191
+ continue;
192
+ }
193
+ if (!cleared) {
194
+ warn(`not burning ${id.slice(0, 20)}\u2026: a burn needs a zero owed balance, and this one is not zero yet`);
195
+ continue;
196
+ }
197
+ const staleTag = entry.position.record.tag;
198
+ step("waiting for the scanner to serve the position record the collect created");
199
+ const indexed = await pollUntil(
200
+ async () => {
201
+ const current = await client.getOwnedPosition({ positionTokenId: id }).catch(() => null);
202
+ return !!current && current.record.tag !== staleTag;
203
+ },
204
+ 30,
205
+ 2e3
206
+ );
207
+ if (!indexed) {
208
+ warn(
209
+ `not burning ${id.slice(0, 20)}\u2026: the scanner has not served the record the collect created (60s). Re-run with --close once it has \u2014 a burn against the spent record would be dropped.`
210
+ );
211
+ continue;
212
+ }
213
+ step(`burning ${id.slice(0, 20)}\u2026`);
214
+ const burn = await client.burn({ positionTokenId: id, poolKey: entry.position.poolKey });
215
+ done(`burned (tx ${burn.transactionId})`);
216
+ burned.push({ positionTokenId: id, transactionId: burn.transactionId });
217
+ } catch (error) {
218
+ const message = error.message;
219
+ warn(`${id.slice(0, 20)}\u2026 failed: ${message}`);
220
+ failed.push({ positionTokenId: id, error: message });
221
+ }
222
+ }
223
+ output(
224
+ { network, submitted: true, collected, burned, failed },
225
+ (data) => {
226
+ console.log(`
227
+ Collected from ${data.collected.length} of ${candidates.length} position(s).`);
228
+ if (data.burned.length) console.log(`Burned ${data.burned.length} drained position(s).`);
229
+ if (data.failed.length) {
230
+ console.log(`${data.failed.length} failed \u2014 the amounts stay owed and can be swept again.`);
231
+ }
232
+ console.log("New balances: `shield-swap balances`.");
233
+ }
234
+ );
235
+ });
236
+ }
237
+ export {
238
+ main
239
+ };
240
+ //# sourceMappingURL=collect-G3GNFL57.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/collect.ts"],"sourcesContent":["/**\n * Collect — sweep what a position is owed into records the account holds.\n *\n * `collect` asks for explicit amounts and pays the withdrawal address fixed at\n * mint, so there is nothing to choose: the amounts come from chain. Two figures\n * matter and they are not the same one.\n *\n * `tokens_owed0/1` in the positions mapping is what the contract has already\n * booked — principal from an earlier decrease, plus fees settled at that time.\n *\n * Fees earned since then are not booked yet. The finalize settles them before\n * it checks the request, so they are collectable today; `getOwnedPosition`\n * mirrors that settlement as `uncollectedFees0/1`. For a drained position the\n * two figures are identical, because fee accrual scales with liquidity.\n *\n * So the request is the mirrored total, and `--booked-only` falls back to the\n * chain's booked figure alone for a caller who wants no estimate in the loop.\n *\n * `--close` burns the position afterwards. A burn needs zero liquidity and zero\n * owed, so it only applies to a position already drained with\n * `shield-swap liquidity --decrease --percent 100`.\n *\n * SPENDS REAL FUNDS with --execute. Without it, prints the plan and stops.\n *\n * Usage:\n * shield-swap collect # what every position is owed\n * shield-swap collect --execute # collect from all of them\n * shield-swap collect --position <id> --execute\n * shield-swap collect --position <id> --close --execute # collect, then burn\n * shield-swap collect --booked-only --execute\n */\nimport type { OwnedPosition, TokenInfo } from '@provablehq/shield-swap-sdk'\nimport { loadSession, formatAmount, pollUntil } from '../session.js'\nimport { flags, step, done, warn, output, confirmed, run } from '../shared.js'\n\nconst USAGE = `shield-swap collect — withdraw a position's owed tokens, and optionally close it\n\n --position <id> one position; omit for every owed position\n --close burn each position left fully drained\n --booked-only request only what the chain has already booked,\n leaving fees accrued since the last operation\n --network <testnet|mainnet> default testnet\n --execute actually submit\n --json machine-readable output\n\nPayment goes to the withdrawal address fixed at mint, not to whoever runs this.`\n\n/**\n * A position with something to sweep, and the two figures behind the request.\n *\n * @property booked0 What the positions mapping has already credited in token0.\n * @property booked1 Token1 counterpart of `booked0`.\n * @property request0 What this run asks for in token0 — the booked figure plus\n * the fees the finalize settles first, unless `--booked-only`.\n * @property request1 Token1 counterpart of `request0`.\n * @property liquidity The position's live liquidity, which decides whether\n * `--close` can burn it.\n */\ntype Owed = {\n position: OwnedPosition\n label: string\n token0: TokenInfo\n token1: TokenInfo\n booked0: bigint\n booked1: bigint\n request0: bigint\n request1: bigint\n liquidity: bigint\n}\n\n/**\n * Runs the `collect` 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(\n { position: { type: 'string' }, close: { type: 'boolean' }, 'booked-only': { type: 'boolean' } },\n USAGE,\n argv,\n )\n\n await run(async () => {\n const { client, network } = await loadSession({ network: args.network as string | undefined })\n done(`session on ${network}`)\n\n step('scanning position records and joining chain state')\n const owned = args.position\n ? await client\n .getOwnedPosition({ positionTokenId: args.position as string })\n .then((position) => (position ? [position] : []))\n : await client.getOwnedPositions()\n if (args.position && !owned.length) {\n throw new Error(\n `this account holds no position record for ${args.position as string} on ${network}. ` +\n 'List what it does hold with `shield-swap positions`.',\n )\n }\n\n const tokens = await client.listTokens()\n const infoOf = (id: string) => tokens.find((token) => token.id === id)\n\n // What each position can actually be asked for, re-read per position: the scan\n // above can be seconds old by the time a proof lands, and a request above what\n // the finalize allows reverts and still costs a fee.\n const candidates: Owed[] = []\n for (const position of owned) {\n const token0 = infoOf(position.token0Id)\n const token1 = infoOf(position.token1Id)\n const label = `${token0?.symbol ?? '?'}/${token1?.symbol ?? '?'}`\n if (position.frozen) {\n warn(`skipping ${position.positionTokenId}: frozen, so a collect reverts until an admin unfreezes it`)\n continue\n }\n if (!position.state) {\n warn(\n `skipping ${position.positionTokenId}: no entry in the positions mapping — a mint still ` +\n 'finalizing, or one already burned whose record the scanner still serves',\n )\n continue\n }\n if (!token0 || !token1) {\n warn(`skipping ${position.positionTokenId}: the registry does not describe both of its tokens`)\n continue\n }\n // Re-read the whole joined state, not just the mapping entry: the requested\n // figure below is the settlement mirror, which is derived from the position's\n // checkpoint AND the pool's live fee growth. Refreshing only `tokens_owed`\n // would leave the default path asking for a mirror computed at scan time,\n // which is exactly the stale read this guards against.\n const fresh = await client.getOwnedPosition({ positionTokenId: position.positionTokenId })\n const onchain = fresh?.state\n if (!onchain) {\n warn(`skipping ${position.positionTokenId}: its positions entry disappeared between the two reads`)\n continue\n }\n // Booked is what the chain has already credited; the default asks for the\n // mirror of the settlement the finalize performs first, which is never below\n // the booked figure.\n const request0 = args['booked-only'] ? onchain.tokensOwed0 : onchain.uncollectedFees0\n const request1 = args['booked-only'] ? onchain.tokensOwed1 : onchain.uncollectedFees1\n if (request0 === 0n && request1 === 0n) {\n // Warned rather than printed, so `--json` still emits exactly one object.\n if (args.position) {\n warn(\n `position ${position.positionTokenId} (${label}) is owed nothing. ` +\n (position.state.liquidity > 0n\n ? 'Withdraw some liquidity first with `shield-swap liquidity --position <id> --decrease --percent 100`.'\n : 'It is drained and swept — `--close` would burn it.'),\n )\n }\n continue\n }\n candidates.push({\n position,\n label,\n token0,\n token1,\n booked0: onchain.tokensOwed0,\n booked1: onchain.tokensOwed1,\n request0,\n request1,\n liquidity: onchain.liquidity,\n })\n }\n\n if (!candidates.length) {\n output({ network, submitted: false, collected: [], burned: [], failed: [] }, () => {\n console.log('\\nNothing to collect. `shield-swap positions` shows what each position is owed.')\n })\n return\n }\n\n // One labelled row per fact, with the position id as the row that opens each\n // group: the label column carries the structure the indentation used to.\n const planLines: Array<readonly [string, string]> = []\n for (const entry of candidates) {\n planLines.push([entry.label, `${entry.position.positionTokenId.slice(0, 20)}…`])\n planLines.push([\n 'take',\n `${formatAmount(entry.request0, entry.token0.decimals, entry.token0.symbol)} + ` +\n `${formatAmount(entry.request1, entry.token1.decimals, entry.token1.symbol)}`,\n ])\n const accrued0 = entry.request0 - entry.booked0\n const accrued1 = entry.request1 - entry.booked1\n if (accrued0 > 0n || accrued1 > 0n) {\n planLines.push([\n 'of it',\n `${formatAmount(accrued0, entry.token0.decimals, entry.token0.symbol)} + ` +\n `${formatAmount(accrued1, entry.token1.decimals, entry.token1.symbol)} is fees earned since the ` +\n 'last operation, which the finalize settles first',\n ])\n }\n planLines.push(['pays', entry.position.withdrawal])\n if (args.close) {\n planLines.push([\n 'then',\n entry.liquidity === 0n\n ? 'burn the drained position'\n : `NOT burned — it still holds ${entry.liquidity} liquidity`,\n ])\n }\n }\n if (!confirmed({ execute: args.execute as boolean | undefined, network, plan: planLines })) {\n output(\n {\n network,\n submitted: false,\n positions: candidates.map((entry) => ({\n positionTokenId: entry.position.positionTokenId,\n poolKey: entry.position.poolKey,\n request0: entry.request0,\n request1: entry.request1,\n booked0: entry.booked0,\n booked1: entry.booked1,\n liquidity: entry.liquidity,\n })),\n },\n () => {},\n )\n return\n }\n\n // One imports map per pool rather than per position: it is a network read of\n // each token program's source, and positions in a pool share both tokens.\n const importsByPool = new Map<string, Record<string, string>>()\n const importsFor = async (entry: Owed) => {\n const cached = importsByPool.get(entry.position.poolKey)\n if (cached) return cached\n const resolved = await client.resolveDexImports({\n tokenPrograms: [entry.token0.ammTokenProgram, entry.token1.ammTokenProgram].filter(\n (program): program is string => !!program,\n ),\n })\n importsByPool.set(entry.position.poolKey, resolved)\n return resolved\n }\n\n const collected: Array<{ positionTokenId: string; transactionId: string; amount0: bigint; amount1: bigint }> = []\n const burned: Array<{ positionTokenId: string; transactionId: string }> = []\n const failed: Array<{ positionTokenId: string; error: string }> = []\n\n // Sequential: each collect spends and re-issues its position record, and the\n // proving time dwarfs any gain from overlapping them.\n for (const entry of candidates) {\n const id = entry.position.positionTokenId\n try {\n step(`collecting ${entry.label} from ${id.slice(0, 20)}… — this takes a minute or two`)\n const result = await client.collect({\n positionTokenId: id,\n poolKey: entry.position.poolKey,\n amount0Requested: entry.request0,\n amount1Requested: entry.request1,\n imports: await importsFor(entry),\n })\n done(\n `collected ${formatAmount(entry.request0, entry.token0.decimals, entry.token0.symbol)} + ` +\n `${formatAmount(entry.request1, entry.token1.decimals, entry.token1.symbol)} (tx ${result.transactionId})`,\n )\n collected.push({ positionTokenId: id, transactionId: result.transactionId, amount0: entry.request0, amount1: entry.request1 })\n\n // Mapping reads lag their writes, so the cleared balance is expected to\n // take a few seconds to show.\n const cleared = await pollUntil(\n async () => {\n const onchain = await client.getPosition({ positionTokenId: id })\n return !!onchain && onchain.tokens_owed0 === 0n && onchain.tokens_owed1 === 0n\n },\n 10,\n 3_000,\n )\n if (!cleared) {\n warn(\n 'the position still shows an owed balance — either the mapping has not caught up, or fees ' +\n 'accrued while this ran and are collectable on the next pass',\n )\n }\n\n if (!args.close) continue\n if (entry.liquidity > 0n) {\n warn(\n `not burning ${id.slice(0, 20)}…: a burn needs zero liquidity, and it holds ${entry.liquidity}. ` +\n 'Drain it with `shield-swap liquidity --position <id> --decrease --percent 100` first.',\n )\n continue\n }\n if (!cleared) {\n warn(`not burning ${id.slice(0, 20)}…: a burn needs a zero owed balance, and this one is not zero yet`)\n continue\n }\n\n // The collect spent the position record and issued a new one. A burn built\n // on the spent record carries a serial number the chain has consumed, so\n // the node drops it at verification: it never reaches a block, and the only\n // symptom is a confirmation wait against a transaction nothing has heard\n // of. Presence is not enough — the spent record satisfies that too, so the\n // tag has to change.\n const staleTag = entry.position.record.tag\n step('waiting for the scanner to serve the position record the collect created')\n const indexed = await pollUntil(\n async () => {\n // The hosted scanner answers with intermittent 401s; a failed poll is\n // retried inside the window rather than ending the run.\n const current = await client.getOwnedPosition({ positionTokenId: id }).catch(() => null)\n return !!current && current.record.tag !== staleTag\n },\n 30,\n 2_000,\n )\n if (!indexed) {\n warn(\n `not burning ${id.slice(0, 20)}…: the scanner has not served the record the collect created ` +\n '(60s). Re-run with --close once it has — a burn against the spent record would be dropped.',\n )\n continue\n }\n\n step(`burning ${id.slice(0, 20)}…`)\n const burn = await client.burn({ positionTokenId: id, poolKey: entry.position.poolKey })\n done(`burned (tx ${burn.transactionId})`)\n burned.push({ positionTokenId: id, transactionId: burn.transactionId })\n } catch (error) {\n // One position's failure must not abandon the rest: each is a separate\n // transaction, and the others' proceeds are still there to be swept.\n const message = (error as Error).message\n warn(`${id.slice(0, 20)}… failed: ${message}`)\n failed.push({ positionTokenId: id, error: message })\n }\n }\n\n output(\n { network, submitted: true, collected, burned, failed },\n (data) => {\n console.log(`\\nCollected from ${data.collected.length} of ${candidates.length} position(s).`)\n if (data.burned.length) console.log(`Burned ${data.burned.length} drained position(s).`)\n if (data.failed.length) {\n console.log(`${data.failed.length} failed — the amounts stay owed and can be swept again.`)\n }\n console.log('New balances: `shield-swap balances`.')\n },\n )\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAmCA,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwCd,eAAsB,KAAK,MAA+B;AACxD,QAAM,OAAO;AAAA,IACX,EAAE,UAAU,EAAE,MAAM,SAAS,GAAG,OAAO,EAAE,MAAM,UAAU,GAAG,eAAe,EAAE,MAAM,UAAU,EAAE;AAAA,IAC/F;AAAA,IACA;AAAA,EACF;AAEA,QAAM,IAAI,YAAY;AACpB,UAAM,EAAE,QAAQ,QAAQ,IAAI,MAAM,YAAY,EAAE,SAAS,KAAK,QAA8B,CAAC;AAC7F,SAAK,cAAc,OAAO,EAAE;AAE5B,SAAK,mDAAmD;AACxD,UAAM,QAAQ,KAAK,WACf,MAAM,OACH,iBAAiB,EAAE,iBAAiB,KAAK,SAAmB,CAAC,EAC7D,KAAK,CAAC,aAAc,WAAW,CAAC,QAAQ,IAAI,CAAC,CAAE,IAClD,MAAM,OAAO,kBAAkB;AACnC,QAAI,KAAK,YAAY,CAAC,MAAM,QAAQ;AAClC,YAAM,IAAI;AAAA,QACR,6CAA6C,KAAK,QAAkB,OAAO,OAAO;AAAA,MAEpF;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,OAAO,WAAW;AACvC,UAAM,SAAS,CAAC,OAAe,OAAO,KAAK,CAAC,UAAU,MAAM,OAAO,EAAE;AAKrE,UAAM,aAAqB,CAAC;AAC5B,eAAW,YAAY,OAAO;AAC5B,YAAM,SAAS,OAAO,SAAS,QAAQ;AACvC,YAAM,SAAS,OAAO,SAAS,QAAQ;AACvC,YAAM,QAAQ,GAAG,QAAQ,UAAU,GAAG,IAAI,QAAQ,UAAU,GAAG;AAC/D,UAAI,SAAS,QAAQ;AACnB,aAAK,YAAY,SAAS,eAAe,4DAA4D;AACrG;AAAA,MACF;AACA,UAAI,CAAC,SAAS,OAAO;AACnB;AAAA,UACE,YAAY,SAAS,eAAe;AAAA,QAEtC;AACA;AAAA,MACF;AACA,UAAI,CAAC,UAAU,CAAC,QAAQ;AACtB,aAAK,YAAY,SAAS,eAAe,qDAAqD;AAC9F;AAAA,MACF;AAMA,YAAM,QAAQ,MAAM,OAAO,iBAAiB,EAAE,iBAAiB,SAAS,gBAAgB,CAAC;AACzF,YAAM,UAAU,OAAO;AACvB,UAAI,CAAC,SAAS;AACZ,aAAK,YAAY,SAAS,eAAe,yDAAyD;AAClG;AAAA,MACF;AAIA,YAAM,WAAW,KAAK,aAAa,IAAI,QAAQ,cAAc,QAAQ;AACrE,YAAM,WAAW,KAAK,aAAa,IAAI,QAAQ,cAAc,QAAQ;AACrE,UAAI,aAAa,MAAM,aAAa,IAAI;AAEtC,YAAI,KAAK,UAAU;AACjB;AAAA,YACE,YAAY,SAAS,eAAe,KAAK,KAAK,yBAC3C,SAAS,MAAM,YAAY,KACxB,yGACA;AAAA,UACR;AAAA,QACF;AACA;AAAA,MACF;AACA,iBAAW,KAAK;AAAA,QACd;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,QAAQ;AAAA,QACjB,SAAS,QAAQ;AAAA,QACjB;AAAA,QACA;AAAA,QACA,WAAW,QAAQ;AAAA,MACrB,CAAC;AAAA,IACH;AAEA,QAAI,CAAC,WAAW,QAAQ;AACtB,aAAO,EAAE,SAAS,WAAW,OAAO,WAAW,CAAC,GAAG,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,GAAG,MAAM;AACjF,gBAAQ,IAAI,iFAAiF;AAAA,MAC/F,CAAC;AACD;AAAA,IACF;AAIA,UAAM,YAA8C,CAAC;AACrD,eAAW,SAAS,YAAY;AAC9B,gBAAU,KAAK,CAAC,MAAM,OAAO,GAAG,MAAM,SAAS,gBAAgB,MAAM,GAAG,EAAE,CAAC,QAAG,CAAC;AAC/E,gBAAU,KAAK;AAAA,QACb;AAAA,QACA,GAAG,aAAa,MAAM,UAAU,MAAM,OAAO,UAAU,MAAM,OAAO,MAAM,CAAC,MACtE,aAAa,MAAM,UAAU,MAAM,OAAO,UAAU,MAAM,OAAO,MAAM,CAAC;AAAA,MAC/E,CAAC;AACD,YAAM,WAAW,MAAM,WAAW,MAAM;AACxC,YAAM,WAAW,MAAM,WAAW,MAAM;AACxC,UAAI,WAAW,MAAM,WAAW,IAAI;AAClC,kBAAU,KAAK;AAAA,UACb;AAAA,UACA,GAAG,aAAa,UAAU,MAAM,OAAO,UAAU,MAAM,OAAO,MAAM,CAAC,MAChE,aAAa,UAAU,MAAM,OAAO,UAAU,MAAM,OAAO,MAAM,CAAC;AAAA,QAEzE,CAAC;AAAA,MACH;AACA,gBAAU,KAAK,CAAC,QAAQ,MAAM,SAAS,UAAU,CAAC;AAClD,UAAI,KAAK,OAAO;AACd,kBAAU,KAAK;AAAA,UACb;AAAA,UACA,MAAM,cAAc,KAChB,8BACA,oCAA+B,MAAM,SAAS;AAAA,QACpD,CAAC;AAAA,MACH;AAAA,IACF;AACA,QAAI,CAAC,UAAU,EAAE,SAAS,KAAK,SAAgC,SAAS,MAAM,UAAU,CAAC,GAAG;AAC1F;AAAA,QACE;AAAA,UACE;AAAA,UACA,WAAW;AAAA,UACX,WAAW,WAAW,IAAI,CAAC,WAAW;AAAA,YACpC,iBAAiB,MAAM,SAAS;AAAA,YAChC,SAAS,MAAM,SAAS;AAAA,YACxB,UAAU,MAAM;AAAA,YAChB,UAAU,MAAM;AAAA,YAChB,SAAS,MAAM;AAAA,YACf,SAAS,MAAM;AAAA,YACf,WAAW,MAAM;AAAA,UACnB,EAAE;AAAA,QACJ;AAAA,QACA,MAAM;AAAA,QAAC;AAAA,MACT;AACA;AAAA,IACF;AAIA,UAAM,gBAAgB,oBAAI,IAAoC;AAC9D,UAAM,aAAa,OAAO,UAAgB;AACxC,YAAM,SAAS,cAAc,IAAI,MAAM,SAAS,OAAO;AACvD,UAAI,OAAQ,QAAO;AACnB,YAAM,WAAW,MAAM,OAAO,kBAAkB;AAAA,QAC9C,eAAe,CAAC,MAAM,OAAO,iBAAiB,MAAM,OAAO,eAAe,EAAE;AAAA,UAC1E,CAAC,YAA+B,CAAC,CAAC;AAAA,QACpC;AAAA,MACF,CAAC;AACD,oBAAc,IAAI,MAAM,SAAS,SAAS,QAAQ;AAClD,aAAO;AAAA,IACT;AAEA,UAAM,YAAyG,CAAC;AAChH,UAAM,SAAoE,CAAC;AAC3E,UAAM,SAA4D,CAAC;AAInE,eAAW,SAAS,YAAY;AAC9B,YAAM,KAAK,MAAM,SAAS;AAC1B,UAAI;AACF,aAAK,cAAc,MAAM,KAAK,SAAS,GAAG,MAAM,GAAG,EAAE,CAAC,0CAAgC;AACtF,cAAM,SAAS,MAAM,OAAO,QAAQ;AAAA,UAClC,iBAAiB;AAAA,UACjB,SAAS,MAAM,SAAS;AAAA,UACxB,kBAAkB,MAAM;AAAA,UACxB,kBAAkB,MAAM;AAAA,UACxB,SAAS,MAAM,WAAW,KAAK;AAAA,QACjC,CAAC;AACD;AAAA,UACE,aAAa,aAAa,MAAM,UAAU,MAAM,OAAO,UAAU,MAAM,OAAO,MAAM,CAAC,MAChF,aAAa,MAAM,UAAU,MAAM,OAAO,UAAU,MAAM,OAAO,MAAM,CAAC,QAAQ,OAAO,aAAa;AAAA,QAC3G;AACA,kBAAU,KAAK,EAAE,iBAAiB,IAAI,eAAe,OAAO,eAAe,SAAS,MAAM,UAAU,SAAS,MAAM,SAAS,CAAC;AAI7H,cAAM,UAAU,MAAM;AAAA,UACpB,YAAY;AACV,kBAAM,UAAU,MAAM,OAAO,YAAY,EAAE,iBAAiB,GAAG,CAAC;AAChE,mBAAO,CAAC,CAAC,WAAW,QAAQ,iBAAiB,MAAM,QAAQ,iBAAiB;AAAA,UAC9E;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,YAAI,CAAC,SAAS;AACZ;AAAA,YACE;AAAA,UAEF;AAAA,QACF;AAEA,YAAI,CAAC,KAAK,MAAO;AACjB,YAAI,MAAM,YAAY,IAAI;AACxB;AAAA,YACE,eAAe,GAAG,MAAM,GAAG,EAAE,CAAC,qDAAgD,MAAM,SAAS;AAAA,UAE/F;AACA;AAAA,QACF;AACA,YAAI,CAAC,SAAS;AACZ,eAAK,eAAe,GAAG,MAAM,GAAG,EAAE,CAAC,wEAAmE;AACtG;AAAA,QACF;AAQA,cAAM,WAAW,MAAM,SAAS,OAAO;AACvC,aAAK,0EAA0E;AAC/E,cAAM,UAAU,MAAM;AAAA,UACpB,YAAY;AAGV,kBAAM,UAAU,MAAM,OAAO,iBAAiB,EAAE,iBAAiB,GAAG,CAAC,EAAE,MAAM,MAAM,IAAI;AACvF,mBAAO,CAAC,CAAC,WAAW,QAAQ,OAAO,QAAQ;AAAA,UAC7C;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,YAAI,CAAC,SAAS;AACZ;AAAA,YACE,eAAe,GAAG,MAAM,GAAG,EAAE,CAAC;AAAA,UAEhC;AACA;AAAA,QACF;AAEA,aAAK,WAAW,GAAG,MAAM,GAAG,EAAE,CAAC,QAAG;AAClC,cAAM,OAAO,MAAM,OAAO,KAAK,EAAE,iBAAiB,IAAI,SAAS,MAAM,SAAS,QAAQ,CAAC;AACvF,aAAK,cAAc,KAAK,aAAa,GAAG;AACxC,eAAO,KAAK,EAAE,iBAAiB,IAAI,eAAe,KAAK,cAAc,CAAC;AAAA,MACxE,SAAS,OAAO;AAGd,cAAM,UAAW,MAAgB;AACjC,aAAK,GAAG,GAAG,MAAM,GAAG,EAAE,CAAC,kBAAa,OAAO,EAAE;AAC7C,eAAO,KAAK,EAAE,iBAAiB,IAAI,OAAO,QAAQ,CAAC;AAAA,MACrD;AAAA,IACF;AAEA;AAAA,MACE,EAAE,SAAS,WAAW,MAAM,WAAW,QAAQ,OAAO;AAAA,MACtD,CAAC,SAAS;AACR,gBAAQ,IAAI;AAAA,iBAAoB,KAAK,UAAU,MAAM,OAAO,WAAW,MAAM,eAAe;AAC5F,YAAI,KAAK,OAAO,OAAQ,SAAQ,IAAI,UAAU,KAAK,OAAO,MAAM,uBAAuB;AACvF,YAAI,KAAK,OAAO,QAAQ;AACtB,kBAAQ,IAAI,GAAG,KAAK,OAAO,MAAM,8DAAyD;AAAA,QAC5F;AACA,gBAAQ,IAAI,uCAAuC;AAAA,MACrD;AAAA,IACF;AAAA,EACF,CAAC;AACH;","names":[]}
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ reportUsage,
4
+ setJsonMode
5
+ } from "./chunk-IBVZHLUT.js";
6
+ import {
7
+ help,
8
+ setNoColor
9
+ } from "./chunk-IHYFMX5A.js";
10
+
11
+ // src/registry.ts
12
+ var COMMANDS = {
13
+ setup: {
14
+ summary: "Set up all credentials required for Shield Swap.",
15
+ load: () => import("./setup-CZI3SHUT.js")
16
+ },
17
+ pools: {
18
+ summary: "List pools with their on-chain depth and whether they are tradeable.",
19
+ load: () => import("./pools-NWQNFJ7W.js")
20
+ },
21
+ balances: {
22
+ summary: "Private and public holdings per token.",
23
+ load: () => import("./balances-6SU4DCKM.js")
24
+ },
25
+ positions: {
26
+ summary: "Liquidity positions held + their ranges and fees earned (with option to collect).",
27
+ load: () => import("./positions-MILT3BRU.js")
28
+ },
29
+ swap: {
30
+ summary: "Sell one token for another and claim the output.",
31
+ load: () => import("./swap-W72XGG7Y.js")
32
+ },
33
+ "swap-concurrent": {
34
+ summary: "Make multiple swaps concurrently.",
35
+ load: () => import("./swap-concurrent-IHGWJMST.js")
36
+ },
37
+ history: {
38
+ summary: "Swap history and status of swaps.",
39
+ load: () => import("./swap-history-FBXGMVRJ.js")
40
+ },
41
+ mint: {
42
+ summary: "Open a liquidity position over a tick range.",
43
+ load: () => import("./mint-6OKWODQG.js")
44
+ },
45
+ liquidity: {
46
+ summary: "Add to an open position, or remove liquidity and book it as owed.",
47
+ load: () => import("./liquidity-RB5MKGPA.js")
48
+ },
49
+ collect: {
50
+ summary: "Sweep what a position is owed into records, optionally closing it.",
51
+ load: () => import("./collect-G3GNFL57.js")
52
+ },
53
+ "liquidity-e2e": {
54
+ summary: "The whole liquidity lifecycle in one run \u2014 mint through burn.",
55
+ load: () => import("./liquidity-e2e-WCTSSZYS.js")
56
+ }
57
+ };
58
+ function usage() {
59
+ const width = Math.max(...Object.keys(COMMANDS).map((name2) => name2.length));
60
+ const lines = Object.entries(COMMANDS).map(([name2, { summary }]) => ` ${name2.padEnd(width)} ${summary}`);
61
+ return `shield-swap \u2014 trade on Shield Swap from the command line
62
+
63
+ Usage: shield-swap <command> [options]
64
+
65
+ ${lines.join("\n")}
66
+
67
+ Run \`shield-swap <command> --help\` for a command's own flags.
68
+
69
+ Common to every command below setup:
70
+ --network <testnet|mainnet> default testnet; mainnet is never implicit
71
+ --execute actually submit; without it, plans and stops
72
+ --json one machine-readable object, nothing else
73
+ --no-color plain output; NO_COLOR and a non-TTY do this too
74
+ -h, --help this text, or a command's own when it follows one
75
+
76
+ setup takes --network and its own options, but neither --execute nor --json:
77
+ it is check-then-act throughout and reports progress as text.`;
78
+ }
79
+
80
+ // src/index.ts
81
+ var [name, ...argv] = process.argv.slice(2);
82
+ setJsonMode(argv.includes("--json"));
83
+ setNoColor(argv.includes("--no-color"));
84
+ if (!name || name === "--help" || name === "-h" || name === "help") {
85
+ console.log(help(usage()));
86
+ process.exit(0);
87
+ }
88
+ var command = COMMANDS[name];
89
+ if (!command) {
90
+ const near = Object.keys(COMMANDS).filter((candidate) => candidate.startsWith(name) || name.startsWith(candidate));
91
+ reportUsage(
92
+ `unknown command \`${name}\`.${near.length ? ` Did you mean \`${near.join("` or `")}\`?` : ""}`,
93
+ usage(),
94
+ argv
95
+ );
96
+ }
97
+ var { main } = await command.load();
98
+ await main(argv);
99
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/registry.ts","../src/index.ts"],"sourcesContent":["/**\n * The subcommand registry and the top-level usage block.\n *\n * Separate from `index.ts` so it can be imported without running the\n * dispatcher: `index.ts` reads `process.argv` and exits at module scope, which\n * makes it unimportable from anything that is not the binary itself.\n */\n\n/**\n * One subcommand: what it does, and how to load it.\n *\n * @property summary One line for the top-level listing. No trailing period is\n * added, so write it as a sentence.\n * @property load Imports the command's module. A thunk rather than a static\n * import so an unused command — and the SDK surface it pulls in — is never\n * evaluated.\n */\nexport type Command = {\n summary: string\n load: () => Promise<{ main: (argv: string[]) => Promise<void> }>\n}\n\n/**\n * Every subcommand, in the order the README recommends running them.\n *\n * The single place a subcommand is declared: adding a key here is what makes it\n * reachable, listed in `--help`, and covered by the registry test.\n */\nexport const COMMANDS: Record<string, Command> = {\n setup: {\n summary: 'Set up all credentials required for Shield Swap.',\n load: () => import('./commands/setup.js'),\n },\n pools: {\n summary: 'List pools with their on-chain depth and whether they are tradeable.',\n load: () => import('./commands/pools.js'),\n },\n balances: {\n summary: 'Private and public holdings per token.',\n load: () => import('./commands/balances.js'),\n },\n positions: {\n summary: 'Liquidity positions held + their ranges and fees earned (with option to collect).',\n load: () => import('./commands/positions.js'),\n },\n swap: {\n summary: 'Sell one token for another and claim the output.',\n load: () => import('./commands/swap.js'),\n },\n 'swap-concurrent': {\n summary: 'Make multiple swaps concurrently.',\n load: () => import('./commands/swap-concurrent.js'),\n },\n history: {\n summary: 'Swap history and status of swaps.',\n load: () => import('./commands/swap-history.js'),\n },\n mint: {\n summary: 'Open a liquidity position over a tick range.',\n load: () => import('./commands/mint.js'),\n },\n liquidity: {\n summary: 'Add to an open position, or remove liquidity and book it as owed.',\n load: () => import('./commands/liquidity.js'),\n },\n collect: {\n summary: 'Sweep what a position is owed into records, optionally closing it.',\n load: () => import('./commands/collect.js'),\n },\n 'liquidity-e2e': {\n summary: 'The whole liquidity lifecycle in one run — mint through burn.',\n load: () => import('./commands/liquidity-e2e.js'),\n },\n}\n\n/**\n * Builds the top-level usage block, with the summaries aligned in a column.\n *\n * @returns The block printed for `shield-swap`, `--help`, and an unknown command.\n */\nexport function usage(): string {\n const width = Math.max(...Object.keys(COMMANDS).map((name) => name.length))\n const lines = Object.entries(COMMANDS).map(([name, { summary }]) => ` ${name.padEnd(width)} ${summary}`)\n return `shield-swap — trade on Shield Swap from the command line\n\nUsage: shield-swap <command> [options]\n\n${lines.join('\\n')}\n\nRun \\`shield-swap <command> --help\\` for a command's own flags.\n\nCommon to every command below setup:\n --network <testnet|mainnet> default testnet; mainnet is never implicit\n --execute actually submit; without it, plans and stops\n --json one machine-readable object, nothing else\n --no-color plain output; NO_COLOR and a non-TTY do this too\n -h, --help this text, or a command's own when it follows one\n\nsetup takes --network and its own options, but neither --execute nor --json:\nit is check-then-act throughout and reports progress as text.`\n}\n","#!/usr/bin/env node\n/**\n * The `shield-swap` command: account setup, pool and balance reads, swaps, and\n * liquidity, against a live Shield Swap deployment.\n *\n * This file only routes. Each subcommand owns its own flags, its own `--help`,\n * and its own output; the registry that names them lives in `registry.ts`.\n *\n * Two rules hold across every subcommand and are enforced in `shared.ts`:\n * nothing spends without `--execute`, and mainnet is never implicit.\n */\nimport { COMMANDS, usage } from './registry.js'\nimport { reportUsage, setJsonMode } from './shared.js'\nimport { help, setNoColor } from './color.js'\n\nconst [name, ...argv] = process.argv.slice(2)\n\n// Both set before dispatch rather than inside each command: `--json` promises one\n// object on stdout and nothing else, and a command that reported progress before\n// reaching its own `setJsonMode` would already have broken that. `--no-color` has\n// to be in place before the first line for the same reason.\nsetJsonMode(argv.includes('--json'))\nsetNoColor(argv.includes('--no-color'))\n\nif (!name || name === '--help' || name === '-h' || name === 'help') {\n // Exit 0 for a bare invocation too: listing the commands is what someone\n // running `shield-swap` with nothing wants, and it is the first thing anyone\n // types. EX_USAGE there makes the wrapper report a failed command — `pnpm\n // shield-swap` printing the help and then `ELIFECYCLE Command failed` reads as\n // a broken install. A wrong command still exits 64, below.\n console.log(help(usage()))\n process.exit(0)\n}\n\nconst command = COMMANDS[name]\nif (!command) {\n // Suggest rather than just reject: the subcommand names are close enough to\n // each other that a near miss is far more likely than an invented one.\n const near = Object.keys(COMMANDS).filter((candidate) => candidate.startsWith(name) || name.startsWith(candidate))\n reportUsage(\n `unknown command \\`${name}\\`.${near.length ? ` Did you mean \\`${near.join('` or `')}\\`?` : ''}`,\n usage(),\n argv,\n )\n}\n\nconst { main } = await command.load()\nawait main(argv)\n"],"mappings":";;;;;;;;;;;AA4BO,IAAM,WAAoC;AAAA,EAC/C,OAAO;AAAA,IACL,SAAS;AAAA,IACT,MAAM,MAAM,OAAO,qBAAqB;AAAA,EAC1C;AAAA,EACA,OAAO;AAAA,IACL,SAAS;AAAA,IACT,MAAM,MAAM,OAAO,qBAAqB;AAAA,EAC1C;AAAA,EACA,UAAU;AAAA,IACR,SAAS;AAAA,IACT,MAAM,MAAM,OAAO,wBAAwB;AAAA,EAC7C;AAAA,EACA,WAAW;AAAA,IACT,SAAS;AAAA,IACT,MAAM,MAAM,OAAO,yBAAyB;AAAA,EAC9C;AAAA,EACA,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,MAAM,MAAM,OAAO,oBAAoB;AAAA,EACzC;AAAA,EACA,mBAAmB;AAAA,IACjB,SAAS;AAAA,IACT,MAAM,MAAM,OAAO,+BAA+B;AAAA,EACpD;AAAA,EACA,SAAS;AAAA,IACP,SAAS;AAAA,IACT,MAAM,MAAM,OAAO,4BAA4B;AAAA,EACjD;AAAA,EACA,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,MAAM,MAAM,OAAO,oBAAoB;AAAA,EACzC;AAAA,EACA,WAAW;AAAA,IACT,SAAS;AAAA,IACT,MAAM,MAAM,OAAO,yBAAyB;AAAA,EAC9C;AAAA,EACA,SAAS;AAAA,IACP,SAAS;AAAA,IACT,MAAM,MAAM,OAAO,uBAAuB;AAAA,EAC5C;AAAA,EACA,iBAAiB;AAAA,IACf,SAAS;AAAA,IACT,MAAM,MAAM,OAAO,6BAA6B;AAAA,EAClD;AACF;AAOO,SAAS,QAAgB;AAC9B,QAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,KAAK,QAAQ,EAAE,IAAI,CAACA,UAASA,MAAK,MAAM,CAAC;AAC1E,QAAM,QAAQ,OAAO,QAAQ,QAAQ,EAAE,IAAI,CAAC,CAACA,OAAM,EAAE,QAAQ,CAAC,MAAM,KAAKA,MAAK,OAAO,KAAK,CAAC,KAAK,OAAO,EAAE;AACzG,SAAO;AAAA;AAAA;AAAA;AAAA,EAIP,MAAM,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAalB;;;ACrFA,IAAM,CAAC,MAAM,GAAG,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC;AAM5C,YAAY,KAAK,SAAS,QAAQ,CAAC;AACnC,WAAW,KAAK,SAAS,YAAY,CAAC;AAEtC,IAAI,CAAC,QAAQ,SAAS,YAAY,SAAS,QAAQ,SAAS,QAAQ;AAMlE,UAAQ,IAAI,KAAK,MAAM,CAAC,CAAC;AACzB,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,UAAU,SAAS,IAAI;AAC7B,IAAI,CAAC,SAAS;AAGZ,QAAM,OAAO,OAAO,KAAK,QAAQ,EAAE,OAAO,CAAC,cAAc,UAAU,WAAW,IAAI,KAAK,KAAK,WAAW,SAAS,CAAC;AACjH;AAAA,IACE,qBAAqB,IAAI,MAAM,KAAK,SAAS,mBAAmB,KAAK,KAAK,QAAQ,CAAC,QAAQ,EAAE;AAAA,IAC7F,MAAM;AAAA,IACN;AAAA,EACF;AACF;AAEA,IAAM,EAAE,KAAK,IAAI,MAAM,QAAQ,KAAK;AACpC,MAAM,KAAK,IAAI;","names":["name"]}