mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 01:40:12 +01:00
wip
This commit is contained in:
parent
498e45b413
commit
50614b5e9e
@ -311,4 +311,4 @@ RUBY VERSION
|
||||
ruby 3.1.6p260
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.13
|
||||
2.3.27
|
||||
|
@ -232,7 +232,7 @@ platform :ios do
|
||||
type: "development",
|
||||
platform: "catalyst",
|
||||
app_identifier: app_identifiers,
|
||||
readonly: true,
|
||||
readonly: false,
|
||||
clone_branch_directly: true
|
||||
)
|
||||
end
|
||||
@ -243,7 +243,7 @@ platform :ios do
|
||||
type: "appstore",
|
||||
platform: "catalyst",
|
||||
app_identifier: app_identifiers,
|
||||
readonly: true,
|
||||
readonly: false,
|
||||
clone_branch_directly: true
|
||||
)
|
||||
end
|
||||
|
157
fastlane/README.md
Normal file
157
fastlane/README.md
Normal file
@ -0,0 +1,157 @@
|
||||
fastlane documentation
|
||||
----
|
||||
|
||||
# Installation
|
||||
|
||||
Make sure you have the latest version of the Xcode command line tools installed:
|
||||
|
||||
```sh
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
|
||||
|
||||
# Available Actions
|
||||
|
||||
## Android
|
||||
|
||||
### android prepare_keystore
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android prepare_keystore
|
||||
```
|
||||
|
||||
Prepare the keystore file
|
||||
|
||||
### android update_version_build_and_sign_apk
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android update_version_build_and_sign_apk
|
||||
```
|
||||
|
||||
Update version, build number, and sign APK
|
||||
|
||||
### android upload_to_browserstack_and_comment
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android upload_to_browserstack_and_comment
|
||||
```
|
||||
|
||||
Upload APK to BrowserStack and post result as PR comment
|
||||
|
||||
----
|
||||
|
||||
|
||||
## iOS
|
||||
|
||||
### ios register_devices_from_txt
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios register_devices_from_txt
|
||||
```
|
||||
|
||||
Register new devices from a file
|
||||
|
||||
### ios create_temp_keychain
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios create_temp_keychain
|
||||
```
|
||||
|
||||
Create a temporary keychain
|
||||
|
||||
### ios setup_provisioning_profiles
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios setup_provisioning_profiles
|
||||
```
|
||||
|
||||
Synchronize certificates and provisioning profiles
|
||||
|
||||
### ios fetch_dev_profiles_catalyst
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios fetch_dev_profiles_catalyst
|
||||
```
|
||||
|
||||
Fetch development certificates and provisioning profiles for Mac Catalyst
|
||||
|
||||
### ios fetch_appstore_profiles_catalyst
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios fetch_appstore_profiles_catalyst
|
||||
```
|
||||
|
||||
Fetch App Store certificates and provisioning profiles for Mac Catalyst
|
||||
|
||||
### ios setup_catalyst_provisioning_profiles
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios setup_catalyst_provisioning_profiles
|
||||
```
|
||||
|
||||
Setup provisioning profiles for Mac Catalyst
|
||||
|
||||
### ios clear_derived_data_lane
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios clear_derived_data_lane
|
||||
```
|
||||
|
||||
Clear derived data
|
||||
|
||||
### ios increment_build_number_lane
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios increment_build_number_lane
|
||||
```
|
||||
|
||||
Increment build number
|
||||
|
||||
### ios install_pods
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios install_pods
|
||||
```
|
||||
|
||||
Install CocoaPods dependencies
|
||||
|
||||
### ios upload_to_testflight_lane
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios upload_to_testflight_lane
|
||||
```
|
||||
|
||||
Upload IPA to TestFlight
|
||||
|
||||
### ios build_app_lane
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios build_app_lane
|
||||
```
|
||||
|
||||
Build the iOS app
|
||||
|
||||
### ios deploy
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios deploy
|
||||
```
|
||||
|
||||
Deploy to TestFlight
|
||||
|
||||
### ios update_release_notes
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios update_release_notes
|
||||
```
|
||||
|
||||
Update 'What's New' section in App Store Connect for the 'Prepare for Submission' version
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||
|
||||
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
|
||||
|
||||
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
18
fastlane/report.xml
Normal file
18
fastlane/report.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites>
|
||||
<testsuite name="fastlane.lanes">
|
||||
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000181">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: match" time="46.393765">
|
||||
|
||||
</testcase>
|
||||
|
||||
</testsuite>
|
||||
</testsuites>
|
@ -2,7 +2,7 @@
|
||||
|
||||
import { NativeEventEmitter, NativeModules } from 'react-native';
|
||||
|
||||
const { TransactionsMonitorEventEmitter, TransactionsMonitor: TransactionsMonitorModule } = NativeModules;
|
||||
const { TransactionsMonitorEventEmitter, TransactionsMonitor, LiveActivityManager } = NativeModules;
|
||||
|
||||
interface TransactionConfirmedEvent {
|
||||
txid: string;
|
||||
@ -25,7 +25,6 @@ export const subscribeToTransactionConfirmed = (callback: (txid: string) => void
|
||||
callback(event.txid);
|
||||
});
|
||||
|
||||
// Return the unsubscribe function
|
||||
return () => {
|
||||
subscription.remove();
|
||||
};
|
||||
@ -36,12 +35,12 @@ export const subscribeToTransactionConfirmed = (callback: (txid: string) => void
|
||||
* @param txid - The transaction ID to monitor.
|
||||
*/
|
||||
export const addExternalTxId = async (txid: string): Promise<void> => {
|
||||
if (!TransactionsMonitorModule || !TransactionsMonitorModule.addExternalTxId) {
|
||||
throw new Error('TransactionsMonitorModule or addExternalTxId method is not available.');
|
||||
if (!TransactionsMonitor || !TransactionsMonitor.addExternalTxId) {
|
||||
throw new Error('TransactionsMonitor or addExternalTxId method is not available.');
|
||||
}
|
||||
|
||||
try {
|
||||
await TransactionsMonitorModule.addExternalTxId(txid);
|
||||
await TransactionsMonitor.addExternalTxId(txid);
|
||||
console.debug(`External txid ${txid} added for monitoring.`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to add external txid ${txid}:`, error);
|
||||
@ -54,12 +53,12 @@ export const addExternalTxId = async (txid: string): Promise<void> => {
|
||||
* @param txid - The transaction ID to remove.
|
||||
*/
|
||||
export const removeExternalTxId = async (txid: string): Promise<void> => {
|
||||
if (!TransactionsMonitorModule || !TransactionsMonitorModule.removeExternalTxId) {
|
||||
throw new Error('TransactionsMonitorModule or removeExternalTxId method is not available.');
|
||||
if (!TransactionsMonitor || !TransactionsMonitor.removeExternalTxId) {
|
||||
throw new Error('TransactionsMonitor or removeExternalTxId method is not available.');
|
||||
}
|
||||
|
||||
try {
|
||||
await TransactionsMonitorModule.removeExternalTxId(txid);
|
||||
await TransactionsMonitor.removeExternalTxId(txid);
|
||||
console.debug(`External txid ${txid} removed from monitoring.`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to remove external txid ${txid}:`, error);
|
||||
@ -72,12 +71,12 @@ export const removeExternalTxId = async (txid: string): Promise<void> => {
|
||||
* @returns Promise that resolves to an array of transaction IDs.
|
||||
*/
|
||||
export const getAllTxIds = async (): Promise<string[]> => {
|
||||
if (!TransactionsMonitorModule || !TransactionsMonitorModule.getAllTxIds) {
|
||||
throw new Error('TransactionsMonitorModule or getAllTxIds method is not available.');
|
||||
if (!TransactionsMonitor || !TransactionsMonitor.getAllTxIds) {
|
||||
throw new Error('TransactionsMonitor or getAllTxIds method is not available.');
|
||||
}
|
||||
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
TransactionsMonitorModule.getAllTxIds((error: string | null, txids: string[] | null) => {
|
||||
TransactionsMonitor.getAllTxIds((error: string | null, txids: string[] | null) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else if (txids) {
|
||||
@ -95,19 +94,53 @@ export const getAllTxIds = async (): Promise<string[]> => {
|
||||
* @returns Promise that resolves to the number of confirmations.
|
||||
*/
|
||||
export const fetchTransactionConfirmations = async (txid: string): Promise<number> => {
|
||||
if (!TransactionsMonitorModule || !TransactionsMonitorModule.fetchConfirmations) {
|
||||
throw new Error('TransactionsMonitorModule or fetchConfirmations method is not available.');
|
||||
if (!TransactionsMonitor || !TransactionsMonitor.fetchConfirmations) {
|
||||
throw new Error('TransactionsMonitor or fetchConfirmations method is not available.');
|
||||
}
|
||||
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
TransactionsMonitorModule.fetchConfirmations(txid, (error: string | null, confirmations: number | null) => {
|
||||
TransactionsMonitor.fetchConfirmations(txid, (error: string | null, confirmations: number | null) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else if (confirmations !== null) {
|
||||
resolve(confirmations);
|
||||
} else {
|
||||
reject(new Error('No confirmations data received.'));
|
||||
reject('No confirmations data received.');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts the persistent Live Activity for Dynamic Island.
|
||||
*/
|
||||
export const startPersistentLiveActivity = async (): Promise<void> => {
|
||||
if (!LiveActivityManager || !LiveActivityManager.startPersistentLiveActivity) {
|
||||
throw new Error('LiveActivityManager or startPersistentLiveActivity method is not available.');
|
||||
}
|
||||
|
||||
try {
|
||||
await LiveActivityManager.startPersistentLiveActivity();
|
||||
console.debug('Persistent Live Activity started.');
|
||||
} catch (error) {
|
||||
console.error('Failed to start Persistent Live Activity:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ends the persistent Live Activity for Dynamic Island.
|
||||
*/
|
||||
export const endPersistentLiveActivity = async (): Promise<void> => {
|
||||
if (!LiveActivityManager || !LiveActivityManager.endPersistentLiveActivity) {
|
||||
throw new Error('LiveActivityManager or endPersistentLiveActivity method is not available.');
|
||||
}
|
||||
|
||||
try {
|
||||
await LiveActivityManager.endPersistentLiveActivity();
|
||||
console.debug('Persistent Live Activity ended.');
|
||||
} catch (error) {
|
||||
console.error('Failed to end Persistent Live Activity:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 70;
|
||||
objectVersion = 73;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@ -127,8 +127,6 @@
|
||||
B4D0B2642C1DEA99006B6B1B /* ReceiveType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2632C1DEA99006B6B1B /* ReceiveType.swift */; };
|
||||
B4D0B2662C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2652C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift */; };
|
||||
B4D0B2682C1DED67006B6B1B /* ReceiveMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2672C1DED67006B6B1B /* ReceiveMethod.swift */; };
|
||||
B4E2CA7B2CCC61F1009540B0 /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4E2CA7A2CCC61F1009540B0 /* KeychainManager.swift */; };
|
||||
B4E2CA7C2CCC61F1009540B0 /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4E2CA7A2CCC61F1009540B0 /* KeychainManager.swift */; };
|
||||
B4E2CA7D2CCC61F1009540B0 /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4E2CA7A2CCC61F1009540B0 /* KeychainManager.swift */; };
|
||||
B4E2CA852CCC675E009540B0 /* MarketAPI+Electrum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5142558EBA3009312A5 /* MarketAPI+Electrum.swift */; };
|
||||
B4E2CA862CCC6778009540B0 /* SwiftTCPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40FC3F829CCD1AC0007EBAC /* SwiftTCPClient.swift */; };
|
||||
@ -137,6 +135,9 @@
|
||||
B4E2CAD82CCCA5F5009540B0 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3A252FE1A3004D72DF /* WidgetKit.framework */; };
|
||||
B4E2CAD92CCCA5F5009540B0 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */; };
|
||||
B4E2CAE62CCCA5F6009540B0 /* TransactionsMonitorExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = B4E2CAD72CCCA5F5009540B0 /* TransactionsMonitorExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
B4E2CAFD2CCD3FF4009540B0 /* ElectrumFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4E2CAFB2CCD3FF4009540B0 /* ElectrumFetcher.swift */; };
|
||||
B4E2CAFF2CCD4004009540B0 /* ElectrumFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4E2CAFB2CCD3FF4009540B0 /* ElectrumFetcher.swift */; };
|
||||
B4E2CB002CCD4012009540B0 /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4E2CA7A2CCC61F1009540B0 /* KeychainManager.swift */; };
|
||||
B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; };
|
||||
B4EFF73B2C3F6C5E0095D655 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4EFF73A2C3F6C5E0095D655 /* MockData.swift */; };
|
||||
C978A716948AB7DEC5B6F677 /* (null) in Frameworks */ = {isa = PBXBuildFile; };
|
||||
@ -377,6 +378,7 @@
|
||||
B4E2CAC02CCCA57E009540B0 /* LiveActivityExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LiveActivityExtension.entitlements; sourceTree = "<group>"; };
|
||||
B4E2CAD72CCCA5F5009540B0 /* TransactionsMonitorExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TransactionsMonitorExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B4E2CAE72CCCA5F6009540B0 /* TransactionsMonitorExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TransactionsMonitorExtension.entitlements; sourceTree = "<group>"; };
|
||||
B4E2CAFB2CCD3FF4009540B0 /* ElectrumFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElectrumFetcher.swift; sourceTree = "<group>"; };
|
||||
B4EFF73A2C3F6C5E0095D655 /* MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockData.swift; sourceTree = "<group>"; };
|
||||
B642AFB13483418CAB6FF25E /* libRCTQRCodeLocalImage.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTQRCodeLocalImage.a; sourceTree = "<group>"; };
|
||||
B9D9B3A7B2CB4255876B67AF /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
|
||||
@ -399,13 +401,27 @@
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
LiveActivityManagerModule.m,
|
||||
TransactionsMonitorEventEmitterModule.m,
|
||||
TransactionsMonitorModule.m,
|
||||
);
|
||||
target = B4E2CAD62CCCA5F5009540B0 /* TransactionsMonitorExtension */;
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
|
||||
B4E2CB072CCD406C009540B0 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {
|
||||
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
|
||||
buildPhase = B4E2CAD52CCCA5F5009540B0 /* Resources */;
|
||||
membershipExceptions = (
|
||||
TransactionsMonitorEventEmitterModule.m,
|
||||
TransactionsMonitorModule.m,
|
||||
);
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
B4E2CADA2CCCA5F5009540B0 /* TransactionsMonitor */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (B4E2CAE82CCCA5F7009540B0 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = TransactionsMonitor; sourceTree = "<group>"; };
|
||||
B4E2CADA2CCCA5F5009540B0 /* TransactionsMonitor */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (B4E2CAE82CCCA5F7009540B0 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, B4E2CB072CCD406C009540B0 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = TransactionsMonitor; sourceTree = "<group>"; };
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -753,6 +769,7 @@
|
||||
B44033C82BCC34AC00162242 /* Shared */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B4E2CAFB2CCD3FF4009540B0 /* ElectrumFetcher.swift */,
|
||||
B4E2CA7A2CCC61F1009540B0 /* KeychainManager.swift */,
|
||||
B450109A2C0FCD7E00619044 /* Utilities */,
|
||||
6D2AA8062568B8E50090B089 /* Fiat */,
|
||||
@ -997,11 +1014,11 @@
|
||||
};
|
||||
B4E2CAD62CCCA5F5009540B0 = {
|
||||
CreatedOnToolsVersion = 16.0;
|
||||
LastSwiftMigration = 1600;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "BlueWallet" */;
|
||||
compatibilityVersion = "Xcode 15.0";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
@ -1038,6 +1055,7 @@
|
||||
B41B76832B66B2FF002C48D5 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */,
|
||||
B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift" */,
|
||||
);
|
||||
preferredProjectObjectVersion = 60;
|
||||
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@ -1247,6 +1265,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B44033E92BCC371A00162242 /* MarketData.swift in Sources */,
|
||||
B4E2CAFD2CCD3FF4009540B0 /* ElectrumFetcher.swift in Sources */,
|
||||
B44033CA2BCC350A00162242 /* Currency.swift in Sources */,
|
||||
B4E2CA862CCC6778009540B0 /* SwiftTCPClient.swift in Sources */,
|
||||
B44033EE2BCC374500162242 /* Numeric+abbreviated.swift in Sources */,
|
||||
@ -1309,7 +1328,6 @@
|
||||
B44033C62BCC332400162242 /* Balance.swift in Sources */,
|
||||
B44033E62BCC36FF00162242 /* WalletData.swift in Sources */,
|
||||
6DD410BF266CB13D0087DE03 /* Placeholders.swift in Sources */,
|
||||
B4E2CA7C2CCC61F1009540B0 /* KeychainManager.swift in Sources */,
|
||||
6DD410B0266CAF5C0087DE03 /* WalletInformationWidget.swift in Sources */,
|
||||
6DD410B1266CAF5C0087DE03 /* MarketAPI+Electrum.swift in Sources */,
|
||||
6DD410B8266CAF5C0087DE03 /* UserDefaultsExtension.swift in Sources */,
|
||||
@ -1355,7 +1373,6 @@
|
||||
B44033E52BCC36FF00162242 /* WalletData.swift in Sources */,
|
||||
B44033EF2BCC374500162242 /* Numeric+abbreviated.swift in Sources */,
|
||||
B4D0B2622C1DEA11006B6B1B /* ReceivePageInterfaceController.swift in Sources */,
|
||||
B4E2CA7B2CCC61F1009540B0 /* KeychainManager.swift in Sources */,
|
||||
B44033C52BCC332400162242 /* Balance.swift in Sources */,
|
||||
6D4AF18425D215D1009DD853 /* UserDefaultsExtension.swift in Sources */,
|
||||
B4AB225D2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */,
|
||||
@ -1368,6 +1385,8 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B4E2CAFF2CCD4004009540B0 /* ElectrumFetcher.swift in Sources */,
|
||||
B4E2CB002CCD4012009540B0 /* KeychainManager.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -1578,7 +1597,7 @@
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = Stickers/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/usr/lib/swift",
|
||||
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
|
||||
@ -1621,7 +1640,7 @@
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = Stickers/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/usr/lib/swift",
|
||||
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
|
||||
@ -1666,7 +1685,7 @@
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = A7W54YZ4WU;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = Widgets/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -1722,7 +1741,7 @@
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = A7W54YZ4WU;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = Widgets/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -1930,7 +1949,7 @@
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 7.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 8.7;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -1979,7 +1998,7 @@
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 7.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 8.7;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@ -2026,7 +2045,7 @@
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 7.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 8.7;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -2073,7 +2092,7 @@
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 7.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 8.7;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@ -2086,18 +2105,21 @@
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = TransactionsMonitorExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = A7W54YZ4WU;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
@ -2108,7 +2130,7 @@
|
||||
INFOPLIST_FILE = TransactionsMonitor/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = TransactionsMonitor;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 BlueWallet. All rights reserved.";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -2121,10 +2143,13 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TransactionsMonitor;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development io.bluewallet.bluewallet.TransactionsMonitor";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development io.bluewallet.bluewallet.TransactionsMonitor catalyst";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "TransactionsMonitor/TransactionsMonitorExtension-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@ -2140,6 +2165,7 @@
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
@ -2148,18 +2174,20 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = A7W54YZ4WU;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = TransactionsMonitor/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = TransactionsMonitor;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 BlueWallet. All rights reserved.";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -2170,9 +2198,13 @@
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TransactionsMonitor;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore io.bluewallet.bluewallet.TransactionsMonitor";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match AppStore io.bluewallet.bluewallet.TransactionsMonitor catalyst";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "TransactionsMonitor/TransactionsMonitorExtension-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
|
@ -67,7 +67,7 @@
|
||||
<key>TransactionsMonitorExtension.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>130</integer>
|
||||
<integer>8</integer>
|
||||
</dict>
|
||||
<key>WalletInformationAndMarketWidget.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
|
@ -4,6 +4,8 @@
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.usernotifications.time-sensitive</key>
|
||||
<true/>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
|
@ -4,6 +4,8 @@
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.usernotifications.time-sensitive</key>
|
||||
<true/>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
|
@ -2,6 +2,10 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.usernotifications.time-sensitive</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.io.bluewallet.bluewallet</string>
|
||||
|
@ -2,6 +2,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>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.io.bluewallet.bluewallet</string>
|
||||
|
2253
ios/Podfile.lock
2253
ios/Podfile.lock
File diff suppressed because it is too large
Load Diff
185
ios/Shared/ElectrumFetcher.swift
Normal file
185
ios/Shared/ElectrumFetcher.swift
Normal file
@ -0,0 +1,185 @@
|
||||
//
|
||||
// ElectrumFetcherProtocol.swift
|
||||
// BlueWallet
|
||||
//
|
||||
// Created by Marcos Rodriguez on 10/26/24.
|
||||
// Copyright © 2024 BlueWallet. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
// ElectrumFetcher.swift
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
protocol ElectrumFetcherProtocol {
|
||||
func fetchTransactionConfirmations(txid: String) async throws -> Int
|
||||
}
|
||||
|
||||
class ElectrumFetcher: ElectrumFetcherProtocol {
|
||||
|
||||
private var connection: NWConnection?
|
||||
private let host: NWEndpoint.Host
|
||||
private let port: NWEndpoint.Port
|
||||
private let useSSL: Bool
|
||||
private let queue = DispatchQueue(label: "ElectrumFetcherQueue")
|
||||
private let requestTimeout: TimeInterval = 10
|
||||
|
||||
init(host: String, port: UInt16, useSSL: Bool) {
|
||||
self.host = NWEndpoint.Host(host)
|
||||
self.port = NWEndpoint.Port(rawValue: port) ?? .init(integerLiteral: 50001) // Default port if invalid
|
||||
self.useSSL = useSSL
|
||||
}
|
||||
|
||||
deinit {
|
||||
disconnect()
|
||||
}
|
||||
|
||||
func connect() async throws {
|
||||
let parameters = NWParameters.tcp
|
||||
if useSSL {
|
||||
let tlsOptions = NWProtocolTLS.Options()
|
||||
sec_protocol_options_set_min_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv12)
|
||||
parameters.defaultProtocolStack.applicationProtocols.insert(tlsOptions, at: 0)
|
||||
}
|
||||
|
||||
connection = NWConnection(host: host, port: port, using: parameters)
|
||||
connection?.stateUpdateHandler = { [weak self] newState in
|
||||
switch newState {
|
||||
case .ready:
|
||||
print("ElectrumFetcher: Connected to \(self?.host.description ?? "Unknown Host"):\(self?.port.rawValue ?? 0)")
|
||||
case .failed(let error):
|
||||
print("ElectrumFetcher: Connection failed with error: \(error.localizedDescription)")
|
||||
case .cancelled:
|
||||
print("ElectrumFetcher: Connection cancelled.")
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
connection?.start(queue: queue)
|
||||
|
||||
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
||||
connection?.stateUpdateHandler = { newState in
|
||||
switch newState {
|
||||
case .ready:
|
||||
continuation.resume()
|
||||
case .failed(let error):
|
||||
continuation.resume(throwing: error)
|
||||
case .cancelled:
|
||||
continuation.resume(throwing: NSError(domain: "ElectrumFetcher", code: -1, userInfo: [NSLocalizedDescriptionKey: "Connection cancelled"]))
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func disconnect() {
|
||||
connection?.cancel()
|
||||
connection = nil
|
||||
print("ElectrumFetcher: Disconnected from \(host.description):\(port.rawValue)")
|
||||
}
|
||||
|
||||
private func sendRequest(request: [String: Any]) async throws -> [String: Any] {
|
||||
let requestData = try JSONSerialization.data(withJSONObject: request, options: [])
|
||||
var requestWithNewline = requestData
|
||||
requestWithNewline.append(contentsOf: "\n".utf8)
|
||||
|
||||
if connection?.state != .ready {
|
||||
try await connectWithRetry()
|
||||
}
|
||||
|
||||
try await send(data: requestWithNewline)
|
||||
print("ElectrumFetcher: Sent request \(request["method"] ?? "unknown method")")
|
||||
|
||||
let responseData = try await receive(timeout: requestTimeout)
|
||||
print("ElectrumFetcher: Received response for request \(request["method"] ?? "unknown method")")
|
||||
|
||||
guard let responseJSON = try? JSONSerialization.jsonObject(with: responseData, options: []) as? [String: Any] else {
|
||||
throw NSError(domain: "ElectrumFetcher", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid JSON response"])
|
||||
}
|
||||
|
||||
return responseJSON
|
||||
}
|
||||
|
||||
private func send(data: Data) async throws {
|
||||
guard let connection = connection else {
|
||||
throw NSError(domain: "ElectrumFetcher", code: -1, userInfo: [NSLocalizedDescriptionKey: "No active connection"])
|
||||
}
|
||||
|
||||
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
||||
connection.send(content: data, completion: .contentProcessed { error in
|
||||
if let error = error {
|
||||
continuation.resume(throwing: error)
|
||||
} else {
|
||||
continuation.resume()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func receive(timeout: TimeInterval) async throws -> Data {
|
||||
guard let connection = connection else {
|
||||
throw NSError(domain: "ElectrumFetcher", code: -1, userInfo: [NSLocalizedDescriptionKey: "No active connection"])
|
||||
}
|
||||
|
||||
return try await withTaskCancellationHandler(operation: {
|
||||
return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Data, Error>) in
|
||||
connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, context, isComplete, error in
|
||||
if let error = error {
|
||||
continuation.resume(throwing: error)
|
||||
} else if let data = data, !data.isEmpty {
|
||||
continuation.resume(returning: data)
|
||||
} else {
|
||||
continuation.resume(throwing: NSError(domain: "ElectrumFetcher", code: -1, userInfo: [NSLocalizedDescriptionKey: "No data received"]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}, onCancel: {
|
||||
connection.cancel()
|
||||
})
|
||||
}
|
||||
|
||||
private func connectWithRetry(maxRetries: Int = 3) async throws {
|
||||
var attempt = 0
|
||||
var delayTime: TimeInterval = 1
|
||||
|
||||
while attempt < maxRetries {
|
||||
do {
|
||||
try await connect()
|
||||
return
|
||||
} catch {
|
||||
attempt += 1
|
||||
if attempt >= maxRetries {
|
||||
throw error
|
||||
}
|
||||
print("ElectrumFetcher: Connection attempt \(attempt) failed with error: \(error.localizedDescription). Retrying in \(delayTime) seconds...")
|
||||
try await Task.sleep(nanoseconds: UInt64(delayTime * 1_000_000_000))
|
||||
delayTime *= 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchTransactionConfirmations(txid: String) async throws -> Int {
|
||||
let requestId = UUID().uuidString
|
||||
|
||||
let requestDict: [String: Any] = [
|
||||
"id": requestId,
|
||||
"method": "blockchain.transaction.get_confirmations",
|
||||
"params": [txid]
|
||||
]
|
||||
|
||||
let responseJSON = try await sendRequest(request: requestDict)
|
||||
|
||||
if let error = responseJSON["error"] as? [String: Any], let message = error["message"] as? String {
|
||||
throw NSError(domain: "ElectrumFetcher", code: -1, userInfo: [NSLocalizedDescriptionKey: message])
|
||||
}
|
||||
|
||||
if let result = responseJSON["result"] as? Int {
|
||||
return result
|
||||
} else {
|
||||
throw NSError(domain: "ElectrumFetcher", code: -1, userInfo: [NSLocalizedDescriptionKey: "Unexpected response format"])
|
||||
}
|
||||
}
|
||||
}
|
34
ios/TransactionsMonitor/LiveActivityManager.swift
Normal file
34
ios/TransactionsMonitor/LiveActivityManager.swift
Normal file
@ -0,0 +1,34 @@
|
||||
// LiveActivityManager.swift
|
||||
|
||||
import ActivityKit
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
@objc(LiveActivityManager)
|
||||
class LiveActivityManager: NSObject {
|
||||
|
||||
func startPersistentLiveActivity() {
|
||||
let attributes = TransactionsMonitorAttributes(name: "BlueWallet")
|
||||
let contentState = TransactionsMonitorAttributes.ContentState(emoji: "😀")
|
||||
|
||||
do {
|
||||
let activity = try Activity<TransactionsMonitorAttributes>.request(
|
||||
attributes: attributes,
|
||||
contentState: contentState,
|
||||
pushType: nil
|
||||
)
|
||||
print("Live Activity started with ID: \(activity.id)")
|
||||
} catch (let error) {
|
||||
print("Error starting Live Activity: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
func endPersistentLiveActivity() {
|
||||
for activity in Activity<TransactionsMonitorAttributes>.activities {
|
||||
Task {
|
||||
await activity.end(dismissalPolicy: .immediate)
|
||||
print("Live Activity ended with ID: \(activity.id)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
ios/TransactionsMonitor/LiveActivityManagerModule.m
Normal file
10
ios/TransactionsMonitor/LiveActivityManagerModule.m
Normal file
@ -0,0 +1,10 @@
|
||||
// LiveActivityManagerModule.m
|
||||
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
||||
@interface RCT_EXTERN_MODULE(LiveActivityManager, NSObject)
|
||||
|
||||
RCT_EXTERN_METHOD(startPersistentLiveActivity)
|
||||
RCT_EXTERN_METHOD(endPersistentLiveActivity)
|
||||
|
||||
@end
|
@ -1,85 +1,135 @@
|
||||
//
|
||||
// TransactionsMonitor.swift
|
||||
// TransactionsMonitor
|
||||
// BlueWallet
|
||||
//
|
||||
// Created by Marcos Rodriguez on 10/26/24.
|
||||
// Copyright © 2024 BlueWallet. All rights reserved.
|
||||
//
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
struct Provider: TimelineProvider {
|
||||
func placeholder(in context: Context) -> SimpleEntry {
|
||||
SimpleEntry(date: Date(), emoji: "😀")
|
||||
// TransactionsMonitor.swift
|
||||
|
||||
import Foundation
|
||||
import React
|
||||
|
||||
@objc(TransactionsMonitor)
|
||||
class TransactionsMonitor: NSObject {
|
||||
|
||||
private let electrumFetcher: ElectrumFetcherProtocol
|
||||
private let keychainManager: KeychainManager
|
||||
private let service = "transactionData"
|
||||
private let account = "transactions"
|
||||
private let externalTxidsKey = "external_txids"
|
||||
|
||||
override init() {
|
||||
let settings = UserDefaultsGroup.getElectrumSettings()
|
||||
let host = settings.host ?? "electrum.blockstream.info"
|
||||
let port = settings.port ?? 50002
|
||||
let useSSL = settings.sslPort != nil
|
||||
self.electrumFetcher = ElectrumFetcher(host: host, port: port, useSSL: useSSL)
|
||||
self.keychainManager = KeychainManager.shared
|
||||
super.init()
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
|
||||
let entry = SimpleEntry(date: Date(), emoji: "😀")
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
||||
var entries: [SimpleEntry] = []
|
||||
|
||||
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
|
||||
let currentDate = Date()
|
||||
for hourOffset in 0 ..< 5 {
|
||||
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
|
||||
let entry = SimpleEntry(date: entryDate, emoji: "😀")
|
||||
entries.append(entry)
|
||||
}
|
||||
|
||||
let timeline = Timeline(entries: entries, policy: .atEnd)
|
||||
completion(timeline)
|
||||
}
|
||||
|
||||
// func relevances() async -> WidgetRelevances<Void> {
|
||||
// // Generate a list containing the contexts this widget is relevant in.
|
||||
// }
|
||||
}
|
||||
|
||||
struct SimpleEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let emoji: String
|
||||
}
|
||||
|
||||
struct TransactionsMonitorEntryView : View {
|
||||
var entry: Provider.Entry
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Time:")
|
||||
Text(entry.date, style: .time)
|
||||
|
||||
Text("Emoji:")
|
||||
Text(entry.emoji)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TransactionsMonitor: Widget {
|
||||
let kind: String = "TransactionsMonitor"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: kind, provider: Provider()) { entry in
|
||||
if #available(iOS 17.0, *) {
|
||||
TransactionsMonitorEntryView(entry: entry)
|
||||
.containerBackground(.fill.tertiary, for: .widget)
|
||||
} else {
|
||||
TransactionsMonitorEntryView(entry: entry)
|
||||
.padding()
|
||||
.background()
|
||||
|
||||
@objc
|
||||
func startMonitoringTransactions() {
|
||||
Task {
|
||||
do {
|
||||
let allTxids = try await fetchAllTxids()
|
||||
for txid in allTxids {
|
||||
await monitorTransaction(txid: txid)
|
||||
}
|
||||
print("TransactionsMonitor: Started monitoring \(allTxids.count) transactions.")
|
||||
} catch {
|
||||
print("TransactionsMonitor: Error fetching txids: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
.configurationDisplayName("My Widget")
|
||||
.description("This is an example widget.")
|
||||
}
|
||||
}
|
||||
|
||||
#Preview(as: .systemSmall) {
|
||||
TransactionsMonitor()
|
||||
} timeline: {
|
||||
SimpleEntry(date: .now, emoji: "😀")
|
||||
SimpleEntry(date: .now, emoji: "🤩")
|
||||
}
|
||||
|
||||
@objc
|
||||
func getAllTxIds(_ callback: @escaping RCTResponseSenderBlock) {
|
||||
Task {
|
||||
do {
|
||||
let allTxids = try fetchAllTxidsSync()
|
||||
callback([NSNull(), allTxids])
|
||||
} catch {
|
||||
callback([error.localizedDescription, NSNull()])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
func addExternalTxId(_ txid: String) {
|
||||
Task {
|
||||
do {
|
||||
try await addExternalTxIdAsync(txid: txid)
|
||||
print("TransactionsMonitor: External txid \(txid) added for monitoring.")
|
||||
} catch {
|
||||
print("TransactionsMonitor: Error adding external txid \(txid): \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
func removeExternalTxId(_ txid: String) {
|
||||
Task {
|
||||
do {
|
||||
try await removeExternalTxIdAsync(txid: txid)
|
||||
print("TransactionsMonitor: External txid \(txid) removed from monitoring.")
|
||||
} catch {
|
||||
print("TransactionsMonitor: Error removing external txid \(txid): \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addExternalTxIdAsync(txid: String) async throws {
|
||||
var externalTxids = try keychainManager.retrieveCodable(service: externalTxidsKey, account: account, type: [String].self) ?? []
|
||||
externalTxids.append(txid)
|
||||
try keychainManager.saveCodable(object: externalTxids, service: externalTxidsKey, account: account)
|
||||
await monitorTransaction(txid: txid)
|
||||
}
|
||||
|
||||
private func removeExternalTxIdAsync(txid: String) async throws {
|
||||
var externalTxids = try keychainManager.retrieveCodable(service: externalTxidsKey, account: account, type: [String].self) ?? []
|
||||
if let index = externalTxids.firstIndex(of: txid) {
|
||||
externalTxids.remove(at: index)
|
||||
try keychainManager.saveCodable(object: externalTxids, service: externalTxidsKey, account: account)
|
||||
} else {
|
||||
throw NSError(domain: "TransactionsMonitor", code: -1, userInfo: [NSLocalizedDescriptionKey: "Txid not found in external monitoring"])
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchAllTxids() async throws -> [String] {
|
||||
var txids: [String] = []
|
||||
if let walletTxs = try keychainManager.retrieveCodable(service: service, account: account, type: [Transaction].self) {
|
||||
txids += walletTxs.map { $0.txid }
|
||||
}
|
||||
if let externalTxids = try keychainManager.retrieveCodable(service: externalTxidsKey, account: account, type: [String].self) {
|
||||
txids += externalTxids
|
||||
}
|
||||
return txids
|
||||
}
|
||||
|
||||
private func fetchAllTxidsSync() throws -> [String] {
|
||||
var txids: [String] = []
|
||||
if let walletTxs = try keychainManager.retrieveCodable(service: service, account: account, type: [Transaction].self) {
|
||||
txids += walletTxs.map { $0.txid }
|
||||
}
|
||||
if let externalTxids = try keychainManager.retrieveCodable(service: externalTxidsKey, account: account, type: [String].self) {
|
||||
txids += externalTxids
|
||||
}
|
||||
return txids
|
||||
}
|
||||
|
||||
private func monitorTransaction(txid: String) async {
|
||||
do {
|
||||
let confirmations = try await electrumFetcher.fetchTransactionConfirmations(txid: txid)
|
||||
if confirmations > 0 {
|
||||
TransactionsMonitorEventEmitter.sendTransactionConfirmedEvent(txid: txid)
|
||||
try await removeExternalTxIdAsync(txid: txid)
|
||||
}
|
||||
} catch {
|
||||
print("TransactionsMonitor: Error monitoring txid \(txid): \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,4 @@
|
||||
//
|
||||
// TransactionsMonitorBundle.swift
|
||||
// TransactionsMonitor
|
||||
//
|
||||
// Created by Marcos Rodriguez on 10/26/24.
|
||||
// Copyright © 2024 BlueWallet. All rights reserved.
|
||||
//
|
||||
// TransactionsMonitorBundle.swift
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
@ -12,7 +6,7 @@ import SwiftUI
|
||||
@main
|
||||
struct TransactionsMonitorBundle: WidgetBundle {
|
||||
var body: some Widget {
|
||||
TransactionsMonitor()
|
||||
TransactionsMonitorWidget()
|
||||
TransactionsMonitorLiveActivity()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
//
|
||||
// TransactionsMonitorEventEmitter.swift
|
||||
// BlueWallet
|
||||
//
|
||||
// Created by Marcos Rodriguez on 10/26/24.
|
||||
// Copyright © 2024 BlueWallet. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
// TransactionsMonitorEventEmitter.swift
|
||||
|
||||
import Foundation
|
||||
import React
|
||||
|
||||
@objc(TransactionsMonitorEventEmitter)
|
||||
class TransactionsMonitorEventEmitter: RCTEventEmitter {
|
||||
|
||||
static var shared: TransactionsMonitorEventEmitter?
|
||||
static var hasListeners = false
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
TransactionsMonitorEventEmitter.shared = self
|
||||
}
|
||||
|
||||
override static func requiresMainQueueSetup() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func startObserving() {
|
||||
TransactionsMonitorEventEmitter.hasListeners = true
|
||||
}
|
||||
|
||||
override func stopObserving() {
|
||||
TransactionsMonitorEventEmitter.hasListeners = false
|
||||
}
|
||||
|
||||
override func supportedEvents() -> [String] {
|
||||
return ["TransactionConfirmed"]
|
||||
}
|
||||
|
||||
static func sendTransactionConfirmedEvent(txid: String) {
|
||||
if let emitter = TransactionsMonitorEventEmitter.shared, hasListeners {
|
||||
emitter.sendEvent(withName: "TransactionConfirmed", body: ["txid": txid])
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
// TransactionsMonitorEventEmitterModule.m
|
||||
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
||||
@interface RCT_EXTERN_MODULE(TransactionsMonitorEventEmitter, RCTEventEmitter)
|
||||
|
||||
@end
|
@ -0,0 +1,4 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
@ -1,10 +1,4 @@
|
||||
//
|
||||
// TransactionsMonitorLiveActivity.swift
|
||||
// TransactionsMonitor
|
||||
//
|
||||
// Created by Marcos Rodriguez on 10/26/24.
|
||||
// Copyright © 2024 BlueWallet. All rights reserved.
|
||||
//
|
||||
// TransactionsMonitorLiveActivity.swift
|
||||
|
||||
import ActivityKit
|
||||
import WidgetKit
|
||||
@ -25,33 +19,34 @@ struct TransactionsMonitorLiveActivity: Widget {
|
||||
ActivityConfiguration(for: TransactionsMonitorAttributes.self) { context in
|
||||
// Lock screen/banner UI goes here
|
||||
VStack {
|
||||
Text("Hello \(context.state.emoji)")
|
||||
Text("Monitoring Transactions")
|
||||
Text(context.state.emoji)
|
||||
.font(.largeTitle)
|
||||
}
|
||||
.activityBackgroundTint(Color.cyan)
|
||||
.activitySystemActionForegroundColor(Color.black)
|
||||
|
||||
} dynamicIsland: { context in
|
||||
DynamicIsland {
|
||||
// Expanded UI goes here. Compose the expanded UI through
|
||||
// various regions, like leading/trailing/center/bottom
|
||||
// Expanded UI
|
||||
DynamicIslandExpandedRegion(.leading) {
|
||||
Text("Leading")
|
||||
Text("🔍")
|
||||
}
|
||||
DynamicIslandExpandedRegion(.trailing) {
|
||||
Text("Trailing")
|
||||
Text("📈")
|
||||
}
|
||||
DynamicIslandExpandedRegion(.bottom) {
|
||||
Text("Bottom \(context.state.emoji)")
|
||||
// more content
|
||||
Text("Status: \(context.state.emoji)")
|
||||
// more content if needed
|
||||
}
|
||||
} compactLeading: {
|
||||
Text("L")
|
||||
Text("🔍")
|
||||
} compactTrailing: {
|
||||
Text("T \(context.state.emoji)")
|
||||
Text("📈")
|
||||
} minimal: {
|
||||
Text(context.state.emoji)
|
||||
}
|
||||
.widgetURL(URL(string: "http://www.apple.com"))
|
||||
.widgetURL(URL(string: "http://www.bluewallet.com"))
|
||||
.keylineTint(Color.red)
|
||||
}
|
||||
}
|
||||
@ -59,18 +54,18 @@ struct TransactionsMonitorLiveActivity: Widget {
|
||||
|
||||
extension TransactionsMonitorAttributes {
|
||||
fileprivate static var preview: TransactionsMonitorAttributes {
|
||||
TransactionsMonitorAttributes(name: "World")
|
||||
TransactionsMonitorAttributes(name: "BlueWallet")
|
||||
}
|
||||
}
|
||||
|
||||
extension TransactionsMonitorAttributes.ContentState {
|
||||
fileprivate static var smiley: TransactionsMonitorAttributes.ContentState {
|
||||
TransactionsMonitorAttributes.ContentState(emoji: "😀")
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate static var starEyes: TransactionsMonitorAttributes.ContentState {
|
||||
TransactionsMonitorAttributes.ContentState(emoji: "🤩")
|
||||
}
|
||||
fileprivate static var starEyes: TransactionsMonitorAttributes.ContentState {
|
||||
TransactionsMonitorAttributes.ContentState(emoji: "🤩")
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("Notification", as: .content, using: TransactionsMonitorAttributes.preview) {
|
||||
|
12
ios/TransactionsMonitor/TransactionsMonitorModule.m
Normal file
12
ios/TransactionsMonitor/TransactionsMonitorModule.m
Normal file
@ -0,0 +1,12 @@
|
||||
// TransactionsMonitorModule.m
|
||||
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
||||
@interface RCT_EXTERN_MODULE(TransactionsMonitor, NSObject)
|
||||
|
||||
RCT_EXTERN_METHOD(startMonitoringTransactions)
|
||||
RCT_EXTERN_METHOD(getAllTxIds:(RCTResponseSenderBlock)callback)
|
||||
RCT_EXTERN_METHOD(addExternalTxId:(NSString *)txid)
|
||||
RCT_EXTERN_METHOD(removeExternalTxId:(NSString *)txid)
|
||||
|
||||
@end
|
75
ios/TransactionsMonitor/TransactionsMonitorWidget.swift
Normal file
75
ios/TransactionsMonitor/TransactionsMonitorWidget.swift
Normal file
@ -0,0 +1,75 @@
|
||||
// TransactionsMonitorWidget.swift
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
struct Provider: TimelineProvider {
|
||||
func placeholder(in context: Context) -> SimpleEntry {
|
||||
SimpleEntry(date: Date(), status: "Idle")
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
|
||||
let entry = SimpleEntry(date: Date(), status: "Active")
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
||||
var entries: [SimpleEntry] = []
|
||||
|
||||
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
|
||||
let currentDate = Date()
|
||||
for hourOffset in 0 ..< 5 {
|
||||
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
|
||||
let status = hourOffset % 2 == 0 ? "Active" : "Idle"
|
||||
let entry = SimpleEntry(date: entryDate, status: status)
|
||||
entries.append(entry)
|
||||
}
|
||||
|
||||
let timeline = Timeline(entries: entries, policy: .atEnd)
|
||||
completion(timeline)
|
||||
}
|
||||
}
|
||||
|
||||
struct SimpleEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let status: String
|
||||
}
|
||||
|
||||
struct TransactionsMonitorEntryView : View {
|
||||
var entry: Provider.Entry
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Status:")
|
||||
Text(entry.status)
|
||||
.font(.headline)
|
||||
.foregroundColor(entry.status == "Active" ? .green : .red)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TransactionsMonitorWidget: Widget {
|
||||
let kind: String = "TransactionsMonitorWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: kind, provider: Provider()) { entry in
|
||||
if #available(iOS 17.0, *) {
|
||||
TransactionsMonitorEntryView(entry: entry)
|
||||
.containerBackground(.fill.tertiary, for: .widget)
|
||||
} else {
|
||||
TransactionsMonitorEntryView(entry: entry)
|
||||
.padding()
|
||||
.background()
|
||||
}
|
||||
}
|
||||
.configurationDisplayName("Transactions Monitor")
|
||||
.description("Monitors your transactions and updates the Dynamic Island accordingly.")
|
||||
}
|
||||
}
|
||||
|
||||
#Preview(as: .systemSmall) {
|
||||
TransactionsMonitorWidget()
|
||||
} timeline: {
|
||||
SimpleEntry(date: .now, status: "Active")
|
||||
SimpleEntry(date: .now.addingTimeInterval(3600), status: "Idle")
|
||||
}
|
@ -6,5 +6,9 @@
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)group.io.bluewallet.bluewallet</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
Loading…
Reference in New Issue
Block a user