BitBoxApp is a cryptocurrency hardware wallet companion app. A Go backend provides an HTTP API consumed by a React/TypeScript single-page app. The same frontend is embedded in desktop (Qt WebEngine), Android, and iOS shells via platform-specific bridges.
The repository powers the BitBoxApp. Core Go services live under backend/ with entrypoints in
cmd/ (e.g., cmd/servewallet). Frontend clients reside in frontends/ (web for React, qt for
the desktop app, android and ios for mobile artifacts). Shared assets and docs sit in docs/,
while automation lives in scripts/. Use vendor/ for pinned Go modules.
Key backend packages:
backend/coins/btc/— Bitcoin (also used for LTC);backend/coins/eth/— Ethereum + ERC20 tokensbackend/coins/btc/handlers/handlers.go— account-level HTTP handlers shared by all account typesbackend/devices/bitbox02/— BitBox02 USB/Bluetooth device driversbackend/handlers/handlers.go— top-level HTTP routing (gorilla/mux)
See go.mod and frontends/web/package.json for required Go and Node.js versions. Run
make envinit once to install golangci-lint, gomobile, moq, mockery, goimports, and other
dev tooling.
Run make buildweb before the first make webdev to install npm dependencies. Then run
make servewallet and make webdev in separate terminals.
make servewalletstarts the Go backend (port 8082) in testnet mode by default. Pair withmake webdev(port 8080) for live UI development.- Backend variants:
make servewallet-mainnet,make servewallet-regtest,make servewallet-simulator,make servewallet-prodservers. - The Go backend does not hot-reload — restart it after code changes. The Vite frontend hot-reloads automatically.
make buildwebperforms a clean web build (npm ci && npm run build) used by desktop packages.make webtestandmake webtestwatchrun Vitest unit tests (one-shot and watch mode).make webe2etestruns Playwright E2E tests.make weblintruns ESLint + type-check.make webfixauto-fixes lint issues.make ci(or./scripts/ci.sh) mirrors CI: Go race tests (withGORACE="halt_on_error=1"), frontend build, linting, and i18n format checks. Use it before large pull requests.make go-vendorre-vendors Go dependencies.- All Go commands require
-mod=vendor(e.g.,go build -mod=vendor ./...).
Routes are registered via gorilla/mux in backend/handlers/handlers.go. Two handler wrapper
functions exist:
getAPIRouter— handler signaturefunc(r *http.Request) (interface{}, error); errors serialized as{ "error": "..." }. Do not use this variant in new code.getAPIRouterNoError— handler signaturefunc(r *http.Request) interface{}; the response can include an error message or error code (e.g.{ "success": false, "errorMessage": "...", "errorCode": "..." }). Always use this variant for new handlers.
All handlers are methods on the Handlers struct. Request/response types are typically defined
inline within the handler function. See backend/handlers/handlers.go for examples.
Handlers such as those in backend/handlers/handlers.go should stay thin: parse the request (body,
query params, route params, etc.), call the appropriate backend/account function, and convert the
result to JSON output. Avoid implementing business logic, validation flow, or other decision-making
in handlers. If a handler change would make it reasonable to add a handler unit test to verify
handler logic beyond input parsing, that is usually a design smell. Move that logic into an
appropriate backend package, and keep handlers as request/response adapters.
Use the errp package in backend/util/errp/ for error wrapping and typed error codes. See the
package for available functions and error code patterns.
Structured logging via github.com/sirupsen/logrus. Obtain a logger with
logging.Get().WithGroup("handlers") and use .WithField("key", value).Info("message").
React with TypeScript, Vite (bundler), CSS Modules (styling), i18next (i18n), React Router (hash-based routing).
All components are functional with explicit TypeScript types. Props types are named TProps
(prefixed with T). Use named exports only — no default exports. Default values go in function
parameter destructuring. See src/components/balance/balance.tsx for a typical example.
Page-level components in src/routes/ often use a wrapper/inner split. The wrapper resolves
the account from a route code and passes fully typed props to the inner component. See
src/routes/account/send/send.tsx for an example.
Keep routing-specific coupling low. Prefer passing data through regular component props, app state, or URL-derived inputs over introducing more React Router-specific APIs when a more generic approach works, as this keeps future routing changes easier.
Used extensively for success/failure API responses and mutually exclusive props. See
src/api/account.ts for response types and src/components/forms/button.tsx for prop unions.
Functions in src/api/ should stay as thin wrappers around the underlying request helper. They
should only type the function arguments and the returned promise, and should not add extra client
side control flow or business logic on top of the request.
Key custom hooks in src/hooks/ handle all async data:
useLoad(apiCall, deps)— Calls a promise, returnsundefinedwhile loading, re-calls when deps change. Always checksmounted.currentbeforesetState.useSubscribe(subscription)— Subscribes to a WebSocket event, returnsundefineduntil the first event. Unsubscribes on unmount.useSync(apiCall, subscription)— Loads data once viaapiCall, then stays in sync viasubscription. Best for data that changes in real time (account status, balance).useMountedRef()— Returns a ref that istruewhile mounted,falseafter unmount. Used to guard asyncsetStatecalls.useDefault(value, fallback)— Returnsfallbackwhenvalueisundefined.useDebounce(value, delay)— Debounces a value bydelayms.
Convention: undefined means "loading". Never use a separate isLoading boolean. Render a
skeleton or nothing while the value is undefined.
Important:
- Check
src/hooks/before writing new logic — reuse existing hooks. - Extract reusable hooks instead of writing long
useEffectchains, and write tests for them.
React Context API — contexts live in src/contexts/. Each context has a definition file and a
provider file. All providers are composed in <Providers> in contexts/providers.tsx. See
existing contexts for the pattern.
CSS Modules co-located with components. See src/style/variables.css for shared design tokens.
Use useTranslation() from react-i18next to get t(). Translation keys live in
src/locales/{lang}/app.json (namespace: app). English (en/app.json) is the reference. Keys
are nested by feature area and must be sorted alphabetically. New user-facing strings must have
a translation key — never hardcode display text.
Shared form components live in src/components/forms/. See the directory for available components
(Button, Input, Select, Checkbox, etc.) and their prop patterns. Validation is done in
component state — no form library is used.
- Use the shared
connectKeystore()+KeystoreConnectPromptflow. Do not build page-local connection state machines or duplicate prompt error UI. - Trigger
connectKeystore()from the explicit user action that needs the device, following the existing simple flows insrc/routes/account/sign-message/use-sign-message.ts,src/routes/bitsurance/widget.tsx, andsrc/routes/market/pocket.tsx. - Treat
connectKeystore()failure as an early return. Do not addwrongKeystorehandling in the calling screen; that case is owned by the shared prompt and is not aconnectKeystore()response the caller should handle. - Do not add
try/catcharoundconnectKeystore()in normal frontend flows.
- Must pass
gofmtandgoimports. Linting viagolangci-lint run(config in.golangci.yml).
- ESLint config in
eslint.config.js. - Components are PascalCased, co-located with styles and tests.
- Types prefixed with
T(TProps,TBalance,TAccount). - Named exports only — no default exports.
- Path alias:
@/maps tosrc/(e.g.,import { getBalance } from '@/api/account').
- Components:
my-component.tsx(kebab-case filenames, PascalCase component names) - Styles:
my-component.module.css - Tests:
my-component.test.tsx - Hooks:
hooks/mount.ts,hooks/api.ts - API modules:
api/account.ts,api/rates.ts
Place Go tests in _test.go files and run go test -mod=vendor ./... (optionally via
scripts/coverage.sh to emit coverage.cov). Frontend unit specs live beside components as
*.test.ts(x); invoke make webtest for the suite. Use make webe2etest for Playwright smoke
flows and document new scenarios in frontends/web/tests/README.md if they require fixtures.
- If you change an interface that has a
//go:generatedirective in its docstring, run the correspondinggo generatecommand for that file before finishing the change. For example, if you change theKeystoreinterface, regenerate the keystore mocks.
See src/components/forms/button.test.tsx for component test examples and src/hooks/api.test.ts
for hook test examples. Key patterns: use render + screen queries, wrap in MemoryRouter for
routing components, use renderHook for hooks, and mock API calls with vi.fn().
- when reviewing a removed function call, check that the removed behavior was not required and was not dropped by accident during a refactor.
- when reviewing a removed function call, check if the callee became unused and should also be removed.
- when reviewing handlers such as those in
backend/handlers/handlers.go, check that they only parse the request, call the appropriate backend/account function, and convert the result to JSON output. If a handler contains business logic or has involved unit tests, treat that as a design issue and suggest moving the logic appropriate backend package.
Follow the existing context: summary convention (lowercase imperative, no trailing period)—e.g.,
frontend: simplify account selector. Keep commits atomic and under ~500 lines when
possible.