Structure, setup, and the commands you'll use daily.
- Node.js >= 22 and pnpm 10.28+ (see
packageManagerinpackage.json) - iOS: Xcode 15+, CocoaPods via Bundler (Ruby), iOS Simulator
- Android: Android Studio + SDKs, JDK 17, emulator or device
- macOS: Watchman for fast reloads
pnpm install
pnpm run setup # Git hooks: lint/format/copyright, then tests on push
pnpm --filter mobile expo:prebuild # First run, or to regenerate native projectsMetro in one terminal:
pnpm mobile:startA platform target in another:
pnpm ios
pnpm androidBoth also work from the app folder: pnpm -C apps/mobile start|ios|android.
To regenerate native projects from scratch, pnpm -C apps/mobile expo:prebuild:clean.
Workspace packages in packages/* build to dist/. Turbo builds them before running the mobile app
or tests, so most development needs no manual build step.
Each package's build runs vite build for the JavaScript and then tsc -p tsconfig.build.json for
the declarations. That config carries the exclude list keeping test-only modules out of the
published type surface — and because tsconfig globs have no brace expansion, those patterns are
written out one per line. pnpm run check:dts-emit guards both halves.
Note
In local development Metro resolves packages directly from their src/index.ts,
so package changes show up in the app with no build.
pnpm build # every package, plus generated configuration
pnpm build:packages # workspace packages only
pnpm dev:packages # watch mode, for editors and testsRun a local Algorand node and point the app at it instead of live networks. Needs Docker running and
the AlgoKit CLI (brew install algorandfoundation/tap/algokit).
pnpm localnet # start LocalNet (algod :4001, indexer :8980, kmd :4002)
pnpm localnet:fund --new 100 # create + fund a throwaway account (prints mnemonic)
pnpm localnet:fund --new-quantum 100 # same, for a quantum account (prints seed hex)
pnpm ios # or: pnpm android
pnpm localnet:stopPoint the app at it from Settings, Developer, Node Settings, Custom network. Selecting it opens a sheet for the algod and indexer URLs, their optional tokens, and the genesis hash and ID; Fetch from node fills the genesis values in. Save and switch applies the config at runtime, with no rebuild, no env rewrite, and MainNet and TestNet untouched.
Two things to watch:
- On a physical device,
localhostresolves to the device, not your machine. Use the host's LAN address (http://192.168.1.50:4001). pnpm localnet:resetregenerates the genesis hash, so re-enter the custom config afterwards.
pnpm localnet:quantum-checkRuns an end-to-end check of a quantum-signed transaction against LocalNet: derives a Falcon-1024 address, funds it, builds a payment with a PQ-raised fee, checks the assembled PQ envelope against algosdk's own PQ signer, then broadcasts and waits for confirmation.
Default LocalNet (algod 4.7.4-stable) has no pqsig support, so the script
reports PENDING at exit 0 once the broadcast is rejected for that reason
specifically. Any other failure reports FAIL with a non-zero exit.
For a real PASS: confirmed in round N, point LocalNet at a pqsig-capable node. Two things are
needed together: the master image, and a genesis whose consensus enables Falcon-1024 (v42,
inherited by future). Edit ~/.config/algokit/sandbox/:
# docker-compose.yml -> image: algorand/algod:master
# algod_network_template.json -> add "ConsensusProtocol": "future", under "Genesis"
docker compose -f ~/.config/algokit/sandbox/docker-compose.yml down -v
docker compose -f ~/.config/algokit/sandbox/docker-compose.yml up -d
pnpm localnet:quantum-check # -> PASS: confirmed in round NThe check talks to localhost:4001 directly and needs no app config. To exercise the app against
this node, re-enter the custom network in Settings, Developer, Node Settings. Recreating the
containers changes the genesis hash, so the previously saved config no longer matches.
algokit localnet start and reset both rewrite those two files, so re-apply the edits after
running either. algorand/algod:nightly is not a substitute; it still lacks pqsig.
Warning
This swap kills the indexer, but the app still works. Consensus v42 also enables LoadTracking,
adding a ld field to the block header. The published conduit-localnet and indexer images
predate it and cannot decode it, so conduit dies with
error decoding block for round 1: msgpack decode error ... key ld and the
indexer stays pinned at round 0 (docker logs algokit_sandbox_conduit;
curl -s localhost:8980/health).
Balances, the asset list and sending are unaffected, because account-syncer.ts reads them from
algod and holdings only fall back to the indexer past algod's resource cap. Expect indexer-backed
surfaces (transaction history, large accounts) to be empty. If a balance reads 0.00 on LocalNet it
is far more likely a stale account_balances row than the indexer: pull-to-refresh does NOT
re-fetch when a row already exists, so relaunch the app after funding.
pnpm test:conformance runs the app's builders, keystore, signing and error-handling code against
this LocalNet instead of a mock. conformance/README.md covers what it
proves, its prerequisites and its known gaps.
pera-react-native/
├── apps/
│ ├── mobile/ # React Native app (UI layer)
│ └── browser/ # Chrome MV3 browser extension
├── packages/ # Headless business logic (one per domain)
│ ├── accounts/ # Account management and state
│ ├── assets/ # Asset management
│ ├── blockchain/ # Algorand-specific code (node/indexer)
│ ├── config/ # Configuration and environment
│ ├── database/ # Local persistence
│ ├── devtools/ # Development tools
│ │ └── tsconfig/ # Shared TypeScript configuration
│ ├── kms/ # Key Management System integration
│ ├── shared/ # Common utilities, types, and models
│ ├── signing/ # Signing pipeline
│ ├── walletconnect/ # WalletConnect integration
│ └── … # contacts, swaps, staking, card, nfd, …
├── extensions/ # Platform adapters behind one interface
│ ├── platform/ # The platform contract
│ ├── platform-react-native/ # React Native implementation
│ ├── platform-chrome/ # Chrome implementation
│ ├── provider/ # `getProvider()` accessor used by packages
│ └── … # ledger-*, keystore-chrome, passkey-autofill
├── conformance/ # LocalNet conformance suite
├── tools/ # Development and CI scripts
├── specs/ # OpenAPI specifications
└── docs/ # Project documentation
extensions/ means platform drivers, not browser extensions. See
Architecture for why the two names collide.
The workspace definition is in pnpm-workspace.yaml.
- Task runner and cache: Turborepo (scripts in
package.json) - Formatting: oxfmt
- Linting: Oxlint via
.oxlintrc.json - Dead code, cycles, duplication: fallow via
.fallowrc.jsonc - TypeScript project references via
packages/devtools/tsconfig
pnpm build # build all packages
pnpm build:packages # build only workspace packages
pnpm dev:packages # watch mode for package development
pnpm test # run all tests (unit + integration)
pnpm test:unit # run unit tests only
pnpm test:coverage # run tests with coverage
pnpm lint # report lint/type-aware issues
pnpm lint:fix # auto-fix lint/type-aware issues
pnpm lint:copyright # add/update necessary copyright headers
pnpm lint:i18n # report i18n errors
pnpm lint:docs # report stale doc/comment references
pnpm format # format files
pnpm fallow # report unused code, circular deps, duplicationfallow finds cross-module unused exports, files, types and
dependencies, plus circular dependencies and code duplication, which neither Oxlint nor tsc cover.
Config lives in .fallowrc.jsonc.
It runs in CI as an advisory, non-blocking job (Dead Code in
ci-pre-merge.yml): findings appear in the job summary but never
fail a PR. The plan is to triage the existing findings, then ratchet specific rules to blocking with
a --baseline so only new findings fail. Do removals in reviewed PRs, not via fallow fix.
| Doc | Covers |
|---|---|
| Architecture | Layering, platform drivers, per-platform features |
| Style Guide | Decisions behind the enforced rules |
| Testing | Unit, integration harness, locale tour |
| Security | Reporting a flaw, key custody, vault limits, supply chain |
| Offline & Paused State | DB-first reads, the paused render contract |
| Translation Guide | Locale bundles, plural traps, per-language rules |
| WebView Architecture | The in-app webview bridge, its trust model and v3 method set |
| Pera Card | Baanx contract, onboarding, AutoDraw |
| CI Automation | Jira sync and release publishing |
| Release | Tags, pipelines, store submission |
| Documentation Standards | Where a given piece of documentation belongs |
| Contributing | Branches, commits, PRs |
For app-specific notes, see apps/mobile/README.md.