Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions BDKSwiftExampleWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
AE4984882A1BBBD8009951E2 /* BDKSwiftExampleWalletTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BDKSwiftExampleWalletTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
AE49848C2A1BBBD8009951E2 /* BDKSwiftExampleWalletTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BDKSwiftExampleWalletTests.swift; sourceTree = "<group>"; };
AE4984A52A1BBCB8009951E2 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
AE6474732CE559E000A270C6 /* BDKSwiftExampleWallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BDKSwiftExampleWallet.entitlements; sourceTree = "<group>"; };
AE6715F92A9A9220005C193F /* BDKSwiftExampleWalletPriceServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BDKSwiftExampleWalletPriceServiceTests.swift; sourceTree = "<group>"; };
AE6715FC2A9AC056005C193F /* PriceServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceServiceError.swift; sourceTree = "<group>"; };
AE6715FE2A9AC066005C193F /* FeeServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeeServiceError.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -387,6 +388,7 @@
AE49847A2A1BBBD6009951E2 /* BDKSwiftExampleWallet */ = {
isa = PBXGroup;
children = (
AE6474732CE559E000A270C6 /* BDKSwiftExampleWallet.entitlements */,
AE11D5EB2B784B2900D67366 /* Info.plist */,
AE1C34222A424440008F807A /* App */,
AE7F670A2A7451B600CED561 /* Model */,
Expand Down Expand Up @@ -854,6 +856,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = BDKSwiftExampleWallet/BDKSwiftExampleWallet.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"BDKSwiftExampleWallet/Preview Content\"";
Expand All @@ -862,6 +865,7 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BDKSwiftExampleWallet/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "BDK Wallet";
INFOPLIST_KEY_NFCReaderUsageDescription = "We need NFC access to verify addresses.";
INFOPLIST_KEY_NSCameraUsageDescription = "\"To scan QR codes\"";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
Expand All @@ -887,6 +891,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = BDKSwiftExampleWallet/BDKSwiftExampleWallet.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"BDKSwiftExampleWallet/Preview Content\"";
Expand All @@ -895,6 +900,7 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BDKSwiftExampleWallet/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "BDK Wallet";
INFOPLIST_KEY_NFCReaderUsageDescription = "We need NFC access to verify addresses.";
INFOPLIST_KEY_NSCameraUsageDescription = "\"To scan QR codes\"";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
Expand Down
10 changes: 10 additions & 0 deletions BDKSwiftExampleWallet/BDKSwiftExampleWallet.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>TAG</string>
</array>
</dict>
</plist>
4 changes: 1 addition & 3 deletions BDKSwiftExampleWallet/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>NSPasteboardUsageDescription</key>
<string>&quot;To allow users to copy and paste text between the app and other apps&quot;</string>
<key>NSCameraUsageDescription</key>
<string>&quot;To scan QR codes&quot;</string>
<string>"To allow users to copy and paste text between the app and other apps"</string>
</dict>
</plist>
85 changes: 84 additions & 1 deletion BDKSwiftExampleWallet/View Model/Receive/ReceiveViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
//

import BitcoinDevKit
import CoreNFC
import Foundation
import Observation

@Observable
class ReceiveViewModel {
class ReceiveViewModel: NSObject, NFCNDEFReaderSessionDelegate {
let bdkClient: BDKClient

private var nfcSession: NFCNDEFReaderSession?

var address: String = ""
var receiveViewError: AppError?
var showingReceiveViewErrorAlert = false
Expand All @@ -35,3 +38,83 @@ class ReceiveViewModel {
}

}

extension ReceiveViewModel {
func startNFCSession() {
guard NFCNDEFReaderSession.readingAvailable else {
receiveViewError = .generic(message: "NFC not available on this device")
showingReceiveViewErrorAlert = true
return
}

nfcSession = NFCNDEFReaderSession(
delegate: self,
queue: nil,
invalidateAfterFirstRead: false
)
nfcSession?.alertMessage = "Hold your iPhone near the Coldcard to verify the address"
nfcSession?.begin()
}

// MARK: - NFCNDEFReaderSessionDelegate

func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
guard let tag = tags.first else {
return
}

session.connect(to: tag) { error in
if let error = error {
session.invalidate(errorMessage: "Connection error: \(error.localizedDescription)")
return
}

let payload = NFCNDEFPayload(
format: .media,
type: "text/plain".data(using: .utf8)!,
identifier: Data(),
payload: self.address.data(using: .utf8)!
)

let message = NFCNDEFMessage(records: [payload])

tag.writeNDEF(message) { error in
if let error = error {
session.invalidate(errorMessage: "Write failed: \(error.localizedDescription)")
} else {
session.alertMessage = "Address passed to Coldcard successfully"
session.invalidate()
}
}

}
}

func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
if let nfcError = error as? NFCReaderError,
nfcError.code == .readerSessionInvalidationErrorUserCanceled
{
return
}

DispatchQueue.main.async {
self.receiveViewError = .generic(message: error.localizedDescription)
self.showingReceiveViewErrorAlert = true
}
}

func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
// Required delegate method, but no action needed when session becomes active
}

func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
if let message = messages.first,
let record = message.records.first,
let payload = String(data: record.payload, encoding: .utf8)
{
// Handle response
}
session.invalidate()
}

}
8 changes: 8 additions & 0 deletions BDKSwiftExampleWallet/View/Receive/ReceiveView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import BitcoinUI
import CoreNFC
import SwiftUI

struct ReceiveView: View {
Expand Down Expand Up @@ -51,6 +52,13 @@ struct ReceiveView: View {
)
.padding()

Button {
viewModel.startNFCSession()
} label: {
Image(systemName: "wave.3.right")
.foregroundColor(.primary)
}

HStack {
Button {
UIPasteboard.general.string = viewModel.address
Expand Down