Skip to content

Commit bb7df22

Browse files
authored
feat: verify address
1 parent 121b6bb commit bb7df22

File tree

5 files changed

+109
-4
lines changed

5 files changed

+109
-4
lines changed

BDKSwiftExampleWallet.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
AE4984882A1BBBD8009951E2 /* BDKSwiftExampleWalletTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BDKSwiftExampleWalletTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
138138
AE49848C2A1BBBD8009951E2 /* BDKSwiftExampleWalletTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BDKSwiftExampleWalletTests.swift; sourceTree = "<group>"; };
139139
AE4984A52A1BBCB8009951E2 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
140+
AE6474732CE559E000A270C6 /* BDKSwiftExampleWallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BDKSwiftExampleWallet.entitlements; sourceTree = "<group>"; };
140141
AE6715F92A9A9220005C193F /* BDKSwiftExampleWalletPriceServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BDKSwiftExampleWalletPriceServiceTests.swift; sourceTree = "<group>"; };
141142
AE6715FC2A9AC056005C193F /* PriceServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceServiceError.swift; sourceTree = "<group>"; };
142143
AE6715FE2A9AC066005C193F /* FeeServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeeServiceError.swift; sourceTree = "<group>"; };
@@ -387,6 +388,7 @@
387388
AE49847A2A1BBBD6009951E2 /* BDKSwiftExampleWallet */ = {
388389
isa = PBXGroup;
389390
children = (
391+
AE6474732CE559E000A270C6 /* BDKSwiftExampleWallet.entitlements */,
390392
AE11D5EB2B784B2900D67366 /* Info.plist */,
391393
AE1C34222A424440008F807A /* App */,
392394
AE7F670A2A7451B600CED561 /* Model */,
@@ -854,6 +856,7 @@
854856
buildSettings = {
855857
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
856858
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
859+
CODE_SIGN_ENTITLEMENTS = BDKSwiftExampleWallet/BDKSwiftExampleWallet.entitlements;
857860
CODE_SIGN_STYLE = Automatic;
858861
CURRENT_PROJECT_VERSION = 1;
859862
DEVELOPMENT_ASSET_PATHS = "\"BDKSwiftExampleWallet/Preview Content\"";
@@ -862,6 +865,7 @@
862865
GENERATE_INFOPLIST_FILE = YES;
863866
INFOPLIST_FILE = BDKSwiftExampleWallet/Info.plist;
864867
INFOPLIST_KEY_CFBundleDisplayName = "BDK Wallet";
868+
INFOPLIST_KEY_NFCReaderUsageDescription = "We need NFC access to verify addresses.";
865869
INFOPLIST_KEY_NSCameraUsageDescription = "\"To scan QR codes\"";
866870
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
867871
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@@ -887,6 +891,7 @@
887891
buildSettings = {
888892
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
889893
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
894+
CODE_SIGN_ENTITLEMENTS = BDKSwiftExampleWallet/BDKSwiftExampleWallet.entitlements;
890895
CODE_SIGN_STYLE = Automatic;
891896
CURRENT_PROJECT_VERSION = 1;
892897
DEVELOPMENT_ASSET_PATHS = "\"BDKSwiftExampleWallet/Preview Content\"";
@@ -895,6 +900,7 @@
895900
GENERATE_INFOPLIST_FILE = YES;
896901
INFOPLIST_FILE = BDKSwiftExampleWallet/Info.plist;
897902
INFOPLIST_KEY_CFBundleDisplayName = "BDK Wallet";
903+
INFOPLIST_KEY_NFCReaderUsageDescription = "We need NFC access to verify addresses.";
898904
INFOPLIST_KEY_NSCameraUsageDescription = "\"To scan QR codes\"";
899905
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
900906
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>com.apple.developer.nfc.readersession.formats</key>
6+
<array>
7+
<string>TAG</string>
8+
</array>
9+
</dict>
10+
</plist>

BDKSwiftExampleWallet/Info.plist

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
<key>ITSAppUsesNonExemptEncryption</key>
66
<false/>
77
<key>NSPasteboardUsageDescription</key>
8-
<string>&quot;To allow users to copy and paste text between the app and other apps&quot;</string>
9-
<key>NSCameraUsageDescription</key>
10-
<string>&quot;To scan QR codes&quot;</string>
8+
<string>"To allow users to copy and paste text between the app and other apps"</string>
119
</dict>
1210
</plist>

BDKSwiftExampleWallet/View Model/Receive/ReceiveViewModel.swift

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
//
77

88
import BitcoinDevKit
9+
import CoreNFC
910
import Foundation
1011
import Observation
1112

1213
@Observable
13-
class ReceiveViewModel {
14+
class ReceiveViewModel: NSObject, NFCNDEFReaderSessionDelegate {
1415
let bdkClient: BDKClient
1516

17+
private var nfcSession: NFCNDEFReaderSession?
18+
1619
var address: String = ""
1720
var receiveViewError: AppError?
1821
var showingReceiveViewErrorAlert = false
@@ -35,3 +38,83 @@ class ReceiveViewModel {
3538
}
3639

3740
}
41+
42+
extension ReceiveViewModel {
43+
func startNFCSession() {
44+
guard NFCNDEFReaderSession.readingAvailable else {
45+
receiveViewError = .generic(message: "NFC not available on this device")
46+
showingReceiveViewErrorAlert = true
47+
return
48+
}
49+
50+
nfcSession = NFCNDEFReaderSession(
51+
delegate: self,
52+
queue: nil,
53+
invalidateAfterFirstRead: false
54+
)
55+
nfcSession?.alertMessage = "Hold your iPhone near the Coldcard to verify the address"
56+
nfcSession?.begin()
57+
}
58+
59+
// MARK: - NFCNDEFReaderSessionDelegate
60+
61+
func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
62+
guard let tag = tags.first else {
63+
return
64+
}
65+
66+
session.connect(to: tag) { error in
67+
if let error = error {
68+
session.invalidate(errorMessage: "Connection error: \(error.localizedDescription)")
69+
return
70+
}
71+
72+
let payload = NFCNDEFPayload(
73+
format: .media,
74+
type: "text/plain".data(using: .utf8)!,
75+
identifier: Data(),
76+
payload: self.address.data(using: .utf8)!
77+
)
78+
79+
let message = NFCNDEFMessage(records: [payload])
80+
81+
tag.writeNDEF(message) { error in
82+
if let error = error {
83+
session.invalidate(errorMessage: "Write failed: \(error.localizedDescription)")
84+
} else {
85+
session.alertMessage = "Address passed to Coldcard successfully"
86+
session.invalidate()
87+
}
88+
}
89+
90+
}
91+
}
92+
93+
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
94+
if let nfcError = error as? NFCReaderError,
95+
nfcError.code == .readerSessionInvalidationErrorUserCanceled
96+
{
97+
return
98+
}
99+
100+
DispatchQueue.main.async {
101+
self.receiveViewError = .generic(message: error.localizedDescription)
102+
self.showingReceiveViewErrorAlert = true
103+
}
104+
}
105+
106+
func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
107+
// Required delegate method, but no action needed when session becomes active
108+
}
109+
110+
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
111+
if let message = messages.first,
112+
let record = message.records.first,
113+
let payload = String(data: record.payload, encoding: .utf8)
114+
{
115+
// Handle response
116+
}
117+
session.invalidate()
118+
}
119+
120+
}

BDKSwiftExampleWallet/View/Receive/ReceiveView.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import BitcoinUI
9+
import CoreNFC
910
import SwiftUI
1011

1112
struct ReceiveView: View {
@@ -51,6 +52,13 @@ struct ReceiveView: View {
5152
)
5253
.padding()
5354

55+
Button {
56+
viewModel.startNFCSession()
57+
} label: {
58+
Image(systemName: "wave.3.right")
59+
.foregroundColor(.primary)
60+
}
61+
5462
HStack {
5563
Button {
5664
UIPasteboard.general.string = viewModel.address

0 commit comments

Comments
 (0)