feat: add dshield CLI + shared @dshield/core package - #159
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every DShield interaction previously required the Next.js frontend and a
browser-based wallet. This PR adds a
dshieldcommand-line client so powerusers, integrators, and CI can script deposits, withdrawals, note management, and
compliance reports with no browser.
To satisfy the "no parallel reimplementation" requirement, it also extracts the
note / commitment / Merkle / proof / format logic into a new shared
@dshield/corepackage that both the frontend and the CLI consume. A noteminted by the CLI is spendable in the app and vice-versa, because they run the
exact same code.
A full deposit → withdraw cycle can be completed against a local/testnet
deployment using only the CLI, no browser.
What changed
New:
packages/core(@dshield/core)Isomorphic (browser + Node), no
localStorage/window/ app-routefetch:poseidon2.ts— Poseidon2 hasher, commitment / nullifier / KYC / recipienthashing, Merkle-tree builder.
notes.ts— the note codec:serializeNote/parseNote, compact linkencoding,
ShieldedNote,PENDING_LEAF_INDEX,generateRandomField,parseNotes.prover-core.ts—runProof(Noir witness execution + UltraHonk proving).prover.ts— circuit JSONs + witness builders +proveWithdrawal/proveCompliance/proveDisclosure.report.ts—computeReportIdentity+formatReportText+ComplianceReport.format.ts—usdcToStroops,formatAmount, decimals/symbol.New:
cli/(@dshield/cli, bindshield)Commander-based CLI that runs its TypeScript source through tsx (no build
step). Commands:
dshield deposit --amount <usdc> [--dry-run]— shields USDC, prints a note.--dry-runbuilds + stores the note offline (no network/signing). On testnetworks it can mint test USDC via the issuer key to cover the deposit.
dshield withdraw (--note | --commitment) [--to] [--amount]— full or partialwithdrawal with a real zero-knowledge proof; re-shields the remainder.
dshield notes list | export [--out] | import <input>— local note store.dshield compliance disclose (--note | --commitment) [--out] [--offline]—the on-chain compliance report (recomputes the commitment/nullifier and
confirms deposit + withdrawal status), never embedding the note itself.
Wallet handling is distinct from the browser wallet-kit flow:
--secret-key/DSHIELD_SECRET_KEY/~/.dshield/key/config), or
--sign-with <command>(unsigned XDR onstdin → signed XDR on stdout).
Config resolves from flags →
DSHIELD_*env →~/.dshield/config.json→frontend/.env.local(deployment output, auto-detected) → defaults. Notes andkeyfile live under
~/.dshield. Full docs incli/README.md.Changed:
frontend/now consumes@dshield/coreposeidon2.ts,prover-core.ts,format.tsbecome thin re-exports of core.notes.ts,prover.ts,report.tsimport their shared halves from core andkeep only the browser-specific parts (localStorage store, Web Worker, on-chain
report assembly). App pages/components are unchanged — the
@/lib/*publicAPIs are preserved.
@dshield/coreis linked from source via alink:dependency +transpilePackages(Turbopack) innext.config.ts.Workspace + CI
pnpm-workspace.yaml/package.jsoncoverpackages/*+cli(
frontend/intentionally keeps its own workspace + lockfile).clijob: typecheck, unit tests,dshield --help, and an offlinedry-run deposit against a local home dir.
lint/frontendjobs additionally run a root install (so@dshield/core'sruntime deps resolve) and refresh core's copy of the compiled
hasher.json.Acceptance criteria
deposit,withdraw,notes, andcompliance disclosemirror the frontend's exactSoroban contract calls and note lifecycle.
@dshield/coreexisting and both the app and CLI importing it (no duplicatedcrypto). A CLI test asserts a deposited note's commitment equals
core.computeCommitment(...).Testing
@dshield/core: 18 unit tests (hashing parity vs the Noir fixture, notecodec round-trips, witness mapping).
@dshield/cli: 8 tests (command parsing/help/version, offline dry-run deposit,notes import/list/export round-trip).
frontend: 153 unit tests pass;pnpm lintclean;next buildsucceedsconsuming core from source.
dshield --helpand an offline dry-run deposit verified locally.Notes / follow-ups
frontend/package.jsongains a singlelink:dependency andnext.config.tsgains
transpilePackages— the minimum needed for the bundler to compile corefrom source; the frontend workspace root and CI install directory are otherwise
unchanged.
compliance discloseproduces the shipped on-chain report. The ZKthreshold-disclosure proof is wired in
@dshield/core(proveDisclosure) butnot yet exposed as a CLI command.
exercised by the
e2ejob /just deploy+ the new CLI commands.closes #140