git-lfs's pre-push hook rejects pushes that reference LFS objects the
local checkout doesn't have. Since we skipped smudge on checkout
(GIT_LFS_SKIP_SMUDGE=1), no LFS content is local. But we're only
pushing branch pointers — no new LFS bytes to upload. Tell lfs to
allow the incomplete push via 'git config lfs.allowincompletepush
true', per the hint the hook itself prints.
Run 90 hit two problems in sequence:
1. Built-in $GITEA_TOKEN is read-only by default in Gitea Actions, so
'git push origin main' 404'd ('failed to push some refs'). Swapped
to a new GIT_PUSH_TOKEN repo secret (admin-scoped PAT) which the
checkout action uses when wiring the authenticated remote.
2. None of our bot accounts are currently in the Infra Matrix room, so
the notification POST would 403 and fail the whole run. Made that
step continue-on-error — the sync is the critical path; a missed
ping is recoverable (check Actions UI, invite a bot later, etc).
The repo's .gitattributes (inherited from upstream) routes certain paths
through git-lfs. Gitea's LFS store doesn't hold those blobs, so on
checkout the smudge filter tries to download them, 404s, and leaves git
in a state where subsequent 'git fetch' calls appear to succeed but
don't actually populate refs.
Run 89 was bitten by this: checkout 'succeeded' with an LFS smudge
fatal, then 'git fetch upstream develop' ran silently, 'git merge
--ff-only upstream/develop' failed because upstream/develop ref
didn't exist locally, and the workflow logged a misleading warning
blaming a divergence that wasn't there.
Setting GIT_LFS_SKIP_SMUDGE=1 keeps LFS pointers as-is. We don't need
image bytes to ff-merge and diff refs.
The Gitea pull-mirror of element-hq/element-x-android is slow to
populate its initial clone (~12 GB). Rather than block workflow
verification on the mirror landing, fetch straight from GitHub — the
runner has outbound access and GitHub isn't flaky. The mirror stays in
place as a fallback / LAN-cache for humans doing manual git fetches.
Daily cron at 12:00 UTC (plus manual dispatch) that:
1. Fetches from the Sulkta-Coop/element-x-upstream pull-mirror
2. Fast-forwards main to upstream/develop if it has advanced
3. Measures how many commits behind main the wallet branch is now
4. Posts a ping to the Infra Matrix room so we know a rebase is due
Uses the house-bot (Matrix) account for notifications; token lives in
the repo's MATRIX_HOUSE_BOT_TOKEN Actions secret.
Removed .github/workflows/* — upstream's 18 workflows are GitHub-specific
(GITHUB_TOKEN scopes, Firebase / Sonar / Sentry / Localazy secrets we
don't have, macOS runners, etc). They were triggering on every push and
failing immediately, flooding the runner log. We're not proposing these
back upstream — we're a fork that doesn't publish to Play/F-Droid, so
their CI isn't ours to run.
If we ever need to see upstream's workflow definitions for reference,
they're one click away on github.com/element-hq/element-x-android.
The Rust SDK removed the low-level SecretStoreWrapper.putSecret/getSecret
API between 26.03.x and 26.04.x — it was an escape hatch we were using
to pin arbitrary bytes into a Matrix 4S slot. The SDK maintainers never
contracted that primitive; locking it down lets their recovery code
evolve without worrying about third-party storage.
This commit replaces that dependency with a self-contained design we
own end-to-end, so future SDK moves no longer break our backup flow.
### Design
- Slot: `com.sulkta.wallet.seed.v1` in Matrix account data.
Our namespace, not a Matrix-spec 4S slot — we are NOT impersonating
Matrix secret storage, we are holding our own opaque blob.
- Envelope (JSON): version tag, algorithm tag, random 12-byte IV, GCM
output (ciphertext || tag), AAD = slot name. AES-256-GCM via stock
javax.crypto. AAD binds a blob to its slot so a blob can't be lifted
from one namespace and successfully opened in another.
- Key: derived from the user's existing Matrix recovery key via
HKDF-SHA256 with info label "sulkta.wallet.seed.v1". The info label
guarantees we never produce the same key bytes Matrix uses for its
own crypto — same secret, different domain.
- I/O: client.setAccountData(key, json) + client.accountData(key)
via the SDK; the homeserver only ever sees the opaque encrypted blob.
### Files
- api/walletsecretstorage/WalletSecretStorage.kt — new interface
- impl/walletsecretstorage/WalletSecretEnvelope.kt — AES-GCM envelope
(with unit tests: round-trip, wrong key, tampered ct, tampered iv,
wrong AAD, wrong version, malformed JSON)
- impl/walletsecretstorage/RecoveryKeyDerivation.kt — base58 decode
+ parity check + HKDF-SHA256 (with unit tests: determinism,
whitespace tolerance, distinct info labels → distinct keys)
- impl/walletsecretstorage/MatrixAccountDataWalletSecretStorage.kt —
WalletSecretStorage impl wrapping Client account data
- test/walletsecretstorage/FakeWalletSecretStorage.kt — in-memory fake
- api/MatrixClient.kt: old .secretStorage → .walletSecretStorage
- features/wallet/.../WalletBackupServiceImpl.kt — rewired to use the
new interface; hasBackupWithoutKey now goes through the same path
instead of manually poking the raw Matrix HTTP API.
- DELETED: api/secretstorage/SecretStorage.kt, SecretStore.kt, impl/
secretstorage/RustSecretStorage.kt — the old SDK-dependent path.
### Backward compat note
Users who backed up a wallet seed on the OLD SDK have a blob in Matrix's
4S at `com.sulkta.cardano.wallet_seed`. This branch cannot read those.
Since the prior integration was only tested internally, acceptable
today — anyone with an old backup re-enters their mnemonic.
TracingConfiguration gained a required sentryConfig parameter between
26.03.x and 26.04.x. Pass null — we don't use SDK-side Sentry.
Timeline.sendRaw was moved off Timeline onto Room. Add sendRawEvent to
the JoinedRoom API interface, implement in JoinedRustRoom by calling
innerRoom.sendRaw, and have RustTimeline.sendRaw proxy through the
owning JoinedRoom. Our /pay event path keeps working without callers
having to know about the SDK move.
* Take into account homeserver capabilities: add `HomeserverCapabilitiesProvider` to check if the HS allows changing the user's display name or avatar. Also, modify the edit user profile screen to reflect these values.
* Add `/myavatar` command. Filter both `/nick` and `/myavatar` commands based on the homeserver capabilities.
* Update screenshots
* Assume the use can change their display name and avatar url if the capabilities check fails: if they try to change those, the HS will return an error anyway.
* Disable also `/myroomname` and `/myroomavatar` based on the HS capabilities.
---------
Co-authored-by: ElementBot <android@element.io>
Add threads list screen for rooms:
- Add `ThreadsListService` to subscribe to thread changes in the room.
- Create `ThreadsListView` and its associated node a presenters (the UI may change).
- Add a menu icon in the room screen to open it.
This is still pending info about unread threads, so several UI components related to it will be hidden.
* Add feature flag and use it to hide the access to this new screen
---------
Co-authored-by: ElementBot <android@element.io>
* feat: Add confirmation modal when inviting unknown users
* tests: Add preview tests for invite confirmation modal
* tests: Add unit tests for invite confirmation modal
* feat: Switch confirmation sheet contents based on identity state
* tests: Add history sharing unit tests for `DefaultStartDMActionTest`
* tests: Update snapshots for `CreateDmConfirmationBottomSheet`
* chore: Fix tiny nits
* fix: Remove default param on `ConfirmingStartDmWithMatrixUser`
* refactor: Use new AsyncAction over boolean flag
* fix: Add sleeps to tests
* refactor: Remove `PromptOrInvite` and switch on async action
* fix: Remove redundant `assertThat`
* feat: Alllow invite confirmation modal to be dismissed
* tests: Update snapshots for InvitePeopleView
* fix: Adjust `CreateDmConfirmationBottomSheet` to conform to design
* feat: Use localazy translations and plurals
* fix: When users are unselected, unselect them in search results too
* tests: Use aMatrixUserList to provide multiple users
* Update screenshots
* fix: Add missing parameter in UserProfilePresenterTest
---------
Co-authored-by: Andy Balaam <andy.balaam@matrix.org>
Co-authored-by: ElementBot <android@element.io>
* Fix `isInAirGappedEnvironment` check for older APIs: use `networkCapabilities.hasCapability` instead of `networkCapabilities.capabilities.contains`, which only works on Android 12 and newer versions
* Check for air-gapped env in the FOSS app too: this unifies the notification behaviour on EXA and Element Pro