Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3fb5d5f
feat: unified approval template + slide-to-approve
TheTrunk Jul 15, 2026
05e2259
feat: multi-chain batch sync + pairing verification (scan-to-verify)
TheTrunk Jul 15, 2026
9eccd4b
feat: signed-action History screen (local, encrypted, biometric-gated)
TheTrunk Jul 16, 2026
9908f67
feat: Phase 5-6 polish — word-verification, PillarMark, privacy mode,…
TheTrunk Jul 16, 2026
a34ed5f
refactor: decompose Home.tsx + migrate icons to Lucide
TheTrunk Jul 16, 2026
a805f52
feat: bundle Inter as the app font (real weights on both platforms)
TheTrunk Jul 16, 2026
574fe83
feat: decode allowance-granting calldata on the approval screen
TheTrunk Jul 16, 2026
ff9ff2d
fix: final recheck — correct setApprovalForAll selector, polish
TheTrunk Jul 17, 2026
9fb12ae
chore: slim native binaries — drop duplicate fonts and vector-icons (…
TheTrunk Jul 17, 2026
30d6a63
chore: dependency upgrade + vulnerability sweep
TheTrunk Jul 17, 2026
77728d6
chore: version 2.0.0 + release notes
TheTrunk Jul 17, 2026
5209a73
feat: final sweep — onboarding, settings and success moments on v2
TheTrunk Jul 18, 2026
88242f5
feat: v2 launch screen + redesigned Settings
TheTrunk Jul 18, 2026
86e1610
feat: periodic backup checkup on the Key (mobile recovery check)
TheTrunk Jul 18, 2026
0f59493
fix: chain sync batch — testnet xpub collisions, decline/invalid hand…
TheTrunk Jul 22, 2026
a4f9056
fix: EVM/UTXO vault sign binding + multi-output approval display
TheTrunk Jul 22, 2026
4247427
fix: verify-scan theme leak + fullscreen scanner; wipe sign history
TheTrunk Jul 22, 2026
28a65b9
feat: v2 polish — password recovery, approval screens, sync
TheTrunk Jul 27, 2026
a9b9c2a
fixes
TheTrunk Aug 3, 2026
0c51e83
ui improvements
TheTrunk Aug 4, 2026
03ab816
chore: bump @runonflux/utxo-lib to 1.0.5
TheTrunk Aug 6, 2026
ae44704
docs: add store listing metadata (fastlane layout)
TheTrunk Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,6 @@ yarn-error.log

.nyc_output/
coverage/

# dev-only screenshot artifacts (regenerable — any *-shots dir + capture scripts)
.*-shots/
1 change: 1 addition & 0 deletions @types/navigation.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type MainParamsList = {
Welcome: undefined;
Create: undefined;
Restore: undefined;
History: undefined;
LavaMoatTest: undefined;
};

Expand Down
5 changes: 4 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ ruby ">= 2.6.10"
gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
gem 'xcodeproj', '< 1.26.0'
gem 'concurrent-ruby', '< 1.3.4'
# concurrent-ruby < 1.3.7 has lock/atomicity CVEs (GHSA-h8w8-99g7-qmvj et al).
# The old '< 1.3.4' pin guarded against a Logger break in activesupport < 7.1;
# we run activesupport 7.2 with an explicit 'logger' gem, so it is no longer needed.
gem 'concurrent-ruby', '>= 1.3.7'

# json >= 2.10 fails to compile on the Xcode Cloud image: mkmf misdetects
# rb_hash_bulk_insert/rb_str_to_interned_str as missing, and the gem's static
Expand Down
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ GEM
netrc (~> 0.11)
cocoapods-try (1.2.0)
colored2 (3.1.2)
concurrent-ruby (1.3.3)
concurrent-ruby (1.3.7)
connection_pool (3.0.2)
drb (2.2.3)
escape (0.0.4)
Expand Down Expand Up @@ -110,7 +110,7 @@ DEPENDENCIES
benchmark
bigdecimal
cocoapods (>= 1.13, != 1.15.1, != 1.15.0)
concurrent-ruby (< 1.3.4)
concurrent-ruby (>= 1.3.7)
json (< 2.10)
logger
mutex_m
Expand All @@ -136,7 +136,7 @@ CHECKSUMS
cocoapods-trunk (1.6.0) sha256=5f5bda8c172afead48fa2d43a718cf534b1313c367ba1194cebdeb9bfee9ed31
cocoapods-try (1.2.0) sha256=145b946c6e7747ed0301d975165157951153d27469e6b2763c83e25c84b9defe
colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a
concurrent-ruby (1.3.3) sha256=4f9cd28965c4dcf83ffd3ea7304f9323277be8525819cb18a3b61edcb56a7c6a
concurrent-ruby (1.3.7) sha256=4412caec3a5ea2e5fdc52076724c071a81f2c0593d83b2ac8cbb8ca63b3151b0
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
escape (0.0.4) sha256=e49f44ae2b4f47c6a3abd544ae77fe4157802794e32f19b8e773cbc4dcec4169
Expand Down
42 changes: 42 additions & 0 deletions RELEASE_NOTES_v2.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# SSP Key v2.0.0

A complete redesign of SSP Key on the new SSP design system, with the approval
moment rebuilt from the ground up. Same keys, same 2-of-2 self-custody, zero
migration: updating in place keeps your Key, pairing, and history exactly as
they were.

## New

- **Slide to approve** — every signature is a deliberate gesture. Release
early and nothing happens.
- **Clearer approvals** — every request type shares one clean layout with
plain-language summaries decoded on your device, never trusted from the
network. Token approvals now warn loudly about unlimited allowances and
collection-wide NFT operator grants, naming the spender.
- **Batch chain sync** — approve once to activate multiple chains for your
wallet, with live progress.
- **Pairing verification code** — your Key shows the same 6 words as your
wallet after pairing; matching words prove your pairing wasn't tampered
with. Scan your wallet's code to verify automatically.
- **Real backup verification** — a quick word challenge replaces the
"I backed it up" checkbox when creating a new key.
- **Signing history** — a local, encrypted, biometric-gated log of everything
this Key has co-signed. Never leaves your device.
- **Privacy mode** — mask identities in your history at a tap.

## Changed

- Complete visual refresh: the new SSP brand (amber, Inter with true bold on
Android, Lucide icons, the pillar mark), light and dark.
- Smaller app: unused icon libraries and duplicate assets removed (~6 MB).
- Motion respects your system's reduce-motion setting.

## Security

- No changes to how transactions are signed or how your seed is stored —
verified by independent audit, including token-level proof that the large
internal refactor changed zero behavior.
- All dependencies updated and exactly pinned; every fixable vulnerability
resolved.
- Works with SSP Wallet v1 and v2 — update in any order; a few v2 features
switch on once both apps are updated.
10 changes: 2 additions & 8 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ android {
applicationId "io.runonflux.sspkey"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 59
versionName "1.28.0"
versionCode 60
versionName "2.0.0"
}
signingConfigs {
debug {
Expand Down Expand Up @@ -134,9 +134,3 @@ dependencies {
implementation jscFlavor
}
}

project.ext.vectoricons = [
iconFontNames: [ 'Feather.ttf', 'MaterialCommunityIcons.ttf' ] // only these fonts, icons are used
]

apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle")
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
import com.facebook.react.common.assets.ReactFontManager
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost

class MainApplication : Application(), ReactApplication {
Expand All @@ -22,6 +23,9 @@ class MainApplication : Application(), ReactApplication {

override fun onCreate() {
super.onCreate()
// Register Inter XML font family so fontFamily: 'Inter' + fontWeight (400-700)
// resolves to real font faces instead of faux-bolding a single file.
ReactFontManager.getInstance().addCustomFont(this, "Inter", R.font.inter)
loadReactNative(this)
}
}
32 changes: 32 additions & 0 deletions android/app/src/main/res/font/inter.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<font
android:font="@font/inter_regular"
android:fontStyle="normal"
android:fontWeight="400"
app:font="@font/inter_regular"
app:fontStyle="normal"
app:fontWeight="400" />
<font
android:font="@font/inter_medium"
android:fontStyle="normal"
android:fontWeight="500"
app:font="@font/inter_medium"
app:fontStyle="normal"
app:fontWeight="500" />
<font
android:font="@font/inter_semibold"
android:fontStyle="normal"
android:fontWeight="600"
app:font="@font/inter_semibold"
app:fontStyle="normal"
app:fontWeight="600" />
<font
android:font="@font/inter_bold"
android:fontStyle="normal"
android:fontWeight="700"
app:font="@font/inter_bold"
app:fontStyle="normal"
app:fontWeight="700" />
</font-family>
Binary file added android/app/src/main/res/font/inter_bold.ttf
Binary file not shown.
Binary file added android/app/src/main/res/font/inter_medium.ttf
Binary file not shown.
Binary file added android/app/src/main/res/font/inter_regular.ttf
Binary file not shown.
Binary file added android/app/src/main/res/font/inter_semibold.ttf
Binary file not shown.
Binary file added assets/fonts/Inter-Bold.ttf
Binary file not shown.
Binary file added assets/fonts/Inter-Medium.ttf
Binary file not shown.
Binary file added assets/fonts/Inter-Regular.ttf
Binary file not shown.
Binary file added assets/fonts/Inter-SemiBold.ttf
Binary file not shown.
21 changes: 21 additions & 0 deletions assets/fonts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Inter Fonts — Canonical Source Files

These are the canonical Inter v4.1 static TTFs (Regular, Medium, SemiBold, Bold) used by SSP Key.

## How they are wired into each platform

- **iOS**: linked manually — listed in `ios/SSPKey/Info.plist` under `UIAppFonts` and referenced
as plain file resources in `ios/SSPKey.xcodeproj/project.pbxproj` (paths point at this directory).
- **Android**: NOT sourced from here at build time. Android uses the XML font family at
`android/app/src/main/res/font/inter.xml` (with `inter_*.ttf` copies in the same directory),
registered in `MainApplication.kt` via `ReactFontManager.addCustomFont(this, "Inter", R.font.inter)`.

## react-native-asset is deliberately NOT used

There is no `assets` entry in `react-native.config.js` on purpose. react-native-asset used to copy
these TTFs into `android/app/src/main/assets/fonts/`, duplicating the `res/font/` copies (~1.6 MB of
dead weight in the APK — Android resolves the `Inter` family exclusively through `res/font/inter.xml`).

Do not re-add an `assets` config or run `npx react-native-asset` — it would reintroduce the
duplicates. This directory exists to document provenance and to serve as the source for the iOS
resource references and any future re-generation of the Android `res/font` copies.
10 changes: 10 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ export default [
'@typescript-eslint/no-unsafe-return': 'off',
},
},
{
// Reanimated shared values are read/written from gesture worklets created
// during render — the designed Reanimated pattern. react-hooks/refs
// cannot distinguish a SharedValue from a React ref and flags it. Scoped
// to the one gesture component; do not disable globally.
files: ['src/components/request/SlideToApprove.tsx'],
rules: {
'react-hooks/refs': 'off',
},
},
{
files: ['tests/**/*.ts', 'tests/**/*.tsx'],
rules: {
Expand Down
18 changes: 18 additions & 0 deletions fastlane/metadata/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Store listing metadata

Source of truth for the SSP Key store listings. Standard fastlane layout so it can be pushed with `fastlane supply` (Android) / `fastlane deliver` (iOS) later, or copy-pasted into the consoles manually.

| File | Store field | Limit |
| --- | --- | --- |
| `android/en-US/title.txt` | Play Store app name | 30 chars |
| `android/en-US/short_description.txt` | Play Store short description | 80 chars |
| `android/en-US/full_description.txt` | Play Store full description | 4000 chars |
| `en-US/name.txt` | App Store name | 30 chars |
| `en-US/subtitle.txt` | App Store subtitle | 30 chars |
| `en-US/promotional_text.txt` | App Store promotional text (editable without review) | 170 chars |
| `en-US/keywords.txt` | App Store keywords (comma-separated, no spaces needed) | 100 chars |
| `en-US/description.txt` | App Store description | 4000 chars |

Only English is maintained here; other locales are handled separately (Crowdin for in-app strings).

Screenshot assets live in `../screenshots/` (see its README for sizes and regeneration).
42 changes: 42 additions & 0 deletions fastlane/metadata/android/en-US/full_description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
SSP Key – Secure. Simple. Powerful.
The second key to your SSP Wallet: true two-factor, 2-of-2 multisignature self-custody.

SSP Key turns your phone into the second factor of a genuine two-of-two multisig wallet. The SSP Wallet browser extension holds one private key, and SSP Key holds the other. Every transaction must be signed by both devices — so a stolen laptop, a phished password, or a compromised browser is never enough to move your funds. There is no single seed phrase that can be stolen from one device.

🔒 How it works

• A transaction is created and signed in SSP Wallet on your computer.
• The request instantly appears on your phone in SSP Key.
• You review the details and approve — SSP Key adds the second signature.
• Keys are generated on your devices and never leave them. No seed, private key, or extended private key is ever shared between devices.

✅ Approve with confidence

See exactly what you are signing before you sign it: recipient, amount, fee, and chain, with decoded contract calls for dApp interactions. Approvals are protected by your fingerprint, face unlock, or PIN.

🔍 Open source and audited

The entire codebase is public — anyone can review, audit, and contribute. SSP Wallet, SSP Key, and the SSP smart contracts have been independently audited by Halborn. SSP Key also integrates LavaMoat runtime protection to defend against supply-chain attacks.

🌐 Multi-chain, one key

One SSP Key secures all your chains: Bitcoin (Native SegWit), Ethereum, and major EVM networks — Polygon, Base, BNB Smart Chain, Avalanche — plus Litecoin, Bitcoin Cash, Dogecoin, Zcash, Ravencoin, and Flux, with custom token support on EVM chains. On Ethereum and EVM chains, SSP is the first wallet with true Schnorr multi-signature Account Abstraction (ERC-4337).

⚡ Web3 ready

Connect SSP Wallet to thousands of dApps through WalletConnect v2 (Reown). Every dApp transaction and signature still requires approval on your SSP Key — multi-signature security for all of Web3.

📴 Works offline too

Sign transactions entirely offline using QR-code synchronization between your devices — self-custody that works even in restricted or air-gapped environments.

🚀 Getting started

1. Install SSP Wallet in your browser (Chrome or Firefox).
2. Install SSP Key (this app) on your phone.
3. Scan the sync QR code — your 2-of-2 wallet is ready in minutes.
4. Approve transactions with a single slide, secured by biometrics.

Guides and documentation: docs.sspwallet.io

SSP Key is built for people who value security, transparency, and real self-custody — whether you are new to crypto or protecting serious holdings.
1 change: 1 addition & 0 deletions fastlane/metadata/android/en-US/short_description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The second key to your SSP Wallet — true 2-of-2 multisig self-custody.
1 change: 1 addition & 0 deletions fastlane/metadata/android/en-US/title.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SSP Key: Crypto Wallet 2FA
42 changes: 42 additions & 0 deletions fastlane/metadata/en-US/description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
SSP Key – Secure. Simple. Powerful.
The second key to your SSP Wallet: true two-factor, 2-of-2 multisignature self-custody.

SSP Key turns your phone into the second factor of a genuine two-of-two multisig wallet. The SSP Wallet browser extension holds one private key, and SSP Key holds the other. Every transaction must be signed by both devices — so a stolen laptop, a phished password, or a compromised browser is never enough to move your funds. There is no single seed phrase that can be stolen from one device.

HOW IT WORKS

- A transaction is created and signed in SSP Wallet on your computer.
- The request instantly appears on your phone in SSP Key.
- You review the details and approve — SSP Key adds the second signature.
- Keys are generated on your devices and never leave them. No seed, private key, or extended private key is ever shared between devices.

APPROVE WITH CONFIDENCE

See exactly what you are signing before you sign it: recipient, amount, fee, and chain, with decoded contract calls for dApp interactions. Approvals are protected by Face ID, Touch ID, or your PIN.

OPEN SOURCE AND AUDITED

The entire codebase is public — anyone can review, audit, and contribute. SSP Wallet, SSP Key, and the SSP smart contracts have been independently audited by Halborn. SSP Key also integrates LavaMoat runtime protection to defend against supply-chain attacks.

MULTI-CHAIN, ONE KEY

One SSP Key secures all your chains: Bitcoin (Native SegWit), Ethereum, and major EVM networks — Polygon, Base, BNB Smart Chain, Avalanche — plus Litecoin, Bitcoin Cash, Dogecoin, Zcash, Ravencoin, and Flux, with custom token support on EVM chains. On Ethereum and EVM chains, SSP is the first wallet with true Schnorr multi-signature Account Abstraction (ERC-4337).

WEB3 READY

Connect SSP Wallet to thousands of dApps through WalletConnect v2 (Reown). Every dApp transaction and signature still requires approval on your SSP Key — multi-signature security for all of Web3.

WORKS OFFLINE TOO

Sign transactions entirely offline using QR-code synchronization between your devices — self-custody that works even in restricted or air-gapped environments.

GETTING STARTED

1. Install SSP Wallet in your browser (Chrome or Firefox).
2. Install SSP Key (this app) on your phone.
3. Scan the sync QR code — your 2-of-2 wallet is ready in minutes.
4. Approve transactions with a single slide, secured by biometrics.

Guides and documentation: docs.sspwallet.io

SSP Key is built for people who value security, transparency, and real self-custody — whether you are new to crypto or protecting serious holdings.
1 change: 1 addition & 0 deletions fastlane/metadata/en-US/keywords.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
crypto,wallet,multisig,bitcoin,ethereum,2fa,self custody,security,btc,eth,web3,defi,authenticator
1 change: 1 addition & 0 deletions fastlane/metadata/en-US/name.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SSP Key
1 change: 1 addition & 0 deletions fastlane/metadata/en-US/promotional_text.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Your phone holds the second key: every transaction needs both your browser and your SSP Key to sign. True 2-of-2 multisig self-custody — open source, audited by Halborn.
1 change: 1 addition & 0 deletions fastlane/metadata/en-US/subtitle.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2FA Key for your Crypto Wallet
Loading
Loading