diff --git a/LICENSE b/LICENSE
index 75d827609..baf51928e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,42 +1,21 @@
-(c) Copyright 2023 by BitHyve UK Ltd.
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject
-to the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
-ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
-CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-"Commons Clause" License Condition v1.0
-
-The Software is provided to you by the Licensor under the License,
-as defined below, subject to the following condition.
-
-Without limiting other conditions in the License, the grant of
-rights under the License will not include, and the License does not
-grant to you, the right to Sell the Software.
-
-For purposes of the foregoing, "Sell" means practicing any or all
-of the rights granted to you under the License to provide to third
-parties, for a fee or other consideration (including without
-limitation fees for hosting or consulting/ support services related
-to the Software), a product or service whose value derives, entirely
-or substantially, from the functionality of the Software. Any license
-notice or attribution required by the License must also include
-this Commons Clause License Condition notice.
-
-Software: All BitcoinKeeper associated files.
-License: MIT
-Licensor: BitHyve UK Ltd.
+MIT License
+
+Copyright (c) 2023 bithyve
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Readme.md b/Readme.md
index b6202d3de..20a5da6bd 100644
--- a/Readme.md
+++ b/Readme.md
@@ -1,20 +1,18 @@
# Bitcoin Keeper - Your Private Bitcoin Vault
-Bitcoin Keeper is a React Native app written in TypeScript. It uses Yarn as a package manager to handle dependencies.
+Affordable and easy-to-use, security for all your sats, BIP-85, Multisig, Own Node, Whirlpool, and Hardware Wallet support. Built with React Native
-
+
+
+
+[![Playstore](https://bitcoinkeeper.app/wp-content/uploads/2023/05/gpbtn.png)](https://play.google.com/store/apps/details?id=io.hexawallet.bitcoinkeeper)
+[![Appstore](https://bitcoinkeeper.app/wp-content/uploads/2023/05/applebtn.png)](https://apps.apple.com/us/app/bitcoin-keeper/id1545535925)
## Prerequisites
-Before getting started, make sure you have the following software installed on your machine:
+Before getting started, make sure you have proper [React Native development environment](https://reactnative.dev/docs/environment-setup) on your machine
-- [Node.js > 12](https://nodejs.org) and npm (Recommended: Use [nvm](https://github.com/nvm-sh/nvm))
-- [Watchman](https://facebook.github.io/watchman)
-- [Xcode 12](https://developer.apple.com/xcode)
-- [Cocoapods 1.10.1](https://cocoapods.org)
-- [JDK > 11](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html)
-- [Android Studio and Android SDK](https://developer.android.com/studio)
## Getting Started
@@ -33,40 +31,65 @@ Before getting started, make sure you have the following software installed on y
```shell
yarn install
```
-
-## Development
-To start the development server and run the app on a connected device or emulator, use the following commands:
+
+## Build and Run
+
+### Whirlpool prerequisites
+To use the Whirlpool, you'll need to have the following platform-specific binaries and place them in the specified directories:
#### Android
+
+* Extract [jniLibs](https://github.com/bithyve/bitcoin-keeper/releases/download/v1.0.8/jniLibs.zip) to the following directory if not present already:
```bash
-yarn androidDevelopmentDebug
+android/app/src/main/
```
-
+
#### iOS
+
+* Copy the [libwhirlpool.a](https://github.com/bithyve/bitcoin-keeper/releases/download/v1.0.8/libwhirlpool.a) to the following directory:
```bash
-yarn ios --scheme=hexa_keeper_dev
+ios/libwhirlpool.a
```
-These commands will build and launch the app on the respective platforms.
+### Varients
+The project has testnet and mainnet varients. The development varient is configured to use testnet and production varient to use mainnet.
-### Whirlpool prerequisites
-To use the Whirlpool feature, you'll need to have the following platform-specific binaries and place them in the specified directories:
+Start metro metro
+```bash
+yarn start
+```
-#### Android
+#### Development
+To run the development app on a connected device or emulator:
-* Copy the libwhirlpool.so file from the release notes' asset section to the following directory:
+**Android**
+```bash
+yarn androidDevelopmentDebug
+```
+
+**iOS**
```bash
-android/app/src/main/jniLibs/{arch_dir}/libwhirlpool.so
+yarn ios --scheme=hexa_keeper_dev
```
-Replace {arch_dir} with the appropriate architecture directory (e.g., arm64-v8a, armeabi-v7a, x86).
-#### iOS
+#### Production
+To run the production app on a connected device or emulator:
-* Copy the libwhirlpool.a file from the release notes' asset section to the following directory:
+**Android**
```bash
-ios/libwhirlpool.a
+yarn androidProductionDebug
```
+
+**iOS**
+```bash
+yarn ios --scheme=hexa_keeper
+```
+These commands will build and launch the app on the respective platforms.
+## PGP
+```bash
+389F 4CAD A078 5AC0 E28A 0C18 1BEB DE26 1DC3 CF62
+```
## Testing
This project uses **Jest** as the testing framework. To run the tests, use the following command:
@@ -75,5 +98,9 @@ yarn test
```
## License
This project is licensed under the **MIT License.**
+
+## Community
+* Follow us on [Twitter](https://twitter.com/bitcoinKeeper_)
+* Join our [Telegram](https://t.me/bitcoinkeeper)
diff --git a/android/app/build.gradle b/android/app/build.gradle
index f33fea54f..d091f383d 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -137,6 +137,8 @@ android {
pickFirst 'lib/x86_64/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/arm64-v8a/libc++_shared.so'
+ pickFirst 'lib/x86_64/libjsc.so'
+ pickFirst 'lib/arm64-v8a/libjsc.so'
}
ndkVersion rootProject.ext.ndkVersion
@@ -147,8 +149,8 @@ android {
applicationId "io.hexawallet.keeper"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 208
- versionName "1.0.8"
+ versionCode 214
+ versionName "1.1.0"
missingDimensionStrategy 'react-native-camera', 'general'
missingDimensionStrategy 'store', 'play'
multiDexEnabled true
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 7d5dca637..f656c939e 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -346,6 +346,8 @@ PODS:
- glog
- react-native-biometrics (2.2.0):
- React-Core
+ - react-native-blob-util (0.18.3):
+ - React-Core
- react-native-camera (4.2.1):
- React-Core
- react-native-camera/RCT (= 4.2.1)
@@ -477,8 +479,6 @@ PODS:
- React-Core
- RNLocalize (2.2.2):
- React-Core
- - RNPermissions (3.6.1):
- - React-Core
- RNReanimated (2.9.1):
- DoubleConversion
- FBLazyVector
@@ -512,6 +512,8 @@ PODS:
- RNSentry (4.3.1):
- React-Core
- Sentry (= 7.24.1)
+ - RNShare (9.2.3):
+ - React-Core
- RNSVG (12.4.3):
- React-Core
- Sentry (7.24.1):
@@ -568,6 +570,7 @@ DEPENDENCIES:
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
- react-native-biometrics (from `../node_modules/react-native-biometrics`)
+ - react-native-blob-util (from `../node_modules/react-native-blob-util`)
- react-native-camera (from `../node_modules/react-native-camera`)
- react-native-config (from `../node_modules/react-native-config`)
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
@@ -603,10 +606,10 @@ DEPENDENCIES:
- RNIap (from `../node_modules/react-native-iap`)
- RNKeychain (from `../node_modules/react-native-keychain`)
- RNLocalize (from `../node_modules/react-native-localize`)
- - RNPermissions (from `../node_modules/react-native-permissions`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`)
- "RNSentry (from `../node_modules/@sentry/react-native`)"
+ - RNShare (from `../node_modules/react-native-share`)
- RNSVG (from `../node_modules/react-native-svg`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
@@ -683,6 +686,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/logger"
react-native-biometrics:
:path: "../node_modules/react-native-biometrics"
+ react-native-blob-util:
+ :path: "../node_modules/react-native-blob-util"
react-native-camera:
:path: "../node_modules/react-native-camera"
react-native-config:
@@ -753,14 +758,14 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-keychain"
RNLocalize:
:path: "../node_modules/react-native-localize"
- RNPermissions:
- :path: "../node_modules/react-native-permissions"
RNReanimated:
:path: "../node_modules/react-native-reanimated"
RNScreens:
:path: "../node_modules/react-native-screens"
RNSentry:
:path: "../node_modules/@sentry/react-native"
+ RNShare:
+ :path: "../node_modules/react-native-share"
RNSVG:
:path: "../node_modules/react-native-svg"
Yoga:
@@ -812,6 +817,7 @@ SPEC CHECKSUMS:
React-jsinspector: c5989c77cb89ae6a69561095a61cce56a44ae8e8
React-logger: a0833912d93b36b791b7a521672d8ee89107aff1
react-native-biometrics: 3b95f2eb074d537d3a8b05d853c17778f6685c4a
+ react-native-blob-util: 2d36383bb52c15c5451be81cb7ddf22bc34a12a6
react-native-camera: 3eae183c1d111103963f3dd913b65d01aef8110f
react-native-config: 7cd105e71d903104e8919261480858940a6b9c0e
react-native-document-picker: 958e2bc82e128be69055be261aeac8d872c8d34c
@@ -847,10 +853,10 @@ SPEC CHECKSUMS:
RNIap: c397f49db45af3b10dca64b2325f21bb8078ad21
RNKeychain: ff836453cba46938e0e9e4c22e43d43fa2c90333
RNLocalize: 95a43f85e41a966be7bc9cff2437128911c52da0
- RNPermissions: dcdb7b99796bbeda6975a6e79ad519c41b251b1c
RNReanimated: 5c8c17e26787fd8984cd5accdc70fef2ca70aafd
RNScreens: 4a1af06327774490d97342c00aee0c2bafb497b7
RNSentry: 1cd4360f7c668f4d7e8f860e41da884f00e7d814
+ RNShare: da6d90b6dc332f51f86498041d6e34211f96b630
RNSVG: f3b60aeeaa81960e2e0536c3a9eef50b667ef3a9
Sentry: 1ed2d3f2973658bf6ab7ed43857c8e321a1625dd
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
diff --git a/ios/hexa_keeper.xcodeproj/project.pbxproj b/ios/hexa_keeper.xcodeproj/project.pbxproj
index 7b41aa3e9..20e52c40f 100644
--- a/ios/hexa_keeper.xcodeproj/project.pbxproj
+++ b/ios/hexa_keeper.xcodeproj/project.pbxproj
@@ -814,7 +814,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = hexa_keeper/hexa_keeper.entitlements;
- CURRENT_PROJECT_VERSION = 208;
+ CURRENT_PROJECT_VERSION = 214;
DEVELOPMENT_TEAM = Y5TCB759QL;
ENABLE_BITCODE = NO;
HEADER_SEARCH_PATHS = (
@@ -921,7 +921,7 @@
"$(inherited)",
"\"$(SRCROOT)\"",
);
- MARKETING_VERSION = 1.0.8;
+ MARKETING_VERSION = 1.1.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -943,7 +943,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = hexa_keeper/hexa_keeper.entitlements;
- CURRENT_PROJECT_VERSION = 208;
+ CURRENT_PROJECT_VERSION = 214;
DEVELOPMENT_TEAM = Y5TCB759QL;
HEADER_SEARCH_PATHS = (
"$(inherited)",
@@ -1049,7 +1049,7 @@
"$(inherited)",
"\"$(SRCROOT)\"",
);
- MARKETING_VERSION = 1.0.8;
+ MARKETING_VERSION = 1.1.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1193,7 +1193,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "dev-AppIcon";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = hexa_keeper_dev.entitlements;
- CURRENT_PROJECT_VERSION = 208;
+ CURRENT_PROJECT_VERSION = 214;
DEVELOPMENT_TEAM = Y5TCB759QL;
ENABLE_BITCODE = NO;
HEADER_SEARCH_PATHS = (
@@ -1301,7 +1301,7 @@
"$(PROJECT_DIR)",
"\"$(SRCROOT)\"",
);
- MARKETING_VERSION = 1.0.8;
+ MARKETING_VERSION = 1.1.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1324,7 +1324,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "dev-AppIcon";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = hexa_keeper_dev.entitlements;
- CURRENT_PROJECT_VERSION = 208;
+ CURRENT_PROJECT_VERSION = 214;
DEVELOPMENT_TEAM = Y5TCB759QL;
HEADER_SEARCH_PATHS = (
"$(inherited)",
@@ -1431,7 +1431,7 @@
"$(PROJECT_DIR)",
"\"$(SRCROOT)\"",
);
- MARKETING_VERSION = 1.0.8;
+ MARKETING_VERSION = 1.1.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
diff --git a/package.json b/package.json
index a8f3d71a4..72f687e80 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "hexa_keeper",
- "version": "1.0.8",
+ "version": "1.1.0",
"private": true,
"scripts": {
"ios": "react-native run-ios",
@@ -75,6 +75,7 @@
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^2.3.2",
"react-native-get-random-values": "^1.7.2",
+ "react-native-html-to-pdf": "^0.12.0",
"react-native-iap": "12.10.5",
"react-native-image-picker": "^4.10.3",
"react-native-keychain": "^8.0.0",
@@ -83,7 +84,7 @@
"react-native-mmkv": "^2.3.3",
"react-native-modal": "^13.0.1",
"react-native-nfc-manager": "^3.13.3",
- "react-native-permissions": "^3.6.1",
+ "react-native-pdf": "^6.7.1",
"react-native-qr-decode-image-camera": "^1.1.1",
"react-native-qrcode-svg": "^6.1.2",
"react-native-randombytes": "^3.0.0",
diff --git a/src/assets/images/AddPhoneEmail.svg b/src/assets/images/AddPhoneEmail.svg
new file mode 100644
index 000000000..f6e985311
--- /dev/null
+++ b/src/assets/images/AddPhoneEmail.svg
@@ -0,0 +1,20 @@
+
diff --git a/src/assets/images/InheritanceSupportIllustration.svg b/src/assets/images/InheritanceSupportIllustration.svg
new file mode 100644
index 000000000..0958aa7cc
--- /dev/null
+++ b/src/assets/images/InheritanceSupportIllustration.svg
@@ -0,0 +1,51 @@
+
diff --git a/src/assets/images/InheritanceToolsIllustration.svg b/src/assets/images/InheritanceToolsIllustration.svg
new file mode 100644
index 000000000..3397965cc
--- /dev/null
+++ b/src/assets/images/InheritanceToolsIllustration.svg
@@ -0,0 +1,85 @@
+
diff --git a/src/assets/images/blue_wallet.svg b/src/assets/images/blue_wallet.svg
deleted file mode 100644
index b2676b016..000000000
--- a/src/assets/images/blue_wallet.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-
diff --git a/src/assets/images/inheritanceBrown.svg b/src/assets/images/inheritanceBrown.svg
new file mode 100644
index 000000000..99e0c3de3
--- /dev/null
+++ b/src/assets/images/inheritanceBrown.svg
@@ -0,0 +1,13 @@
+
diff --git a/src/assets/images/time.svg b/src/assets/images/time.svg
new file mode 100644
index 000000000..01ce593cb
--- /dev/null
+++ b/src/assets/images/time.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/common/content/language/en.json b/src/common/content/language/en.json
index 44fe3b6e6..478791def 100644
--- a/src/common/content/language/en.json
+++ b/src/common/content/language/en.json
@@ -461,7 +461,9 @@
"host": "Host",
"useSSL": "Use SSL for this node",
"nodeConnectionSuccess": "Node connected successfully",
- "nodeConnectionFailure": "Unable to connect to Electrum Server. Invalid node details or, not known"
+ "nodeConnectionFailure": "Unable to connect to Electrum Server. Invalid node details or, not known",
+ "ManageWallets": "Manage Wallets",
+ "ManageWalletsSub": "Hide wallets and unhide them."
},
"onboarding": {
"Comprehensive": "Comprehensive",
diff --git a/src/common/data/defaultData/defaultData.tsx b/src/common/data/defaultData/defaultData.tsx
index f91c04a75..9effaf3d1 100644
--- a/src/common/data/defaultData/defaultData.tsx
+++ b/src/common/data/defaultData/defaultData.tsx
@@ -1,6 +1,7 @@
import SecutityTip from 'src/assets/images/securityTip.svg';
import WhirlpoolLoader from 'src/components/WhirlpoolLoader';
import SigningDeviceSafe from 'src/assets/images/signingDeviceSafe.svg'
+import InheritanceToolsIllustration from 'src/assets/images/InheritanceToolsIllustration.svg'
import React from 'react';
import LoadingAnimation from 'src/components/Loader';
@@ -45,7 +46,7 @@ export const securityTips = [
title: 'Introducing Inheritance Tools',
subTitle:
'Use Inheritance documents for your inheritance planning. Inheritance Key is an assisted key that can be availed by your heir',
- assert: ,
+ assert: ,
message: 'Consult your estate planner for incorporating documents from this app in your will',
},
{
diff --git a/src/common/data/models/interfaces/Uai.ts b/src/common/data/models/interfaces/Uai.ts
index 0f8ac9feb..9e41461de 100644
--- a/src/common/data/models/interfaces/Uai.ts
+++ b/src/common/data/models/interfaces/Uai.ts
@@ -18,4 +18,5 @@ export enum uaiType {
SIGNING_DEVICES_HEALTH_CHECK = 'SIGNING_DEVICES_HEALTH_CHECK',
DEFAULT = 'DEFAULT',
RELEASE_MESSAGE = 'RELEASE_MESSAGE',
+ IKS_REQUEST = 'IKS_REQUEST',
}
diff --git a/src/components/AppActivityIndicator/ActivityIndicatorView.tsx b/src/components/AppActivityIndicator/ActivityIndicatorView.tsx
index 74049a7e8..bde4d313f 100644
--- a/src/components/AppActivityIndicator/ActivityIndicatorView.tsx
+++ b/src/components/AppActivityIndicator/ActivityIndicatorView.tsx
@@ -1,7 +1,13 @@
import React from 'react';
import { StyleSheet, ActivityIndicator, Modal, View } from 'react-native';
-function ActivityIndicatorView({ visible, showLoader = true }: { visible: boolean; showLoader }) {
+function ActivityIndicatorView({
+ visible,
+ showLoader = true,
+}: {
+ visible: boolean;
+ showLoader?: boolean;
+}) {
if (visible) {
return (
diff --git a/src/components/CameraUnauthorized.tsx b/src/components/CameraUnauthorized.tsx
index 62a2caddd..bb4d0cf25 100644
--- a/src/components/CameraUnauthorized.tsx
+++ b/src/components/CameraUnauthorized.tsx
@@ -1,41 +1,40 @@
import React from 'react';
-import { StyleSheet, TouchableOpacity, View } from 'react-native';
-import Permissions from 'react-native-permissions';
+import { Linking, StyleSheet, TouchableOpacity, View } from 'react-native';
import { Box } from 'native-base';
-import Text from './KeeperText';
import { wp, hp } from 'src/common/data/responsiveness/responsive';
+import Text from './KeeperText';
function CameraUnauthorized() {
-
const requestPermission = () => {
- Permissions.openSettings();
+ Linking.openSettings();
};
return (
-
+
+ fontSize: 13,
+ }}
+ >
Camera access is turned off
Turn on the camera in your device settings
-
+ marginTop: hp(15),
+ }}
+ >
container: {
borderRadius: 10,
alignItems: 'center',
- padding: '4%',
+ padding: '3%',
},
title: {
fontSize: 19,
@@ -222,7 +222,7 @@ const getStyles = (subTitleWidth) =>
close: {
position: 'absolute',
right: 20,
- top: 20,
+ top: 16,
},
seeFAQs: {
fontSize: 13,
@@ -241,7 +241,8 @@ const getStyles = (subTitleWidth) =>
alignSelf: 'flex-start',
borderBottomWidth: 0,
backgroundColor: 'transparent',
- width: '95%',
+ width: '90%',
+ marginTop: wp(5)
},
bodyContainer: {
width: '80%',
diff --git a/src/core/services/interfaces/index.ts b/src/core/services/interfaces/index.ts
index 708e5b068..3f25189e9 100644
--- a/src/core/services/interfaces/index.ts
+++ b/src/core/services/interfaces/index.ts
@@ -47,20 +47,18 @@ export interface InheritanceAlert {
export interface InheritanceConfiguration {
m: number;
n: number;
- identifiers: string[];
+ descriptors: string[];
bsms?: string;
}
export interface InheritancePolicy {
notification: InheritanceNotification;
- alert: InheritanceAlert;
+ alert?: InheritanceAlert;
}
-export interface InheritanceKey {
- vaultId: string;
- xIndex: number;
+export interface InheritanceKeyInfo {
configuration: InheritanceConfiguration;
- policy: InheritancePolicy;
+ policy?: InheritancePolicy;
}
export interface InheritanceKeyRequest {
diff --git a/src/core/services/operations/InheritanceKey.ts b/src/core/services/operations/InheritanceKey.ts
index e9f564b96..dfee4afea 100644
--- a/src/core/services/operations/InheritanceKey.ts
+++ b/src/core/services/operations/InheritanceKey.ts
@@ -1,6 +1,7 @@
import { AxiosResponse } from 'axios';
import config from '../../config';
import {
+ InheritanceAlert,
InheritanceConfiguration,
InheritanceNotification,
InheritancePolicy,
@@ -11,18 +12,14 @@ const { HEXA_ID, SIGNING_SERVER } = config;
export default class InheritanceKeyServer {
/**
- * @param {string} vaultId
+ * @param {string} vaultShellId
* @param {InheritancePolicy} policy
* @returns Promise
*/
- static setupIK = async (
- vaultId: string,
- configuration: InheritanceConfiguration,
- policy: InheritancePolicy
+ static initializeIKSetup = async (
+ vaultShellId: string
): Promise<{
setupData: {
- configuration: InheritanceConfiguration;
- policy: InheritancePolicy;
inheritanceXpub: string;
masterFingerprint: string;
derivationPath: string;
@@ -30,9 +27,39 @@ export default class InheritanceKeyServer {
}> => {
let res: AxiosResponse;
try {
- res = await RestClient.post(`${SIGNING_SERVER}v2/setupInheritanceKey`, {
+ res = await RestClient.post(`${SIGNING_SERVER}v2/initializeIKSetup`, {
+ HEXA_ID,
+ vaultId: vaultShellId,
+ });
+ } catch (err) {
+ if (err.response) throw new Error(err.response.data.err);
+ if (err.code) throw new Error(err.code);
+ }
+
+ const { setupData } = res.data;
+ return {
+ setupData,
+ };
+ };
+
+ /**
+ * @param {string} vaultShellId
+ * @param {InheritanceConfiguration} configuration
+ * @param {InheritancePolicy} policy
+ * @returns Promise
+ */
+ static finalizeIKSetup = async (
+ vaultShellId: string,
+ configuration: InheritanceConfiguration,
+ policy: InheritancePolicy
+ ): Promise<{
+ setupSuccessful: boolean;
+ }> => {
+ let res: AxiosResponse;
+ try {
+ res = await RestClient.post(`${SIGNING_SERVER}v2/finalizeIKSetup`, {
HEXA_ID,
- vaultId,
+ vaultId: vaultShellId,
configuration,
policy,
});
@@ -41,32 +68,31 @@ export default class InheritanceKeyServer {
if (err.code) throw new Error(err.code);
}
- const { setupSuccessful, setupData } = res.data;
- if (!setupSuccessful) throw new Error('Inheritance Key setup failed');
+ const { setupSuccessful } = res.data;
return {
- setupData,
+ setupSuccessful,
};
};
/**
- * @param {string} vaultId
+ * @param {string} vaultShellId
* @param {string[]} existingThresholdIDescriptors
* @param {InheritanceConfiguration} newConfiguration
* @returns {Promise} updated
*/
static updateInheritanceConfig = async (
- vaultId: string,
- existingThresholdIDescriptors: string[],
+ vaultShellId: string,
+ existingThresholdDescriptors: string[],
newConfiguration: InheritanceConfiguration
): Promise<{
updated: boolean;
}> => {
let res: AxiosResponse;
try {
- res = await RestClient.post(`${SIGNING_SERVER}v2/updateInheritancePolicy`, {
+ res = await RestClient.post(`${SIGNING_SERVER}v2/updateInheritanceConfig`, {
HEXA_ID,
- vaultId,
- existingThresholdIDescriptors,
+ vaultId: vaultShellId,
+ existingThresholdDescriptors,
newConfiguration,
});
} catch (err) {
@@ -82,15 +108,16 @@ export default class InheritanceKeyServer {
};
/**
- * @param {string} vaultId
+ * @param {string} vaultShellId
* @param {any} updates
* @param {string[]} thresholdDescriptors
* @returns {Promise} updated
*/
static updateInheritancePolicy = async (
- vaultId: string,
+ vaultShellId: string,
updates: {
notification?: InheritanceNotification;
+ alert?: InheritanceAlert;
},
thresholdDescriptors: string[]
): Promise<{
@@ -100,7 +127,7 @@ export default class InheritanceKeyServer {
try {
res = await RestClient.post(`${SIGNING_SERVER}v2/updateInheritancePolicy`, {
HEXA_ID,
- vaultId,
+ vaultId: vaultShellId,
updates,
thresholdDescriptors,
});
@@ -117,7 +144,7 @@ export default class InheritanceKeyServer {
};
static signPSBT = async (
- vaultId: string,
+ vaultShellId: string,
serializedPSBT: string,
childIndexArray: Array<{
subPath: number[];
@@ -136,7 +163,7 @@ export default class InheritanceKeyServer {
try {
res = await RestClient.post(`${SIGNING_SERVER}v2/signTransactionViaInheritanceKey`, {
HEXA_ID,
- vaultId,
+ vaultId: vaultShellId,
serializedPSBT,
childIndexArray,
thresholdDescriptors,
@@ -154,11 +181,14 @@ export default class InheritanceKeyServer {
static requestInheritanceKey = async (
requestId: string,
- vaultId: string,
+ vaultShellId: string,
thresholdDescriptors: string[]
): Promise<{
- isRequestApproved: boolean;
- isRequestDeclined: boolean;
+ requestStatus: {
+ approvesIn: number;
+ isApproved: boolean;
+ isDeclined: boolean;
+ };
setupInfo?: {
inheritanceXpub: string;
masterFingerprint: string;
@@ -172,7 +202,7 @@ export default class InheritanceKeyServer {
res = await RestClient.post(`${SIGNING_SERVER}v2/requestInheritanceKey`, {
HEXA_ID,
requestId,
- vaultId,
+ vaultId: vaultShellId,
thresholdDescriptors,
});
} catch (err) {
@@ -180,11 +210,9 @@ export default class InheritanceKeyServer {
if (err.code) throw new Error(err.code);
}
- const { isRequestApproved, isRequestDeclined, setupInfo } = res.data;
-
+ const { requestStatus, setupInfo } = res.data;
return {
- isRequestApproved,
- isRequestDeclined,
+ requestStatus,
setupInfo,
};
};
diff --git a/src/core/services/operations/SigningServer.ts b/src/core/services/operations/SigningServer.ts
index 0e23c6957..e51bcda27 100644
--- a/src/core/services/operations/SigningServer.ts
+++ b/src/core/services/operations/SigningServer.ts
@@ -12,13 +12,13 @@ const { HEXA_ID, SIGNING_SERVER } = config;
export default class SigningServer {
/**
- * @param {string} vaultId: for versions > 1.0.1, signer is registered against vaultId
+ * @param {string} vaultShellId: for versions > 1.0.1, signer is registered against vaultShellId
* @param {string} appId: for versions <= 1.0.1, signer is registered against appId
* @param {SignerPolicy} policy
* @returns Promise
*/
static register = async (
- vaultId: string,
+ vaultShellId: string,
appId: string,
policy: SignerPolicy
): Promise<{
@@ -33,7 +33,7 @@ export default class SigningServer {
try {
res = await RestClient.post(`${SIGNING_SERVER}v2/setupSigner`, {
HEXA_ID,
- vaultId,
+ vaultId: vaultShellId,
appId,
policy,
});
@@ -50,7 +50,7 @@ export default class SigningServer {
};
static validate = async (
- vaultId: string,
+ vaultShellId: string,
appId: string,
verificationToken
): Promise<{
@@ -60,7 +60,7 @@ export default class SigningServer {
try {
res = await RestClient.post(`${SIGNING_SERVER}v2/validateSingerSetup`, {
HEXA_ID,
- vaultId,
+ vaultId: vaultShellId,
appId,
verificationToken,
});
@@ -78,7 +78,7 @@ export default class SigningServer {
};
static fetchSignerSetup = async (
- vaultId: string,
+ vaultShellId: string,
appId: string,
verificationToken
): Promise<{
@@ -92,7 +92,7 @@ export default class SigningServer {
try {
res = await RestClient.post(`${SIGNING_SERVER}v2/fetchSignerSetup`, {
HEXA_ID,
- vaultId,
+ vaultId: vaultShellId,
appId,
verificationToken,
});
@@ -116,7 +116,7 @@ export default class SigningServer {
};
static updatePolicy = async (
- vaultId: string,
+ vaultShellId: string,
appId: string,
updates: {
restrictions?: SignerRestriction;
@@ -129,7 +129,7 @@ export default class SigningServer {
try {
res = await RestClient.post(`${SIGNING_SERVER}v2/updateSignerPolicy`, {
HEXA_ID,
- vaultId,
+ vaultId: vaultShellId,
appId,
updates,
});
@@ -146,7 +146,7 @@ export default class SigningServer {
};
static signPSBT = async (
- vaultId: string,
+ vaultShellId: string,
appId: string,
verificationToken: number,
serializedPSBT: string,
@@ -167,7 +167,7 @@ export default class SigningServer {
try {
res = await RestClient.post(`${SIGNING_SERVER}v2/signTransaction`, {
HEXA_ID,
- vaultId,
+ vaultId: vaultShellId,
appId,
verificationToken,
serializedPSBT,
@@ -186,7 +186,7 @@ export default class SigningServer {
};
static checkSignerHealth = async (
- vaultId: string,
+ vaultShellId: string,
appId: string
): Promise<{
isSignerAvailable: boolean;
@@ -195,7 +195,7 @@ export default class SigningServer {
try {
res = await RestClient.post(`${SIGNING_SERVER}v2/checkSignerHealth`, {
HEXA_ID,
- vaultId,
+ vaultId: vaultShellId,
appId,
});
} catch (err) {
diff --git a/src/core/wallets/enums/index.ts b/src/core/wallets/enums/index.ts
index 759404f38..1f5287efc 100644
--- a/src/core/wallets/enums/index.ts
+++ b/src/core/wallets/enums/index.ts
@@ -59,9 +59,9 @@ export enum NodeType {
export enum VisibilityType {
DEFAULT = 'DEFAULT',
- DURESS = 'DURESS',
+ // DURESS = 'DURESS',
HIDDEN = 'HIDDEN',
- ARCHIVED = 'ARCHIVED',
+ // ARCHIVED = 'ARCHIVED',
}
export enum EntityKind {
diff --git a/src/core/wallets/interfaces/vault.ts b/src/core/wallets/interfaces/vault.ts
index 9298d2430..0a651a29b 100644
--- a/src/core/wallets/interfaces/vault.ts
+++ b/src/core/wallets/interfaces/vault.ts
@@ -1,5 +1,5 @@
/* eslint-disable no-unused-vars */
-import { SignerPolicy } from 'src/core/services/interfaces';
+import { InheritanceKeyInfo, SignerPolicy } from 'src/core/services/interfaces';
import { BIP85Config, Balances, Transaction, UTXO } from '.';
import {
EntityKind,
@@ -51,10 +51,11 @@ export interface VaultSigner {
lastHealthCheck: Date;
addedOn: Date;
registered: boolean;
- signerPolicy?: SignerPolicy;
masterFingerprint: string;
derivationPath: string;
xpubDetails: XpubDetailsType;
+ signerPolicy?: SignerPolicy;
+ inheritanceKeyInfo?: InheritanceKeyInfo;
}
export interface Vault {
diff --git a/src/core/wallets/operations/index.ts b/src/core/wallets/operations/index.ts
index 7f4a48fda..d32a5a220 100644
--- a/src/core/wallets/operations/index.ts
+++ b/src/core/wallets/operations/index.ts
@@ -52,6 +52,14 @@ import WalletUtilities from './utils';
const ECPair = ECPairFactory(ecc);
+const feeSurcharge = (wallet: Wallet | Vault) =>
+ /* !! TESTNET ONLY !!
+ as the redeem script for vault is heavy(esp. 3-of-5/3-of-6),
+ the nodes reject the tx if the overall fee for the tx is low(which is the case w/ electrum)
+ therefore we up the feeRatesPerByte by 1 to handle this case until we find a better sol
+ */
+ config.NETWORK_TYPE === NetworkType.TESTNET && wallet.entityKind === EntityKind.VAULT ? 1 : 0;
+
export default class WalletOperations {
public static getNextFreeExternalAddress = ({
entity,
@@ -504,7 +512,7 @@ export default class WalletOperations {
}).address,
});
}
- const { fee } = coinselectSplit(inputUTXOs, outputUTXOs, feePerByte);
+ const { fee } = coinselectSplit(inputUTXOs, outputUTXOs, feePerByte + feeSurcharge(wallet));
return {
fee,
@@ -549,7 +557,7 @@ export default class WalletOperations {
const defaultFeePerByte = averageTxFees[defaultTxPriority].feePerByte;
const defaultEstimatedBlocks = averageTxFees[defaultTxPriority].estimatedBlocks;
- const assets = coinselect(inputUTXOs, outputUTXOs, defaultFeePerByte);
+ const assets = coinselect(inputUTXOs, outputUTXOs, defaultFeePerByte + feeSurcharge(wallet));
const defaultPriorityInputs = assets.inputs;
const defaultPriorityOutputs = assets.outputs;
const defaultPriorityFee = assets.fee;
@@ -582,7 +590,7 @@ export default class WalletOperations {
const { inputs, outputs, fee } = coinselect(
inputUTXOs,
outputUTXOs,
- averageTxFees[priority].feePerByte
+ averageTxFees[priority].feePerByte + feeSurcharge(wallet)
);
const debitedAmount = netAmount + fee;
if (!inputs || debitedAmount > confirmedBalance) {
@@ -616,7 +624,11 @@ export default class WalletOperations {
customTxFeePerByte: number
): TransactionPrerequisiteElements => {
const inputUTXOs = wallet.specs.confirmedUTXOs;
- const { inputs, outputs, fee } = coinselect(inputUTXOs, outputUTXOs, customTxFeePerByte);
+ const { inputs, outputs, fee } = coinselect(
+ inputUTXOs,
+ outputUTXOs,
+ customTxFeePerByte + feeSurcharge(wallet)
+ );
if (!inputs) return { fee };
@@ -1021,7 +1033,10 @@ export default class WalletOperations {
});
} else if (signer.type === SignerType.MOBILE_KEY || signer.type === SignerType.SEED_WORDS) {
signingPayload.push({ payloadTarget, inputs });
- } else if (signer.type === SignerType.POLICY_SERVER) {
+ } else if (
+ signer.type === SignerType.POLICY_SERVER ||
+ signer.type === SignerType.INHERITANCEKEY
+ ) {
const childIndexArray = [];
for (const input of inputs) {
let subPath;
diff --git a/src/hardware/index.ts b/src/hardware/index.ts
index eb853a268..41a470ed1 100644
--- a/src/hardware/index.ts
+++ b/src/hardware/index.ts
@@ -35,6 +35,7 @@ export const generateSignerFromMetaData = ({
isMock = false,
xpubDetails = {} as XpubDetailsType,
signerPolicy = null,
+ inheritanceKeyInfo = null,
}): VaultSigner => {
const networkType = WalletUtilities.getNetworkFromPrefix(xpub.slice(0, 4));
const network = WalletUtilities.getNetworkByType(config.NETWORK_TYPE);
@@ -66,6 +67,7 @@ export const generateSignerFromMetaData = ({
registered: UNVERIFYING_SIGNERS.includes(signerType) || isMock,
xpubDetails,
signerPolicy,
+ inheritanceKeyInfo,
};
return signer;
};
diff --git a/src/hooks/useSignerIntel.tsx b/src/hooks/useSignerIntel.tsx
index 54c06dbb5..e7f94a3e2 100644
--- a/src/hooks/useSignerIntel.tsx
+++ b/src/hooks/useSignerIntel.tsx
@@ -10,6 +10,7 @@ import { getSignerSigTypeInfo, isSignerAMF } from 'src/hardware';
import idx from 'idx';
import WalletUtilities from 'src/core/wallets/operations/utils';
import config from 'src/core/config';
+import useToastMessage from './useToastMessage';
const hasPlanChanged = (vault: Vault, subscriptionScheme): VaultMigrationType => {
if (vault) {
@@ -83,18 +84,29 @@ export const updateSignerForScheme = (signer: VaultSigner, schemeN) => {
return signer;
};
-const useSignerIntel = () => {
+const useSignerIntel = ({ isInheritance }) => {
const { activeVault } = useVault();
const { subscriptionScheme, plan } = usePlan();
- const currentSignerLimit = subscriptionScheme.n;
+ const currentSignerLimit = subscriptionScheme.n + (isInheritance ? 1 : 0);
const planStatus = hasPlanChanged(activeVault, subscriptionScheme);
const vaultSigners = useAppSelector((state) => state.vault.signers);
const [signersState, setSignersState] = useState(vaultSigners);
+ const { showToast } = useToastMessage();
useEffect(() => {
- const fills = getPrefillForSignerList(planStatus, vaultSigners, currentSignerLimit);
+ const doesTrezorExist = vaultSigners.some((signer) => signer.type === SignerType.TREZOR);
+ let sanitisedSigners = vaultSigners;
+ if (doesTrezorExist && plan !== SubscriptionTier.L1.toUpperCase()) {
+ sanitisedSigners = vaultSigners.filter((item) => item.type !== SignerType.TREZOR);
+ showToast(
+ 'Trezor has been disabled for multisig temporarily. But you can still use the single-sig vault or migrate to a multisig vault without Trezor.',
+ null,
+ 7000
+ );
+ }
+ const fills = getPrefillForSignerList(planStatus, sanitisedSigners, currentSignerLimit);
setSignersState(
- vaultSigners
+ sanitisedSigners
.map((signer) => updateSignerForScheme(signer, subscriptionScheme.n))
.concat(fills)
);
diff --git a/src/hooks/useWallets.tsx b/src/hooks/useWallets.tsx
index 567ceef13..243498721 100644
--- a/src/hooks/useWallets.tsx
+++ b/src/hooks/useWallets.tsx
@@ -3,7 +3,7 @@ import { RealmWrapperContext } from 'src/storage/realm/RealmProvider';
import { Wallet } from 'src/core/wallets/interfaces/wallet';
import { RealmSchema } from 'src/storage/realm/enum';
import { getJSONFromRealmObject } from 'src/storage/realm/utils';
-import { WalletType } from 'src/core/wallets/enums';
+import { VisibilityType, WalletType } from 'src/core/wallets/enums';
type useWalletsInterface = ({ getAll, walletIds }?: { getAll?: boolean; walletIds?: string[] }) => {
wallets: Wallet[];
@@ -13,7 +13,7 @@ const useWallets: useWalletsInterface = ({ walletIds, getAll = false } = {}) =>
const { useQuery, useObject } = useContext(RealmWrapperContext);
const wallets: Wallet[] = useQuery(RealmSchema.Wallet);
const walletsWithoutWhirlpool: Wallet[] = useQuery(RealmSchema.Wallet).filtered(
- `type != "${WalletType.PRE_MIX}" && type != "${WalletType.POST_MIX}" && type != "${WalletType.BAD_BANK}"`
+ `type != "${WalletType.PRE_MIX}" && type != "${WalletType.POST_MIX}" && type != "${WalletType.BAD_BANK}" && presentationData.visibility == "${VisibilityType.DEFAULT}"`
);
if (getAll) {
return { wallets: wallets.map(getJSONFromRealmObject) };
diff --git a/src/navigation/Navigator.tsx b/src/navigation/Navigator.tsx
index 04400c13f..e5beca1fa 100644
--- a/src/navigation/Navigator.tsx
+++ b/src/navigation/Navigator.tsx
@@ -33,6 +33,7 @@ import SendConfirmation from 'src/screens/Send/SendConfirmation';
import SendScreen from 'src/screens/Send/SendScreen';
import SetupColdCard from 'src/screens/AddColdCard/SetupColdCard';
import SetupInheritance from 'src/screens/Inheritance/SetupInheritance';
+import PreviewPDF from 'src/screens/Inheritance/components/PreviewPDF';
import InheritanceStatus from 'src/screens/Inheritance/InheritanceStatus';
import InheritanceSetupInfo from 'src/screens/Inheritance/InheritanceSetupInfo';
import IKSAddEmailPhone from 'src/screens/Inheritance/IKSAddEmailPhone';
@@ -54,6 +55,7 @@ import SplashScreen from 'src/screens/Splash/SplashScreen';
import TapSignerRecovery from 'src/screens/VaultRecovery/TapsignerRecovery';
import TimelockScreen from 'src/screens/Vault/TimelockScreen';
import TorSettings from 'src/screens/AppSettings/TorSettings';
+import ManageWallets from 'src/screens/AppSettings/ManageWallets';
import TransactionDetails from 'src/screens/ViewTransactions/TransactionDetails';
import VaultDetails from 'src/screens/Vault/VaultDetails';
import VaultRecovery from 'src/screens/VaultRecovery/VaultRecovery';
@@ -156,6 +158,7 @@ function AppStack() {
+
@@ -163,7 +166,9 @@ function AppStack() {
+
+
diff --git a/src/screens/AppSettings/AppSettings.tsx b/src/screens/AppSettings/AppSettings.tsx
index 227f66d2a..33f0142fb 100644
--- a/src/screens/AppSettings/AppSettings.tsx
+++ b/src/screens/AppSettings/AppSettings.tsx
@@ -75,8 +75,8 @@ function AppSettings({ navigation }) {
biometryType === 'TouchID'
? 'Touch ID'
: biometryType === 'FaceID'
- ? 'Face ID'
- : biometryType;
+ ? 'Face ID'
+ : biometryType;
setSensorType(type);
}
} catch (error) {
@@ -230,6 +230,14 @@ function AppSettings({ navigation }) {
icon={false}
onPress={() => navigation.navigate('ChangeLanguage')}
/>
+ navigation.navigate('ManageWallets')}
+ />
diff --git a/src/screens/AppSettings/ManageWallets.tsx b/src/screens/AppSettings/ManageWallets.tsx
new file mode 100644
index 000000000..4ce04e7cb
--- /dev/null
+++ b/src/screens/AppSettings/ManageWallets.tsx
@@ -0,0 +1,249 @@
+import React, { useContext, useEffect, useState } from 'react';
+import Text from 'src/components/KeeperText';
+import { StyleSheet, FlatList, ScrollView, TouchableOpacity } from 'react-native';
+import { Box } from 'native-base';
+import HeaderTitle from 'src/components/HeaderTitle';
+import ScreenWrapper from 'src/components/ScreenWrapper';
+import { hp, wp } from 'src/common/data/responsiveness/responsive';
+import { RealmWrapperContext } from 'src/storage/realm/RealmProvider';
+import { LocalizationContext } from 'src/common/content/LocContext';
+import { RealmSchema } from 'src/storage/realm/enum';
+import { VisibilityType } from 'src/core/wallets/enums';
+import { Wallet } from 'src/core/wallets/interfaces/wallet';
+import WalletInsideGreen from 'src/assets/images/Wallet_inside_green.svg';
+import BtcBlack from 'src/assets/images/btc_black.svg';
+import { SatsToBtc } from 'src/common/constants/Bitcoin';
+import dbManager from 'src/storage/realm/dbManager';
+import { useNavigation, useRoute } from '@react-navigation/native';
+import { Shadow } from 'react-native-shadow-2';
+import KeeperModal from 'src/components/KeeperModal';
+
+const styles = StyleSheet.create({
+ learnMoreContainer: {
+ borderWidth: 0.5,
+ borderRadius: 5,
+ paddingHorizontal: 5,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ learnMoreText: {
+ fontSize: 12,
+ letterSpacing: 0.6,
+ alignSelf: 'center',
+ },
+ cancelBtn: {
+ marginRight: wp(20),
+ borderRadius: 10,
+ },
+ btnText: {
+ fontSize: 12,
+ letterSpacing: 0.84,
+ },
+ createBtn: {
+ paddingVertical: hp(15),
+ borderRadius: 10,
+ paddingHorizontal: 20,
+ },
+})
+
+function ListItem({ title, subtitle, balance, btnTitle, onBtnPress }) {
+ return (
+
+
+
+ {title}
+ {subtitle}
+
+
+ {SatsToBtc(balance)}
+
+
+
+
+
+
+ {btnTitle}
+
+
+
+
+ )
+}
+
+function ManageWallets() {
+ const { translations } = useContext(LocalizationContext);
+ const { settings } = translations;
+ const { useQuery } = useContext(RealmWrapperContext);
+ const visibleWallets: Wallet[] = useQuery(RealmSchema.Wallet).filtered(
+ `presentationData.visibility == "${VisibilityType.DEFAULT}"`
+ );
+ const hiddenWallets: Wallet[] = useQuery(RealmSchema.Wallet).filtered(
+ `presentationData.visibility == "${VisibilityType.HIDDEN}"`
+ );
+ const [showBalanceAlert, setShowBalanceAlert] = useState(false)
+ const navigation = useNavigation()
+ const route = useRoute()
+
+ const [selectedWallet, setSelectedWallet] = useState(null)
+
+ useEffect(() => {
+ if (route.params?.isAuthenticated) {
+ unhideWallet(selectedWallet)
+ navigation.setParams({ isAuthenticated: false });
+ }
+ }, [route.params?.isAuthenticated]);
+
+ const hideWallet = (wallet: Wallet, checkBalance = true) => {
+ if (wallet.specs.balances.confirmed > 0 && checkBalance) {
+ setShowBalanceAlert(true)
+ setSelectedWallet(wallet)
+ return
+ }
+ try {
+ dbManager.updateObjectById(RealmSchema.Wallet, wallet.id, {
+ presentationData: {
+ name: wallet.presentationData.name,
+ description: wallet.presentationData.description,
+ visibility: VisibilityType.HIDDEN,
+ shell: wallet.presentationData.shell,
+ }
+ })
+ } catch (error) {
+ console.log(error)
+ }
+ }
+
+ const unhideWallet = (wallet: Wallet) => {
+ try {
+ dbManager.updateObjectById(RealmSchema.Wallet, wallet.id, {
+ presentationData: {
+ name: wallet.presentationData.name,
+ description: wallet.presentationData.description,
+ visibility: VisibilityType.DEFAULT,
+ shell: wallet.presentationData.shell,
+ }
+ })
+ } catch (error) {
+ console.log(error)
+ }
+ }
+
+ // eslint-disable-next-line react/no-unstable-nested-components
+ function BalanceAlertModalContent() {
+ return (
+
+
+ {
+ hideWallet(selectedWallet, false)
+ setShowBalanceAlert(false)
+ }}
+ activeOpacity={0.5}
+ >
+
+ Continue to Hide
+
+
+
+ {
+ setShowBalanceAlert(false)
+ const walletIndex = visibleWallets.findIndex(wallet => wallet.id === selectedWallet.id)
+ navigation.navigate('WalletDetails', { walletId: selectedWallet.id, walletIndex })
+ }}
+ >
+
+
+
+ Move Funds
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ hideWallet(item)}
+ />
+ }
+ showsVerticalScrollIndicator={false}
+ keyExtractor={item => item.id}
+ />
+
+
+ {
+ setSelectedWallet(item)
+ navigation.navigate('Login', { relogin: true, screen: 'ManageWallets', title: 'Enter Passcode to Unhide Wallet' });
+ }}
+ />
+ }
+ showsVerticalScrollIndicator={false}
+ keyExtractor={item => item.id}
+ />
+
+
+ { setShowBalanceAlert(false) }}
+ visible={showBalanceAlert}
+ title="You have funds in your wallet"
+ subTitle="It seems you have a balance in your wallet. Are you sure do you want to hide it?"
+ Content={BalanceAlertModalContent}
+ subTitleColor="light.secondaryText"
+ subTitleWidth={wp(210)}
+ closeOnOverlayClick={() => { }}
+ showButtons
+ showCloseIcon={false}
+ />
+
+ )
+}
+
+export default ManageWallets
diff --git a/src/screens/ChoosePlanScreen/ChoosePlan.tsx b/src/screens/ChoosePlanScreen/ChoosePlan.tsx
index 8c1ab16ff..3cb841991 100644
--- a/src/screens/ChoosePlanScreen/ChoosePlan.tsx
+++ b/src/screens/ChoosePlanScreen/ChoosePlan.tsx
@@ -1,7 +1,7 @@
/* eslint-disable prefer-destructuring */
-import { ActivityIndicator, Platform, ScrollView, Alert, Linking, TouchableOpacity, Image } from 'react-native';
+import { ActivityIndicator, Platform, ScrollView, Alert, Linking, TouchableOpacity, Image, StyleSheet } from 'react-native';
import Text from 'src/components/KeeperText';
-import { Box } from 'native-base';
+import { Box, Pressable } from 'native-base';
import RNIap, {
getSubscriptions,
purchaseErrorListener,
@@ -374,33 +374,45 @@ function ChoosePlan(props) {
)}
-
-
-
+
+
+
+
Restore Purchases
-
+
);
}
+const styles = StyleSheet.create({
+ noteWrapper: {
+ bottom: 1,
+ margin: 1,
+ alignItems: "center",
+ flexDirection: "row",
+ justifyContent: "center",
+ width: '100%'
+ },
+ restorePurchaseWrapper: {
+ padding: 1,
+ margin: 1,
+ borderRadius: 5,
+ borderWidth: 0.7,
+ alignItems: 'center',
+ justifyContent: 'center'
+ }
+})
export default ChoosePlan;
diff --git a/src/screens/HomeScreen/UaiDisplay.tsx b/src/screens/HomeScreen/UaiDisplay.tsx
index 59daeb8de..46ce2fd98 100644
--- a/src/screens/HomeScreen/UaiDisplay.tsx
+++ b/src/screens/HomeScreen/UaiDisplay.tsx
@@ -6,24 +6,27 @@ import { useDispatch } from 'react-redux';
import { UAI, uaiType } from 'src/common/data/models/interfaces/Uai';
import { uaiActioned } from 'src/store/sagaActions/uai';
import KeeperModal from 'src/components/KeeperModal';
-import { StyleSheet } from 'react-native';
+import { Alert, StyleSheet } from 'react-native';
import { TransferType } from 'src/common/data/enums/TransferType';
import ToastErrorIcon from 'src/assets/images/toast_error.svg';
import useVault from 'src/hooks/useVault';
import useToastMessage from 'src/hooks/useToastMessage';
+import InheritanceKeyServer from 'src/core/services/operations/InheritanceKey';
+import ActivityIndicatorView from 'src/components/AppActivityIndicator/ActivityIndicatorView';
import UAIView from '../NewHomeScreen/components/HeaderDetails/components/UAIView';
function UaiDisplay({ uaiStack }) {
const [uai, setUai] = useState({});
const [uaiConfig, setUaiConfig] = useState({});
const [showModal, setShowModal] = useState(false);
+ const [modalActionLoader, setmodalActionLoader] = useState(false);
const { activeVault } = useVault();
const { showToast } = useToastMessage();
const dispatch = useDispatch();
const navigtaion = useNavigation();
- const getUaiTypeDefinations = (type) => {
+ const getUaiTypeDefinations = (type: string, entityId?: string) => {
switch (type) {
case uaiType.RELEASE_MESSAGE:
return {
@@ -45,11 +48,14 @@ function UaiDisplay({ uaiStack }) {
btnText: ' Transfer Now',
},
cta: () => {
- navigtaion.navigate('SendConfirmation', {
- uaiSetActionFalse,
- walletId: uai?.entityId,
- transferType: TransferType.WALLET_TO_VAULT,
- });
+ activeVault
+ ? navigtaion.navigate('SendConfirmation', {
+ uaiSetActionFalse,
+ walletId: uai?.entityId,
+ transferType: TransferType.WALLET_TO_VAULT,
+ })
+ : showToast('No vaults found', );
+
setShowModal(false);
},
};
@@ -71,23 +77,57 @@ function UaiDisplay({ uaiStack }) {
navigtaion.navigate('AddSigningDevice');
},
};
+ case uaiType.IKS_REQUEST:
+ return {
+ modalDetails: {
+ heading: 'Inheritance Key request',
+ subTitle: `Request:${entityId}`,
+ displayText:
+ 'There is a request by someone for accessing the Inheritance Key you have set up using this app',
+ btnText: 'Decline',
+ },
+ cta: async (entityId) => {
+ try {
+ setmodalActionLoader(true);
+ if (entityId) {
+ const res = await InheritanceKeyServer.declineInheritanceKeyRequest(entityId);
+ if (res?.declined) {
+ showToast('IKS declined');
+ uaiSetActionFalse();
+ setShowModal(false);
+ } else {
+ Alert.alert('Something went Wrong!');
+ }
+ }
+ } catch (err) {
+ Alert.alert('Something went Wrong!');
+ console.log('Error in declining request');
+ }
+ setShowModal(false);
+ setmodalActionLoader(false);
+ },
+ };
case uaiType.DEFAULT:
return {
cta: () => {
- activeVault ? navigtaion.navigate('VaultDetails') : showToast('No vaults found', );
+ activeVault
+ ? navigtaion.navigate('VaultDetails')
+ : showToast('No vaults found', );
},
};
default:
return {
cta: () => {
- activeVault ? navigtaion.navigate('VaultDetails') : showToast('No vaults found', );
+ activeVault
+ ? navigtaion.navigate('VaultDetails')
+ : showToast('No vaults found', );
},
};
}
};
useEffect(() => {
- setUaiConfig(getUaiTypeDefinations(uai?.uaiType));
+ setUaiConfig(getUaiTypeDefinations(uai?.uaiType, uai?.entityId));
}, [uai]);
useEffect(() => {
@@ -113,7 +153,7 @@ function UaiDisplay({ uaiStack }) {
title={uai?.title}
primaryCallbackText="CONTINUE"
secondaryCallbackText={uai?.uaiType !== uaiType.DEFAULT ? 'SKIP' : null}
- secondaryCallback={uaiSetActionFalse}
+ secondaryCallback={uai?.uaiType !== uaiType.DEFAULT ? uaiSetActionFalse : null}
primaryCallback={pressHandler}
/>
uaiConfig?.cta(uai?.entityId)}
Content={() => {uai?.displayText}}
/>
+
>
);
}
diff --git a/src/screens/Inheritance/IKSAddEmailPhone.tsx b/src/screens/Inheritance/IKSAddEmailPhone.tsx
index ea447b818..15a8a0995 100644
--- a/src/screens/Inheritance/IKSAddEmailPhone.tsx
+++ b/src/screens/Inheritance/IKSAddEmailPhone.tsx
@@ -1,53 +1,119 @@
-import React, { useState } from 'react'
-import ScreenWrapper from 'src/components/ScreenWrapper'
-import HeaderTitle from 'src/components/HeaderTitle'
+import React, { useState } from 'react';
+import ScreenWrapper from 'src/components/ScreenWrapper';
+import HeaderTitle from 'src/components/HeaderTitle';
import { useNavigation } from '@react-navigation/native';
import { Box, Input } from 'native-base';
import Buttons from 'src/components/Buttons';
import { StyleSheet } from 'react-native';
import { hp } from 'src/common/data/responsiveness/responsive';
+import { Vault, VaultSigner } from 'src/core/wallets/interfaces/vault';
+import useVault from 'src/hooks/useVault';
+import { SignerType } from 'src/core/wallets/enums';
+import { InheritancePolicy } from 'src/core/services/interfaces';
+import idx from 'idx';
+import InheritanceKeyServer from 'src/core/services/operations/InheritanceKey';
+import useToastMessage from 'src/hooks/useToastMessage';
+import { captureError } from 'src/core/services/sentry';
+import { RealmSchema } from 'src/storage/realm/enum';
+import dbManager from 'src/storage/realm/dbManager';
+import TickIcon from 'src/assets/images/icon_tick.svg';
function IKSAddEmailPhone() {
- const navigtaion = useNavigation();
- const [emailOrPhone, setEmailOrPhone] = useState('');
- return (
-
- navigtaion.goBack()}
- title='Add phone or email'
- subtitle='If notification is not declined continuously for 30 days, the Key would be activated'
- paddingLeft={22}
- />
-
- {
- setEmailOrPhone(text);
- }}
- />
-
- navigtaion.navigate('EnterOTPEmailConfirmation')}
- />
-
- )
+ const navigtaion = useNavigation();
+ const [email, setEmail] = useState('');
+ const vault: Vault = useVault().activeVault;
+ const { showToast } = useToastMessage();
+
+ const updateIKSPolicy = async (email: string) => {
+ try {
+ const [ikSigner] = vault.signers.filter(
+ (signer) => signer.type === SignerType.INHERITANCEKEY
+ );
+ const thresholdDescriptors = vault.signers.map((signer) => signer.signerId).slice(0, 2);
+
+ const existingPolicy: InheritancePolicy | any =
+ idx(ikSigner, (_) => _.inheritanceKeyInfo.policy) || {};
+
+ const updatedPolicy: InheritancePolicy = {
+ ...existingPolicy,
+ alert: {
+ emails: [email],
+ },
+ };
+
+ const { updated } = await InheritanceKeyServer.updateInheritancePolicy(
+ vault.shellId,
+ {
+ alert: updatedPolicy.alert,
+ },
+ thresholdDescriptors
+ );
+
+ if (updated) {
+ const updatedIKSigner: VaultSigner = {
+ ...ikSigner,
+ inheritanceKeyInfo: {
+ ...ikSigner.inheritanceKeyInfo,
+ policy: updatedPolicy,
+ },
+ };
+ const updatedSigners = vault.signers.map((signer) => {
+ if (signer.type === SignerType.INHERITANCEKEY) return updatedIKSigner;
+ return signer;
+ });
+
+ dbManager.updateObjectById(RealmSchema.Vault, vault.id, {
+ signers: updatedSigners,
+ });
+ showToast('Email added', );
+ navigtaion.goBack();
+ } else showToast('Failed to add email');
+ } catch (err) {
+ captureError(err);
+ showToast('Failed to add email');
+ }
+ };
+
+ return (
+
+ navigtaion.goBack()}
+ title="Add email"
+ subtitle="If notification is not declined continuously for 30 days, the Key would be activated"
+ paddingLeft={22}
+ />
+
+ {
+ setEmail(text);
+ }}
+ />
+
+ {
+ updateIKSPolicy(email);
+ }}
+ />
+
+ );
}
const styles = StyleSheet.create({
- inputWrapper: {
- marginVertical: hp(60),
- marginHorizontal: 4,
- },
- input: {
- width: "90%",
- fontSize: 14,
- paddingLeft: 5
- }
-})
-export default IKSAddEmailPhone
\ No newline at end of file
+ inputWrapper: {
+ marginVertical: hp(60),
+ marginHorizontal: 4,
+ },
+ input: {
+ width: '90%',
+ fontSize: 14,
+ paddingLeft: 5,
+ },
+});
+export default IKSAddEmailPhone;
diff --git a/src/screens/Inheritance/InheritanceStatus.tsx b/src/screens/Inheritance/InheritanceStatus.tsx
index 426bcb4fb..c56444427 100644
--- a/src/screens/Inheritance/InheritanceStatus.tsx
+++ b/src/screens/Inheritance/InheritanceStatus.tsx
@@ -1,14 +1,14 @@
-import React, { useState } from 'react';
+import React, { useContext, useEffect, useState } from 'react';
import { Box, ScrollView } from 'native-base';
import { StyleSheet } from 'react-native';
-import { useNavigation } from '@react-navigation/native';
+import { CommonActions, useNavigation } from '@react-navigation/native';
import HeaderTitle from 'src/components/HeaderTitle';
import ScreenWrapper from 'src/components/ScreenWrapper';
-import { setInheritance } from 'src/store/reducers/settings';
-import { useAppDispatch } from 'src/store/hooks';
+import { setIKPDFPaths, setInheritance, setKeySecurityTipsPath, setLetterToAttornyPath, setRecoveryInstructionPath } from 'src/store/reducers/settings';
+import { useAppDispatch, useAppSelector } from 'src/store/hooks';
import SafeguardingTips from 'src/assets/images/SafeguardingTips.svg';
-import SetupIK from 'src/assets/images/SetupIK.svg'
+import SetupIK from 'src/assets/images/SetupIK.svg';
import Letter from 'src/assets/images/LETTER.svg';
import Recovery from 'src/assets/images/recovery.svg';
import ToastErrorIcon from 'src/assets/images/toast_error.svg';
@@ -17,98 +17,181 @@ import TickIcon from 'src/assets/images/icon_tick.svg';
import Text from 'src/components/KeeperText';
import Note from 'src/components/Note/Note';
import { hp, windowHeight, wp } from 'src/common/data/responsiveness/responsive';
-import DownloadFile from 'src/utils/DownloadPDF';
import useToastMessage from 'src/hooks/useToastMessage';
-import InheritanceSupportView from './components/InheritanceSupportView';
-import InheritanceDownloadView from './components/InheritanceDownloadView';
+import useVault from 'src/hooks/useVault';
+import { SignerType } from 'src/core/wallets/enums';
+import GenerateRecoveryInstrPDF from 'src/utils/GenerateRecoveryInstrPDF';
+import { Vault } from 'src/core/wallets/interfaces/vault';
+import { RealmSchema } from 'src/storage/realm/enum';
+import { getJSONFromRealmObject } from 'src/storage/realm/utils';
+import { genrateOutputDescriptors } from 'src/core/utils';
+import { RealmWrapperContext } from 'src/storage/realm/RealmProvider';
+import GenerateSecurityTipsPDF from 'src/utils/GenerateSecurityTipsPDF';
+import GenerateLetterToAtternyPDF from 'src/utils/GenerateLetterToAtternyPDF';
import IKSetupSuccessModal from './components/IKSetupSuccessModal';
+import InheritanceDownloadView from './components/InheritanceDownloadView';
+import InheritanceSupportView from './components/InheritanceSupportView';
function InheritanceStatus() {
- const { showToast } = useToastMessage();
- const navigtaion = useNavigation();
- const dispatch = useAppDispatch();
- const [visibleModal, setVisibleModal] = useState(false);
- const [visibleErrorView] = useState(false);
- return (
-
- navigtaion.goBack()}
- learnMore
- learnMorePressed={() => {
- dispatch(setInheritance(true));
- }}
- />
-
-
- }
- title='Key Security Tips'
- subTitle='How to store your keys securely'
- onPress={() => DownloadFile('Key Security Tips').then(() => {
- showToast('Document has been downloaded.', );
- })}
- isDownload
- />
- }
- title='Setup Inheritance Key'
- subTitle='Add an assisted key to create a 3 of 6 Vault'
- onPress={() => showToast('Inheritance key setup flow: coming soon', )}
+ const { showToast } = useToastMessage();
+ const navigtaion = useNavigation();
+ const dispatch = useAppDispatch();
+ const { keySecurityTips, letterToAttorny, recoveryInstruction } = useAppSelector((state) => state.settings);
+ const [visibleModal, setVisibleModal] = useState(false);
+ const [visibleErrorView] = useState(false);
+
+ const { activeVault } = useVault();
+ const { useQuery } = useContext(RealmWrapperContext);
+ const vault: Vault = useQuery(RealmSchema.Vault)
+ .map(getJSONFromRealmObject)
+ .filter((vault) => !vault.archived)[0];
+ const fingerPrints = vault.signers.map(signer => signer.masterFingerprint)
+
+ const descriptorString = genrateOutputDescriptors(vault);
+ const [isSetupDone, setIsSetupDone] = useState(false);
+
+ useEffect(() => {
+ if (activeVault && activeVault.signers) {
+ const [ikSigner] = activeVault.signers.filter(
+ (signer) => signer.type === SignerType.INHERITANCEKEY
+ );
+ if (ikSigner) setIsSetupDone(true);
+ else setIsSetupDone(false);
+ }
+ }, [activeVault]);
+
+ return (
+
+ navigtaion.goBack()}
+ learnMore
+ learnMorePressed={() => {
+ dispatch(setInheritance(true));
+ }}
+ />
+
+
+ }
+ title="Key Security Tips"
+ subTitle="How to store your keys securely"
+ previewPDF={() => {
+ if (keySecurityTips) {
+ navigtaion.navigate('PreviewPDF', { source: keySecurityTips })
+ } else {
+ showToast('Document hasn\'t downloaded yet.', );
+ }
+
+ }}
+ downloadPDF={() => {
+ GenerateSecurityTipsPDF().then((res) => {
+ if (res) {
+ dispatch(setKeySecurityTipsPath(res))
+ }
+ showToast('Document has been downloaded.', );
+ })
+ }}
+ isDownload
+ />
+ }
+ title="Setup Inheritance Key"
+ subTitle="Add an assisted key to create a 3 of 6 Vault"
+ isSetupDone={isSetupDone}
+ onPress={() => {
+ if (isSetupDone) {
+ showToast('You have successfully added the Inheritance Key.', )
+ return
+ }
+ navigtaion.dispatch(
+ CommonActions.navigate('AddSigningDevice', { isInheritance: true })
+ );
+ }}
+ />
+ {/* Error view - Need to add condition for this */}
+ {visibleErrorView && (
+
+ Signing Devices have been changed
+
+
+ )}
+ }
+ title="Letter to the Attorney"
+ subTitle="A partly filled pdf template"
+ previewPDF={() => {
+ if (letterToAttorny) {
+ navigtaion.navigate('PreviewPDF', { source: letterToAttorny })
+ } else {
+ showToast('Document hasn\'t downloaded yet.', );
+ }
+
+ }}
+ downloadPDF={() => {
+ GenerateLetterToAtternyPDF(fingerPrints).then((res) => {
+ if (res) {
+ dispatch(setLetterToAttornyPath(res))
+ }
+ showToast('Document has been downloaded.', );
+ })
+ }}
+ isDownload
+ />
+ }
+ title="Recovery Instructions"
+ subTitle="A document for the heir only"
+ previewPDF={() => {
+ if (recoveryInstruction) {
+ navigtaion.navigate('PreviewPDF', { source: recoveryInstruction })
+ } else {
+ showToast('Document hasn\'t downloaded yet.', );
+ }
- />
- {/* Error view - Need to add condition for this */}
- {visibleErrorView &&
- Signing Devices have been changed
-
- }
- }
- title='Letter to the attorney'
- subTitle='A partly filled pdf template'
- onPress={() => DownloadFile('Letter to the attorney').then(() => {
- showToast('Document has been downloaded.', );
- })}
- isDownload
- />
- }
- title='Recovery Instructions'
- subTitle='A document for the heir only'
- onPress={() => DownloadFile('Restoring Inheritance Vault').then(() => {
- showToast('Document has been downloaded.', );
- })}
- isDownload
- />
-
- {/* */}
-
- {/* */}
- setVisibleModal(false)} />
-
- )
+ }}
+ downloadPDF={() =>
+ GenerateRecoveryInstrPDF(activeVault.signers, descriptorString).then((res) => {
+ if (res) {
+ dispatch(setRecoveryInstructionPath(res))
+ }
+ showToast('Document has been downloaded.', );
+ })
+ }
+ isDownload
+ />
+
+ {/* */}
+
+ {/* */}
+ setVisibleModal(false)} />
+
+ );
}
const styles = StyleSheet.create({
- signingDevicesView: {
- alignSelf: 'flex-end',
- flexDirection: 'row',
- marginTop: hp(20),
- right: 3
- },
- scrollViewWrapper: {
- height: windowHeight > 800 ? '50%' : '40%'
- },
- signingDevicesText: {
- color: '#E07962',
- fontSize: 14
- },
- note: {
- bottom: hp(5),
- justifyContent: 'center',
- width: wp(320),
- },
-})
-export default InheritanceStatus
\ No newline at end of file
+ signingDevicesView: {
+ alignSelf: 'flex-end',
+ flexDirection: 'row',
+ marginTop: hp(20),
+ right: 3,
+ },
+ scrollViewWrapper: {
+ height: windowHeight > 800 ? '50%' : '40%',
+ },
+ signingDevicesText: {
+ color: '#E07962',
+ fontSize: 14,
+ },
+ note: {
+ bottom: hp(5),
+ justifyContent: 'center',
+ width: wp(320),
+ },
+});
+export default InheritanceStatus;
diff --git a/src/screens/Inheritance/SetupInheritance.tsx b/src/screens/Inheritance/SetupInheritance.tsx
index c7ca8be94..31521f1d9 100644
--- a/src/screens/Inheritance/SetupInheritance.tsx
+++ b/src/screens/Inheritance/SetupInheritance.tsx
@@ -11,7 +11,7 @@ import KeeperModal from 'src/components/KeeperModal';
import { useAppDispatch, useAppSelector } from 'src/store/hooks';
import { setInheritance } from 'src/store/reducers/settings';
// icons and asserts
-import Assert from 'src/assets/images/illustration.svg';
+import Assert from 'src/assets/images/InheritanceSupportIllustration.svg';
import Vault from 'src/assets/images/vault.svg';
import Letter from 'src/assets/images/LETTER.svg';
import LetterIKS from 'src/assets/images/LETTER_IKS.svg';
@@ -23,12 +23,16 @@ import { SubscriptionTier } from 'src/common/data/enums/SubscriptionTier';
import usePlan from 'src/hooks/usePlan';
import GradientIcon from 'src/screens/WalletDetailScreen/components/GradientIcon';
import { TouchableOpacity } from 'react-native';
+import useVault from 'src/hooks/useVault';
function SetupInheritance() {
const navigtaion = useNavigation();
const dispatch = useAppDispatch();
const introModal = useAppSelector((state) => state.settings.inheritanceModal);
const { plan } = usePlan();
+ const { activeVault } = useVault();
+
+ const shouldActivateInheritance = () => plan === SubscriptionTier.L3.toUpperCase() && activeVault;
const inheritanceData = [
{
@@ -103,17 +107,15 @@ function SetupInheritance() {
const proceedCallback = () => {
dispatch(setInheritance(false));
- if (plan === SubscriptionTier.L3.toUpperCase()) {
- navigtaion.navigate('InheritanceStatus')
- }
+ if (shouldActivateInheritance()) navigtaion.navigate('InheritanceStatus');
};
+
const toSetupInheritance = () => {
- if (plan !== SubscriptionTier.L3.toUpperCase()) {
- navigtaion.navigate('ChoosePlan');
- } else {
- dispatch(setInheritance(true))
- }
- }
+ if (shouldActivateInheritance()) dispatch(setInheritance(true));
+ else if (plan !== SubscriptionTier.L3.toUpperCase()) navigtaion.navigate('ChoosePlan');
+ else if (!activeVault) navigtaion.navigate('AddSigningDevice');
+ };
+
return (
@@ -121,7 +123,7 @@ function SetupInheritance() {
onPressHandler={() => navigtaion.goBack()}
learnMore
learnMorePressed={() => {
- // dispatch(setInheritance(true));
+ dispatch(setInheritance(true));
}}
/>
@@ -143,24 +145,19 @@ function SetupInheritance() {
- {plan !== SubscriptionTier.L3.toUpperCase()
- ? `This can be activated once you are at the ${SubscriptionTier.L3} level`
- : `Setup Inheritance Key or view documents`}
+ {shouldActivateInheritance()
+ ? `Manage Inheritance key or view documents`
+ : `This can be activated once you are at the ${SubscriptionTier.L3} level and have a vault`}
700 ? hp(50) : hp(20) }} testID="btn_ISContinue">
- toSetupInheritance()}
- >
+ toSetupInheritance()}>
- {plan !== SubscriptionTier.L3.toUpperCase()
- ? `Upgrade Now`
- : `Proceed`}
+ {shouldActivateInheritance() ? 'Proceed' : `Upgrade Now`}
diff --git a/src/screens/Inheritance/components/InheritanceDownloadView.tsx b/src/screens/Inheritance/components/InheritanceDownloadView.tsx
index 8c8741737..5f5be9d18 100644
--- a/src/screens/Inheritance/components/InheritanceDownloadView.tsx
+++ b/src/screens/Inheritance/components/InheritanceDownloadView.tsx
@@ -1,78 +1,117 @@
import React from 'react';
-import { Box } from 'native-base';
+import { Box, Pressable } from 'native-base';
import { StyleSheet, TouchableOpacity } from 'react-native';
import Text from 'src/components/KeeperText';
-import ToastErrorIcon from 'src/assets/images/download.svg';
+import DownloadIcon from 'src/assets/images/download.svg';
+import ViewIcon from 'src/assets/images/icon_show.svg';
import { hp } from 'src/common/data/responsiveness/responsive';
-
+import TickIcon from 'src/assets/images/icon_tick.svg';
function InheritanceDownloadView(props) {
- return (
-
-
- {props.icon}
-
-
- {props.title}
- {props.subTitle}
-
-
- {props.isDownload ?
-
-
- Download
-
- :
-
- Setup
-
- }
-
-
- )
+ return (
+
+ {props.icon}
+
+
+ {props.title}
+
+
+ {props.subTitle}
+
+
+
+ {props.isDownload ? (
+
+
+
+ {/* Download */}
+
+
+
+ {/* Download */}
+
+
+ ) : (
+
+ {props.isSetupDone ? (
+
+
+
+
+ ) : (
+
+ Setup
+
+ )}
+
+ )}
+
+
+ );
}
const styles = StyleSheet.create({
- wrapper: {
- width: '100%',
- flexDirection: 'row',
- backgroundColor: '#FDF7F0',
- borderRadius: 10,
- paddingHorizontal: 10,
- paddingVertical: 20,
- alignItems: 'center',
- marginTop: hp(15)
- },
- downloadBtn: {
- flexDirection: 'row',
- backgroundColor: '#E3BE96',
- padding: 5,
- alignItems: 'center',
- justifyContent: 'center',
- borderRadius: 10,
- },
- downloadBtnText: {
- color: '#725436',
- fontSize: 12,
- },
- iconWrapper: {
- width: '13%'
- },
- titleWrapper: {
- width: '57%',
- },
- btnWrapper: {
- width: '30%'
- },
- titleText: {
- fontSize: 14,
- fontWeight: '400',
- letterSpacing: 0.80
-
- },
- subTitleText: {
- fontSize: 12,
- letterSpacing: 0.80,
- width: '96%'
- }
-})
-export default InheritanceDownloadView
\ No newline at end of file
+ wrapper: {
+ width: '100%',
+ flexDirection: 'row',
+ backgroundColor: '#FDF7F0',
+ borderRadius: 10,
+ paddingHorizontal: 10,
+ paddingVertical: 20,
+ alignItems: 'center',
+ marginTop: hp(15),
+ },
+ downloadBtnWrapper: {
+ flexDirection: 'row',
+ width: '100%',
+ justifyContent: 'space-between',
+ },
+ downloadBtn: {
+ width: '45%',
+ padding: 5,
+ paddingVertical: 10,
+ backgroundColor: '#E3BE96',
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderRadius: 10,
+ },
+ successTickBtn: {
+ width: '45%',
+ padding: 5,
+ paddingVertical: 10,
+ alignItems: 'center',
+ justifyContent: 'center',
+ alignSelf: 'center'
+ },
+ setupBtn: {
+ flexDirection: 'row',
+ backgroundColor: '#E3BE96',
+ padding: 5,
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderRadius: 10,
+ },
+ setupBtnText: {
+ color: '#725436',
+ fontSize: 12,
+ },
+ iconWrapper: {
+ width: '13%',
+ },
+ titleWrapper: {
+ width: '57%',
+ },
+ btnWrapper: {
+ width: '30%',
+ },
+ titleText: {
+ fontSize: 14,
+ fontWeight: '400',
+ letterSpacing: 0.8,
+ },
+ subTitleText: {
+ fontSize: 12,
+ letterSpacing: 0.8,
+ width: '96%',
+ },
+});
+export default InheritanceDownloadView;
diff --git a/src/screens/Inheritance/components/PreviewPDF.tsx b/src/screens/Inheritance/components/PreviewPDF.tsx
new file mode 100644
index 000000000..d55293a41
--- /dev/null
+++ b/src/screens/Inheritance/components/PreviewPDF.tsx
@@ -0,0 +1,30 @@
+import { Dimensions, StyleSheet } from 'react-native'
+import React from 'react'
+import Pdf from 'react-native-pdf';
+import { Box } from 'native-base';
+
+function PreviewPDF({ route }: any) {
+ const { source } = route.params;
+ return (
+
+
+
+ )
+}
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ marginTop: 25,
+ },
+ pdf: {
+ flex: 1,
+ width: Dimensions.get('window').width,
+ height: Dimensions.get('window').height,
+ }
+});
+
+export default PreviewPDF
\ No newline at end of file
diff --git a/src/screens/LoginScreen/Login.tsx b/src/screens/LoginScreen/Login.tsx
index 50c6e8e2a..f9105eff2 100644
--- a/src/screens/LoginScreen/Login.tsx
+++ b/src/screens/LoginScreen/Login.tsx
@@ -42,7 +42,7 @@ const TIMEOUT = 60;
const RNBiometrics = new ReactNativeBiometrics();
function LoginScreen({ navigation, route }) {
- const { relogin } = route.params;
+ const { relogin, title, screen } = route.params;
const dispatch = useAppDispatch();
const [passcode, setPasscode] = useState('');
const [loginError, setLoginError] = useState(false);
@@ -168,7 +168,11 @@ function LoginScreen({ navigation, route }) {
if (isAuthenticated) {
setLoginModal(false);
if (relogin) {
- navigation.goBack();
+ navigation.navigate({
+ name: screen,
+ params: { isAuthenticated: true },
+ merge: true,
+ });
} else if (appId !== '') {
updateFCM();
navigation.replace('App');
@@ -382,7 +386,7 @@ function LoginScreen({ navigation, route }) {
marginTop: hp(65),
}}
>
- {login.welcomeback}
+ {relogin ? title : login.welcomeback}
diff --git a/src/screens/Mix/components/SwiperModal.tsx b/src/screens/Mix/components/SwiperModal.tsx
index a81de30d4..1336bda36 100644
--- a/src/screens/Mix/components/SwiperModal.tsx
+++ b/src/screens/Mix/components/SwiperModal.tsx
@@ -147,7 +147,7 @@ function SwiperModal({ enable }) {
const styles = StyleSheet.create({
contentContaner: {
- width: wp(290),
+ width: wp(286),
},
swiperModalIcon: {
alignSelf: 'center',
diff --git a/src/screens/NewHomeScreen/VaultScreen.tsx b/src/screens/NewHomeScreen/VaultScreen.tsx
index 544a30d74..ea1db6f55 100644
--- a/src/screens/NewHomeScreen/VaultScreen.tsx
+++ b/src/screens/NewHomeScreen/VaultScreen.tsx
@@ -96,7 +96,7 @@ function VaultScreen() {
}
title="Inheritance Tools"
- subTitle="Add Inheritance key or view documents"
+ subTitle="Manage Inheritance key or view documents"
iconBackColor="light.learnMoreBorder"
onPress={() => {
navigation.dispatch(CommonActions.navigate({ name: 'SetupInheritance' }));
diff --git a/src/screens/NewHomeScreen/components/HeaderDetails/components/UAIView.tsx b/src/screens/NewHomeScreen/components/HeaderDetails/components/UAIView.tsx
index 92b9ff481..c608d198c 100644
--- a/src/screens/NewHomeScreen/components/HeaderDetails/components/UAIView.tsx
+++ b/src/screens/NewHomeScreen/components/HeaderDetails/components/UAIView.tsx
@@ -14,16 +14,20 @@ function UAIView({
}) {
return (
-
+ {title}
-
+
{secondaryCallbackText}
{primaryCallbackText && primaryCallback && (
-
+
{primaryCallbackText}
@@ -49,7 +53,7 @@ const styles = StyleSheet.create({
fontSize: 12,
width: '100%',
fontFamily: Fonts.RobotoCondensedSemiBold,
- letterSpacing: 0.6
+ letterSpacing: 0,
},
skipWrapper: {
width: '16%',
diff --git a/src/screens/NewHomeScreen/index.tsx b/src/screens/NewHomeScreen/index.tsx
index 0401e26d5..a4e771d0c 100644
--- a/src/screens/NewHomeScreen/index.tsx
+++ b/src/screens/NewHomeScreen/index.tsx
@@ -1,6 +1,6 @@
/* eslint-disable react/no-unstable-nested-components */
import { Linking, Platform, StyleSheet, Text, TouchableOpacity } from 'react-native';
-import React, { useCallback, useEffect } from 'react';
+import React, { useEffect } from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import WalletIcon from 'src/assets/images/walletTab.svg';
import WalletActiveIcon from 'src/assets/images/walleTabFilled.svg';
@@ -11,6 +11,7 @@ import { urlParamsToObj } from 'src/core/utils';
import { WalletType } from 'src/core/wallets/enums';
import useToastMessage from 'src/hooks/useToastMessage';
import Fonts from 'src/common/Fonts';
+import { Box } from 'native-base';
import VaultScreen from './VaultScreen';
import WalletsScreen from './WalletsScreen';
@@ -25,6 +26,7 @@ function TabButton({
textColorActive,
textColor,
}) {
+
return (
{
- if (route.name === 'Vault') {
- const active = navigation.isFocused('Vault');
- return (
-
- );
- }
- const active = navigation.isFocused('Wallet');
+
+ function TabBarButton({ focused, state, descriptors }) {
return (
-
- );
- }, []);
+
+ {state.routes.map((route, index) => {
+ const { options } = descriptors[route.key];
+ const label =
+ options.tabBarLabel !== undefined
+ ? options.tabBarLabel
+ : options.title !== undefined
+ ? options.title
+ : route.name;
+
+ const isFocused = state.index === index;
+
+ const onPress = () => {
+ if (!isFocused) {
+ navigation.navigate({ name: route.name, merge: true });
+ }
+ };
+
+ return (
+
+ );
+ })}
+
+
+
+ )
+
+
+
+ }
+
+
+
return (
({
- tabBarButton: ({ onPress }) => (
-
- ),
- tabBarStyle: styles.tabBarStyle,
- headerShown: false,
- })}
+ screenOptions={{ headerShown: false }}
+ tabBar={props => }
>
@@ -169,7 +180,7 @@ const styles = StyleSheet.create({
justifyContent: 'center',
borderRadius: 30,
paddingHorizontal: 27,
- paddingVertical: 10,
+ paddingVertical: 15,
marginHorizontal: 10,
},
label: {
@@ -187,4 +198,4 @@ const styles = StyleSheet.create({
height: Platform.OS === 'android' ? hp(55) : hp(80),
paddingVertical: Platform.OS === 'android' ? hp(10) : hp(15),
},
-});
+})
diff --git a/src/screens/Recovery/EnterSeedScreen.tsx b/src/screens/Recovery/EnterSeedScreen.tsx
index 3e54c91d7..e14e2d788 100644
--- a/src/screens/Recovery/EnterSeedScreen.tsx
+++ b/src/screens/Recovery/EnterSeedScreen.tsx
@@ -401,6 +401,7 @@ function EnterSeedScreen({ route }) {
height: onChangeIndex === 4 || onChangeIndex === 5 ? hp(90) : null,
},
]}
+ keyboardShouldPersistTaps='handled'
nestedScrollEnabled
>
diff --git a/src/screens/Recovery/ScanQRFileRecovery.tsx b/src/screens/Recovery/ScanQRFileRecovery.tsx
index 6034c8a1f..455a9b8bb 100644
--- a/src/screens/Recovery/ScanQRFileRecovery.tsx
+++ b/src/screens/Recovery/ScanQRFileRecovery.tsx
@@ -76,11 +76,11 @@ function ScanQRFileRecovery({ route }) {
onBarCodeRead={onBarCodeRead}
useNativeZoom
/>
-
- {qrPercent !== 100 && }
- {`Scanned ${qrPercent}%`}
-
+
+ {qrPercent !== 100 && }
+ {`Scanned ${qrPercent}%`}
+
{allowFileUploads && }
-
+
);
}
const styles = StyleSheet.create({
qrcontainer: {
borderRadius: 10,
overflow: 'hidden',
- marginVertical: 25,
+ marginVertical: 15,
alignItems: 'center',
},
cameraView: {
diff --git a/src/screens/Recovery/SigningDeviceConfigRecovery.tsx b/src/screens/Recovery/SigningDeviceConfigRecovery.tsx
index 8c7c6df7a..b023a02d2 100644
--- a/src/screens/Recovery/SigningDeviceConfigRecovery.tsx
+++ b/src/screens/Recovery/SigningDeviceConfigRecovery.tsx
@@ -59,7 +59,7 @@ function ColdCardSetupContent() {
- {`Export the vault config by going to Setting > Multisig > Then select the wallet > Export `}
+ {`Export the Vault config by going to Setting > Multisig > Then select the wallet > Export `}
diff --git a/src/screens/Send/AddSendAmount.tsx b/src/screens/Send/AddSendAmount.tsx
index 882e5637c..3fb00598e 100644
--- a/src/screens/Send/AddSendAmount.tsx
+++ b/src/screens/Send/AddSendAmount.tsx
@@ -139,7 +139,7 @@ function AddSendAmount({ route }) {
transferType,
note,
label: labelsToAdd.filter(
- (item) => !(item.name === recipient.presentationData.name && item.isSystem) // remove wallet labels are they are internal refrerences
+ (item) => !(item.name === idx(recipient, (_) => _.presentationData.name) && item.isSystem) // remove wallet labels are they are internal refrerences
),
})
);
@@ -251,7 +251,7 @@ function AddSendAmount({ route }) {
{errorMessage && (
diff --git a/src/screens/SignTransaction/SignTransactionScreen.tsx b/src/screens/SignTransaction/SignTransactionScreen.tsx
index c58334906..44ccc68e0 100644
--- a/src/screens/SignTransaction/SignTransactionScreen.tsx
+++ b/src/screens/SignTransaction/SignTransactionScreen.tsx
@@ -35,6 +35,7 @@ import SignerModals from './SignerModals';
import SignerList from './SignerList';
import {
signTransactionWithColdCard,
+ signTransactionWithInheritanceKey,
signTransactionWithMobileKey,
signTransactionWithSeedWords,
signTransactionWithSigningServer,
@@ -159,10 +160,12 @@ function SignTransactionScreen() {
signerId,
signingServerOTP,
seedBasedSingerMnemonic,
+ thresholdDescriptors,
}: {
signerId?: string;
signingServerOTP?: string;
seedBasedSingerMnemonic?: string;
+ thresholdDescriptors?: string[];
} = {}) => {
const activeId = signerId || activeSignerId;
const currentSigner = signers.filter((signer) => signer.signerId === activeId)[0];
@@ -217,6 +220,14 @@ function SignTransactionScreen() {
});
dispatch(updatePSBTEnvelops({ signedSerializedPSBT, signerId }));
dispatch(healthCheckSigner([currentSigner]));
+ } else if (SignerType.INHERITANCEKEY === signerType) {
+ const { signedSerializedPSBT } = await signTransactionWithInheritanceKey({
+ signingPayload,
+ serializedPSBT,
+ shellId,
+ thresholdDescriptors,
+ });
+ dispatch(updatePSBTEnvelops({ signedSerializedPSBT, signerId }));
} else if (SignerType.SEED_WORDS === signerType) {
const { signedSerializedPSBT } = await signTransactionWithSeedWords({
signingPayload,
@@ -234,7 +245,12 @@ function SignTransactionScreen() {
[activeSignerId, serializedPSBTEnvelops]
);
- const callbackForSigners = ({ type, signerId, signerPolicy }: VaultSigner) => {
+ const callbackForSigners = ({
+ type,
+ signerId,
+ signerPolicy,
+ inheritanceKeyInfo,
+ }: VaultSigner) => {
setActiveSignerId(signerId);
if (areSignaturesSufficient()) {
showToast('We already have enough signatures, you can now broadcast.');
@@ -307,6 +323,13 @@ function SignTransactionScreen() {
case SignerType.OTHER_SD:
setOtherSDModal(true);
break;
+ case SignerType.INHERITANCEKEY:
+ // if (inheritanceKeyInfo) {
+ // const thresholdDescriptors = inheritanceKeyInfo.configuration.descriptors.slice(0, 2);
+ // signTransaction({ signerId, thresholdDescriptors });
+ // } else showToast('Inheritance config missing');
+ showToast('Signing via Inheritance Key is not available', );
+ break;
default:
showToast(`action not set for ${type}`);
break;
diff --git a/src/screens/SignTransaction/signWithSD.ts b/src/screens/SignTransaction/signWithSD.ts
index 1ad876eb3..7c327c418 100644
--- a/src/screens/SignTransaction/signWithSD.ts
+++ b/src/screens/SignTransaction/signWithSD.ts
@@ -7,8 +7,8 @@ import idx from 'idx';
import { signWithTapsigner, readTapsigner } from 'src/hardware/tapsigner';
import { signWithColdCard } from 'src/hardware/coldcard';
import { isSignerAMF } from 'src/hardware';
-import { Vault } from 'src/core/wallets/interfaces/vault';
import { EntityKind } from 'src/core/wallets/enums';
+import InheritanceKeyServer from 'src/core/services/operations/InheritanceKey';
export const signTransactionWithTapsigner = async ({
setTapsignerModal,
@@ -157,6 +157,31 @@ export const signTransactionWithSigningServer = async ({
}
};
+export const signTransactionWithInheritanceKey = async ({
+ signingPayload,
+ serializedPSBT,
+ shellId,
+ thresholdDescriptors,
+}) => {
+ try {
+ const childIndexArray = idx(signingPayload, (_) => _[0].childIndexArray);
+ if (!childIndexArray) throw new Error('Invalid signing payload');
+
+ const vaultId = shellId;
+ const { signedPSBT } = await InheritanceKeyServer.signPSBT(
+ vaultId,
+ serializedPSBT,
+ childIndexArray,
+ thresholdDescriptors
+ );
+ if (!signedPSBT) throw new Error('inheritance key server: failed to sign');
+ return { signedSerializedPSBT: signedPSBT };
+ } catch (error) {
+ captureError(error);
+ Alert.alert(error.message);
+ }
+};
+
export const signTransactionWithSeedWords = async ({
signingPayload,
defaultVault,
diff --git a/src/screens/UTXOManagement/UTXOManagement.tsx b/src/screens/UTXOManagement/UTXOManagement.tsx
index bb47a27ef..14baaaf1d 100644
--- a/src/screens/UTXOManagement/UTXOManagement.tsx
+++ b/src/screens/UTXOManagement/UTXOManagement.tsx
@@ -252,7 +252,7 @@ function UTXOManagement({ route, navigation }) {
return (
-
+ setLearnModalVisible(true)} />
{isWhirlpoolWallet ? (
,
+ Instructions: [
+ 'Manually provide the signing device details',
+ `The hardened part of the derivation path of the xpub has to be denoted with a " h " or " ' ". Please do not use any other charecter`,
+ ],
+ title: 'Setting up a Inheritance Key',
+ subTitle: 'Keep your signing device ready before proceeding',
+};
+function AddIKS({ vault, visible, close }: { vault: Vault; visible: boolean; close: () => void }) {
+ const dispatch = useDispatch();
+ const { showToast } = useToastMessage();
+
+ const [inProgress, setInProgress] = useState(false);
+ const [backupBSMS, setBackupBSMS] = useState(false);
+
+ useEffect(() => {
+ dispatch(setBackupBSMSForIKS(backupBSMS));
+ }, [backupBSMS]);
+
+ const Content = useCallback(
+ () => (
+
+ {config.Illustration}
+ {
+ setBackupBSMS(!backupBSMS);
+ }}
+ style={{ flexDirection: 'row', alignItems: 'center', marginTop: 10 }}
+ >
+ {backupBSMS ? (
+
+ ) : (
+
+ )}
+ Backup Vault Config (BSMS)
+
+
+ {config.Instructions.map((instruction) => (
+
+ ))}
+
+
+ ),
+ [backupBSMS]
+ );
+
+ const setupInheritanceKey = async () => {
+ try {
+ close();
+ setInProgress(true);
+ const { setupData } = await InheritanceKeyServer.initializeIKSetup(vault.shellId);
+ const { inheritanceXpub: xpub, derivationPath, masterFingerprint } = setupData;
+ const inheritanceKey = generateSignerFromMetaData({
+ xpub,
+ derivationPath,
+ xfp: masterFingerprint,
+ signerType: SignerType.INHERITANCEKEY,
+ storageType: SignerStorage.WARM,
+ isMultisig: true,
+ });
+ setInProgress(false);
+ dispatch(addSigningDevice(inheritanceKey));
+ showToast(`${inheritanceKey.signerName} added successfully`, );
+ } catch (err) {
+ console.log({ err });
+ showToast(`Failed to add inheritance key`, );
+ }
+ };
+
+ return (
+ <>
+
+
+ >
+ );
+}
+
+export default AddIKS;
diff --git a/src/screens/Vault/AddSigningDevice.tsx b/src/screens/Vault/AddSigningDevice.tsx
index 7d480db39..0371df85f 100644
--- a/src/screens/Vault/AddSigningDevice.tsx
+++ b/src/screens/Vault/AddSigningDevice.tsx
@@ -1,10 +1,10 @@
import { Dimensions, Pressable } from 'react-native';
import Text from 'src/components/KeeperText';
import { Box, FlatList, HStack, VStack } from 'native-base';
-import { CommonActions, useNavigation } from '@react-navigation/native';
+import { CommonActions, useNavigation, useRoute } from '@react-navigation/native';
import React, { useContext, useEffect, useState } from 'react';
import { VaultSigner } from 'src/core/wallets/interfaces/vault';
-import { VaultMigrationType } from 'src/core/wallets/enums';
+import { SignerType, VaultMigrationType } from 'src/core/wallets/enums';
import {
addSigningDevice,
removeSigningDevice,
@@ -34,6 +34,7 @@ import { globalStyles } from 'src/common/globalStyles';
import { SDIcons } from './SigningDeviceIcons';
import DescriptionModal from './components/EditDescriptionModal';
import VaultMigrationController from './VaultMigrationController';
+import AddIKS from './AddIKS';
const { width } = Dimensions.get('screen');
@@ -47,7 +48,17 @@ export const checkSigningDevice = async (id) => {
}
};
-function SignerItem({ signer, index }: { signer: VaultSigner | undefined; index: number }) {
+function SignerItem({
+ signer,
+ index,
+ setInheritanceInit,
+ inheritanceSigner,
+}: {
+ signer: VaultSigner | undefined;
+ index: number;
+ setInheritanceInit: any;
+ inheritanceSigner: VaultSigner;
+}) {
const dispatch = useDispatch();
const navigation = useNavigation();
const { plan } = usePlan();
@@ -56,12 +67,20 @@ function SignerItem({ signer, index }: { signer: VaultSigner | undefined; index:
const removeSigner = () => dispatch(removeSigningDevice(signer));
const navigateToSignerList = () =>
navigation.dispatch(CommonActions.navigate('SigningDeviceList'));
+
+ const callback = () => {
+ if (index === 5 && !inheritanceSigner) {
+ setInheritanceInit(true);
+ } else {
+ navigateToSignerList();
+ }
+ };
const openDescriptionModal = () => setVisible(true);
const closeDescriptionModal = () => setVisible(false);
if (!signer) {
return (
-
+
@@ -171,12 +190,20 @@ function AddSigningDevice() {
const [vaultCreating, setCreating] = useState(false);
const { activeVault } = useVault();
const navigation = useNavigation();
+ const route = useRoute() as { params: { isInheritance: boolean } };
const dispatch = useDispatch();
const { subscriptionScheme, plan } = usePlan();
const vaultSigners = useAppSelector((state) => state.vault.signers);
const { relayVaultUpdateLoading } = useAppSelector((state) => state.bhr);
const { translations } = useContext(LocalizationContext);
const { common } = translations;
+ const [inheritanceInit, setInheritanceInit] = useState(false);
+
+ const signers = activeVault?.signers || [];
+ const isInheritance =
+ route?.params?.isInheritance ||
+ signers.filter((signer) => signer.type === SignerType.INHERITANCEKEY)[0];
+
const {
planStatus,
signersState,
@@ -184,7 +211,11 @@ function AddSigningDevice() {
amfSigners,
misMatchedSigners,
invalidSigners,
- } = useSignerIntel();
+ } = useSignerIntel({ isInheritance });
+
+ const inheritanceSigner: VaultSigner = signersState.filter(
+ (signer) => signer?.type === SignerType.INHERITANCEKEY
+ )[0];
useEffect(() => {
if (activeVault && !vaultSigners.length) {
@@ -196,7 +227,14 @@ function AddSigningDevice() {
setCreating(true);
};
- const renderSigner = ({ item, index }) => ;
+ const renderSigner = ({ item, index }) => (
+
+ );
let preTitle: string;
if (planStatus === VaultMigrationType.DOWNGRADE) {
@@ -208,7 +246,9 @@ function AddSigningDevice() {
}
const subtitle =
subscriptionScheme.n > 1
- ? `Vault with a ${subscriptionScheme.m} of ${subscriptionScheme.n} setup will be created`
+ ? `Vault with a ${subscriptionScheme.m} of ${
+ subscriptionScheme.n + (isInheritance ? 1 : 0)
+ } setup will be created${isInheritance ? ' for inheritance' : ''}`
: `Vault with ${subscriptionScheme.m} of ${subscriptionScheme.n} setup will be created`;
return (
@@ -224,6 +264,7 @@ function AddSigningDevice() {
setCreating={setCreating}
signersState={signersState}
planStatus={planStatus}
+ isInheritance={isInheritance}
/>
+ setInheritanceInit(false)}
+ />
);
}
diff --git a/src/screens/Vault/HardwareModalMap.tsx b/src/screens/Vault/HardwareModalMap.tsx
index 1cd2f957b..427b66461 100644
--- a/src/screens/Vault/HardwareModalMap.tsx
+++ b/src/screens/Vault/HardwareModalMap.tsx
@@ -31,7 +31,7 @@ import SeedWordsIllustration from 'src/assets/images/illustration_seed_words.svg
import SigningServerIllustration from 'src/assets/images/signingServer_illustration.svg';
import TapsignerSetupImage from 'src/assets/images/TapsignerSetup.svg';
import OtherSDSetup from 'src/assets/images/illustration_othersd.svg';
-import InheritanceKeyIllustration from 'src/assets/images/illustration_inheritanceKey.svg';
+// import InheritanceKeyIllustration from 'src/assets/images/illustration_inheritanceKey.svg';
import BitboxImage from 'src/assets/images/bitboxSetup.svg';
import TrezorSetup from 'src/assets/images/trezor_setup.svg';
import { VaultSigner, XpubDetailsType } from 'src/core/wallets/interfaces/vault';
@@ -95,23 +95,24 @@ const getSignerContent = (
Illustration: ,
Instructions: isTestnet()
? [
- ccInstructions,
- `Make sure you enable Testnet mode on the coldcard if you are running the app in the Testnet mode from Advance option > Danger Zone > Testnet and enable it.`,
- ]
+ ccInstructions,
+ `Make sure you enable Testnet mode on the coldcard if you are running the app in the Testnet mode from Advance option > Danger Zone > Testnet and enable it.`,
+ ]
: [ccInstructions],
title: coldcard.SetupTitle,
subTitle: `${coldcard.SetupDescription}`,
};
case SignerType.JADE:
- const jadeInstructions = `Make sure the Jade is setup with a companion app and Unlocked. Then export the xPub by going to Settings > Xpub Export. Also to be sure that the wallet type and script type is set to ${isMultisig ? 'MultiSig' : 'SingleSig'
- } and Native Segwit in the options section.`;
+ const jadeInstructions = `Make sure the Jade is setup with a companion app and Unlocked. Then export the xPub by going to Settings > Xpub Export. Also to be sure that the wallet type and script type is set to ${
+ isMultisig ? 'MultiSig' : 'SingleSig'
+ } and Native Segwit in the options section.`;
return {
Illustration: ,
Instructions: isTestnet()
? [
- jadeInstructions,
- `Make sure you enable Testnet mode on the Jade while creating the wallet with the companion app if you are running Keeper in the Testnet mode.`,
- ]
+ jadeInstructions,
+ `Make sure you enable Testnet mode on the Jade while creating the wallet with the companion app if you are running Keeper in the Testnet mode.`,
+ ]
: [jadeInstructions],
title: 'Setting up Blockstream Jade',
subTitle: 'Keep your Jade ready and unlocked before proceeding',
@@ -144,23 +145,24 @@ const getSignerContent = (
Illustration: ,
Instructions: isTestnet()
? [
- keystoneInstructions,
- `Make sure you enable Testnet mode on the Keystone if you are running the app in the Testnet mode from Side Menu > Settings > Blockchain > Testnet and confirm`,
- ]
+ keystoneInstructions,
+ `Make sure you enable Testnet mode on the Keystone if you are running the app in the Testnet mode from Side Menu > Settings > Blockchain > Testnet and confirm`,
+ ]
: [keystoneInstructions],
title: isHealthcheck ? 'Verify Keystone' : 'Setting up Keystone',
subTitle: 'Keep your Keystone ready before proceeding',
};
case SignerType.PASSPORT:
- const passportInstructions = `Export the xPub from the Account section > Manage Account > Connect Wallet > Keeper > ${isMultisig ? 'Multisig' : 'Singlesig'
- } > QR Code.\n`;
+ const passportInstructions = `Export the xPub from the Account section > Manage Account > Connect Wallet > Keeper > ${
+ isMultisig ? 'Multisig' : 'Singlesig'
+ } > QR Code.\n`;
return {
Illustration: ,
Instructions: isTestnet()
? [
- passportInstructions,
- `Make sure you enable Testnet mode on the Passport if you are running the app in the Testnet mode from Settings > Bitcoin > Network > Testnet and enable it.`,
- ]
+ passportInstructions,
+ `Make sure you enable Testnet mode on the Passport if you are running the app in the Testnet mode from Settings > Bitcoin > Network > Testnet and enable it.`,
+ ]
: [passportInstructions],
title: 'Setting up Passport (Batch 2)',
subTitle: 'Keep your Foundation Passport (Batch 2) ready before proceeding',
@@ -171,22 +173,23 @@ const getSignerContent = (
Instructions: isHealthcheck
? ['A request to the signing server will be made to checks it health']
: [
- `A 2FA authenticator will have to be set up to use this option.`,
- `On providing the correct code from the auth app, the Signing Server will sign the transaction.`,
- ],
+ `A 2FA authenticator will have to be set up to use this option.`,
+ `On providing the correct code from the auth app, the Signing Server will sign the transaction.`,
+ ],
title: isHealthcheck ? 'Verify Signing Server' : 'Setting up a Signing Server',
subTitle: 'A Signing Server will hold one of the keys of the Vault',
};
case SignerType.SEEDSIGNER:
- const seedSignerInstructions = `Make sure the seed is loaded and export the xPub by going to Seeds > Select your master fingerprint > Export Xpub > ${isMultisig ? 'Multisig' : 'Singlesig'
- } > Native Segwit > Keeper.\n`;
+ const seedSignerInstructions = `Make sure the seed is loaded and export the xPub by going to Seeds > Select your master fingerprint > Export Xpub > ${
+ isMultisig ? 'Multisig' : 'Singlesig'
+ } > Native Segwit > Keeper.\n`;
return {
Illustration: ,
Instructions: isTestnet()
? [
- seedSignerInstructions,
- `Make sure you enable Testnet mode on the SeedSigner if you are running the app in the Testnet mode from Settings > Adavnced > Bitcoin network > Testnet and enable it.`,
- ]
+ seedSignerInstructions,
+ `Make sure you enable Testnet mode on the SeedSigner if you are running the app in the Testnet mode from Settings > Adavnced > Bitcoin network > Testnet and enable it.`,
+ ]
: [seedSignerInstructions],
title: isHealthcheck ? 'Verify SeedSigner' : 'Setting up SeedSigner',
subTitle: 'Keep your SeedSigner ready and powered before proceeding',
@@ -251,16 +254,16 @@ const getSignerContent = (
title: 'Keep your signing device ready',
subTitle: 'Keep your signing device ready before proceeding',
};
- case SignerType.INHERITANCEKEY:
- return {
- Illustration: ,
- Instructions: [
- 'Manually provide the signing device details',
- `The hardened part of the derivation path of the xpub has to be denoted with a " h " or " ' ". Please do not use any other charecter`,
- ],
- title: 'Setting up a Inheritance Key',
- subTitle: 'Keep your signing device ready before proceeding',
- };
+ // case SignerType.INHERITANCEKEY:
+ // return {
+ // Illustration: ,
+ // Instructions: [
+ // 'Manually provide the signing device details',
+ // `The hardened part of the derivation path of the xpub has to be denoted with a " h " or " ' ". Please do not use any other charecter`,
+ // ],
+ // title: 'Setting up a Inheritance Key',
+ // subTitle: 'Keep your signing device ready before proceeding',
+ // };
default:
return {
Illustration: null,
@@ -724,16 +727,16 @@ function HardwareModalMap({
})
);
};
- const navigateToSendConfirmation = () => {
- // navigation.dispatch(
- // CommonActions.navigate('SendConfirmation', {
- // sender: {},
- // recipients: {},
- // transferType: TransferType.VAULT_TO_VAULT,
- // })
- // );
- navigation.dispatch(CommonActions.navigate('IKSAddEmailPhone'));
- };
+ // const navigateToSendConfirmation = () => {
+ // // navigation.dispatch(
+ // // CommonActions.navigate('SendConfirmation', {
+ // // sender: {},
+ // // recipients: {},
+ // // transferType: TransferType.VAULT_TO_VAULT,
+ // // })
+ // // );
+ // navigation.dispatch(CommonActions.navigate('IKSAddEmailPhone'));
+ // };
const onQRScan = async (qrData, resetQR) => {
let hw: VaultSigner;
@@ -899,8 +902,8 @@ function HardwareModalMap({
return navigateToAddQrBasedSigner();
case SignerType.OTHER_SD:
return navigateToSetupWithOtherSD();
- case SignerType.INHERITANCEKEY:
- return navigateToSendConfirmation();
+ // case SignerType.INHERITANCEKEY:
+ // return navigateToSendConfirmation();
default:
return null;
}
diff --git a/src/screens/Vault/SigningDeviceDetails.tsx b/src/screens/Vault/SigningDeviceDetails.tsx
index 2b33edc07..0e3a3c92d 100644
--- a/src/screens/Vault/SigningDeviceDetails.tsx
+++ b/src/screens/Vault/SigningDeviceDetails.tsx
@@ -267,7 +267,7 @@ function SigningDeviceDetails({ route }) {
{`Added on ${moment(signer?.addedOn)
.format('DD MMM YYYY, hh:mmA')
- .toLowerCase()}`}
+ }`}
diff --git a/src/screens/Vault/SigningDeviceList.tsx b/src/screens/Vault/SigningDeviceList.tsx
index 814beda4d..58461b1ba 100644
--- a/src/screens/Vault/SigningDeviceList.tsx
+++ b/src/screens/Vault/SigningDeviceList.tsx
@@ -7,7 +7,6 @@ import config, { APP_STAGE } from 'src/core/config';
import { hp, windowHeight, windowWidth, wp } from 'src/common/data/responsiveness/responsive';
import { useAppDispatch, useAppSelector } from 'src/store/hooks';
-import Alert from 'src/assets/images/alert_illustration.svg';
import HeaderTitle from 'src/components/HeaderTitle';
import KeeperModal from 'src/components/KeeperModal';
@@ -73,9 +72,15 @@ const getDeviceStatus = (
message: getDisabled(type, isOnL1, vaultSigners).message,
disabled: getDisabled(type, isOnL1, vaultSigners).disabled,
};
+ case SignerType.TREZOR:
+ return !isOnL1
+ ? { disabled: true, message: 'Multisig with trezor is coming soon!' }
+ : {
+ message: '',
+ disabled: false,
+ };
case SignerType.SEED_WORDS:
case SignerType.KEEPER:
- case SignerType.TREZOR:
case SignerType.JADE:
case SignerType.BITBOX02:
case SignerType.PASSPORT:
@@ -100,7 +105,6 @@ function SigningDeviceList() {
const vaultSigners = useAppSelector((state) => state.vault.signers);
const sdModal = useAppSelector((state) => state.vault.sdIntroModal);
- const [nfcAlert, setNfcAlert] = useState(false);
const [isNfcSupported, setNfcSupport] = useState(true);
const [signersLoaded, setSignersLoaded] = useState(false);
@@ -189,17 +193,6 @@ function SigningDeviceList() {
);
}
- const nfcAlertConternt = () => (
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
-
-
- );
-
return (
);
})}
-
)}
- {
- setNfcAlert(false);
- }}
- title="NFC Not supported"
- subTitle="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed "
- buttonText=" CTA "
- buttonTextColor="light.white"
- textColor="light.primaryText"
- Content={nfcAlertConternt}
- />
{
diff --git a/src/screens/Vault/VaultDetails.tsx b/src/screens/Vault/VaultDetails.tsx
index ada0f802b..9d4200c99 100644
--- a/src/screens/Vault/VaultDetails.tsx
+++ b/src/screens/Vault/VaultDetails.tsx
@@ -1,6 +1,6 @@
/* eslint-disable react/no-unstable-nested-components */
import Text from 'src/components/KeeperText';
-import { Box, HStack, VStack, View } from 'native-base';
+import { Box, HStack, VStack, View, Pressable } from 'native-base';
import { CommonActions, useNavigation } from '@react-navigation/native';
import {
FlatList,
@@ -31,7 +31,7 @@ import Success from 'src/assets/images/Success.svg';
import TransactionElement from 'src/components/TransactionElement';
import { Vault } from 'src/core/wallets/interfaces/vault';
import VaultIcon from 'src/assets/images/icon_vault.svg';
-import { VaultMigrationType } from 'src/core/wallets/enums';
+import { SignerType, VaultMigrationType } from 'src/core/wallets/enums';
import VaultSetupIcon from 'src/assets/images/vault_setup.svg';
import { getJSONFromRealmObject } from 'src/storage/realm/utils';
import moment from 'moment';
@@ -46,6 +46,8 @@ import useToastMessage from 'src/hooks/useToastMessage';
import { SubscriptionTier } from 'src/common/data/enums/SubscriptionTier';
import NoVaultTransactionIcon from 'src/assets/images/emptystate.svg';
import ToastErrorIcon from 'src/assets/images/toast_error.svg';
+import AddPhoneEmailIcon from 'src/assets/images/AddPhoneEmail.svg';
+import RightArrowIcon from 'src/assets/images/icon_arrow.svg';
import EmptyStateView from 'src/components/EmptyView/EmptyStateView';
import useExchangeRates from 'src/hooks/useExchangeRates';
import useCurrencyCode from 'src/store/hooks/state-selectors/useCurrencyCode';
@@ -455,6 +457,9 @@ function VaultDetails({ route, navigation }) {
const keeper: KeeperApp = useQuery(RealmSchema.KeeperApp).map(getJSONFromRealmObject)[0];
const [pullRefresh, setPullRefresh] = useState(false);
const [vaultCreated, setVaultCreated] = useState(vaultTransferSuccessful);
+ const inheritanceSigner = vault.signers.filter(
+ (signer) => signer.type === SignerType.INHERITANCEKEY
+ )[0];
const [tireChangeModal, setTireChangeModal] = useState(false);
const { subscriptionScheme } = usePlan();
const [showBuyRampModal, setShowBuyRampModal] = useState(false);
@@ -486,10 +491,6 @@ function VaultDetails({ route, navigation }) {
setPullRefresh(false);
};
- const closeVaultCreatedDialog = () => {
- setVaultCreated(false);
- };
-
useEffect(() => {
if (hasPlanChanged() !== VaultMigrationType.CHANGE) {
setTireChangeModal(true);
@@ -518,13 +519,40 @@ function VaultDetails({ route, navigation }) {
const NewVaultContent = useCallback(
() => (
-
-
+
- For sending out of the vault you will need the signing devices. This means no one can
- steal your bitcoin in the vault unless they also have the signing devices
+ For sending out of the Vault you will need the signing devices. This means no one can
+ steal your bitcoin in the Vault unless they also have the signing devices
-
+
+
+
+ {inheritanceSigner && (
+ {
+ navigation.navigate('IKSAddEmailPhone');
+ setVaultCreated(false);
+ }}
+ >
+
+
+
+
+
+ Add Email
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing eli
+
+
+
+
+
+
+ )}
+
),
[]
);
@@ -540,8 +568,8 @@ function VaultDetails({ route, navigation }) {
const subtitle =
subscriptionScheme.n > 1
- ? `Vault with a ${subscriptionScheme.m} of ${subscriptionScheme.n} setup will be created`
- : `Vault with ${subscriptionScheme.m} of ${subscriptionScheme.n} setup will be created`;
+ ? `Vault with a ${vault.scheme.m} of ${vault.scheme.n} setup is created`
+ : `Vault with ${vault.scheme.m} of ${vault.scheme.n} setup is created`;
return (
{
+ setVaultCreated(false);
+ }}
+ close={() => setVaultCreated(false)}
Content={NewVaultContent}
/>
},
scrollContainer: {
padding: '8%',
- width: Platform.select({ android: null, ios: '100%' }),
},
knowMore: {
backgroundColor: '#725436',
@@ -737,5 +766,28 @@ const getStyles = (top) =>
color: '#041513',
width: wp(200),
},
+ addPhoneEmailWrapper: {
+ width: '100%',
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginVertical: hp(20),
+ paddingVertical: hp(10),
+ borderRadius: 10,
+ },
+ iconWrapper: {
+ width: '15%',
+ },
+ titleWrapper: {
+ width: '75%',
+ },
+ addPhoneEmailTitle: {
+ fontSize: 14,
+ },
+ addPhoneEmailSubTitle: {
+ fontSize: 12,
+ },
+ rightIconWrapper: {
+ width: '10%',
+ },
});
export default VaultDetails;
diff --git a/src/screens/Vault/VaultMigrationController.tsx b/src/screens/Vault/VaultMigrationController.tsx
index 0aa2c5344..f9a4cd125 100644
--- a/src/screens/Vault/VaultMigrationController.tsx
+++ b/src/screens/Vault/VaultMigrationController.tsx
@@ -21,7 +21,13 @@ import WalletUtilities from 'src/core/wallets/operations/utils';
import { sendPhasesReset } from 'src/store/reducers/send_and_receive';
import { sendPhaseOne } from 'src/store/sagaActions/send_and_receive';
-function VaultMigrationController({ vaultCreating, signersState, planStatus, setCreating }: any) {
+function VaultMigrationController({
+ vaultCreating,
+ signersState,
+ planStatus,
+ setCreating,
+ isInheritance,
+}: any) {
const navigation = useNavigation();
const dispatch = useDispatch();
const { showToast } = useToastMessage();
@@ -136,7 +142,7 @@ function VaultMigrationController({ vaultCreating, signersState, planStatus, set
try {
const vaultInfo: NewVaultInfo = {
vaultType: VaultType.DEFAULT,
- vaultScheme: scheme,
+ vaultScheme: isInheritance ? { m: 3, n: 6 } : scheme,
vaultSigners: signers,
vaultDetails: {
name: 'Vault',
@@ -189,7 +195,7 @@ function VaultMigrationController({ vaultCreating, signersState, planStatus, set
const freshSignersState = sanitizeSigners();
const vaultInfo: NewVaultInfo = {
vaultType: VaultType.DEFAULT,
- vaultScheme: subscriptionScheme,
+ vaultScheme: isInheritance ? { m: 3, n: 6 } : subscriptionScheme,
vaultSigners: freshSignersState,
vaultDetails: {
name: 'Vault',
diff --git a/src/screens/Vault/components/EditDescriptionModal.tsx b/src/screens/Vault/components/EditDescriptionModal.tsx
index 90f616d49..9e144e2cf 100644
--- a/src/screens/Vault/components/EditDescriptionModal.tsx
+++ b/src/screens/Vault/components/EditDescriptionModal.tsx
@@ -22,7 +22,7 @@ function SignerData({ signer }: { signer: VaultSigner }) {
{signer.signerName}
- {`Added ${moment(signer.lastHealthCheck).calendar().toLowerCase()}`}
+ {`Added ${moment(signer.lastHealthCheck).calendar()}`}
diff --git a/src/screens/Vault/components/VaultCreatedModal.tsx b/src/screens/Vault/components/VaultCreatedModal.tsx
index 53650db81..05f5c0ac0 100644
--- a/src/screens/Vault/components/VaultCreatedModal.tsx
+++ b/src/screens/Vault/components/VaultCreatedModal.tsx
@@ -4,6 +4,7 @@ import KeeperModal from 'src/components/KeeperModal';
import Success from 'src/assets/images/Success.svg';
import Text from 'src/components/KeeperText';
import { Vault } from 'src/core/wallets/interfaces/vault';
+import { Box } from 'native-base';
function VaultCreatedModal({
vault,
@@ -17,13 +18,13 @@ function VaultCreatedModal({
const subtitle = vault.scheme.n > 1 ? `Vault with a ${vault.scheme.m} of ${vault.scheme.n} setup will be created` : `Vault with ${vault.scheme.m} of ${vault.scheme.n} setup will be created`;
const NewVaultContent = useCallback(
() => (
-
+
For sending out of the vault you will need the signing devices. This means no one can
steal your bitcoin in the vault unless they also have the signing devices
-
+
),
[]
);
diff --git a/src/screens/VaultRecovery/ColdCardRecovery.tsx b/src/screens/VaultRecovery/ColdCardRecovery.tsx
index b62228abe..70709e815 100644
--- a/src/screens/VaultRecovery/ColdCardRecovery.tsx
+++ b/src/screens/VaultRecovery/ColdCardRecovery.tsx
@@ -72,8 +72,8 @@ function ColdCardReocvery({ route }) {
};
const instructions = isConfigRecovery
- ? 'Export the vault config by going to Setting > Multisig > Then select the wallet > Export '
- : 'Export the xPub by going to Advanced/Tools > Export wallet > Generic JSON. From here choose the account number and transfer over NFC. Make sure you remember the account you had chosen (This is important for recovering your vault).';
+ ? 'Export the Vault config by going to Setting > Multisig > Then select the wallet > Export '
+ : 'Export the xPub by going to Advanced/Tools > Export wallet > Generic JSON. From here choose the account number and transfer over NFC. Make sure you remember the account you had chosen (This is important for recovering your Vault).';
return (
diff --git a/src/screens/VaultRecovery/SignersList.tsx b/src/screens/VaultRecovery/SignersList.tsx
index 591b1d625..0ce25db7c 100644
--- a/src/screens/VaultRecovery/SignersList.tsx
+++ b/src/screens/VaultRecovery/SignersList.tsx
@@ -38,8 +38,14 @@ import LedgerImage from 'src/assets/images/ledger_image.svg';
import TickIcon from 'src/assets/images/icon_tick.svg';
import BitoxImage from 'src/assets/images/bitboxSetup.svg';
import TrezorSetup from 'src/assets/images/trezor_setup.svg';
+import InheritanceKeyServer from 'src/core/services/operations/InheritanceKey';
+import { generateKey } from 'src/core/services/operations/encryption';
+import { setInheritanceRequestId } from 'src/store/reducers/storage';
+import { close } from '@sentry/react-native';
+import ToastErrorIcon from 'src/assets/images/toast_error.svg';
import { SDIcons } from '../Vault/SigningDeviceIcons';
import { KeeperContent } from '../SignTransaction/SignerModals';
+import { formatDuration } from './VaultRecovery';
const getnavigationState = (type) => ({
index: 5,
@@ -53,7 +59,12 @@ const getnavigationState = (type) => ({
],
});
-export const getDeviceStatus = (type: SignerType, isNfcSupported, signingDevices) => {
+export const getDeviceStatus = (
+ type: SignerType,
+ isNfcSupported,
+ signingDevices,
+ inheritanceRequestId
+) => {
switch (type) {
case SignerType.COLDCARD:
case SignerType.TAPSIGNER:
@@ -72,6 +83,17 @@ export const getDeviceStatus = (type: SignerType, isNfcSupported, signingDevices
message: '',
disabled: false,
};
+ case SignerType.INHERITANCEKEY:
+ if (signingDevices.length < 2 || inheritanceRequestId) {
+ return {
+ message: 'Add two other devices first to recover',
+ disabled: true,
+ };
+ }
+ return {
+ message: '',
+ disabled: false,
+ };
case SignerType.SEED_WORDS:
case SignerType.MOBILE_KEY:
case SignerType.KEEPER:
@@ -137,7 +159,7 @@ function ColdCardSetupContent() {
- {`Export the xPub by going to Advanced/Tools > Export wallet > Generic JSON. From here choose the account number and transfer over NFC. Make sure you remember the account you had chosen (This is important for recovering your vault)`}
+ {`Export the xPub by going to Advanced/Tools > Export wallet > Generic JSON. From here choose the account number and transfer over NFC. Make sure you remember the account you had chosen (This is important for recovering your Vault)`}
@@ -510,7 +532,7 @@ function SignersList({ navigation }) {
if (response.xpub) {
const signingServerKey = generateSignerFromMetaData({
xpub: response.xpub,
- derivationPath: response.xpub,
+ derivationPath: response.derivationPath,
xfp: response.masterFingerprint,
signerType: SignerType.POLICY_SERVER,
storageType: SignerStorage.WARM,
@@ -526,8 +548,34 @@ function SignersList({ navigation }) {
}
};
+ const requestInheritanceKey = async (signers: VaultSigner[]) => {
+ try {
+ const requestId = `request-${generateKey(10)}`;
+ const vaultId = relayVaultReoveryShellId;
+ const thresholdDescriptors = signers.map((signer) => signer.signerId);
+
+ const { requestStatus } = await InheritanceKeyServer.requestInheritanceKey(
+ requestId,
+ vaultId,
+ thresholdDescriptors
+ );
+
+ showToast(
+ `Request would approve in ${formatDuration(requestStatus.approvesIn)} if not rejected`,
+
+ );
+ dispatch(setInheritanceRequestId(requestId));
+ navigation.dispatch(CommonActions.navigate('VaultRecoveryAddSigner'));
+ } catch (err) {
+ showToast(`${err}`, );
+ }
+ close();
+ };
+
function HardWareWallet({ disabled, message, type, first = false, last = false }: HWProps) {
const [visible, setVisible] = useState(false);
+ const { signingDevices } = useAppSelector((state) => state.bhr);
+
const onPress = () => {
open();
};
@@ -749,6 +797,20 @@ function SignersList({ navigation }) {
textColor="light.primaryText"
Content={otpContent}
/>
+ }
+ buttonText="Continue"
+ buttonTextColor="light.white"
+ buttonCallback={() => {
+ requestInheritanceKey(signingDevices);
+ }}
+ textColor="light.primaryText"
+ />
state.storage);
+
return (
{
- const { disabled, message } = getDeviceStatus(type, isNfcSupported, signingDevices);
+ const { disabled, message } = getDeviceStatus(
+ type,
+ isNfcSupported,
+ signingDevices,
+ inheritanceRequestId
+ );
return (
- showToast(
- 'Warning: No Vault is assocaited with this signer, please reomve and try with another signer'
- )
- : () => navigation.navigate('LoginStack', { screen: 'SignersList' })
- }
- >
-
+
+
-
-
+ {props.icon}
+
- Add Another
+ {props.title}
- Select signing device
+ {props.subTitle}
-
-
+
+ {props.arrowIcon}
@@ -136,16 +141,17 @@ function SuccessModalContent() {
function VaultRecovery({ navigation }) {
const { showToast } = useToastMessage();
+ const { initateRecovery, recoveryLoading: configRecoveryLoading } = useConfigRecovery();
const dispatch = useDispatch();
- const { signingDevices, relayVaultError, relayVaultUpdate } = useAppSelector(
- (state) => state.bhr
- );
+ const { signingDevices, relayVaultError, relayVaultUpdate, relayVaultReoveryShellId } =
+ useAppSelector((state) => state.bhr);
const [scheme, setScheme] = useState();
const { appId } = useAppSelector((state) => state.storage);
const [signersList, setsignersList] = useState(signingDevices);
const [error, setError] = useState(false);
const [recoveryLoading, setRecoveryLoading] = useState(false);
const [successModalVisible, setSuccessModalVisible] = useState(false);
+ const { inheritanceRequestId } = useAppSelector((state) => state.storage);
async function createNewApp() {
try {
@@ -156,6 +162,56 @@ function VaultRecovery({ navigation }) {
}
}
+ const checkInheritanceKeyRequest = async (signers: VaultSigner[], requestId: string) => {
+ try {
+ const vaultId = relayVaultReoveryShellId;
+ const thresholdDescriptors = signers.map((signer) => signer.signerId);
+ const { requestStatus, setupInfo } = await InheritanceKeyServer.requestInheritanceKey(
+ requestId,
+ vaultId,
+ thresholdDescriptors
+ );
+
+ if (requestStatus.isDeclined) {
+ showToast('Inheritance request has been declined', );
+ // dispatch(setInheritanceRequestId('')); // clear existing request
+ return;
+ }
+
+ if (!requestStatus.isApproved) {
+ showToast(
+ `Request would approve in ${formatDuration(requestStatus.approvesIn)} if not rejected`,
+
+ );
+ }
+
+ if (requestStatus.isApproved && setupInfo) {
+ const inheritanceKey = generateSignerFromMetaData({
+ xpub: setupInfo.inheritanceXpub,
+ derivationPath: setupInfo.derivationPath,
+ xfp: setupInfo.masterFingerprint,
+ signerType: SignerType.INHERITANCEKEY,
+ storageType: SignerStorage.WARM,
+ isMultisig: true,
+ inheritanceKeyInfo: {
+ configuration: setupInfo.configuration,
+ policy: setupInfo.policy,
+ },
+ });
+ if (setupInfo.configuration.bsms) {
+ initateRecovery(setupInfo.configuration.bsms);
+ } else {
+ showToast(`Cannot recreate Vault as BSMS was not present`, );
+ }
+ dispatch(setSigningDevices(inheritanceKey));
+ dispatch(setInheritanceRequestId('')); // clear approved request
+ showToast(`${inheritanceKey.signerName} added successfully`, );
+ }
+ } catch (err) {
+ showToast(`${err}`, );
+ }
+ };
+
useEffect(() => {
setsignersList(
signingDevices.map((signer) => updateSignerForScheme(signer, signingDevices?.length))
@@ -230,12 +286,15 @@ function VaultRecovery({ navigation }) {
dispatch(setTempShellId(response.appId));
} else if (response.error) {
setError(true);
- Alert.alert('No vault is assocaited with this signer, try with another signer');
+ showToast(
+ 'No vault is assocaited with this signer, try with another signer',
+
+ );
}
} catch (err) {
console.log(err);
setError(true);
- Alert.alert('Something Went Wrong!');
+ showToast('Something Went Wrong!', );
}
};
@@ -244,7 +303,7 @@ function VaultRecovery({ navigation }) {
setRecoveryLoading(true);
vaultCheck();
} else {
- Alert.alert("Vault can't be recreated in this scheme");
+ showToast("Vault can't be recreated in this scheme", );
}
};
@@ -270,7 +329,30 @@ function VaultRecovery({ navigation }) {
marginTop: hp(32),
}}
/>
-
+ {inheritanceRequestId && (
+ }
+ arrowIcon={}
+ onPress={() => {
+ checkInheritanceKeyRequest(signingDevices, inheritanceRequestId);
+ }}
+ title="Inheritance Key Request Sent"
+ subTitle="3 weeks remaning"
+ />
+ )}
+ }
+ arrowIcon={}
+ onPress={() => {
+ if (error) {
+ showToast(
+ 'Warning: No Vault is assocaited with this signer, please reomve and try with another signer'
+ );
+ } else navigation.navigate('LoginStack', { screen: 'SignersList' });
+ }}
+ title="Add Another"
+ subTitle="Select signing device"
+ />
) : (
@@ -291,8 +373,12 @@ function VaultRecovery({ navigation }) {
{signingDevices.length > 0 && (
checkInheritanceKeyRequest(signingDevices, inheritanceRequestId)
+ : startRecovery
+ }
primaryLoading={recoveryLoading}
/>
@@ -308,14 +394,15 @@ function VaultRecovery({ navigation }) {
subTitle="Your Keeper Vault has successfully been recovered."
buttonText="Ok"
Content={SuccessModalContent}
- close={() => { }}
+ close={() => {}}
showCloseIcon={false}
buttonCallback={() => {
setSuccessModalVisible(false);
navigation.replace('App');
}}
/>
-
+
+
);
}
diff --git a/src/screens/ViewTransactions/TransactionDetails.tsx b/src/screens/ViewTransactions/TransactionDetails.tsx
index 07498988f..8ffb3e0f8 100644
--- a/src/screens/ViewTransactions/TransactionDetails.tsx
+++ b/src/screens/ViewTransactions/TransactionDetails.tsx
@@ -78,8 +78,7 @@ function TransactionDetails({ route }) {
}
const redirectToBlockExplorer = () => {
openLink(
- `https://mempool.space${config.NETWORK_TYPE === NetworkType.TESTNET ? '/testnet' : ''}/tx/${
- transaction.txid
+ `https://mempool.space${config.NETWORK_TYPE === NetworkType.TESTNET ? '/testnet' : ''}/tx/${transaction.txid
}`
);
};
@@ -102,7 +101,7 @@ function TransactionDetails({ route }) {
{transaction.txid}
- {moment(transaction?.date).format('DD MMM YY • hh:mma')}
+ {moment(transaction?.date).format('DD MMM YY • hh:mm A')}
diff --git a/src/screens/WalletDetails/index.js b/src/screens/WalletDetails/index.js
index 3887223b6..4db11eaea 100644
--- a/src/screens/WalletDetails/index.js
+++ b/src/screens/WalletDetails/index.js
@@ -45,7 +45,7 @@ function TransactionsAndUTXOs({ transactions, setPullRefresh, pullRefresh, walle
const syncing = walletSyncing && wallet ? !!walletSyncing[wallet.id] : false;
return (
-
+ ) => {
state.whirlpoolSwiperModal = action.payload;
},
+ setKeySecurityTipsPath: (state, action: PayloadAction) => {
+ state.keySecurityTips = action.payload
+ },
+ setLetterToAttornyPath: (state, action: PayloadAction) => {
+ state.letterToAttorny = action.payload
+ },
+ setRecoveryInstructionPath: (state, action: PayloadAction) => {
+ state.recoveryInstruction = action.payload
+ },
},
});
@@ -69,6 +84,9 @@ export const {
setInheritance,
setSatsEnabled,
setWhirlpoolSwiperModal,
+ setKeySecurityTipsPath,
+ setLetterToAttornyPath,
+ setRecoveryInstructionPath
} = settingsSlice.actions;
export default settingsSlice.reducer;
diff --git a/src/store/reducers/storage.ts b/src/store/reducers/storage.ts
index 0c82d6cfd..74fe97d67 100644
--- a/src/store/reducers/storage.ts
+++ b/src/store/reducers/storage.ts
@@ -3,54 +3,59 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
const initialState: {
appId: string;
resetCred: {
- hash: string,
- index: number
- },
- failedAttempts: number,
- lastLoginFailedAt: number,
- pinHash: string,
- appVersion: string
+ hash: string;
+ index: number;
+ };
+ failedAttempts: number;
+ lastLoginFailedAt: number;
+ pinHash: string;
+ appVersion: string;
+ inheritanceRequestId: string;
} = {
appId: '',
resetCred: {
hash: '',
- index: null
+ index: null,
},
failedAttempts: 0,
lastLoginFailedAt: null,
pinHash: '',
- appVersion: ''
-}
+ appVersion: '',
+ inheritanceRequestId: '',
+};
const storageSlice = createSlice({
name: 'storage',
initialState,
reducers: {
setAppId: (state, action: PayloadAction) => {
- state.appId = action.payload
+ state.appId = action.payload;
},
increasePinFailAttempts: (state) => {
- state.failedAttempts += 1
- state.lastLoginFailedAt = Date.now()
+ state.failedAttempts += 1;
+ state.lastLoginFailedAt = Date.now();
},
- setPinResetCreds: (state, action: PayloadAction<{ hash: string, index: number }>) => {
+ setPinResetCreds: (state, action: PayloadAction<{ hash: string; index: number }>) => {
state.resetCred = {
hash: action.payload.hash,
- index: action.payload.index
- }
+ index: action.payload.index,
+ };
},
resetPinFailAttempts: (state) => {
- state.failedAttempts = 0
- state.lastLoginFailedAt = null
+ state.failedAttempts = 0;
+ state.lastLoginFailedAt = null;
},
setPinHash: (state, action: PayloadAction) => {
- state.pinHash = action.payload
+ state.pinHash = action.payload;
},
setAppVersion: (state, action: PayloadAction) => {
- state.appVersion = action.payload
+ state.appVersion = action.payload;
+ },
+ setInheritanceRequestId: (state, action: PayloadAction) => {
+ state.inheritanceRequestId = action.payload;
},
- }
-})
+ },
+});
export const {
setAppId,
@@ -58,7 +63,8 @@ export const {
setPinResetCreds,
resetPinFailAttempts,
setPinHash,
- setAppVersion
-} = storageSlice.actions
+ setAppVersion,
+ setInheritanceRequestId,
+} = storageSlice.actions;
-export default storageSlice.reducer
+export default storageSlice.reducer;
diff --git a/src/store/reducers/vaults.ts b/src/store/reducers/vaults.ts
index d85ecd66d..31ebc1dc5 100644
--- a/src/store/reducers/vaults.ts
+++ b/src/store/reducers/vaults.ts
@@ -35,6 +35,7 @@ export type VaultState = {
sdIntroModal: boolean;
whirlpoolIntro: boolean;
tempShellId: string;
+ backupBSMSForIKS: boolean;
};
export type SignerUpdatePayload = {
@@ -57,6 +58,7 @@ const initialState: VaultState = {
sdIntroModal: true,
whirlpoolIntro: true,
tempShellId: null,
+ backupBSMSForIKS: false,
};
const vaultSlice = createSlice({
@@ -158,6 +160,9 @@ const vaultSlice = createSlice({
setTempShellId: (state, action: PayloadAction) => {
state.tempShellId = action.payload;
},
+ setBackupBSMSForIKS: (state, action: PayloadAction) => {
+ state.backupBSMSForIKS = action.payload;
+ },
},
extraReducers: (builder) => {
builder.addCase(ADD_SIGINING_DEVICE, (state) => {
@@ -182,6 +187,7 @@ export const {
clearSigningDevice,
resetVaultMigration,
setTempShellId,
+ setBackupBSMSForIKS,
} = vaultSlice.actions;
export default vaultSlice.reducer;
diff --git a/src/store/sagaActions/vaults.ts b/src/store/sagaActions/vaults.ts
index 0142fea18..cc61c2381 100644
--- a/src/store/sagaActions/vaults.ts
+++ b/src/store/sagaActions/vaults.ts
@@ -8,6 +8,7 @@ export const ADD_NEW_VAULT = 'ADD_NEW_VAULT';
export const ADD_SIGINING_DEVICE = 'ADD_SIGINING_DEVICE';
export const MIGRATE_VAULT = 'MIGRATE_VAULT';
export const FINALISE_VAULT_MIGRATION = 'FINALISE_VAULT_MIGRATION';
+export const FINALIZE_IK_SETUP = 'FINALIZE_IK_SETUP';
export const addNewVault = (payload: {
newVaultInfo: NewVaultInfo;
@@ -37,3 +38,8 @@ export const finaliseVaultMigration = (payload: string) => ({
type: FINALISE_VAULT_MIGRATION,
payload: { vaultId: payload },
});
+
+export const finaliseIKSetup = (vault: Vault) => ({
+ type: FINALIZE_IK_SETUP,
+ payload: { vault },
+});
diff --git a/src/store/sagas/bhr.ts b/src/store/sagas/bhr.ts
index 8757860b1..a094f82a5 100644
--- a/src/store/sagas/bhr.ts
+++ b/src/store/sagas/bhr.ts
@@ -241,7 +241,10 @@ function* getAppImageWorker({ payload }) {
const primarySeed = bip39.mnemonicToSeedSync(primaryMnemonic);
const appID = crypto.createHash('sha256').update(primarySeed).digest('hex');
const encryptionKey = generateEncryptionKey(primarySeed.toString('hex'));
- const { appImage, vaultImage, subscription, UTXOinfos } = yield call(Relay.getAppImage, appID);
+ const { appImage, vaultImage, subscription, UTXOinfos, labels } = yield call(
+ Relay.getAppImage,
+ appID
+ );
// applying the restore upgrade sequence if required
const previousVersion = appImage.version;
@@ -267,7 +270,8 @@ function* getAppImageWorker({ payload }) {
subscription,
appImage,
vaultImage,
- UTXOinfos
+ UTXOinfos,
+ labels
);
} else {
const plebSubscription = {
@@ -286,7 +290,8 @@ function* getAppImageWorker({ payload }) {
plebSubscription,
appImage,
vaultImage,
- UTXOinfos
+ UTXOinfos,
+ labels
);
}
}
@@ -307,7 +312,8 @@ function* recoverApp(
subscription,
appImage,
vaultImage,
- UTXOinfos
+ UTXOinfos,
+ labels
) {
const entropy = yield call(
BIP85.bip39MnemonicToEntropy,
@@ -352,6 +358,7 @@ function* recoverApp(
// Vault recreation
if (vaultImage) {
const vault = JSON.parse(decrypt(encryptionKey, vaultImage.vault));
+
yield call(dbManager.createObject, RealmSchema.Vault, vault);
}
@@ -360,6 +367,12 @@ function* recoverApp(
yield call(dbManager.createObjectBulk, RealmSchema.UTXOInfo, UTXOinfos);
}
yield put(setAppId(appID));
+
+ //Labels Restore
+ if (labels) {
+ yield call(dbManager.createObjectBulk, RealmSchema.Tags, labels);
+ }
+
// seed confirm for recovery
yield call(dbManager.createObject, RealmSchema.BackupHistory, {
title: BackupAction.SEED_BACKUP_CONFIRMED,
diff --git a/src/store/sagas/index.ts b/src/store/sagas/index.ts
index f6f5e9055..0e3805637 100644
--- a/src/store/sagas/index.ts
+++ b/src/store/sagas/index.ts
@@ -18,6 +18,7 @@ import {
addWhirlpoolWalletsWatcher,
addWhirlpoolWalletsLocalWatcher,
updateWalletPathAndPuposeDetailWatcher,
+ finaliseIKSetupWatcher,
} from './wallets';
import {
addUaiStackWatcher,
@@ -97,6 +98,7 @@ const rootSaga = function* () {
addSigningDeviceWatcher,
migrateVaultWatcher,
finaliseVaultMigrationWatcher,
+ finaliseIKSetupWatcher,
updateSignerDetails,
// send and receive
diff --git a/src/store/sagas/login.ts b/src/store/sagas/login.ts
index 5a3eefed3..018bb6c90 100644
--- a/src/store/sagas/login.ts
+++ b/src/store/sagas/login.ts
@@ -131,81 +131,77 @@ function* credentialsAuthWorker({ payload }) {
}
key = yield call(decrypt, hash, encryptedKey);
yield put(setKey(key));
- if (!key) throw new Error('Encryption key is missing');
- const uint8array = yield call(stringToArrayBuffer, key);
- yield call(dbManager.initializeRealm, uint8array);
yield put(setPinHash(hash));
- const previousVersion = yield select((state) => state.storage.appVersion);
- const newVersion = DeviceInfo.getVersion();
- const versionCollection = yield call(dbManager.getCollection, RealmSchema.VersionHistory);
- const lastElement = versionCollection[versionCollection.length - 1];
- const lastVersionCode = lastElement.version.split(/[()]/);
- const currentVersionCode = DeviceInfo.getBuildNumber();
- console.log({ previousVersion, newVersion });
- if (semver.lt(previousVersion, newVersion)) {
- yield call(applyUpgradeSequence, { previousVersion, newVersion });
- } else if (currentVersionCode !== lastVersionCode[1]) {
- yield call(dbManager.createObject, RealmSchema.VersionHistory, {
- version: `${newVersion}(${currentVersionCode})`,
- releaseNote: '',
- date: new Date().toString(),
- title: `Upgraded from ${lastVersionCode[1]} to ${currentVersionCode}`,
- });
- }
- } catch (err) {
- if (payload.reLogin) {
- // yield put(switchReLogin(false));
- } else yield put(credsAuthenticated(false));
- return;
- }
-
- yield put(setKey(key));
-
- if (!payload.reLogin) {
+ if (!key) throw new Error('Encryption key is missing');
// case: login
- if (appId) {
- try {
- const { id, publicId, subscription }: KeeperApp = yield call(
- dbManager.getObjectByIndex,
- RealmSchema.KeeperApp
- );
- const response = yield call(Relay.verifyReceipt, id, publicId);
- yield put(credsAuthenticated(true));
- yield put(setKey(key));
+ if (!payload.reLogin) {
+ const uint8array = yield call(stringToArrayBuffer, key);
+ yield call(dbManager.initializeRealm, uint8array);
+
+ const previousVersion = yield select((state) => state.storage.appVersion);
+ const newVersion = DeviceInfo.getVersion();
+ const versionCollection = yield call(dbManager.getCollection, RealmSchema.VersionHistory);
+ const lastElement = versionCollection[versionCollection.length - 1];
+ const lastVersionCode = lastElement.version.split(/[()]/);
+ const currentVersionCode = DeviceInfo.getBuildNumber();
+ if (semver.lt(previousVersion, newVersion)) {
+ yield call(applyUpgradeSequence, { previousVersion, newVersion });
+ } else if (currentVersionCode !== lastVersionCode[1]) {
+ yield call(dbManager.createObject, RealmSchema.VersionHistory, {
+ version: `${newVersion}(${currentVersionCode})`,
+ releaseNote: '',
+ date: new Date().toString(),
+ title: `Upgraded from ${lastVersionCode[1]} to ${currentVersionCode}`,
+ });
+ }
+ if (appId) {
+ try {
+ const { id, publicId, subscription }: KeeperApp = yield call(
+ dbManager.getObjectByIndex,
+ RealmSchema.KeeperApp
+ );
+ const response = yield call(Relay.verifyReceipt, id, publicId);
+ yield put(credsAuthenticated(true));
+ yield put(setKey(key));
- const history = yield call(dbManager.getCollection, RealmSchema.BackupHistory);
- yield put(setWarning(history));
+ const history = yield call(dbManager.getCollection, RealmSchema.BackupHistory);
+ yield put(setWarning(history));
- yield put(fetchExchangeRates());
- yield put(getMessages());
- yield put(
- uaiChecks([
- uaiType.SIGNING_DEVICES_HEALTH_CHECK,
- uaiType.SECURE_VAULT,
- uaiType.VAULT_MIGRATION,
- uaiType.DEFAULT,
- ])
- );
- yield put(resetSyncing());
- yield call(generateSeedHash);
- yield put(setRecepitVerificationFailed(!response.isValid));
- if (subscription.level === 1 && subscription.name === 'Hodler') {
- yield put(setRecepitVerificationFailed(true));
- } else if (subscription.level === 2 && subscription.name === 'Diamond Hands') {
- yield put(setRecepitVerificationFailed(true));
- } else if (subscription.level !== response.level) {
- yield put(setRecepitVerificationFailed(true));
+ yield put(fetchExchangeRates());
+ yield put(getMessages());
+ yield put(
+ uaiChecks([
+ uaiType.SIGNING_DEVICES_HEALTH_CHECK,
+ uaiType.SECURE_VAULT,
+ uaiType.VAULT_MIGRATION,
+ uaiType.DEFAULT,
+ ])
+ );
+ yield put(resetSyncing());
+ yield call(generateSeedHash);
+ yield put(setRecepitVerificationFailed(!response.isValid));
+ if (subscription.level === 1 && subscription.name === 'Hodler') {
+ yield put(setRecepitVerificationFailed(true));
+ } else if (subscription.level === 2 && subscription.name === 'Diamond Hands') {
+ yield put(setRecepitVerificationFailed(true));
+ } else if (subscription.level !== response.level) {
+ yield put(setRecepitVerificationFailed(true));
+ }
+ yield put(connectToNode());
+ } catch (error) {
+ yield put(setRecepitVerificationError(true));
+ // yield put(credsAuthenticated(false));
+ console.log(error);
}
- } catch (error) {
- yield put(setRecepitVerificationError(true));
- // yield put(credsAuthenticated(false));
- console.log(error);
- }
+ } else yield put(credsAuthenticated(true));
} else yield put(credsAuthenticated(true));
- } else yield put(credsAuthenticated(true));
-
- yield put(connectToNode());
+ } catch (err) {
+ if (payload.reLogin) {
+ yield put(credsAuthenticated(false));
+ // yield put(switchReLogin(false));
+ } else yield put(credsAuthenticated(false));
+ }
}
export const credentialsAuthWatcher = createWatcher(credentialsAuthWorker, CREDS_AUTH);
diff --git a/src/store/sagas/notifications.ts b/src/store/sagas/notifications.ts
index 0039d6df5..39557e9a2 100644
--- a/src/store/sagas/notifications.ts
+++ b/src/store/sagas/notifications.ts
@@ -16,6 +16,11 @@ import {
UPDATE_MESSAGES_STATUS,
} from '../sagaActions/notifications';
import { createWatcher } from '../utilities';
+import dbManager from 'src/storage/realm/dbManager';
+import { RealmSchema } from 'src/storage/realm/enum';
+import { UAI, uaiType } from 'src/common/data/models/interfaces/Uai';
+import { addToUaiStack } from '../sagaActions/uai';
+import { setRefreshUai } from '../reducers/uai';
function* updateFCMTokensWorker({ payload }) {
try {
@@ -51,13 +56,52 @@ export const fetchNotificationsWatcher = createWatcher(
FETCH_NOTIFICATIONS
);
+export function* notficationsToUAI(messages) {
+ for (const message of messages) {
+ if (message.additionalInfo !== null && typeof message.additionalInfo === 'object') {
+ if (
+ message.additionalInfo.type === uaiType.IKS_REQUEST &&
+ message.additionalInfo.reqId !== null
+ ) {
+ const uais = dbManager.getObjectByField(
+ RealmSchema.UAI,
+ message.additionalInfo.reqId,
+ 'entityId'
+ );
+ if (!uais.length) {
+ yield put(
+ addToUaiStack({
+ title: `There is a request for your Inheritance Key. Please review`,
+ isDisplay: true,
+ uaiType: uaiType.IKS_REQUEST,
+ prirority: 100,
+ entityId: message.additionalInfo.reqId,
+ displayText:
+ 'There is a request by someone for accessing the Inheritance Key you have set up using this app',
+ })
+ );
+ } else {
+ const uai = uais[0];
+ let updatedUai: UAI = JSON.parse(JSON.stringify(uai)); // Need to get a better way
+ updatedUai = {
+ ...updatedUai,
+ isActioned: false,
+ };
+ yield call(dbManager.updateObjectById, RealmSchema.UAI, updatedUai.id, updatedUai);
+ }
+ yield put(setRefreshUai());
+ }
+ }
+ }
+}
+
export function* getMessageWorker() {
yield put(fetchNotificationStarted(true));
const storedMessages = yield select((state) => state.notifications.messages);
const appId = yield select((state: RootState) => state.storage.appId);
const timeStamp = yield select((state) => state.notifications.timeStamp);
-
const { messages } = yield call(Relay.getMessages, appId, timeStamp);
+
if (!storedMessages) return;
const newMessageArray = storedMessages.concat(
messages.filter(
@@ -88,7 +132,7 @@ export function* getMessageWorker() {
// })
// );
// }
-
+ yield call(notficationsToUAI, messages);
yield put(messageFetched(newMessageArray));
yield put(storeMessagesTimeStamp());
yield put(fetchNotificationStarted(false));
diff --git a/src/store/sagas/uai.ts b/src/store/sagas/uai.ts
index 35bc974dc..115d2bbe0 100644
--- a/src/store/sagas/uai.ts
+++ b/src/store/sagas/uai.ts
@@ -147,7 +147,7 @@ function* uaiChecksWorker({ payload }) {
} else {
yield put(
addToUaiStack({
- title: `Transfer fund to vault for ${wallet.presentationData.name}`,
+ title: `Transfer fund to Vault from ${wallet.presentationData.name}`,
isDisplay: false,
uaiType: uaiType.VAULT_TRANSFER,
prirority: 80,
diff --git a/src/store/sagas/wallets.ts b/src/store/sagas/wallets.ts
index 8e13f0a6e..a7e0061ca 100644
--- a/src/store/sagas/wallets.ts
+++ b/src/store/sagas/wallets.ts
@@ -5,12 +5,18 @@
import {
DerivationPurpose,
EntityKind,
+ SignerType,
VaultMigrationType,
VaultType,
VisibilityType,
WalletType,
} from 'src/core/wallets/enums';
-import { SignerException, SignerRestriction } from 'src/core/services/interfaces';
+import {
+ InheritanceConfiguration,
+ InheritancePolicy,
+ SignerException,
+ SignerRestriction,
+} from 'src/core/services/interfaces';
import { Vault, VaultScheme, VaultSigner } from 'src/core/wallets/interfaces/vault';
import {
TransferPolicy,
@@ -50,6 +56,8 @@ import {
ELECTRUM_NOT_CONNECTED_ERR,
ELECTRUM_NOT_CONNECTED_ERR_TOR,
} from 'src/core/services/electrum/client';
+import InheritanceKeyServer from 'src/core/services/operations/InheritanceKey';
+import { genrateOutputDescriptors } from 'src/core/utils';
import { RootState } from '../store';
import {
addSigningDevice,
@@ -80,7 +88,9 @@ import {
ADD_NEW_VAULT,
ADD_SIGINING_DEVICE,
FINALISE_VAULT_MIGRATION,
+ FINALIZE_IK_SETUP,
MIGRATE_VAULT,
+ finaliseIKSetup,
} from '../sagaActions/vaults';
import { uaiChecks } from '../sagaActions/uai';
import { updateAppImageWorker, updateVaultImageWorker } from './bhr';
@@ -519,6 +529,7 @@ function* addNewVaultWorker({
yield put(vaultCreated({ hasNewVaultGenerationSucceeded: true }));
yield put(relayVaultUpdateSuccess());
+ yield put(finaliseIKSetup(vault)); // update IKS, if inheritance key has been added
return true;
}
throw new Error('Relay updation failed');
@@ -616,6 +627,90 @@ export const finaliseVaultMigrationWatcher = createWatcher(
FINALISE_VAULT_MIGRATION
);
+function* finaliseIKSetupWorker({ payload }: { payload: { vault: Vault } }) {
+ // finalise the IK setup
+ const { vault } = payload;
+ const [ikSigner] = vault.signers.filter((signer) => signer.type === SignerType.INHERITANCEKEY);
+ const backupBSMSForIKS = yield select((state: RootState) => state.vault.backupBSMSForIKS);
+
+ if (!ikSigner) return;
+ let updatedIkSigner: VaultSigner = null;
+
+ if (ikSigner.inheritanceKeyInfo) {
+ // case: updating config for this new vault which already had IKS as one of its signers
+ const existingConfiguration = ikSigner.inheritanceKeyInfo.configuration;
+ const existingThresholdDescriptors = existingConfiguration.descriptors.slice(0, 2);
+
+ const newIKSConfiguration: InheritanceConfiguration = {
+ m: vault.scheme.m,
+ n: vault.scheme.n,
+ descriptors: vault.signers.map((signer) => signer.signerId),
+ bsms: backupBSMSForIKS ? genrateOutputDescriptors(vault) : null,
+ };
+
+ const { updated } = yield call(
+ InheritanceKeyServer.updateInheritanceConfig,
+ vault.shellId,
+ existingThresholdDescriptors,
+ newIKSConfiguration
+ );
+
+ if (updated) {
+ updatedIkSigner = {
+ ...ikSigner,
+ inheritanceKeyInfo: {
+ ...ikSigner.inheritanceKeyInfo,
+ configuration: newIKSConfiguration,
+ },
+ };
+ } else Alert.alert('Failed to update the inheritance key configuration');
+ } else {
+ // case: setting up a vault w/ IKS for the first time
+ const config: InheritanceConfiguration = {
+ m: vault.scheme.m,
+ n: vault.scheme.n,
+ descriptors: vault.signers.map((signer) => signer.signerId),
+ bsms: backupBSMSForIKS ? genrateOutputDescriptors(vault) : null,
+ };
+
+ const fcmToken = yield select((state: RootState) => state.notifications.fcmToken);
+ const policy: InheritancePolicy = {
+ notification: { targets: [fcmToken] },
+ };
+
+ const { setupSuccessful } = yield call(
+ InheritanceKeyServer.finalizeIKSetup,
+ vault.shellId,
+ config,
+ policy
+ );
+
+ if (setupSuccessful) {
+ updatedIkSigner = {
+ ...ikSigner,
+ inheritanceKeyInfo: {
+ configuration: config,
+ policy,
+ },
+ };
+ } else Alert.alert('Failed to finalise the inheritance key setup');
+ }
+
+ if (updatedIkSigner) {
+ // send updates to realm
+ const updatedSigners = vault.signers.map((signer) => {
+ if (signer.type === SignerType.INHERITANCEKEY) return updatedIkSigner;
+ return signer;
+ });
+
+ yield call(dbManager.updateObjectById, RealmSchema.Vault, vault.id, {
+ signers: updatedSigners,
+ });
+ }
+}
+
+export const finaliseIKSetupWatcher = createWatcher(finaliseIKSetupWorker, FINALIZE_IK_SETUP);
+
function* syncWalletsWorker({
payload,
}: {
diff --git a/src/utils/GenerateLetterToAtternyPDF.ts b/src/utils/GenerateLetterToAtternyPDF.ts
new file mode 100644
index 000000000..ebe687ab4
--- /dev/null
+++ b/src/utils/GenerateLetterToAtternyPDF.ts
@@ -0,0 +1,59 @@
+import RNHTMLtoPDF from 'react-native-html-to-pdf';
+
+const GenerateLetterToAtternyPDF = async (fingerPrints) => {
+ try {
+ const html = `
+
+
+
+
+
+
Letter to the Attorney
+
This document is one of three Inheritance Planning documents provided by Keeper. The other 2 being Key Security Tips and Restoring Inheritance Vault. This document is auto-produced by the Bitcoin Keeper app. The data shared in this document is sensitive. Please be cautious about revealing part or all of its contents to anyone. To learn more, please visit bitcoinkeeper.app
+
Subject: Bitcoin Bequest Information for Inclusion in My Will
+
Dear _________________ ,
+
I hope this letter finds you well. I am writing to provide you with the necessary information to include my bitcoin holdings in my estate plan. As a significant proportion of my wealth is held in bitcoin, it is crucial to address the legal transfer of these assets appropriately.
+
Below, I have outlined the specific details regarding my current bitcoin holdings:
+
1. Bitcoin Key Information:
+
${fingerPrints.map((keys, index) => `
Key ${index + 1} Master Fingerprint: ${keys}
`).join("")}
+
These master fingerprints act as unique identifiers for the respective keys without revealing sensitive details. Following the BIP32 (Bitcoin Improvement Proposal 32) standard, each fingerprint helps identify the associated extended public key (xPub). The xPub serves as a distinct identifier that can be utilized by a digital asset expert or software, adhering to standard BIP32 derivation paths, to locate and validate the keys during the transfer process.
+
Please note that the funds associated with these keys may be held in any combination of single-key or multi-signature (multisig) wallets. Regardless of the specific configuration, I intend that any wealth controlled by these keys be legally transferred to the designated heir or intended beneficiary.
+
My explicit intention is to transfer the legal title to my bitcoin holdings to the designated heir or intended beneficiary. However, it is important to note that access to the actual keys and the bitcoin will be provided separately to the intended beneficiary. This letter solely addresses the transfer of legal title and the inclusion of my bitcoin assets in my estate plan.
+
These master fingerprints and the accompanying explanation of their usage can be recorded in my will if it is of any help. Further details regarding the designated beneficiary, executor, and supplementary instructions will be provided separately during our estate planning discussions. I have complete faith in your expertise to handle this confidential information securely. Please do not hesitate to contact me if you require additional documentation or information. Your support and meticulous attention to detail in facilitating the transfer of the legal title to my bitcoin holdings are greatly appreciated.
+
Thank you for your professional assistance in preparing my estate plan and ensuring the proper transfer of the legal title to my bitcoin assets according to my wishes.
This document is one of three Inheritance Planning documents provided by Keeper. The other 2 being Key Security Tips and Restoring Inheritance Vault. This document is auto-produced by the Bitcoin Keeper app. The data shared in this document is sensitive. Please be cautious about revealing part or all of its contents to anyone. To learn more, please visit bitcoinkeeper.app
Vault Recovery Instructions with or without the Keeper App:
+
This document is one of three Inheritance Planning documents provided by Keeper. The other 2 are Letter to the Attorney and Key Security Tips. This document is auto-produced by the Bitcoin Keeper app. The data shared in this document is sensitive. Please be cautious about revealing part or all of its contents to anyone. To learn more, please visit bitcoinkeeper.app.
+
Getting Started:
+
An m-of-n multisig setup enhances security by distributing control and access, thus reducing the risk of unauthorized access or fraudulent activities. The bitcoin you inherited is within such a setup, i.e. the Vault. This document provides the method to recover the Vault and gain custody of your Inheritance.
+
If you have not previously handled bitcoin, we highly recommend contacting the people mentioned in the “Technical Assistance” section below.
+
Please note that failure to recover the Vault successfully may result in the loss of bitcoin forever. Great caution is advised.
+
Technical Assistance:
+
Recovering a multi-sig vault has a few steps involved. If you need assistance, you could reach out to any of the people in the list below for help. They are the trusted contacts of the person giving away the bitcoin (You do not have to share the keys with them)
+
Person 1:
+
Name:
+
Ph. No.:
+
Alt. Ph. No.:
+
Email:
+
Home Address:
+
Office Address:
+
Person 2:
+
Name:
+
Ph. No.:
+
Alt. Ph. No.:
+
Email:
+
Home Address:
+
Office Address:
+
Person 3:
+
Name:
+
Ph. No.:
+
Alt. Ph. No.:
+
Email:
+
Home Address:
+
Office Address:
+
Person 4:
+
Name:
+
Ph. No.:
+
Alt. Ph. No.:
+
Email:
+
Home Address:
+
Office Address:
+
Keeper Customer Support:
+
Telegram: https://t.me/bitcoinkeeper
+
Twitter: https://twitter.com/bitcoinkeeper_
+
Email: info@bithyve.com
+
Restoring the Vault:
+
We have attached the Output Descriptor file as an Annexure to this document. To recover the Vault, please input the Output Descriptor file in a wallet (such as Electrum or Sparrow) that supports a multi-sig setup. You could, of course, use Keeper to recover the Vault, but it’s not necessary that you do. Look for the “Recovery” button/section when setting up a wallet. Follow the steps from there.
+
Please note that the funds associated with these keys may be in any combination of single-key or multi-signature (multisig) wallets.
This document is one of three Inheritance Planning documents provided by Keeper. The other 2 are Letter to the Attorney and Key Security Tips. This document is auto-produced by the Bitcoin Keeper app. The data shared in this document is sensitive. Please be cautious about revealing part or all of its contents to anyone. To learn more, please visit bitcoinkeeper.app.
This document is one of three Inheritance Planning documents provided by Keeper. The other 2 being: Letter to the Attorney and Recovery Instructions for your heir. This document is auto-produced by the Bitcoin Keeper app. To learn more visit bitcoinkeeper.app
+
Getting Started:
+
A multisig enhances wallet security by distributing control and access, thus reducing the risk of unauthorized access, fraudulent activities, and loss of funds. The bitcoin your heir would inherit is within such a setup, i.e. your Vault. This document offers suggestions for storing keys and access & recovery mechanisms safely so that your intended beneficiary* can easily access your bitcoin when needed.
+
Also, the following information is only meant to help you start planning your bitcoin inheritance and should be considered as partial information for your bitcoin inheritance planning. We recommend you also speak with your estate planners to customize a plan that works best for you and to ensure that legal title also passes to your heir(s).
+
*Please note that the term intended beneficiary is not being used in legal terminology.
+
Checklist before storing away your keys:
+
Setup
+
+
Initialize devices and update firmware
+
Save one seed phrase in a tamper-evident bag
+
Record the tamper-evident bag serial number in a password manager
+
Record device PIN(s) in a password manager
+
Connect devices to BitcoinKeeper
+
+
Testing
+
+
Receive a transaction
+
Send a transaction
+
Complete a health check on all devices
+
+
Some prompts to consider a place safe:
+
+
Please ensure that the safe place is easily accessible to your heir
+
The place should not be prone to natural calamities like floods, earthquakes, etc.
+
Keep your key in a place away from fire, water, moisture, corroding elements, electromagnets, etc.
+
Please consider the political situation in the area being designated safe. The safe place should not be rendered inaccessible due to political turmoil in the region.
+
Make sure that the key is away from children and pets!
+
+
Key Storage: We’ve identified five places where you could potentially store your keys. The remainder of the document offers cautions and considerations as you decide how and where to store them.
+
1) Safety Deposit Box:
+
+
Your Safety Deposit Box should be accessible 24/7/365.
+
Make sure that the nominee of the locker is the same as the heir for your bitcoin so that they can access it after you’ve moved on.
+
Keep different keys in different locations. You should never have two hardware wallets in the same place simultaneously.
+
You may consider storing the key in a deposit box with a larger bank than a smaller one.
+
Please ensure that the deposit box is at a bank branch that’s in a relatively safe neighbourhood.
+
Ask the bank, if they go under, whether you would still have access to the deposit box seamlessly. There may be a possibility for the safety box to be frozen and thus inaccessible if the bank goes under. If the safety deposit box may be frozen, then you should know for how long. Try to get commitments from the bank on paper regarding this.
+
Which bank personnel would have access to the deposit box under extraordinary circumstances? It is recommended to get their details with relevant commitments to you, the lessee of the deposit box.
+
Do consider the state of affairs in the country where you’re leasing the deposit box.
+
Keep your device in a Faraday bag to prevent it from being compromised by EMP’s and scanning devices.
+
+
2) Soft Keys
+
+
Make sure that you’ve not used the phone(s) with the soft keys to access untrustworthy websites and apps.
+
Make sure that your wifi is private/ trustworthy.
+
It is recommended that you do not use the phone with the soft keys for day-to-day use.
+
Please store the device with the soft keys away from elements like moisture, heat, magnetic fields, etc.
+
It is recommended that you store an extra charger with the device for the heir. It is better that you do it rather than depend on the heir as the charger may stop being manufactured, the technology may change, etc.
+
It is recommended that you instruct your heir not to perform an OS update on the phone as the latest OS may not be compatible with the latest version of Keeper.
+
If you plan to store the software key in a device for years on end, please consider the possibility that the battery in the device may degrade and stop working. Make sure to check on the device at least twice a year to see if its functional.
+
+
3) With a trusted contact:
+
+
Ensure that they are truly trustworthy.
+
They should be happy for the heir to inherit the wealth and not hinder the transfer.
+
They should not have access to the other keys. They should know this and be ok with this.
+
Consider the possibility of their death as well (natural and unnatural). Who transfers the trusted contact’s key to your heir, then?
+
If the trusted contact breaks trust or your relationship sours, please consider resetting the quorum of the signing keys. This way, you will render the key with the formerly trusted contact obsolete.
+
Your trusted contact should be able to transfer the key to your heir as soon as possible in the event of your demise.
+
+
4) With your Executor, Trustee, or Agent under Power of Attorney:
+
+
Ensure that you trust them, as they will have access to your bitcoin. Conduct thorough research and consider their reputation, credentials, and experience before entrusting them with this responsibility.
+
Ensure that this trusted person/company has robust security measures in place to protect your bitcoin keys from potential hacks, theft, or loss. Please inquire about the security measures they’ve instituted to ensure the safety of your keys. If you’re dissatisfied, you could request them to upgrade security or perhaps look for another entity to help you.
+
It is essential that the person in this capacity has a solid understanding of how Bitcoin works and the intricacies of handling private keys. They should be familiar with wallet management, transaction processes, and recovery procedures in case of unforeseen circumstances.
+
To tackle unexpected circumstances, a well-defined backup and recovery plan should be in place. This ensures that your heir can access your Bitcoin keys without any complications. Ensure your team has outlined a clear process for securely transferring the keys.
+
Regular Communication: Maintain open and regular communication with your executor/trustee/agent under PoA. Stay informed about any changes in procedures, security measures, or regulations they may face/undertake, that may impact your keys. This ensures that you stay up-to-date and can make informed decisions regarding your estate planning.
+
+
5) With your heir:
+
+
Ensure that your heir has a solid understanding of how Bitcoin works, including key management, wallet security, and transaction processes. Please provide them with resources or educational materials to help them navigate the intricacies of holding and managing Bitcoin.
+
Emphasize the importance of strong security practices to your heir. Please encourage them to use secure wallets, enable two-factor authentication (2FA), regularly update software, and avoid sharing sensitive information related to the Bitcoin keys. Additionally, advise them to be cautious of phishing attempts, scams, and malicious actors who may attempt to steal their Bitcoin.
+
Instruct your heir on the importance of maintaining secure backups of the Bitcoin keys. This includes securely storing backup copies of the private keys or seed phrases in offline locations and keeping them confidential. In case of loss, theft, or damage to the keys, having proper backups will ensure that your heir can still access the inherited Bitcoin.
+
Legal and Tax Implications: Seek legal advice to ensure that your estate planning and the transfer of Bitcoin to your heir align with applicable laws and regulations. Cryptocurrency regulations vary across jurisdictions, and it's essential to be aware of any tax obligations or legal requirements that may arise. Consulting with a lawyer experienced in cryptocurrency and estate planning will help ensure compliance.
+
Regular Updates and Communication: Maintain open communication with your heir about your Bitcoin holdings and any updates or changes you make to your estate plan. Inform them about any changes in security measures, or relevant instructions they should be aware of. This will help avoid confusion or potential loss of access in the future.
+
Contingency Planning: Prepare for unforeseen circumstances by having contingency plans in place. Consider appointing alternate beneficiaries or trusted individuals who can assist your heir if they cannot fulfil their responsibilities.
+
+
Storing keys comes with the responsibility of storing pins and recovery phrases. Here are a few suggestions to store them safely over the long term:
+
Access mechanisms: Pins
+
A PIN (Personal Identification Number) is a security measure to protect access to the wallet and its associated funds. It is typically a numeric code, similar to a password, that must be entered into the hardware wallet's interface to access the stored Bitcoin.
+
The primary purpose of a PIN is to ensure that only the authorized user can access and control the funds stored in the hardware wallet. By requiring a PIN, hardware wallets provide an additional layer of security against unauthorized access, even if the device itself is lost, stolen, or compromised.
+
Keeping the PIN safe is crucial because if it falls into the wrong hands, an attacker could potentially gain control of the Bitcoin stored on the hardware wallet. It is important to choose a PIN that is not easily guessable and not to share it with anyone. Additionally, it is recommended to avoid using obvious PINs like birthdates or repetitive numbers. Furthermore, consider storing the PIN separately from the hardware wallet and other associated information to prevent a single point of failure. However, you can consider using the same pin for all your devices. An important point of caution is to keep things simple!
+
By diligently protecting the PIN, users can significantly reduce the risk of unauthorized access and ensure the security of their Bitcoin holdings. Thus, ensuring pins' safety is step one in safeguarding your keys. Please consider using custom-made steel plates to note them down as they better resist degradation compared to writing/typing them on paper.
+
As an additional security step, consider storing your Pin in a Password Manager as well.
+
+
In case you change your PIN(s), please ensure that you change them at the place where you stored your previous pin as well. This is important as failing to do so may lead to the inaccessibility of keys.
+
Backup mechanism: Recovery phrases
+
Recovery phrases come into play when a key becomes inaccessible. Some of the reasons for inaccessibility may be the degradation of devices storing the keys or the souring of relationships with people entrusted with them. Thus it is important that you not only backup the device storing a key properly, but also test out your recovery mechanism. Once satisfied, please consider etching the seed words onto stainless steel plates for longevity. Please consider etching the seed words of your hardware wallets onto steel plates and storing them in tamper-evident bags.
+
When you are satisfied with the arrangements you’ve made to store access and backup mechanisms, a point to decide is whether you want to store them along with the devices that have your keys or store them separately. This is an important decision and should be taken carefully.
+
To be avoided:
+
Treasure hunts: Please do not simply share keys with people you trust in your lifetime. They may collude and access the Vault after you pass away.
+
Photographing things: Photographs of seed words and pins may give unintended access to your private keys. Please avoid photographing them.
+
Unprotected Storage: Avoid storing your pins/seed words in random files that could be easily accessed or hacked.
+
Entrusting all your keys to one person or storing them all in one place: This action beats the purpose of a multi-sig setup and creates a single point of failure.
+
Closing Notes:
+
+
Don’t fret and overcomplicate your setup.
+
Test your setup
+
Avoid single points of failure
+
Access your setup multiple times during your lifetime. Once a year at least.
+
If you change something somewhere, like a device or a pin, please update the change in relevant places.
This document is one of three Inheritance Planning documents provided by Keeper. The other 2 being: Letter to the Attorney and Recovery Instructions for your heir. This document is auto-produced by the Bitcoin Keeper app. To learn more visit bitcoinkeeper.app.