Skip to content
This repository was archived by the owner on Aug 26, 2025. It is now read-only.

Commit c3570df

Browse files
authored
Merge pull request #248 from tangem/release/3.4.0
Release version 3.4.0
2 parents 2d73d23 + 84660e4 commit c3570df

File tree

82 files changed

+2836
-826
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+2836
-826
lines changed

CHANGELOG

Lines changed: 490 additions & 5 deletions
Large diffs are not rendered by default.

Example/Podfile.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
PODS:
2-
- TangemSdk (3.0.2)
2+
- TangemSdk (3.3.4)
33

44
DEPENDENCIES:
55
- TangemSdk (from `../`)
@@ -9,8 +9,8 @@ EXTERNAL SOURCES:
99
:path: "../"
1010

1111
SPEC CHECKSUMS:
12-
TangemSdk: 7a03e776285f3582e36fa9f6036ad5800c9ae9f1
12+
TangemSdk: 9ea2c230bd616787bdccc5fb3ab36925d06199d4
1313

1414
PODFILE CHECKSUM: fb4745483d8583db4c8ae8aa3ccbde86ca112a6b
1515

16-
COCOAPODS: 1.10.1
16+
COCOAPODS: 1.11.3

Example/TangemSdkExample copy-Info.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
<true/>
2727
<key>NFCReaderUsageDescription</key>
2828
<string>Some reason</string>
29+
<key>NSFaceIDUsageDescription</key>
30+
<string>Use Face ID to save access codes in the app</string>
2931
<key>UILaunchStoryboardName</key>
3032
<string>LaunchScreen</string>
3133
<key>UIRequiredDeviceCapabilities</key>

Example/TangemSdkExample.xcodeproj/project.pbxproj

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
5DDCEC7A25C958220002090D /* UI+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDCEC7425C957930002090D /* UI+.swift */; };
4040
B00A67B3253D8CAC002D7D53 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = B00A67B2253D8CAC002D7D53 /* Utils.swift */; };
4141
B00A67B6253D8DA2002D7D53 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = B00A67B2253D8CAC002D7D53 /* Utils.swift */; };
42+
DC23ED9F2912DD0D0023E626 /* ResetToFactorySettingsTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC23ED9E2912DD0D0023E626 /* ResetToFactorySettingsTask.swift */; };
43+
DC23EDA02912DD520023E626 /* ResetToFactorySettingsTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC23ED9E2912DD0D0023E626 /* ResetToFactorySettingsTask.swift */; };
44+
DC28E0F22877F84B00AE7A84 /* DebugLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC28E0F12877F84B00AE7A84 /* DebugLogger.swift */; };
45+
DC28E0F32877F9BA00AE7A84 /* DebugLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC28E0F12877F84B00AE7A84 /* DebugLogger.swift */; };
4246
/* End PBXBuildFile section */
4347

4448
/* Begin PBXContainerItemProxy section */
@@ -85,6 +89,8 @@
8589
5E2717BD1D7CB2EFFB59D0F4 /* Pods-TangemSdkExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TangemSdkExample.release.xcconfig"; path = "Target Support Files/Pods-TangemSdkExample/Pods-TangemSdkExample.release.xcconfig"; sourceTree = "<group>"; };
8690
ABA3866CD02DAF9913B7E6BD /* Pods-TangemSdkExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TangemSdkExample.debug.xcconfig"; path = "Target Support Files/Pods-TangemSdkExample/Pods-TangemSdkExample.debug.xcconfig"; sourceTree = "<group>"; };
8791
B00A67B2253D8CAC002D7D53 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
92+
DC23ED9E2912DD0D0023E626 /* ResetToFactorySettingsTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetToFactorySettingsTask.swift; sourceTree = "<group>"; };
93+
DC28E0F12877F84B00AE7A84 /* DebugLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugLogger.swift; sourceTree = "<group>"; };
8894
E53EB3E423F2C7C90079CC11 /* TangemSdkExampleDevelopmentUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TangemSdkExampleDevelopmentUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
8995
E53EB3E623F2C7C90079CC11 /* TangemSdkExampleDevelopmentUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TangemSdkExampleDevelopmentUITests.swift; sourceTree = "<group>"; };
9096
E53EB3EE23F2CD860079CC11 /* RobotApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RobotApi.swift; sourceTree = "<group>"; };
@@ -138,6 +144,8 @@
138144
5D3ABA3C26D02C5C0087EF4A /* StaticData.swift */,
139145
5D3C2BCE26D93E7200722E33 /* BackupView.swift */,
140146
5D445B7F26E2A8CF00F6F0FE /* ResetPinView.swift */,
147+
DC28E0F12877F84B00AE7A84 /* DebugLogger.swift */,
148+
DC23ED9E2912DD0D0023E626 /* ResetToFactorySettingsTask.swift */,
141149
);
142150
path = Developer;
143151
sourceTree = "<group>";
@@ -380,8 +388,10 @@
380388
5D032009266A8523008472EC /* ExampleButton.swift in Sources */,
381389
B00A67B3253D8CAC002D7D53 /* Utils.swift in Sources */,
382390
5D445B8026E2A8CF00F6F0FE /* ResetPinView.swift in Sources */,
391+
DC28E0F22877F84B00AE7A84 /* DebugLogger.swift in Sources */,
383392
5DDCEC7525C957930002090D /* UI+.swift in Sources */,
384393
5D3C2BCF26D93E7200722E33 /* BackupView.swift in Sources */,
394+
DC23ED9F2912DD0D0023E626 /* ResetToFactorySettingsTask.swift in Sources */,
385395
5D03200E266A85CB008472EC /* ActivityIndicatorView.swift in Sources */,
386396
5D1C6C7227214EA900E04A8B /* SettingsView.swift in Sources */,
387397
);
@@ -394,10 +404,12 @@
394404
5D2BDF8326DCE5A6002F7E19 /* StaticData.swift in Sources */,
395405
5D031FFE266A6960008472EC /* ContentView.swift in Sources */,
396406
5DBC25BA235F053F005F0C79 /* AppDelegate.swift in Sources */,
407+
DC23EDA02912DD520023E626 /* ResetToFactorySettingsTask.swift in Sources */,
397408
5D032002266A6A15008472EC /* AppModel.swift in Sources */,
398409
5D03200A266A8523008472EC /* ExampleButton.swift in Sources */,
399410
B00A67B6253D8DA2002D7D53 /* Utils.swift in Sources */,
400411
5D445B8126E2A97300F6F0FE /* ResetPinView.swift in Sources */,
412+
DC28E0F32877F9BA00AE7A84 /* DebugLogger.swift in Sources */,
401413
5DDCEC7A25C958220002090D /* UI+.swift in Sources */,
402414
5D2BDF8226DCE5A2002F7E19 /* BackupView.swift in Sources */,
403415
5D03200F266A85CB008472EC /* ActivityIndicatorView.swift in Sources */,
@@ -603,7 +615,7 @@
603615
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
604616
CODE_SIGN_ENTITLEMENTS = TangemSDKExample/TangemSDKExample.entitlements;
605617
CODE_SIGN_STYLE = Automatic;
606-
CURRENT_PROJECT_VERSION = 12;
618+
CURRENT_PROJECT_VERSION = 15;
607619
DEVELOPMENT_TEAM = 4897UJ6D8C;
608620
INFOPLIST_FILE = "TangemSdkExample copy-Info.plist";
609621
LD_RUNPATH_SEARCH_PATHS = (
@@ -623,7 +635,7 @@
623635
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
624636
CODE_SIGN_ENTITLEMENTS = TangemSDKExample/TangemSDKExample.entitlements;
625637
CODE_SIGN_STYLE = Automatic;
626-
CURRENT_PROJECT_VERSION = 12;
638+
CURRENT_PROJECT_VERSION = 15;
627639
DEVELOPMENT_TEAM = 4897UJ6D8C;
628640
INFOPLIST_FILE = "TangemSdkExample copy-Info.plist";
629641
LD_RUNPATH_SEARCH_PATHS = (

Example/TangemSdkExample/AppModel.swift

Lines changed: 59 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import Foundation
1010
import SwiftUI
1111
import TangemSdk
12+
import Combine
1213

1314
class AppModel: ObservableObject {
1415
//MARK:- Inputs
@@ -26,7 +27,7 @@ class AppModel: ObservableObject {
2627
@Published var personalizationConfig: String = ""
2728

2829
//MARK:- Outputs
29-
@Published var logText: String = AppModel.logPlaceholder
30+
@Published var logText: String = DebugLogger.logPlaceholder
3031
@Published var isScanning: Bool = false
3132
@Published var card: Card?
3233
@Published var showWalletSelection: Bool = false
@@ -36,29 +37,51 @@ class AppModel: ObservableObject {
3637
@Published var showSettings: Bool = false
3738
//MARK:- Config
3839
@Published var handleErrors: Bool = true
40+
@Published var displayLogs: Bool = false
41+
@Published var accessCodeRequestPolicy: AccessCodeRequestPolicy = .default
3942

4043
var backupService: BackupService? = nil
44+
var resetPinService: ResetPinService? = nil
4145

4246
private lazy var _tangemSdk: TangemSdk = { .init() }()
47+
private lazy var logger: DebugLogger = .init()
4348

4449
private var tangemSdk: TangemSdk {
4550
var config = Config()
46-
config.logConfig = .verbose
4751
config.linkedTerminal = false
4852
config.allowUntrustedCards = true
4953
config.handleErrors = self.handleErrors
5054
config.filter.allowedCardTypes = FirmwareVersion.FirmwareType.allCases
55+
config.accessCodeRequestPolicy = accessCodeRequestPolicy
56+
if displayLogs {
57+
config.logConfig = .custom(logLevel: Log.Level.allCases,
58+
loggers: [ConsoleLogger(), logger])
59+
} else {
60+
config.logConfig = .verbose
61+
}
5162
_tangemSdk.config = config
5263
return _tangemSdk
5364
}
5465

5566
private var issuerDataResponse: ReadIssuerDataResponse?
5667
private var issuerExtraDataResponse: ReadIssuerExtraDataResponse?
5768
private var savedFiles: [File]?
58-
private static let logPlaceholder = "Logs will appear here"
69+
private var bag: Set<AnyCancellable> = []
70+
71+
init() {
72+
logger
73+
.logsPublisher
74+
.debounce(for: .seconds(0.5), scheduler: DispatchQueue.global())
75+
.receive(on: DispatchQueue.main)
76+
.sink {[weak self] logs in
77+
self?.logText = logs
78+
}
79+
.store(in: &bag)
80+
}
5981

6082
func clear() {
6183
logText = ""
84+
logger.clear()
6285
}
6386

6487
func copy() {
@@ -89,22 +112,14 @@ class AppModel: ObservableObject {
89112
}
90113
}
91114

92-
private func log(_ object: Any) {
93-
let text: String = (object as? JSONStringConvertible)?.json ?? "\(object)"
94-
if logText == AppModel.logPlaceholder {
95-
logText = ""
96-
}
97-
logText = "\(text)\n\n" + logText
98-
}
99-
100115
private func complete(with object: Any) {
101-
log(object)
116+
logger.log(object)
102117
isScanning = false
103118
}
104119

105120
private func complete(with error: TangemSdkError) {
106121
if !error.isUserCancelled {
107-
self.log("\(error.localizedDescription)")
122+
logger.log("\(error.localizedDescription)")
108123
}
109124

110125
isScanning = false
@@ -203,12 +218,6 @@ extension AppModel {
203218
}
204219

205220
func signHash(walletPublicKey: Data) {
206-
guard let cardId = card?.cardId else {
207-
self.complete(with: "Scan card to retrieve cardId")
208-
return
209-
}
210-
211-
212221
let path = try? DerivationPath(rawPath: derivationPath)
213222
if !derivationPath.isEmpty && path == nil {
214223
self.complete(with: "Failed to parse hd path")
@@ -227,18 +236,13 @@ extension AppModel {
227236

228237
tangemSdk.sign(hash: hash,
229238
walletPublicKey: walletPublicKey,
230-
cardId: cardId,
239+
cardId: nil,
231240
derivationPath: path,
232241
initialMessage: Message(header: "Signing hash"),
233242
completion: handleCompletion)
234243
}
235244

236245
func signHashes(walletPublicKey: Data) {
237-
guard let cardId = card?.cardId else {
238-
self.complete(with: "Scan card to retrieve cardId")
239-
return
240-
}
241-
242246
let path = try? DerivationPath(rawPath: derivationPath)
243247
if !derivationPath.isEmpty && path == nil {
244248
self.complete(with: "Failed to parse hd path")
@@ -251,7 +255,7 @@ extension AppModel {
251255

252256
tangemSdk.sign(hashes: hashes,
253257
walletPublicKey: walletPublicKey,
254-
cardId: cardId,
258+
cardId: nil,
255259
derivationPath: path,
256260
initialMessage: Message(header: "Signing hashes"),
257261
completion: handleCompletion)
@@ -316,7 +320,7 @@ extension AppModel {
316320
createWallet.run(in: session) { result2 in
317321
switch result2 {
318322
case .success(let response):
319-
self.log(response)
323+
self.logger.log(response)
320324
case .failure:
321325
break
322326
}
@@ -571,6 +575,10 @@ extension AppModel {
571575
func resetBackup() {
572576
tangemSdk.startSession(with: ResetBackupCommand(), completion: handleCompletion)
573577
}
578+
579+
func resetToFactory() {
580+
tangemSdk.startSession(with: ResetToFactorySettingsTask(), completion: handleCompletion)
581+
}
574582
}
575583

576584
//MARK:- Json RPC
@@ -591,7 +599,7 @@ extension AppModel {
591599
}
592600

593601
private func printJson() {
594-
log(json)
602+
logger.log(json)
595603
}
596604
}
597605

@@ -608,7 +616,7 @@ extension AppModel {
608616
}
609617

610618
private func printPersonalizationConfig() {
611-
log(personalizationConfig)
619+
logger.log(personalizationConfig)
612620
}
613621

614622
func personalize() {
@@ -626,7 +634,7 @@ extension AppModel {
626634

627635
tangemSdk.startSession(with: personalizeCommand, completion: handleCompletion)
628636
} catch {
629-
log(error)
637+
logger.log(error)
630638
}
631639
}
632640
}
@@ -665,6 +673,7 @@ extension AppModel {
665673
case depersonalize
666674
case personalize
667675
case resetBackup
676+
case resetToFactory
668677
}
669678

670679
private func chooseMethod(walletPublicKey: Data? = nil) {
@@ -696,6 +705,7 @@ extension AppModel {
696705
case .jsonrpc: runJsonRpc()
697706
case .personalize: personalize()
698707
case .resetBackup: resetBackup()
708+
case .resetToFactory: resetToFactory()
699709
}
700710
}
701711
}
@@ -711,6 +721,16 @@ extension AppModel {
711721
showSettings = true
712722
}
713723

724+
func onRemoveAccessCodes() {
725+
let repo = AccessCodeRepository()
726+
repo.clear()
727+
}
728+
729+
func onResetService() {
730+
resetPinService = ResetPinService(config: tangemSdk.config)
731+
showResetPin = true
732+
}
733+
714734
@ViewBuilder
715735
func makeSettingsDestination() -> some View {
716736
SettingsView().environmentObject(self)
@@ -724,4 +744,13 @@ extension AppModel {
724744
BackupView()
725745
}
726746
}
747+
748+
@ViewBuilder
749+
func makePinResetDestination() -> some View {
750+
if let service = self.resetPinService {
751+
ResetPinView().environmentObject(service)
752+
} else {
753+
ResetPinView()
754+
}
755+
}
727756
}

Example/TangemSdkExample/ContentView.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ struct ContentView: View {
2525
isActive: $model.showSettings,
2626
label: {EmptyView()})
2727

28+
NavigationLink(
29+
destination: model.makePinResetDestination(),
30+
isActive: $model.showResetPin,
31+
label: {EmptyView()})
32+
2833
GeometryReader { geo in
2934
VStack {
3035
ScrollView {
@@ -48,13 +53,14 @@ struct ContentView: View {
4853
Button("Clear", action: model.clear)
4954
Button("Copy", action: model.copy)
5055
Button("Backup", action: model.onBackup)
56+
Button("Reset", action: model.onResetService)
5157
}
5258

5359
additionalView
5460
.padding(.top, 4)
5561

5662
Picker("", selection: $model.method) {
57-
ForEach(0..<AppModel.Method.allCases.count) { index in
63+
ForEach(0..<AppModel.Method.allCases.count, id: \.self) { index in
5864
Text(AppModel.Method.allCases[index].rawValue)
5965
.tag(AppModel.Method.allCases[index])
6066
}
@@ -75,7 +81,7 @@ struct ContentView: View {
7581
}
7682
.navigationBarTitle("SDK", displayMode: .inline)
7783
.navigationBarItems(trailing: Button(action: model.onSettings,
78-
label: { Image(systemName: "gearshape")}))
84+
label: { Image(systemName: "gear")}))
7985
}
8086
.padding(.bottom, 8)
8187
.actionSheet(isPresented: $model.showWalletSelection) {
@@ -108,7 +114,7 @@ struct ContentView: View {
108114
.bold()
109115

110116
Picker("", selection: $model.attestationMode) {
111-
ForEach(0..<AttestationTask.Mode.allCases.count) { index in
117+
ForEach(0..<AttestationTask.Mode.allCases.count, id: \.self) { index in
112118
Text(AttestationTask.Mode.allCases[index].rawValue)
113119
.tag(AttestationTask.Mode.allCases[index])
114120
}
@@ -127,7 +133,7 @@ struct ContentView: View {
127133

128134
if let supportedCurves = model.card?.supportedCurves {
129135
Picker("", selection: $model.curve) {
130-
ForEach(0..<supportedCurves.count) { index in
136+
ForEach(0..<supportedCurves.count, id: \.self) { index in
131137
Text(supportedCurves[index].rawValue)
132138
.tag(supportedCurves[index])
133139
}

0 commit comments

Comments
 (0)