Oubli is a privacy-preserving mobile wallet built on Starknet. The native iOS and Android apps are thin shells over a Rust workspace that owns auth, encrypted storage, wallet state, swaps, and the UniFFI bridge.
AGENTS.md: operator guide and repo constraintsdocs/change-map.md: what else to touch when you edit a given subsystemdocs/env.md: env files, safe defaults, and networked workflowsdocs/architecture-deep-dive.md: long-form architecture and implementation notes
crates/oubli-wallet/: wallet state machine, operations, RPC, paymaster, swap orchestrationcrates/oubli-bridge/: UniFFI FFI layer consumed by Swift and Kotlincrates/oubli-swap/: QuickJS runtime embedding the Atomiq SDK bundleoubli-swap-js/: TypeScript source that builds intocrates/oubli-swap/js/bundle.jscrates/oubli-store/: encrypted blob storage and platform storage callbacksios/: SwiftUI appandroid/: Jetpack Compose app
cp .sepolia.env.example .sepolia.env
make env-status
make test-offline
make test-smoke
make check-rust
make check-swapmake defaults to .sepolia.env when it exists. Mainnet workflows are explicit and opt-in.
make test-android-unit
make build-swap-js
make regen-swift
make regen-kotlin
make regen-bindings
make regen-all
make coverage-rust
make coverage-android-unit
make test-sepolia
make OUBLI_ENV_FILE=.mainnet.env OUBLI_ALLOW_MAINNET=1 test-mainnetStart with Rust coverage first, then add Android JVM coverage:
make coverage-rust
make coverage-android-unitThis writes:
target/coverage/rust/summary.txttarget/coverage/rust/lcov.infotarget/coverage/rust/html/index.htmltarget/coverage/android-unit/summary.txttarget/coverage/android-unit/report.xmltarget/coverage/android-unit/html/index.html
See docs/testing/coverage.md for the reporting workflow and release checklist.
These files are checked in and must be regenerated, not hand-edited:
crates/oubli-swap/js/bundle.jsios/Generated/oubli.swiftios/Generated/oubliFFI/oubliFFI.hios/Generated/oubliFFI/module.modulemapandroid/app/src/main/java/uniffi/oubli/oubli.kt
Use:
make verify-swap-bundle
make verify-swift-bindings
make verify-kotlin-bindingsBefore editing any of these areas, check docs/change-map.md:
- Bridge / UDL changes
- Swap TypeScript and runtime changes
- Wallet core, auth, and storage changes
- Generated bindings and bundle outputs
Edit android/app/build.gradle.kts — increment versionCode and versionName.
TAG=v0.1.69 # replace with the release tag you are shipping
git tag "$TAG"
git push origin "$TAG"The Release workflow builds a signed APK and creates a GitHub Release with the APK attached.
Download the release APK and publish with your bunker signer:
TAG=v0.1.69 # replace with the release tag you are shipping
gh release download "$TAG" --pattern "oubli-*.apk" --dir /tmp/oubli-release
mkdir -p android/app/build/outputs/apk/release
cp /tmp/oubli-release/oubli-*.apk android/app/build/outputs/apk/release/app-release.apk
cd android && SIGN_WITH="bunker://..." ~/go/bin/zsp publish -y --skip-preview --skip-certificate-linking zapstore.yamlThis keeps your Nostr signing key on your phone (Amber approves via bunker).
| Secret | Description |
|---|---|
OUBLI_KEYSTORE_BASE64 |
Base64-encoded android/oubli-release.jks |
OUBLI_KEYSTORE_PASSWORD |
Keystore password |
OUBLI_MAINNET_RPC_URL |
Mainnet RPC URL (for build.rs compile-time encoding) |
OUBLI_MAINNET_PAYMASTER_API_KEY |
Mainnet paymaster API key |
OUBLI_FEE_COLLECTOR_PUBKEY |
Fee collector public key |
OUBLI_FEE_PERCENT |
Fee percentage |
- Do not default local automation to mainnet.
- Do not hand-edit generated bindings or bundle outputs.
- Keep signing in Rust. JS should never touch private keys.
- Go through
BlobManagerand platform storage callbacks for secrets at rest.