tap-sdk is the Go source-of-truth implementation of the Taproot Assets
application SDK. It exposes typed wallet, issuer, universe, proof, burn, and
transfer surfaces over tapd without requiring application developers to deal
with low-level protobuf types.
Repository: github.com/lightninglabs/tap-sdk
Language: Go (source of truth for future language SDKs)
Status: Pre-v1.0 (breaking API changes possible)
tap-sdk/
├── Root package (tapsdk)
│ ├── wallet.go — High-level Wallet entrypoint
│ ├── tx_builder.go — Address-based transfer builder
│ ├── interactive_tx_builder.go — Interactive (keyless) transfer builder
│ ├── asset.go — Public asset, collection, and issuance types
│ ├── clients.go — Client interface definitions
│ └── errors.go — SDK error types and sentinels
├── grpc/ — gRPC client implementation
├── rest/ — REST client implementation
├── internal/vpsbt/ — Virtual PSBT encoding
├── internal/codec/ — Cryptographic utilities (alt-leaves, STXO)
├── macaroon/ — Authentication helpers
└── docs/ — User and contributor documentation
└── design/ — Durable design decisions
- SDK types are the only public surface. Users never import
taprpcor othertaproot-assetspackages. - Transports are implementation details. All proto conversions happen at
the
grpc/andrest/package boundaries. - Wallet is the entrypoint. High-level operations go through
Wallet, low-level through theClientinterface. - Public types live at the root. Fixed-size byte arrays and business
structs are discoverable from the main
tapsdkimport. - The SDK is opinionated. The public surface should not blindly mirror
raw
taprpcsemantics when a cleaner Taproot Assets model exists. - Design docs are durable.
docs/design/captures accepted API and architecture decisions, not short-lived implementation plans.
The SDK must distinguish between the protocol's low-level identifiers and the user-facing identity model:
- Fungible assets are identified by group key in the normal SDK surface.
The
asset_idis tranche-level and should not be the primary identifier that application developers work with for fungible assets. - Collectibles / non-fungible assets are identified by asset ID. In that case the tranche is the asset, so exposing the asset ID is correct.
- Low-level client wrappers may still need to translate raw RPC fields, but high-level APIs and docs should consistently present the semantic model: fungible => group key, non-fungible => asset ID.
The SDK should expose an opinionated default address flow:
- V2 addresses are the default for the normal, high-level receive/send surface unless there is a concrete reason to do otherwise.
- Older address versions are considered advanced or compatibility paths and should remain available through lower-level client methods instead of being promoted in the primary UX.
| Command | Purpose |
|---|---|
make build |
Build the SDK |
make lint |
Run golangci-lint (via Docker) |
make fmt |
Format code (gosimports + gofmt) |
make unit |
Run all unit tests |
make unit pkg=<pkg> |
Run tests for specific package |
make unit pkg=<pkg> case=<test> |
Run a specific test case |
make itest-main |
Integration tests against tapd built from taproot-assets main (local-dev default) |
make itest |
Integration tests against the pinned released tapd image (what CI uses) |
make itest-run-main case=<test> |
Run a single itest against tapd-main without re-spinning the stack |
Prefer make itest-main locally. It builds tapd from the latest
taproot-assets main so proto/RPC changes in tapd are exercised before
CI catches them. CI runs make itest (pinned released image) because
bleeding-edge tapd may not have a pre-built image yet — only reach for
plain itest when reproducing CI.
Follow Lightning Labs conventions (see .gemini/styleguide.md):
- 80 character line limit (tabs count as 8 spaces)
- Every exported function must have a comment starting with function name
- Comments explain "why", not "what"
- Table-driven tests with
requireassertions - Logical stanzas separated by blank lines
- No AI watermarks — no
Co-authored-by: Claudeor similar
Format: subsystem: short description
Keep commit messages concise. Commits are not PR descriptions. The subject line should be under ~70 characters; if a body is needed, aim for 2–4 short sentences — not paragraphs, not bulleted lists, not design discussions. Explain the why when it isn't obvious from the diff; skip the body entirely when the subject already says it.
PR descriptions belong in the PR body (with context, test plan, etc.),
not in every commit. A reader running git log --oneline should be
able to skim the change set without drowning.
Examples:
wallet: add FetchAsset request and response typesgrpc: wrap ListBalances RPCwallet: add high-level balance query methodmulti: fix lint issues across packages
Do not include AI/tool attribution in branch names, PR titles, PR bodies, or
commit messages. In particular, never use codex/ branch prefixes or
[codex] PR title prefixes.
Use normal repository style instead:
- Branch: plain descriptive name, e.g.
standardize-fee-rates-sat-vb - Commit: subsystem style, e.g.
multi: standardize fee rates on sat/vB - PR title: subsystem style, e.g.
multi: standardize fee rates on sat/vB
- Create or update a design doc under
docs/design/only for non-trivial design work, architecture decisions, or API redesigns - Skip dedicated design docs for straightforward low-level RPC wrapper work that does not introduce new user-facing design
- Implement with granular, logical commits (not incremental)
- Force-push to keep history clean
- All CI must pass (lint, format, compile, unit tests)
- Wait for review before merge
When wrapping a new tapd RPC:
- Define SDK types in the root package (request, response, domain objects)
- Implement the gRPC call in the appropriate
grpc/client - Add marshal/unmarshal functions at the
grpc/boundary - Add the method to the appropriate interface in
clients.go - Optionally add a convenience method on
Wallet - Write unit tests with mocks + table-driven patterns
- Add or extend integration tests when the flow is user-visible
- Update README or docs pages when the public surface changes
- Update CHANGELOG.md only when preparing a public release
- tapd — Taproot Assets daemon (gRPC/REST over TLS + macaroons)
- taproot-assets/taprpc — gRPC service definitions (proto, lightweight module)
- btcsuite/btcd — Bitcoin primitives (PSBT, keys, crypto)
taprpc types must never appear in exported signatures outside transport
packages. The grpc/ and rest/ sub-client structs are unexported, and their
internal helpers must remain unexported to prevent taprpc types from leaking
through embedding into transport clients.
When adding new wrappers, ensure all proto-to-SDK conversion happens inside transport packages and that tests outside transport packages only use root SDK types.
The SDK is successful when:
- Any project (lightning-terminal, loop, taproot-assets itests) can use
tap-sdkinstead of rawtaprpccalls. - No consumer needs to import
taproot-assetspackages directly. - The SDK surface is opinionated with good defaults, not a 1:1 proto mirror.
- Comprehensive test coverage exists for all wrapped RPCs.