Skip to content
Draft

v7.4 #594

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
e11d779
fix: build and wallet creation
Maxnflaxl May 8, 2026
dadd8a8
#587: fix faucet crash
Maxnflaxl May 8, 2026
935850b
#588: receive screen redesign
Maxnflaxl May 8, 2026
ac35f7a
bump version
Maxnflaxl May 8, 2026
4192ddd
#593: better explanations on the receive screen
Maxnflaxl May 8, 2026
8e9ab9e
#589: update sync msg
Maxnflaxl May 8, 2026
f7c3e5e
fix deprecation notice
Maxnflaxl May 8, 2026
665f0e3
dont set dark mode based on device default
Maxnflaxl May 8, 2026
82d2d14
fix wallet crash on dapp store page
Maxnflaxl May 8, 2026
09fcf8b
Beam Messenger
Maxnflaxl May 8, 2026
d94dd92
Asset Swaps
Maxnflaxl May 9, 2026
60ad2c1
Beam Messenger UI improvement
Maxnflaxl May 9, 2026
36c59c6
fix(restore): install Rules on the dispatch thread before WalletModel…
Maxnflaxl May 9, 2026
5965b0d
fix(restore): throttle RecoveryProgress::OnProgress to integer-percen…
Maxnflaxl May 9, 2026
8a5e30f
feat(swap): cap order expiration at 12h
Maxnflaxl May 9, 2026
bd56aa5
refactor(settings): move "Clear local data" out of preferences into t…
Maxnflaxl May 9, 2026
85a561c
chore: delete orphaned Receive/b.swift
Maxnflaxl May 9, 2026
eadcd4a
style: SwiftLint cleanup across the codebase
Maxnflaxl May 9, 2026
a11d622
chore: bump copyright year to 2026
Maxnflaxl May 9, 2026
7a03378
bump copyright year to 2026
Maxnflaxl May 9, 2026
bb1aa42
feat(utxo): per-asset grouping with split-coins bottom sheet
Maxnflaxl May 9, 2026
64456e3
fix(settings): dynamic version footer + uniform scroll bounce
Maxnflaxl May 9, 2026
c7334f9
fix(navigation): restore edge-swipe back on every pushed sub-page
Maxnflaxl May 9, 2026
d3a2430
feat(addresses): allow setting Edit Address expiry to Never
Maxnflaxl May 9, 2026
60cf971
refactor(base-table): unify bottom-anchored action bar across screens
Maxnflaxl May 9, 2026
a717a39
fix(wallet): collapse asset-info storm at startup
Maxnflaxl May 9, 2026
ef77c10
fix(receive): generate a fresh wallet address per Receive open
Maxnflaxl May 9, 2026
b676053
perf(assets): load full asset registry once per session
Maxnflaxl May 9, 2026
4c4dcb6
feat(receive): address-type picker + per-address voucher count
Maxnflaxl May 9, 2026
9190461
fix(asset-swaps): pre-flight balance check + sticky footer on details
Maxnflaxl May 9, 2026
2919cf3
disable scroll/bobbing in the sidebar
Maxnflaxl May 10, 2026
f69af11
feat(settings): restructure Node settings + add Peers screen
Maxnflaxl May 10, 2026
f20eb62
feat(sync): phase-aware messages + stabilization window on resume
Maxnflaxl May 10, 2026
3884c86
fix(wallet): prevent upgrade-path wipe and double createWallet on ran…
Maxnflaxl May 11, 2026
58eb94c
fix(bridge): prefetch assets after start, single-shot sqlite temp-dir…
Maxnflaxl May 11, 2026
30b9cfe
fix(asset-swaps): submit idempotency + grothFrom overflow + BMFieldCe…
Maxnflaxl May 11, 2026
08b10ce
fix(threading): main-dispatch caches, BEAM split fee, dark-mode lock,…
Maxnflaxl May 11, 2026
a8f6869
chore(cleanup): retain cycles, dead inspectables, gesture guard, vouc…
Maxnflaxl May 11, 2026
2b474a8
fix(create-flow): drop redundant createWallet from mobile/own-node pa…
Maxnflaxl May 11, 2026
37c1784
fix(asset-swaps): surface asset IDs across create/list/details; stop …
Maxnflaxl May 11, 2026
8dd8865
feat(utxo): per-asset drill-down + consolidate via Split sheet
Maxnflaxl May 11, 2026
8b22b59
feat(utxo): offline (Lelantus) toggle for split + consolidate
Maxnflaxl May 12, 2026
0e290b4
feat(dapps): DApp store, sideloaded install, and My DApps screen
Maxnflaxl May 13, 2026
a6ca324
feat(price-oracle): on-chain BEAM/USD oracle toggle
Maxnflaxl May 13, 2026
412f724
feat(addresses): pin Public Offline at the top of the Active list
Maxnflaxl May 13, 2026
cba596d
refactor(alerts): route confirm/alert UI through BMAlertViewController
Maxnflaxl May 13, 2026
8df0137
fix(price-oracle): refresh oracle price on every wallet sync
Maxnflaxl May 13, 2026
05e5e5c
Bump bundled BEAM libraries to 7.5.14510
Maxnflaxl Jul 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
673 changes: 572 additions & 101 deletions BeamWallet.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1240"
LastUpgradeVersion = "1600"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1240"
LastUpgradeVersion = "1600"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1240"
LastUpgradeVersion = "1600"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
112 changes: 79 additions & 33 deletions BeamWallet/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// AppDelegate.swift
// BeamWallet
//
// Copyright 2018 Beam Development
// Copyright 2026 Beam Development
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -45,58 +45,82 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
CrashEye.add(delegate: self)

if #available(iOS 15.0, *) {
UITableView.appearance().sectionHeaderTopPadding = CGFloat(0)
}

if #available(iOS 13.0, *) {
let SVGCoder = SDImageSVGCoder.shared
SDImageCodersManager.shared.addCoder(SVGCoder)
}

UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)

UIApplication.shared.isIdleTimerDisabled = true

Localizable.shared.reset()
Settings.sharedManager()

KeyboardListener.shared.start()

NotificationManager.sharedManager.requestPermissions()

// if Settings.sharedManager().target != Mainnet {
// CrowdinManager.updateLocalizations()
// }

AppModel.sharedManager().checkRecoveryWallet()
AppModel.sharedManager().addDelegate(self)
Settings.sharedManager().addDelegate(self)

let added = AppModel.sharedManager().isWalletAlreadyAdded()

let rootController = BaseNavigationController.navigationController(rootViewController: added ? EnterWalletPasswordViewController() : WellcomeViewController())


// Backfill the integrity marker for installs that predate the flag
// (v7.3 → v7.4 upgrade): if the DB file is on disk, the wallet was
// healthy before the upgrade — set the flag so the recovery prompt
// below doesn't wipe their data on first launch.
if added && !OnboardManager.shared.isWalletInitializedFlag() {
OnboardManager.shared.markWalletInitialized()
}

// DB file present but integrity marker missing implies a crash between
// DB init and onWalledOpened — fall back to onboarding with a recovery prompt.
let needsRecovery = added && !OnboardManager.shared.isWalletInitializedFlag()

let rootViewController: UIViewController = (added && !needsRecovery)
? EnterWalletPasswordViewController()
: WellcomeViewController()
let rootController = BaseNavigationController.navigationController(rootViewController: rootViewController)

self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.backgroundColor = UIColor.main.navy
self.window?.rootViewController = rootController
self.window?.makeKeyAndVisible()

if #available(iOS 12.0, *) {
let isDark = self.window?.rootViewController?.traitCollection.userInterfaceStyle == .dark
Settings.sharedManager().setDefaultDarkMode(isDark)
}
else {
Settings.sharedManager().setDefaultDarkMode(false)

if needsRecovery {
DispatchQueue.main.async {
rootController.confirmAlert(
title: Localizable.shared.strings.wallet_recovery_title,
message: Localizable.shared.strings.wallet_recovery_message,
cancelTitle: Localizable.shared.strings.cancel,
confirmTitle: Localizable.shared.strings.reset_and_retry,
cancelHandler: { _ in },
confirmHandler: { _ in
AppModel.sharedManager().resetWallet(true)
}
)
}
}

Settings.sharedManager().setDefaultDarkMode(false)

ShortcutManager.launchWithOptions(launchOptions: launchOptions)


if let crash = UserDefaults.standard.string(forKey: "crash"), let crash_name = UserDefaults.standard.string(forKey: "crash_name") {

var name = "";
var name = ""

if Settings.sharedManager().target == Mainnet {
name = "GoogleServiceMain"
Expand Down Expand Up @@ -240,9 +264,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {


func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {

//options[.sourceApplication] as? String == "com.beam.runner"


// options[.sourceApplication] as? String == "com.beam.runner"

if url.isFileURL && url.pathExtension.lowercased() == "dapp" {
return handleSideloadedDApp(url: url)
}

if let params = url.queryParameters, params.count == 2,
let amount = params["amount"],
let userId = params["user_id"] {
Expand All @@ -263,6 +291,26 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return true
}

private func handleSideloadedDApp(url: URL) -> Bool {
guard AppModel.sharedManager().isLoggedin else { return false }
let secured = url.startAccessingSecurityScopedResource()
defer { if secured { url.stopAccessingSecurityScopedResource() } }
guard let data = try? Data(contentsOf: url) else { return false }

do {
_ = try DAppManager.shared.installFromZip(data: data,
fallbackName: Localizable.shared.strings.dapps_sideloaded_name,
fallbackIcon: "")
} catch {
return false
}

guard let top = UIApplication.getTopMostViewController(),
let nav = top.navigationController else { return true }
nav.pushViewController(MyDAppsViewController(), animated: true)
return true
}

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
}

Expand Down Expand Up @@ -325,11 +373,11 @@ extension AppDelegate: WalletModelDelegate {
if let result = data, result.type == .address {
AppModel.sharedManager().cancelDeleteAddress(result.id)
}
}) { data in
}, ended: { data in
if let result = data, result.type == .address {
AppModel.sharedManager().deletePreparedAddresses(result.id)
}
}
})
}
}

Expand All @@ -341,11 +389,11 @@ extension AppDelegate: WalletModelDelegate {
if let result = data, result.type == .transaction {
AppModel.sharedManager().cancelPreparedTransaction(result.id)
}
}) { data in
}, ended: { data in
if let result = data, result.type == .transaction {
AppModel.sharedManager().sendPreparedTransaction(result.id)
}
}
})
}
}

Expand All @@ -363,11 +411,9 @@ extension AppDelegate: WalletModelDelegate {
t.isIncome && !t.isSelf
}

for transaction in filtered {
if oldTransactions.first(where: { $0 == transaction.id }) == nil {
NotificationManager.sharedManager.scheduleNotification(transaction: transaction)
oldTransactions.append(transaction.id)
}
for transaction in filtered where oldTransactions.first(where: { $0 == transaction.id }) == nil {
NotificationManager.sharedManager.scheduleNotification(transaction: transaction)
oldTransactions.append(transaction.id)
}

userDefaults.set(oldTransactions, forKey: "transactions_ids")
Expand Down
63 changes: 61 additions & 2 deletions BeamWallet/BeamSDK/AppModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// AppModel.h
// BeamWallet
//
// Copyright 2018 Beam Development
// Copyright 2026 Beam Development
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -44,6 +44,14 @@
#import "AssetsManager.h"
#import "StringManager.h"
#import "BMApp.h"
#import "BMInstantMessage.h"
#import "BMChat.h"
#import "BMDexOrder.h"
#import "BMInstalledDApp.h"
#import "BMAvailableDApp.h"
#import "BMPublisher.h"

@class WalletAPIClient;

enum {
BMRestoreManual = 0,
Expand Down Expand Up @@ -79,8 +87,14 @@ typedef int BMRestoreType;
-(void)onNotificationsChanged;
-(void)onChangeCalculated:(double)amount;
-(void)onMaxPrivacyTokensLeft:(int)tokens;
-(void)onOfflinePaymentsCountForWalletId:(NSString*_Nonnull)walletId count:(int)count;
-(void)onAssetInfoChange;
-(void)onDAPPsLoaded;
-(void)onChatListChanged;
-(void)onChatMessagesLoaded:(NSString*_Nonnull)peerWalletId messages:(NSArray<BMInstantMessage*>*_Nonnull)messages;
-(void)onInstantMessageReceived:(BMInstantMessage*_Nonnull)message;
-(void)onChatRemoved:(NSString*_Nonnull)peerWalletId;
-(void)onDexOrdersChanged:(NSArray<BMDexOrder*>*_Nonnull)orders;
@end

typedef void(^NewAddressGeneratedBlock)(BMAddress* _Nullable address, NSError* _Nullable error);
Expand All @@ -104,12 +118,14 @@ typedef void(^ExportCSVBlock)(NSString * _Nonnull data, NSURL * _Nonnull url);
@property (nonatomic,assign) BOOL isUpdating;
@property (nonatomic,assign) BOOL isConnecting;
@property (nonatomic,assign) BOOL isLoggedin;
// Read from background queues during restore; BOOL writes are word-atomic.
@property (nonatomic,assign) BOOL isRestoreFlow;
@property (nonatomic,assign) BOOL isNodeChanging;
@property (nonatomic,assign) BOOL isOwnNode;
@property (nonatomic,assign) BMRestoreType restoreType;
@property (nonatomic,assign) BOOL isMaxPrivacyRequest;
@property (nonatomic,assign) BOOL isConfigured;
@property (nonatomic,assign) BOOL didLoadFullAssetsList;

@property (nonatomic,strong) BMWalletStatus* _Nullable walletStatus;

Expand All @@ -125,10 +141,17 @@ typedef void(^ExportCSVBlock)(NSString * _Nonnull data, NSURL * _Nonnull url);
@property (nonatomic,strong) NSMutableDictionary*_Nonnull deletedNotifications;
@property (nonatomic,strong) NSMutableArray<BMApp*>*_Nonnull apps;
@property (nonatomic,strong) NSMutableDictionary*_Nonnull needSaveContacts;
@property (nonatomic,strong) NSMutableArray<BMChat*>*_Nonnull chats;
@property (nonatomic,strong) NSMutableDictionary<NSString*, NSMutableArray<BMInstantMessage*>*>*_Nonnull messagesByPeer;
@property (nonatomic,strong) NSMutableArray<BMDexOrder*>*_Nonnull dexOrders;

@property (nonatomic,strong) NSMutableDictionary<NSString*, NSNumber*>*_Nonnull offlinePaymentsByWalletId;

@property (nonatomic, strong) NSTimer * _Nullable connectionTimer;
@property (nonatomic, strong) NSTimer * _Nullable connectionAfterOnlineTimer;

@property (nonatomic, strong, readonly) NSDate * _Nullable lastConnectionChangedAt;

@property (nonatomic, strong) NSString * _Nullable addressGeneratedID;

-(void)handleTimer;
Expand All @@ -137,6 +160,7 @@ typedef void(^ExportCSVBlock)(NSString * _Nonnull data, NSURL * _Nonnull url);
+(AppModel*_Nonnull)sharedManager;

+(NSString*_Nonnull)chooseRandomNode;
+(NSArray<NSString*>*_Nonnull)defaultPeerAddresses;

// delegates
-(void)addDelegate:(id<WalletModelDelegate>_Nullable) delegate;
Expand All @@ -153,6 +177,7 @@ typedef void(^ExportCSVBlock)(NSString * _Nonnull data, NSURL * _Nonnull url);
-(BOOL)canOpenWallet:(NSString*_Nonnull)pass;
-(void)restore:(NSString*_Nonnull)path;
-(void)resetWallet:(BOOL)removeDatabase;
-(void)abortCreateAndReset;
-(void)resetOnlyWallet;
-(void)restartWallet;
-(void)start;
Expand Down Expand Up @@ -183,9 +208,14 @@ typedef void(^ExportCSVBlock)(NSString * _Nonnull data, NSURL * _Nonnull url);
-(void)generateWithdrawAddress:(NewAddressGeneratedBlock _Nonnull )block;

-(void)generateNewWalletAddressWithBlockAndAmount:(int)assetId amount:(double)amount result:(NewAddressGeneratedBlock _Nonnull)block;
-(void)generateOfflineAddress:(NSString*_Nonnull)walletId assetId:(int)assetId amount:(double)amount result:(PublicAddressBlock _Nonnull)block;
-(void)generateOfflineAddress:(NSString*_Nonnull)walletId assetId:(int)assetId amount:(double)amount offlineCount:(uint32_t)offlineCount result:(PublicAddressBlock _Nonnull)block;
-(NSString*_Nonnull)generateRegularAddress:(NSString*_Nonnull)walletId assetId:(int)assetId amount:(double)amount isPermanentAddress:(BOOL)isPermanentAddress;
-(void)generateMaxPrivacyAddress:(NSString*_Nonnull)walletId assetId:(int)assetId amount:(double)amount result:(PublicAddressBlock _Nonnull)block;
-(void)generateSBBSAddress:(NSString*_Nonnull)walletId assetId:(int)assetId amount:(double)amount result:(PublicAddressBlock _Nonnull)block;
-(void)generatePublicOfflineAddress:(NSString*_Nonnull)walletId assetId:(int)assetId amount:(double)amount result:(PublicAddressBlock _Nonnull)block;

-(int)offlinePaymentsCountForWalletId:(NSString*_Nonnull)walletId;
-(void)requestOfflinePaymentsCountForWalletId:(NSString*_Nonnull)walletId;


// addresses
Expand Down Expand Up @@ -236,6 +266,9 @@ typedef void(^ExportCSVBlock)(NSString * _Nonnull data, NSURL * _Nonnull url);
-(BMTransactionParameters*_Nonnull)getTransactionParameters:(NSString*_Nonnull)token;
-(void)calculateFee:(double)amount assetId:(int)assetId fee:(double)fee isShielded:(BOOL) isShielded result:(FeecalculatedBlock _Nonnull )block;

// split
-(void)splitCoins:(int)assetId outputGroths:(NSArray<NSNumber*>*_Nonnull)groths fee:(double)fee;

// logs
-(NSString*_Nonnull)getZipLogs ;
-(void)clearLogs;
Expand Down Expand Up @@ -318,15 +351,41 @@ typedef void(^ExportCSVBlock)(NSString * _Nonnull data, NSURL * _Nonnull url);
-(void)loadApps;
-(void)stopDAO;
-(void)startApp:(UIViewController*_Nonnull)controller app:(BMApp*_Nonnull)app;
-(void)startApp:(UIViewController*_Nonnull)controller app:(BMApp*_Nonnull)app installedRoot:(NSURL* _Nullable)installedRoot;
-(void)startBeamXDaoApp:(UINavigationController*_Nonnull)controller app:(BMApp*_Nonnull)app;
-(void)sendDAOApiResult:(NSString*_Nonnull)json;
-(WalletAPIClient*_Nullable)walletAPIClient;
-(void)approveContractInfo:(NSString*_Nonnull)json info:(NSString*_Nonnull)info
amounts:(NSString*_Nonnull)amounts;
-(void)getAssetInfoAsync:(int)assetId;
-(void)loadFullAssetsList;

-(BMApp*_Nonnull)DAOBeamXApp;
-(BMApp*_Nonnull)daoGalleryApp;
-(BMApp*_Nonnull)daoFaucetApp;
-(BMApp*_Nonnull)votingApp;

// Messenger
-(void)requestChats;
-(void)requestMessagesForPeer:(NSString*_Nonnull)peerWalletId;
-(void)sendInstantMessage:(NSString*_Nonnull)peerWalletId
fromAddress:(NSString*_Nonnull)myWalletId
message:(NSString*_Nonnull)message;
-(void)markChatAsRead:(NSString*_Nonnull)peerWalletId;
-(void)removeChat:(NSString*_Nonnull)peerWalletId;
-(void)addChatStub:(NSString*_Nonnull)peerWalletId contactName:(NSString*_Nullable)contactName myWalletId:(NSString*_Nullable)myWalletId;
-(NSArray<BMInstantMessage*>*_Nonnull)cachedMessagesForPeer:(NSString*_Nonnull)peerWalletId;
-(NSString*_Nullable)lastMyAddressForPeer:(NSString*_Nonnull)peerWalletId;
-(NSString*_Nonnull)resolvedPeerWalletId:(NSString*_Nonnull)peerWalletId;

// Asset Swaps (DEX)
-(void)requestDexOrders;
-(BOOL)publishDexOrderWithSendAsset:(UInt32)sendAssetId
sendAmount:(UInt64)sendAmount
receiveAsset:(UInt32)receiveAssetId
receiveAmount:(UInt64)receiveAmount
expirationMinutes:(UInt32)expirationMinutes;
-(void)cancelDexOrderWithID:(NSString*_Nonnull)hexOrderID;
-(BOOL)acceptDexOrder:(BMDexOrder*_Nonnull)order;

@end
Loading