This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
ZcashLightClientKit is an iOS/macOS Swift Package that implements a Zcash lightwallet client. The Swift layer wraps a Rust core (in rust/) via an libzcashlc XCFramework. Most day-to-day SDK work happens in Swift only — SPM auto-downloads a pre-built XCFramework from GitHub Releases.
Open the package or workspace in Xcode and build against an iOS or macOS target:
swift build— build the package (macOS target).swift test --filter OfflineTests— run the offline unit tests. This is what CI runs (see.github/workflows/swift.yml).xcodebuild ... -testPlan ZcashLightClientKit.xctestplan— the shared test plan enables onlyOfflineTests; other test targets are disabled by default and must be enabled manually when needed.
Test targets are grouped by external dependencies:
| Target | Requires |
|---|---|
OfflineTests |
nothing |
NetworkTests |
internet connection |
DarksideTests / AliasDarksideTests |
a local lightwalletd (Tests/lightwalletd/lightwalletd --no-tls-very-insecure --data-dir /tmp --darkside-very-insecure --log-file /dev/stdout); optionally set LIGHTWALLETD_ADDRESS |
PerformanceTests |
network, not run in CI |
The Rust code in rust/ is compiled into the libzcashlc XCFramework. Two modes, switched automatically by Package.swift based on whether LocalPackages/Package.swift exists:
- Binary release mode (default):
.binaryTargetinPackage.swiftpulls the XCFramework zip from the GitHub Release referenced there (URL + checksum). - Local FFI mode:
LocalPackages/acts as a path-dependency override. The workspace'sFFIBuildertarget auto-rebuilds on Xcode builds.
Scripts:
./Scripts/init-local-ffi.sh— one-time setup; default builds all 5 architectures and createsLocalPackages/.--macos-onlybuilds only the macOS slice from yourrust/(good forswift build/swift teston the Mac). Use--cachedonly when your branch has no FFI changes relative to the release. Use --macos-only to rebuild for fast local development../Scripts/rebuild-local-ffi.sh [ios-sim|ios-device|macos]— fast single-arch incremental rebuild after Rust edits.ios-simis default../Scripts/reset-local-ffi.sh— removeLocalPackages/and switch back to the release binary.
For FFI work, open ZcashSDK.xcworkspace (not Package.swift) so FFIBuilder auto-runs. After switching modes or if headers look stale, in Xcode: Cmd+Shift+K, then File > Packages > Reset Package Caches. When modifying the Rust/Swift FFI boundary, run the full init-local-ffi.sh before PRing — rebuild-local-ffi.sh only covers one arch.
See docs/LOCAL_DEVELOPMENT.md for the full reference.
./Scripts/release.sh <remote> <version>— fully automated release (bumps the XCFramework URL+checksum inPackage.swift, signs a tag, drafts GitHub Release)../Scripts/prepare-release.sh <version>— semi-automated alternative.- The
Build FFI XCFrameworkGitHub Action (workflow_dispatch) produces release artifacts.
- Rust core (
rust/src/) — key derivation, note scanning, transaction construction, block database migrations. - Swift SDK (
Sources/ZcashLightClientKit/) — orchestration, networking, persistence, public API.
The Swift↔Rust bridge lives in Sources/ZcashLightClientKit/Rust/:
ZcashRustBackendconforms toZcashRustBackendWelding— the DB-bound surface.ZcashKeyDerivationBackendconforms toZcashKeyDerivationBackendWelding— the stateless key-derivation surface.
Both are the only callers of the generated C header libzcashlc.
Synchronizer.swiftdefines the public protocol.SDKSynchronizer(inSynchronizer/SDKSynchronizer.swift) is the concrete actor-based implementation.ClosureSDKSynchronizerandCombineSDKSynchronizer(plus theClosureSynchronizer/CombineSynchronizertop-level files) are thin adapters over theasync/awaitAPI. Prefer extending the async API and letting the adapters delegate.Synchronizer/Dependencies.swiftis the DI composition root — it wires the entire object graph (repositories, services, rust backend, compact block processor, Tor client). Most "where does X come from?" questions are answered here.Initializer.swiftis the user-facing entry point that validates paths, configures logging, and hands config toSynchronizer.
Block/CompactBlockProcessor.swift is a Swift actor that drives a state machine (CBPState) over an ordered list of Block/Actions/*Action.swift units: download → validate server → update chain tip → update subtree roots → process suggested scan ranges → scan → enhance → fetch UTXOs → clear cache → resubmit / migrate legacy / rewind. Each Action conforms to the protocol in Block/Actions/Action.swift and mutates a shared ActionContext.
The CompactBlockProcessor downloads compact blocks via Block/Download/, stores them on-disk via Block/FilesystemStorage/ (NOT a sqlite cacheDb anymore — see MIGRATING.md), and invokes scanning/enhancement through the rust backend. Metadata lives in a sqlite dataDb accessed via DAO/ and Repository/.
"Spend before Sync" (non-linear scan order) is the current sync algorithm — blocks may be scanned out-of-order so spendable notes are discovered early; tests and code refer to "scan ranges" and "suggested scan ranges" in this sense.
- gRPC lightwalletd client:
Modules/Service/GRPC/(proto files underProtoBuf/proto/— generated*.pb.swift/*.grpc.swiftfiles are checked in and excluded from SwiftLint; regenerate them, don't hand-edit). - Tor:
Modules/Service/Tor/andTor/TorClient.swift. A Tor directory is provisioned in the Initializer config. Modules/Service/LightWalletService.swiftis the service-level abstraction the rest of the SDK depends on.
Three kinds of generated code in this repo — do not edit by hand:
- Error types —
Error/ZcashError.swiftandError/ZcashErrorCode.swiftare generated fromError/ZcashErrorCodeDefinition.swiftviaError/Sourcery/generateErrorCode.sh(Sourcery). Add new errors by editingZcashErrorCodeDefinition.swiftand rerunning the script. - Test mocks —
Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swiftviaTests/TestUtils/Sourcery/generateMocks.sh. Requires Sourcery 2.3.0 exactly (the script hard-checks the version). - gRPC/protobuf — see above.
Generated files and Tests/ are excluded from the main .swiftlint.yml (tests have their own .swiftlint_tests.yml).
Resources/checkpoints/{mainnet,testnet}/*.json are bundled chain checkpoints, loaded by Checkpoint/BundleCheckpointSource.swift. They seed wallet birthday lookups.
- Logging: never call
print,debugPrint, orNSLogin app/SDK code — SwiftLint enforces this. Use the injectedLogger(see README "Integrating with logging tools"). TheLoggerprotocol is provided toInitializervialoggingPolicy. - String building: use interpolation, not
+concatenation (SwiftLintstring_concatenationis severityerror). - TODOs: format as
TODO: [#<issue_number>] ...— bareTODO:/FIXME:warn. - SwiftLint disables: only the exceptions listed in
SWIFTLINT.mdare permitted, always scoped with// swiftlint:disable:next/disable:previous/ region blocks. - Commits and PRs: every PR must reference an issue. Commit title format is
[#<issue_number>] <self-descriptive title>(seeCONTRIBUTING.md). PRs are typically squash-merged. - Breaking API changes: document them in
MIGRATING.md, and add aCHANGELOG.mdentry for every user-visible change. - Main branch policy:
mainis development-stable (all merges build + tests pass) but clients must depend on published tags, never onmain. - Sync concurrency:
CompactBlockProcessoris a Swift actor. Callers without structured concurrency should hop to@MainActorcontexts rather than blocking.