diff --git a/android/app/src/main/java/it/airgap/vault/plugin/isolatedmodules/FileExplorer.kt b/android/app/src/main/java/it/airgap/vault/plugin/isolatedmodules/FileExplorer.kt
index d00a9e4b..e010374d 100644
--- a/android/app/src/main/java/it/airgap/vault/plugin/isolatedmodules/FileExplorer.kt
+++ b/android/app/src/main/java/it/airgap/vault/plugin/isolatedmodules/FileExplorer.kt
@@ -14,7 +14,12 @@ interface StaticSourcesExplorer {
fun readIsolatedModulesScript(): ByteArray
}
-interface DynamicSourcesExplorer {
+interface DynamicSourcesExplorer {
+ fun removeModules(identifiers: List)
+ fun removeAllModules()
+}
+
+interface SourcesExplorer {
fun listModules(): List
fun readModuleSources(module: M): Sequence
@@ -55,6 +60,14 @@ class FileExplorer private constructor(
}
}
+ fun removeInstalledModules(identifiers: List) {
+ filesExplorer.removeModules(identifiers)
+ }
+
+ fun removeAllInstalledModules() {
+ filesExplorer.removeAllModules()
+ }
+
fun readModuleSources(module: JSModule): Sequence =
when (module) {
is JSModule.Asset -> assetsExplorer.readModuleSources(module)
@@ -70,7 +83,7 @@ class FileExplorer private constructor(
}
private fun loadModules(
- explorer: DynamicSourcesExplorer,
+ explorer: SourcesExplorer,
constructor: (identifier: String, namespace: String?, preferredEnvironment: JSEnvironment.Type, paths: List) -> T,
): List = explorer.listModules().map { module ->
val manifest = JSObject(explorer.readModuleManifest(module).decodeToString())
@@ -96,7 +109,7 @@ class FileExplorer private constructor(
}
}
-private class AssetsExplorer(private val context: Context) : StaticSourcesExplorer, DynamicSourcesExplorer {
+private class AssetsExplorer(private val context: Context) : StaticSourcesExplorer, SourcesExplorer {
override fun readJavaScriptEngineUtils(): ByteArray = context.assets.readBytes(JAVA_SCRIPT_ENGINE_UTILS)
override fun readIsolatedModulesScript(): ByteArray = context.assets.readBytes(SCRIPT)
@@ -117,10 +130,20 @@ private class AssetsExplorer(private val context: Context) : StaticSourcesExplor
}
}
-private class FilesExplorer(private val context: Context) : DynamicSourcesExplorer {
+private class FilesExplorer(private val context: Context) : DynamicSourcesExplorer, SourcesExplorer {
private val modulesDir: File
get() = File(context.filesDir, MODULES_DIR)
+ override fun removeModules(identifiers: List) {
+ identifiers.forEach {
+ File(modulesDir, it).deleteRecursively()
+ }
+ }
+
+ override fun removeAllModules() {
+ modulesDir.deleteRecursively()
+ }
+
override fun listModules(): List = modulesDir.list()?.toList() ?: emptyList()
override fun readModuleSources(module: JSModule.Installed): Sequence =
diff --git a/android/app/src/main/java/it/airgap/vault/plugin/isolatedmodules/IsolatedModules.kt b/android/app/src/main/java/it/airgap/vault/plugin/isolatedmodules/IsolatedModules.kt
index ed1c7ac5..dd93052c 100644
--- a/android/app/src/main/java/it/airgap/vault/plugin/isolatedmodules/IsolatedModules.kt
+++ b/android/app/src/main/java/it/airgap/vault/plugin/isolatedmodules/IsolatedModules.kt
@@ -18,7 +18,7 @@ class IsolatedModules : Plugin() {
private val fileExplorer: FileExplorer by lazy { FileExplorer(context) }
@PluginMethod
- fun previewModule(call: PluginCall) {
+ fun previewDynamicModule(call: PluginCall) {
call.executeCatching {
assertReceived(Param.PATH, Param.DIRECTORY)
@@ -44,7 +44,7 @@ class IsolatedModules : Plugin() {
}
@PluginMethod
- fun registerModule(call: PluginCall) {
+ fun registerDynamicModule(call: PluginCall) {
call.executeCatching {
assertReceived(Param.IDENTIFIER, Param.PROTOCOL_IDENTIFIERS)
@@ -60,7 +60,26 @@ class IsolatedModules : Plugin() {
}
@PluginMethod
- fun loadModules(call: PluginCall) {
+ fun removeDynamicModules(call: PluginCall) {
+ activity.lifecycleScope.launch {
+ call.executeCatching {
+ val jsEvaluator = jsEvaluator.await()
+
+ identifiers?.let {
+ fileExplorer.removeInstalledModules(it)
+ jsEvaluator.deregisterModules(it)
+ } ?: run {
+ fileExplorer.removeAllInstalledModules()
+ jsEvaluator.deregisterAllModules()
+ }
+
+ resolve()
+ }
+ }
+ }
+
+ @PluginMethod
+ fun loadAllModules(call: PluginCall) {
activity.lifecycleScope.launch {
call.executeCatching {
val modules = fileExplorer.loadAssetModules() + fileExplorer.loadInstalledModules()
@@ -117,6 +136,9 @@ class IsolatedModules : Plugin() {
private val PluginCall.identifier: String
get() = getString(Param.IDENTIFIER)!!
+ private val PluginCall.identifiers: List?
+ get() = getArray(Param.PROTOCOL_IDENTIFIERS, null)?.toList()
+
private val PluginCall.protocolIdentifiers: List
get() = getArray(Param.PROTOCOL_IDENTIFIERS).toList()
@@ -145,6 +167,7 @@ class IsolatedModules : Plugin() {
const val PATH = "path"
const val DIRECTORY = "directory"
const val IDENTIFIER = "identifier"
+ const val IDENTIFIERS = "identifiers"
const val PROTOCOL_IDENTIFIERS = "protocolIdentifiers"
const val PROTOCOL_TYPE = "protocolType"
const val TARGET = "target"
diff --git a/android/app/src/main/java/it/airgap/vault/plugin/isolatedmodules/js/JSEvaluator.kt b/android/app/src/main/java/it/airgap/vault/plugin/isolatedmodules/js/JSEvaluator.kt
index ae31f440..d6b8566c 100644
--- a/android/app/src/main/java/it/airgap/vault/plugin/isolatedmodules/js/JSEvaluator.kt
+++ b/android/app/src/main/java/it/airgap/vault/plugin/isolatedmodules/js/JSEvaluator.kt
@@ -22,6 +22,14 @@ class JSEvaluator constructor(
module.registerFor(protocolIdentifiers)
}
+ fun deregisterModules(identifiers: List) {
+ identifiers.forEach { modules.remove(it) }
+ }
+
+ fun deregisterAllModules() {
+ modules.clear()
+ }
+
suspend fun evaluatePreviewModule(module: JSModule): JSObject =
module.environment.run(module, JSModuleAction.Load(null)).also {
module.appendType(it)
diff --git a/ios/App/App/IsolatedModules/FileExplorer.swift b/ios/App/App/IsolatedModules/FileExplorer.swift
index 96fa4ed9..4362c8a3 100644
--- a/ios/App/App/IsolatedModules/FileExplorer.swift
+++ b/ios/App/App/IsolatedModules/FileExplorer.swift
@@ -55,6 +55,14 @@ struct FileExplorer {
})
}
+ func removeModules(_ identifiers: [String]) throws {
+ try documentExplorer.removeModules(identifiers)
+ }
+
+ func removeAllModules() throws {
+ try documentExplorer.removeAllModules()
+ }
+
func readModuleSources(_ module: JSModule) throws -> [Data] {
switch module {
case .asset(let asset):
@@ -77,7 +85,7 @@ struct FileExplorer {
}
}
- private func loadModules(
+ private func loadModules(
using explorer: E,
creatingModuleWith moduleInit: (_ identifier: String, _ namespace: String?, _ preferredEnvironment: JSEnvironmentKind, _ sources: [String]) -> T
) throws -> [T] where E.T == T {
@@ -107,7 +115,7 @@ struct FileExplorer {
// MARK: AssetsExplorer
-private struct AssetsExplorer: DynamicSourcesExplorer {
+private struct AssetsExplorer: SourcesExplorer {
typealias T = JSModule.Asset
static let assetsURL: URL = Bundle.main.url(forResource: "public", withExtension: nil)!.appendingPathComponent("assets")
@@ -149,23 +157,49 @@ private struct AssetsExplorer: DynamicSourcesExplorer {
// MARK: DocumentExplorer
-private struct DocumentExplorer: DynamicSourcesExplorer {
+private struct DocumentExplorer: SourcesExplorer, DynamicSourcesExplorer {
typealias T = JSModule.Instsalled
private static let modulesDir: String = "protocol_modules"
private let fileManager: FileManager
+ private var documentsURL: URL? { fileManager.urls(for: .documentDirectory, in: .userDomainMask).first }
+ private var modulesDirURL: URL? { documentsURL?.appendingPathComponent(Self.modulesDir) }
+
init(fileManager: FileManager) {
self.fileManager = fileManager
}
+ func removeModules(_ identifiers: [String]) throws {
+ guard let modulesDirURL = modulesDirURL else {
+ return
+ }
+
+ var isDirectory: ObjCBool = true
+ try identifiers.forEach {
+ if fileManager.fileExists(atPath: modulesDirURL.path, isDirectory: &isDirectory) {
+ try fileManager.removeItem(at: modulesDirURL.appendingPathComponent($0))
+ }
+ }
+ }
+
+ func removeAllModules() throws {
+ var isDirectory: ObjCBool = true
+ guard let modulesDirURL = modulesDirURL, fileManager.fileExists(atPath: modulesDirURL.path, isDirectory: &isDirectory) else {
+ return
+ }
+
+ try fileManager.removeItem(at: modulesDirURL)
+ }
+
func listModules() throws -> [String] {
- guard let url = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
+ guard let modulesDirURL = modulesDirURL else {
return []
}
- let modulesDirPath = url.appendingPathComponent(Self.modulesDir).path
+ let modulesDirPath = modulesDirURL.path
+
guard fileManager.fileExists(atPath: modulesDirPath) else {
return []
}
@@ -194,9 +228,9 @@ private struct DocumentExplorer: DynamicSourcesExplorer {
}
}
-// MARK: DynamicSourcesExplorer
+// MARK: SourcesExplorer
-private protocol DynamicSourcesExplorer {
+private protocol SourcesExplorer {
associatedtype T
func listModules() throws -> [String]
@@ -205,6 +239,11 @@ private protocol DynamicSourcesExplorer {
func readModuleManifest(_ module: String) throws -> Data
}
+private protocol DynamicSourcesExplorer {
+ func removeModules(_ identifiers: [String]) throws
+ func removeAllModules() throws
+}
+
// MARK: Extensions
private extension FileExplorer {
diff --git a/ios/App/App/IsolatedModules/IsolatedModules.m b/ios/App/App/IsolatedModules/IsolatedModules.m
index de5aa560..aa716e93 100644
--- a/ios/App/App/IsolatedModules/IsolatedModules.m
+++ b/ios/App/App/IsolatedModules/IsolatedModules.m
@@ -10,8 +10,9 @@
// disable true isolation until it's production ready
//CAP_PLUGIN(IsolatedModules, "IsolatedModules",
-// CAP_PLUGIN_METHOD(previewModule, CAPPluginReturnPromise);
-// CAP_PLUGIN_METHOD(registerModule, CAPPluginReturnPromise);
-// CAP_PLUGIN_METHOD(loadModules, CAPPluginReturnPromise);
+// CAP_PLUGIN_METHOD(previewDynamicModule, CAPPluginReturnPromise);
+// CAP_PLUGIN_METHOD(registerDynamicModule, CAPPluginReturnPromise);
+// CAP_PLUGIN_METHOD(removeDynamicModules, CAPPluginReturnPromise);
+// CAP_PLUGIN_METHOD(loadAllModules, CAPPluginReturnPromise);
// CAP_PLUGIN_METHOD(callMethod, CAPPluginReturnPromise);
//)
diff --git a/ios/App/App/IsolatedModules/IsolatedModules.swift b/ios/App/App/IsolatedModules/IsolatedModules.swift
index f2212cc1..38dc786a 100644
--- a/ios/App/App/IsolatedModules/IsolatedModules.swift
+++ b/ios/App/App/IsolatedModules/IsolatedModules.swift
@@ -14,7 +14,7 @@ public class IsolatedModules: CAPPlugin {
private let fileExplorer: FileExplorer = .shared
private lazy var jsEvaluator: JSEvaluator = .init(fileExplorer: fileExplorer)
- @objc func previewModule(_ call: CAPPluginCall) {
+ @objc func previewDynamicModule(_ call: CAPPluginCall) {
call.assertReceived(forMethod: "previewModule", requiredParams: Param.PATH, Param.DIRECTORY)
do {
@@ -41,7 +41,7 @@ public class IsolatedModules: CAPPlugin {
}
}
- @objc func registerModule(_ call: CAPPluginCall) {
+ @objc func registerDynamicModule(_ call: CAPPluginCall) {
call.assertReceived(forMethod: "registerModule", requiredParams: Param.IDENTIFIER, Param.PROTOCOL_IDENTIFIERS)
do {
@@ -64,7 +64,24 @@ public class IsolatedModules: CAPPlugin {
}
}
- @objc func loadModules(_ call: CAPPluginCall) {
+ @objc func removeDynamicModules(_ call: CAPPluginCall) {
+ Task {
+ do {
+ if let identifiers = call.identifiers {
+ try fileExplorer.removeModules(identifiers)
+ await jsEvaluator.deregisterModules(identifiers)
+ } else {
+ try fileExplorer.removeAllModules()
+ await jsEvaluator.deregisterAllModules()
+ }
+ call.resolve()
+ } catch {
+ call.reject("Error: \(error)")
+ }
+ }
+ }
+
+ @objc func loadAllModules(_ call: CAPPluginCall) {
Task {
do {
let protocolType = call.protocolType
@@ -152,6 +169,7 @@ public class IsolatedModules: CAPPlugin {
static let PATH = "path"
static let DIRECTORY = "directory"
static let IDENTIFIER = "identifier"
+ static let IDENTIFIERS = "identifiers"
static let PROTOCOL_IDENTIFIERS = "protocolIdentifiers"
static let PROTOCOL_TYPE = "protocolType"
static let TARGET = "target"
@@ -176,6 +194,15 @@ private extension CAPPluginCall {
}
var identifier: String? { return getString(IsolatedModules.Param.IDENTIFIER) }
+ var identifiers: [String]? {
+ return getArray(IsolatedModules.Param.IDENTIFIERS)?.compactMap {
+ if let string = $0 as? String {
+ return string
+ } else {
+ return nil
+ }
+ }
+ }
var protocolIdentifiers: [String]? {
return getArray(IsolatedModules.Param.PROTOCOL_IDENTIFIERS)?.compactMap {
if let string = $0 as? String {
diff --git a/ios/App/App/IsolatedModules/JS/JSEvaluator.swift b/ios/App/App/IsolatedModules/JS/JSEvaluator.swift
index 2ca510f5..1957ad8f 100644
--- a/ios/App/App/IsolatedModules/JS/JSEvaluator.swift
+++ b/ios/App/App/IsolatedModules/JS/JSEvaluator.swift
@@ -22,6 +22,14 @@ class JSEvaluator {
await modulesManager.registerModule(module, forProtocols: protocolIdentifiers)
}
+ func deregisterModules(_ identifiers: [String]) async {
+ await modulesManager.deregisterModules(identifiers)
+ }
+
+ func deregisterAllModules() async {
+ await modulesManager.deregisterAllModules()
+ }
+
func evaluatePreviewModule(_ module: JSModule) async throws -> [String: Any] {
return try await self.webViewEnv.run(.load(.init(protocolType: nil)), in: module)
}
@@ -146,6 +154,14 @@ class JSEvaluator {
modules[module.identifier] = module
protocolIdentifiers.forEach { identifier in modules[identifier] = module }
}
+
+ func deregisterModules(_ identifiers: [String]) {
+ identifiers.forEach { modules.removeValue(forKey: $0) }
+ }
+
+ func deregisterAllModules() {
+ modules.removeAll()
+ }
}
enum Error: Swift.Error {
diff --git a/package.json b/package.json
index 0fc89176..052c956a 100644
--- a/package.json
+++ b/package.json
@@ -37,45 +37,45 @@
"apply-diagnostic-modules": "node apply-diagnostic-modules.js"
},
"resolutions": {
- "@airgap/aeternity": "0.13.12",
- "@airgap/astar": "0.13.12",
- "@airgap/bitcoin": "0.13.12",
- "@airgap/coinlib-core": "0.13.12",
- "@airgap/coreum": "0.13.12",
- "@airgap/cosmos": "0.13.12",
- "@airgap/cosmos-core": "0.13.12",
- "@airgap/crypto": "0.13.12",
- "@airgap/ethereum": "0.13.12",
- "@airgap/groestlcoin": "0.13.12",
- "@airgap/icp": "0.13.12",
- "@airgap/module-kit": "0.13.12",
- "@airgap/moonbeam": "0.13.12",
- "@airgap/polkadot": "0.13.12",
- "@airgap/serializer": "0.13.12",
- "@airgap/substrate": "0.13.12",
- "@airgap/tezos": "0.13.12"
+ "@airgap/aeternity": "0.13.15",
+ "@airgap/astar": "0.13.15",
+ "@airgap/bitcoin": "0.13.15",
+ "@airgap/coinlib-core": "0.13.15",
+ "@airgap/coreum": "0.13.15",
+ "@airgap/cosmos": "0.13.15",
+ "@airgap/cosmos-core": "0.13.15",
+ "@airgap/crypto": "0.13.15",
+ "@airgap/ethereum": "0.13.15",
+ "@airgap/groestlcoin": "0.13.15",
+ "@airgap/icp": "0.13.15",
+ "@airgap/module-kit": "0.13.15",
+ "@airgap/moonbeam": "0.13.15",
+ "@airgap/polkadot": "0.13.15",
+ "@airgap/serializer": "0.13.15",
+ "@airgap/substrate": "0.13.15",
+ "@airgap/tezos": "0.13.15"
},
"dependencies": {
- "@airgap/aeternity": "0.13.12",
- "@airgap/angular-core": "0.0.36",
- "@airgap/angular-ngrx": "0.0.36",
- "@airgap/astar": "0.13.12",
- "@airgap/bitcoin": "0.13.12",
- "@airgap/coinlib-core": "0.13.12",
- "@airgap/coreum": "0.13.12",
- "@airgap/cosmos": "0.13.12",
- "@airgap/cosmos-core": "0.13.12",
- "@airgap/crypto": "0.13.12",
- "@airgap/ethereum": "0.13.12",
- "@airgap/groestlcoin": "0.13.12",
- "@airgap/icp": "0.13.12",
- "@airgap/module-kit": "0.13.12",
- "@airgap/moonbeam": "0.13.12",
- "@airgap/polkadot": "0.13.12",
+ "@airgap/aeternity": "0.13.15",
+ "@airgap/angular-core": "0.0.37",
+ "@airgap/angular-ngrx": "0.0.37",
+ "@airgap/astar": "0.13.15",
+ "@airgap/bitcoin": "0.13.15",
+ "@airgap/coinlib-core": "0.13.15",
+ "@airgap/coreum": "0.13.15",
+ "@airgap/cosmos": "0.13.15",
+ "@airgap/cosmos-core": "0.13.15",
+ "@airgap/crypto": "0.13.15",
+ "@airgap/ethereum": "0.13.15",
+ "@airgap/groestlcoin": "0.13.15",
+ "@airgap/icp": "0.13.15",
+ "@airgap/module-kit": "0.13.15",
+ "@airgap/moonbeam": "0.13.15",
+ "@airgap/polkadot": "0.13.15",
"@airgap/sapling-wasm": "0.0.7",
- "@airgap/serializer": "0.13.12",
- "@airgap/substrate": "0.13.12",
- "@airgap/tezos": "0.13.12",
+ "@airgap/serializer": "0.13.15",
+ "@airgap/substrate": "0.13.15",
+ "@airgap/tezos": "0.13.15",
"@angular/common": "13.2.5",
"@angular/core": "13.2.5",
"@angular/forms": "13.2.5",
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 11154fd7..25b5804a 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -78,6 +78,48 @@ const routes: Routes = [
loadChildren: () =>
import('./pages/secret-generate-onboarding/secret-generate-onboarding.module').then((m) => m.SecretGenerateOnboardingPageModule)
},
+ {
+ path: 'social-recovery-generate-intro',
+ loadChildren: () =>
+ import('./pages/social-recovery-generate-intro/social-recovery-generate-intro.module').then(
+ (m) => m.SocialRecoveryGenerateIntroPageModule
+ )
+ },
+ {
+ path: 'social-recovery-generate-setup',
+ loadChildren: () =>
+ import('./pages/social-recovery-generate-setup/social-recovery-generate-setup.module').then(
+ (m) => m.SocialRecoveryGenerateSetupPageModule
+ )
+ },
+ {
+ path: 'social-recovery-generate-rules',
+ loadChildren: () =>
+ import('./pages/social-recovery-generate-rules/social-recovery-generate-rules.module').then(
+ (m) => m.SocialRecoveryGenerateRulesPageModule
+ )
+ },
+ {
+ path: 'social-recovery-generate-share-show',
+ loadChildren: () =>
+ import('./pages/social-recovery-generate-share-show/social-recovery-generate-share-show.module').then(
+ (m) => m.SocialRecoveryGenerateShareShowPageModule
+ )
+ },
+ {
+ path: 'social-recovery-generate-share-validate',
+ loadChildren: () =>
+ import('./pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.module').then(
+ (m) => m.SocialRecoveryGenerateShareValidatePageModule
+ )
+ },
+ {
+ path: 'social-recovery-generate-finish',
+ loadChildren: () =>
+ import('./pages/social-recovery-generate-finish/social-recovery-generate-finish.module').then(
+ (m) => m.SocialRecoveryGenerateFinishPageModule
+ )
+ },
{
path: 'social-recovery-import',
loadChildren: () => import('./pages/social-recovery-import/social-recovery-import.module').then((m) => m.SocialRecoveryImportPageModule)
@@ -104,16 +146,24 @@ const routes: Routes = [
},
{
path: 'contact-book-contacts-detail',
- loadChildren: () => import('./pages/contact-book-contacts-detail/contact-book-contacts-detail.module').then((m) => m.ContactBookContactsDetailPageModule)
+ loadChildren: () =>
+ import('./pages/contact-book-contacts-detail/contact-book-contacts-detail.module').then((m) => m.ContactBookContactsDetailPageModule)
},
{
path: 'contact-book-onboarding',
- loadChildren: () => import('./pages/contact-book-onboarding/contact-book-onboarding.module').then((m) => m.ContactBookOnboardingPageModule)
+ loadChildren: () =>
+ import('./pages/contact-book-onboarding/contact-book-onboarding.module').then((m) => m.ContactBookOnboardingPageModule)
},
{
path: 'contact-book-settings',
loadChildren: () => import('./pages/contact-book-settings/contact-book-settings.module').then((m) => m.ContactBookOnboardingPageModule)
},
+
+ {
+ path: 'contact-book-scan',
+ loadChildren: () => import('./pages/contact-book-scan/contact-book-scan.module').then((m) => m.ContactBookScanPageModule)
+ },
+
{
path: 'deserialized-detail',
loadChildren: () => import('./pages/deserialized-detail/deserialized-detail.module').then((m) => m.DeserializedDetailPageModule)
@@ -146,10 +196,6 @@ const routes: Routes = [
path: 'accounts-list',
loadChildren: () => import('./pages/accounts-list/accounts-list.module').then((m) => m.AccountsListPageModule)
},
- {
- path: 'danger-zone',
- loadChildren: () => import('./pages/danger-zone/danger-zone.module').then((m) => m.DangerZonePageModule)
- },
{
path: 'secret-generate-dice',
loadChildren: () => import('./pages/secret-generate-dice/secret-generate-dice.module').then((m) => m.SecretGenerateDicePageModule)
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index d85482bc..a60091d6 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,4 +1,11 @@
-import { APP_PLUGIN, IACMessageTransport, IsolatedModulesService, ProtocolService, SPLASH_SCREEN_PLUGIN, STATUS_BAR_PLUGIN } from '@airgap/angular-core'
+import {
+ APP_PLUGIN,
+ IACMessageTransport,
+ IsolatedModulesService,
+ ProtocolService,
+ SPLASH_SCREEN_PLUGIN,
+ STATUS_BAR_PLUGIN
+} from '@airgap/angular-core'
import { MainProtocolSymbols } from '@airgap/coinlib-core'
import {
TezosSaplingExternalMethodProvider,
@@ -11,7 +18,7 @@ import { AfterViewInit, Component, Inject, NgZone } from '@angular/core'
import { AppPlugin, URLOpenListenerEvent } from '@capacitor/app'
import { SplashScreenPlugin } from '@capacitor/splash-screen'
import { StatusBarPlugin, Style } from '@capacitor/status-bar'
-import { Platform } from '@ionic/angular'
+import { ModalController, Platform } from '@ionic/angular'
import { TranslateService } from '@ngx-translate/core'
import { first } from 'rxjs/operators'
@@ -27,6 +34,7 @@ import { SaplingNativeService } from './services/sapling-native/sapling-native.s
import { SecretsService } from './services/secrets/secrets.service'
import { StartupChecksService } from './services/startup-checks/startup-checks.service'
import { LanguagesType, VaultStorageKey, VaultStorageService } from './services/storage/storage.service'
+import { Router } from '@angular/router'
declare let window: Window & { airGapHasStarted: boolean }
@@ -57,6 +65,9 @@ export class AppComponent implements AfterViewInit {
private readonly httpClient: HttpClient,
private readonly saplingNativeService: SaplingNativeService,
private readonly isolatedModuleService: IsolatedModulesService,
+ private readonly router: Router,
+ private readonly modalController: ModalController,
+
@Inject(APP_PLUGIN) private readonly app: AppPlugin,
@Inject(SECURITY_UTILS_PLUGIN) private readonly securityUtils: SecurityUtilsPlugin,
@Inject(SPLASH_SCREEN_PLUGIN) private readonly splashScreen: SplashScreenPlugin,
@@ -89,6 +100,19 @@ export class AppComponent implements AfterViewInit {
public async ngAfterViewInit(): Promise {
await this.platform.ready()
+ if (this.platform.is('android')) {
+ this.platform.backButton.subscribeWithPriority(-1, async () => {
+ if (this.modalController.getTop()) {
+ const modal = await this.modalController.getTop()
+ if (modal) {
+ this.modalController.dismiss()
+ return
+ }
+ } else {
+ this.navigationService.handleAndroidBackNavigation(this.router.url)
+ }
+ })
+ }
this.app.addListener('appUrlOpen', async (data: URLOpenListenerEvent) => {
await this.isInitialized.promise
if (data.url === DEEPLINK_VAULT_PREFIX || data.url.startsWith(DEEPLINK_VAULT_ADD_ACCOUNT)) {
@@ -138,8 +162,9 @@ export class AppComponent implements AfterViewInit {
private async initializeProtocols(): Promise {
const protocols = await this.isolatedModuleService.loadProtocols('offline', [MainProtocolSymbols.XTZ_SHIELDED])
- const externalMethodProvider: TezosSaplingExternalMethodProvider | undefined =
- await this.saplingNativeService.createExternalMethodProvider()
+ const externalMethodProvider:
+ | TezosSaplingExternalMethodProvider
+ | undefined = await this.saplingNativeService.createExternalMethodProvider()
const shieldedTezProtocol: TezosShieldedTezProtocol = new TezosShieldedTezProtocol(
new TezosSaplingProtocolOptions(
diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts
index 1885860d..9082e988 100644
--- a/src/app/components/components.module.ts
+++ b/src/app/components/components.module.ts
@@ -21,6 +21,7 @@ import { TouchEntropyComponent } from './touch-entropy/touch-entropy.component'
import { TraceInputDirective } from './trace-input/trace-input.directive'
import { TransactionWarningComponent } from './transaction-warning/transaction-warning.component'
import { TransactionComponent } from './transaction/transaction.component'
+import { VerifyKeyAltComponent } from './verify-key-alt/verify-key-alt.component'
import { VerifyKeyComponent } from './verify-key/verify-key.component'
@NgModule({
@@ -34,6 +35,7 @@ import { VerifyKeyComponent } from './verify-key/verify-key.component'
TouchEntropyComponent,
TraceInputDirective,
VerifyKeyComponent,
+ VerifyKeyAltComponent,
MessageSignRequestComponent,
MessageSignResponseComponent,
GridInputComponent,
@@ -51,6 +53,7 @@ import { VerifyKeyComponent } from './verify-key/verify-key.component'
TouchEntropyComponent,
TraceInputDirective,
VerifyKeyComponent,
+ VerifyKeyAltComponent,
MessageSignRequestComponent,
MessageSignResponseComponent,
GridInputComponent,
diff --git a/src/app/components/secret-item/secret-item.component.html b/src/app/components/secret-item/secret-item.component.html
index 022a4fd3..406e353a 100644
--- a/src/app/components/secret-item/secret-item.component.html
+++ b/src/app/components/secret-item/secret-item.component.html
@@ -1,6 +1,6 @@
-
+
@@ -18,13 +18,17 @@
-
+
0" [color]="'tertiary'" shape="round" class="mtzero">+{{ hasMoreWallets }}
-
+
+
+
+
+
diff --git a/src/app/components/secret-item/secret-item.component.scss b/src/app/components/secret-item/secret-item.component.scss
index 70031342..5a77a32c 100644
--- a/src/app/components/secret-item/secret-item.component.scss
+++ b/src/app/components/secret-item/secret-item.component.scss
@@ -13,8 +13,8 @@ ion-icon {
}
ion-button {
- width: 32px;
- height: 32px;
+ width: 45px;
+ height: 45px;
text-align: center;
--padding-end: 0;
--padding-start: 0;
@@ -29,3 +29,15 @@ ion-button {
// 6f53a1
background: linear-gradient(#311b58, #311b58); // TODO: Variables
}
+ion-fab {
+ ion-icon{
+ font-size: 20px;
+ margin-right: 7px;
+ }
+ ion-button {
+ ion-icon {
+ font-size: 30px;
+ margin-right: 0px;
+ }
+ }
+}
diff --git a/src/app/components/verify-key-alt/verify-key-alt.component.html b/src/app/components/verify-key-alt/verify-key-alt.component.html
new file mode 100644
index 00000000..884119b5
--- /dev/null
+++ b/src/app/components/verify-key-alt/verify-key-alt.component.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+ {{ 'verify-key.success_text' | translate }}
+
+
+ 0">
+
+
+
+ {{ word }}
+
+
+
+
+
diff --git a/src/app/components/verify-key-alt/verify-key-alt.component.scss b/src/app/components/verify-key-alt/verify-key-alt.component.scss
new file mode 100644
index 00000000..55a4c42b
--- /dev/null
+++ b/src/app/components/verify-key-alt/verify-key-alt.component.scss
@@ -0,0 +1,95 @@
+.secret--container-60 {
+ height: calc(60% - 36px);
+}
+.secret--container-40 {
+ height: 25vh;
+ position: fixed;
+ width: 100%;
+ left: 0;
+ bottom: 56px;
+}
+hr {
+ border: 2px dashed var(--ion-color-primary);
+ border-style: none none dashed;
+ margin: 8px 0;
+}
+.word-placeholder {
+ min-width: 48px;
+}
+.size__xs {
+ font-size: 11px;
+}
+.typography--mono {
+ text-transform: lowercase;
+}
+
+:host {
+ width: 100%;
+}
+
+.tags-wrapper {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(95px, 1fr));
+ width: 100%;
+ margin-bottom: 28vh;
+
+ .tag {
+ --padding-bottom: 8px;
+ --padding-top: 8px;
+ --padding-start: 18px;
+ --padding-end: 18px;
+ --border-width: 1px
+ height: fit-content;
+
+ .number {
+ font-size: 0.65rem;
+ line-height: 0.75rem;
+ color: rgb(192, 192, 192);
+ width: 100%;
+ text-align: start;
+ margin: 0px;
+ margin-right: 4px;
+ }
+
+ .text {
+ font-size: 0.65rem;
+ line-height: 0.75rem;
+ text-transform: lowercase;
+ font-weight: 700;
+ margin: 0px;
+ }
+ }
+
+ .current {
+ --background: transparent;
+ --background-hover: transparent;
+ --background-activated: transparent;
+ --background-focused: transparent;
+
+ --border-style : dashed;
+ --border-width: 1px;
+ --border-color: var(--ion-color-primary);
+ }
+}
+
+.error-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+
+ svg {
+ overflow: visible;
+ height: 50px;
+ width: 50px;
+ margin-bottom: 24px;
+ }
+
+ p {
+ font-size: 0.75rem;
+ line-height: 0.85rem;
+ font-weight: 500;
+ margin: 0px;
+ text-align: center;
+ }
+}
diff --git a/src/app/components/verify-key-alt/verify-key-alt.component.spec.ts b/src/app/components/verify-key-alt/verify-key-alt.component.spec.ts
new file mode 100644
index 00000000..85cab80a
--- /dev/null
+++ b/src/app/components/verify-key-alt/verify-key-alt.component.spec.ts
@@ -0,0 +1,188 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'
+
+import { UnitHelper } from '../../../../test-config/unit-test-helper'
+import { VerifyKeyAltComponent } from './verify-key-alt.component'
+
+describe('Component: VerifyKey', () => {
+ let component: VerifyKeyAltComponent
+ let fixture: ComponentFixture
+
+ const correctMnemonic: string =
+ 'usage puzzle bottom amused genuine bike brown ripple lend aware symbol genuine neutral tortoise pluck rose brown cliff sing smile appear black occur zero'
+
+ let unitHelper: UnitHelper
+ beforeEach(() => {
+ unitHelper = new UnitHelper()
+ TestBed.configureTestingModule(
+ unitHelper.testBed({
+ declarations: []
+ })
+ )
+ .compileComponents()
+ .catch(console.error)
+ })
+
+ beforeEach(async () => {
+ fixture = TestBed.createComponent(VerifyKeyAltComponent)
+ component = fixture.componentInstance
+ })
+ it('should validate a regular mnemonic, and emit correct event', waitForAsync(() => {
+ component.secret = correctMnemonic
+ fixture.detectChanges()
+ const words = component.secret.split(' ')
+
+ // validate onComplete Event is True
+ component.onComplete.subscribe((event) => {
+ expect(event).toBeTruthy()
+ })
+
+ words.forEach((word: string) => {
+ expect(component.isFull()).toBeFalsy()
+ component.useWord(word)
+ })
+
+ expect(component.isFull()).toBeTruthy()
+ expect(component.isCorrect()).toBeTruthy()
+ }))
+
+ it('should detect a wrong word in a mnemonic', waitForAsync(() => {
+ component.secret = correctMnemonic
+ fixture.detectChanges()
+ const words = component.secret.split(' ')
+
+ // validate onComplete Event is False
+ component.onComplete.subscribe((event) => {
+ expect(event).toBeFalsy()
+ })
+
+ words.forEach((word: string, i: number) => {
+ expect(component.isFull()).toBeFalsy()
+ if (i === 5) {
+ component.useWord('wrongWord')
+ } else {
+ component.useWord(word)
+ }
+ })
+
+ expect(component.isFull()).toBeTruthy()
+ expect(component.isCorrect()).toBeFalsy()
+ }))
+
+ it('should validate a mnemonic where the same word appears 2 times', waitForAsync(() => {
+ component.secret = correctMnemonic
+ fixture.detectChanges()
+ component.ngOnInit()
+ const words = component.secret.split(' ')
+
+ words.forEach((word) => {
+ component.useWord(word)
+ })
+
+ expect(component.isCorrect()).toBeTruthy()
+ }))
+
+ it('should not validate user input that is too short', waitForAsync(() => {
+ component.secret = correctMnemonic
+ fixture.detectChanges()
+ component.ngOnInit()
+ const words: string[] = component.secret.split(' ')
+
+ words.forEach((word: string, i: number) => {
+ if (i === words.length - 1) {
+ return
+ }
+ component.useWord(word)
+ })
+
+ expect(component.isFull()).toBeFalsy()
+ expect(component.isCorrect()).toBeFalsy()
+ component.useWord(words[words.length - 1])
+ expect(component.isFull()).toBeTruthy()
+ expect(component.isCorrect()).toBeTruthy()
+ }))
+
+ it('should give the correct empty spots', waitForAsync(() => {
+ component.secret = correctMnemonic
+ fixture.detectChanges()
+ component.ngOnInit()
+ const words = component.secret.split(' ')
+
+ // first empty spot is zero
+ expect(component.emptySpot(component.currentWords)).toEqual(0)
+
+ component.useWord(words[0])
+
+ // next empty spot is one
+ expect(component.emptySpot(component.currentWords)).toEqual(1)
+ }))
+
+ it('should let users select words to correct them', waitForAsync(() => {
+ component.secret = correctMnemonic
+ fixture.detectChanges()
+ component.ngOnInit()
+ const words = component.secret.split(' ')
+
+ words.forEach((word: string) => {
+ component.useWord(word)
+ })
+
+ // now select a word
+ component.selectWord(5)
+ expect(component.selectedWordIndex).toEqual(5)
+ expect(component.currentWords[5]).toEqual(words[5])
+ }))
+
+ it('should give users 3 words to choose from', waitForAsync(() => {
+ component.secret = correctMnemonic
+ component.ngOnInit()
+ fixture.detectChanges()
+
+ const wordSelector = fixture.nativeElement.querySelector('#wordSelector')
+
+ // check if there are three words
+ expect(wordSelector.children.length).toBe(3)
+
+ let foundWord: boolean = false
+ for (let i: number = 0; i < wordSelector.children.length; i++) {
+ if (wordSelector.children.item(i).textContent.trim() === correctMnemonic.split(' ')[0]) {
+ foundWord = true
+ }
+ }
+
+ // check if one of the words is the correct one
+ expect(foundWord).toBeTruthy()
+ }))
+
+ it('should give users 3 words to choose from if selecting a specific one', waitForAsync(() => {
+ component.secret = correctMnemonic
+ component.ngOnInit()
+ fixture.detectChanges()
+
+ component.secret.split(' ').forEach((word: string, i: number) => {
+ if (i > 10) {
+ return
+ }
+ component.useWord(word)
+ })
+
+ const selectedIndex: number = 5
+ component.selectWord(selectedIndex)
+
+ fixture.detectChanges()
+
+ const wordSelector = fixture.nativeElement.querySelector('#wordSelector')
+
+ // check if there are three words
+ expect(wordSelector.children.length).toBe(3)
+
+ let foundWord: boolean = false
+ for (let i: number = 0; i < wordSelector.children.length; i++) {
+ if (wordSelector.children.item(i).textContent.trim() === correctMnemonic.split(' ')[selectedIndex]) {
+ foundWord = true
+ }
+ }
+
+ // check if one of the words is the correct one
+ expect(foundWord).toBeTruthy()
+ }))
+})
diff --git a/src/app/components/verify-key-alt/verify-key-alt.component.ts b/src/app/components/verify-key-alt/verify-key-alt.component.ts
new file mode 100644
index 00000000..ca01043f
--- /dev/null
+++ b/src/app/components/verify-key-alt/verify-key-alt.component.ts
@@ -0,0 +1,166 @@
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
+import * as bip39 from 'bip39'
+import { sha3_256 } from 'js-sha3'
+
+type SingleWord = string
+
+const ADDITIONAL_WORDS: number = 2
+
+@Component({
+ selector: 'airgap-verify-key-alt',
+ templateUrl: './verify-key-alt.component.html',
+ styleUrls: ['./verify-key-alt.component.scss']
+})
+export class VerifyKeyAltComponent implements OnInit {
+ @Input()
+ public secret: string
+
+ @Output()
+ public onNext = new EventEmitter()
+
+ @Output()
+ public onComplete = new EventEmitter()
+
+ public isCompleted: boolean = false
+
+ public splittedSecret: SingleWord[] = []
+ public currentWords: SingleWord[] = []
+ public promptedWords: SingleWord[] = []
+
+ public selectedWordIndex: number | null = null
+
+ public get currentSelectedWords(): string[] {
+ return this.currentWords.filter((word) => word != null)
+ }
+
+ public ngOnInit(): void {
+ this.splittedSecret = this.secret.toLowerCase().split(' ')
+
+ this.reset()
+ }
+
+ public promptNextWord(): void {
+ if (!this.isFull()) this.onNext.emit()
+ this.promptedWords = []
+
+ const correctWord: SingleWord = this.splittedSecret[this.emptySpot(this.currentWords)]
+
+ this.promptedWords.push(correctWord)
+
+ const wordList: string[] = bip39.wordlists.EN
+
+ for (let i: number = 0; i < ADDITIONAL_WORDS; i++) {
+ const filteredList: string[] = wordList.filter(
+ (originalWord: string) => !this.splittedSecret.find((word: SingleWord) => word === originalWord)
+ )
+
+ let hashedWord: string = sha3_256(correctWord)
+ for (let hashRuns: number = 0; hashRuns <= i; hashRuns++) {
+ hashedWord = sha3_256(hashedWord)
+ }
+
+ const additionalWord: SingleWord = filteredList[this.stringToIntHash(hashedWord, 0, filteredList.length)]
+
+ this.promptedWords.push(additionalWord)
+ }
+
+ this.promptedWords = this.shuffle(this.promptedWords)
+ }
+
+ public shuffle(a: string[]): string[] {
+ let counter: number = a.length
+
+ while (counter > 0) {
+ const index: number = Math.floor(Math.random() * counter)
+
+ counter--
+
+ const temp: string = a[counter]
+ a[counter] = a[index]
+ a[index] = temp
+ }
+
+ return a
+ }
+
+ public stringToIntHash(str: string, lowerbound: number, upperbound: number): number {
+ let result: number = 0
+
+ for (let i: number = 0; i < str.length; i++) {
+ result = result + str.charCodeAt(i)
+ }
+
+ return (result % (upperbound - lowerbound)) + lowerbound
+ }
+
+ public isSelectedWord(word: SingleWord): boolean {
+ if (this.selectedWordIndex !== null) {
+ return this.currentWords[this.selectedWordIndex] === word
+ }
+
+ return false
+ }
+
+ public selectEmptySpot(): void {
+ this.selectedWordIndex = null
+ this.promptNextWord()
+ }
+
+ public useWord(word: SingleWord): void {
+ const index: number = this.emptySpot(this.currentWords)
+
+ // unselect any selected words
+ this.selectedWordIndex = null
+ this.currentWords[index] = word
+
+ // prompt next word
+ if (!this.isFull() && index < this.splittedSecret.length - 1) {
+ this.promptNextWord()
+
+ return
+ }
+
+ if (this.isFull()) {
+ // if all words are placed, check for correctness, else next
+ this.promptedWords = []
+ this.setCompletedState(this.isCorrect())
+ }
+ }
+
+ public setCompletedState(state: boolean) {
+ this.isCompleted = state
+ this.onComplete.emit(state)
+ }
+
+ public emptySpot(array: SingleWord[]): number {
+ if (this.selectedWordIndex !== null) {
+ return this.selectedWordIndex
+ }
+
+ return array.findIndex((word: SingleWord) => word === null)
+ }
+
+ public selectWord(index: number): void {
+ this.selectedWordIndex = index
+ this.promptNextWord()
+ }
+
+ public reset(): void {
+ this.selectedWordIndex = null
+ this.currentWords = Array(this.splittedSecret.length).fill(null)
+ this.promptNextWord()
+ }
+
+ public isFull(): boolean {
+ return this.currentWords.filter((word: string) => word !== null).length === this.splittedSecret.length
+ }
+
+ public isCorrect(): boolean {
+ return (
+ this.currentWords
+ .map((word: SingleWord) => (word ? word : '-'))
+ .join(' ')
+ .trim() === this.secret.trim()
+ )
+ }
+}
diff --git a/src/app/pages/account-address/account-address.page.html b/src/app/pages/account-address/account-address.page.html
index 2e363bfc..70038890 100644
--- a/src/app/pages/account-address/account-address.page.html
+++ b/src/app/pages/account-address/account-address.page.html
@@ -45,7 +45,7 @@ {{ protocolName }} {{ protocolSymbol }}
-
+
{{option.name}}
diff --git a/src/app/pages/account-address/account-address.page.ts b/src/app/pages/account-address/account-address.page.ts
index e2c11f04..d5e6c938 100644
--- a/src/app/pages/account-address/account-address.page.ts
+++ b/src/app/pages/account-address/account-address.page.ts
@@ -37,6 +37,12 @@ const sparrowwallet = {
qrType: QRType.BC_UR
}
+const specterwallet = {
+ icon: 'specterwallet.png',
+ name: 'Specter Wallet',
+ qrType: QRType.BC_UR
+}
+
const metamask = {
icon: 'metamask.webp',
name: 'MetaMask',
@@ -120,7 +126,7 @@ export class AccountAddressPage {
switch (protocolIdentifier) {
case MainProtocolSymbols.BTC_SEGWIT:
- this.syncOptions = [airgapwallet, bluewallet, sparrowwallet]
+ this.syncOptions = [airgapwallet, bluewallet, sparrowwallet, specterwallet]
break
case MainProtocolSymbols.ETH:
this.syncOptions = [airgapwallet]
diff --git a/src/app/pages/contact-book-contacts-detail/contact-book-contacts-detail.page.html b/src/app/pages/contact-book-contacts-detail/contact-book-contacts-detail.page.html
index 64e76c86..72f22b71 100644
--- a/src/app/pages/contact-book-contacts-detail/contact-book-contacts-detail.page.html
+++ b/src/app/pages/contact-book-contacts-detail/contact-book-contacts-detail.page.html
@@ -121,6 +121,9 @@ {{contact?.name || ""}}
+
+
+
@@ -45,7 +45,7 @@ {{ 'secret-edit.passcode.label' | translate }}
{{ 'secret-edit.secret-recovery.heading' | translate }}
diff --git a/src/app/pages/secret-edit/secret-edit.page.ts b/src/app/pages/secret-edit/secret-edit.page.ts
index 2e91b20b..5e2def1b 100644
--- a/src/app/pages/secret-edit/secret-edit.page.ts
+++ b/src/app/pages/secret-edit/secret-edit.page.ts
@@ -93,7 +93,7 @@ export class SecretEditPage {
public goToSocialRecoverySetup(): void {
this.navigationService
- .routeWithState('/social-recovery-setup', { secret: this.secret })
+ .routeWithState('/social-recovery-generate-intro', { secret: this.secret })
.catch(handleErrorLocal(ErrorCategory.IONIC_NAVIGATION))
}
diff --git a/src/app/pages/social-recovery-generate-finish/social-recovery-generate-finish.module.ts b/src/app/pages/social-recovery-generate-finish/social-recovery-generate-finish.module.ts
new file mode 100644
index 00000000..5bfb31e0
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-finish/social-recovery-generate-finish.module.ts
@@ -0,0 +1,20 @@
+import { CommonModule } from '@angular/common'
+import { NgModule } from '@angular/core'
+import { FormsModule, ReactiveFormsModule } from '@angular/forms'
+import { RouterModule, Routes } from '@angular/router'
+import { IonicModule } from '@ionic/angular'
+import { TranslateModule } from '@ngx-translate/core'
+import { SocialRecoveryGenerateFinishPage } from './social-recovery-generate-finish.page'
+
+const routes: Routes = [
+ {
+ path: '',
+ component: SocialRecoveryGenerateFinishPage
+ }
+]
+
+@NgModule({
+ imports: [CommonModule, FormsModule, ReactiveFormsModule, IonicModule, RouterModule.forChild(routes), TranslateModule],
+ declarations: [SocialRecoveryGenerateFinishPage]
+})
+export class SocialRecoveryGenerateFinishPageModule {}
diff --git a/src/app/pages/social-recovery-generate-finish/social-recovery-generate-finish.page.html b/src/app/pages/social-recovery-generate-finish/social-recovery-generate-finish.page.html
new file mode 100644
index 00000000..a174a4e0
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-finish/social-recovery-generate-finish.page.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ 'social-recovery-generate.finish-title' | translate }}
+
{{ 'social-recovery-generate.finish-header' | translate }}
+
+
+
+
+
{{ 'social-recovery-generate.finish-warning-title' | translate }}
+
+
+ {{ 'social-recovery-generate.finish-warning-paragraph' | translate: { numberOfShares: numberOfShares, sharesRequired:
+ sharesRequired } }}
+
+
+
+
+
+ {{ 'social-recovery-generate.finish-button' | translate }}
+
+
diff --git a/src/app/pages/social-recovery-generate-finish/social-recovery-generate-finish.page.scss b/src/app/pages/social-recovery-generate-finish/social-recovery-generate-finish.page.scss
new file mode 100644
index 00000000..f4a18123
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-finish/social-recovery-generate-finish.page.scss
@@ -0,0 +1,95 @@
+.button--rounded ion-button {
+ --padding-end: 0;
+ --padding-start: 0;
+ width: 40px;
+ height: 40px;
+}
+
+ion-content {
+ --padding-bottom: var(--ion-padding, 64px);
+}
+
+h1 {
+ font-size: 1.5rem;
+ line-height: 2rem;
+ margin-bottom: 2rem;
+ text-align: center;
+}
+
+p {
+ text-align: center;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 500;
+ margin-block-start: 0px;
+ margin-block-end: 0px;
+ margin-bottom: 0.8rem;
+ max-width: 300px;
+ color: rgba(255, 255, 255, 0.8);
+}
+
+h2 {
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+ margin-bottom: 0.825rem;
+}
+
+h3 {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 500;
+ color: rgba(255, 255, 255, 0.8);
+ margin: 0px;
+}
+
+svg {
+ fill: white;
+ height: 78px;
+}
+
+.content-wrapper {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 80vh;
+}
+
+.button-wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: fixed;
+ bottom: 10px;
+ left: 0;
+ width: 100%;
+}
+
+.divider {
+ flex: 1;
+}
+
+.warning-container {
+ border: 1px dashed white;
+ border-radius: 4px;
+ padding: 1rem;
+ margin-bottom: 1rem;
+
+ .warning-title-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ margin-bottom: 1rem;
+
+ svg {
+ height: 24px;
+ }
+
+ h2 {
+ font-size: 1rem;
+ line-height: 1.5rem;
+ margin: 0px;
+ font-weight: bold;
+ }
+ }
+}
diff --git a/src/app/pages/social-recovery-generate-finish/social-recovery-generate-finish.page.ts b/src/app/pages/social-recovery-generate-finish/social-recovery-generate-finish.page.ts
new file mode 100644
index 00000000..395741fd
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-finish/social-recovery-generate-finish.page.ts
@@ -0,0 +1,30 @@
+import { Component } from '@angular/core'
+import { ErrorCategory, handleErrorLocal } from 'src/app/services/error-handler/error-handler.service'
+import { NavigationService } from 'src/app/services/navigation/navigation.service'
+
+@Component({
+ selector: 'airgap-social-recovery-generate-finish',
+ templateUrl: './social-recovery-generate-finish.page.html',
+ styleUrls: ['./social-recovery-generate-finish.page.scss']
+})
+export class SocialRecoveryGenerateFinishPage {
+ private required: number
+ private readonly shares: string[]
+
+ get numberOfShares(): number {
+ return this.shares.length
+ }
+
+ get sharesRequired(): number {
+ return this.required
+ }
+
+ constructor(private readonly navigationService: NavigationService) {
+ this.shares = this.navigationService.getState().shares
+ this.required = this.navigationService.getState().required
+ }
+
+ next() {
+ this.navigationService.route('/tabs/tab-settings').catch(handleErrorLocal(ErrorCategory.IONIC_NAVIGATION))
+ }
+}
diff --git a/src/app/pages/social-recovery-generate-finish/social-recovery-generate-finish.spec.ts b/src/app/pages/social-recovery-generate-finish/social-recovery-generate-finish.spec.ts
new file mode 100644
index 00000000..783767ad
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-finish/social-recovery-generate-finish.spec.ts
@@ -0,0 +1,28 @@
+/*
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
+import { async, ComponentFixture, TestBed } from '@angular/core/testing'
+
+import { SocialRecoveryImportPage } from './social-recovery-import.page'
+
+describe('SocialRecoveryImportPage', () => {
+ let component: SocialRecoveryImportPage
+ let fixture: ComponentFixture
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [SocialRecoveryImportPage],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ }).compileComponents()
+ }))
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SocialRecoveryImportPage)
+ component = fixture.componentInstance
+ fixture.detectChanges()
+ })
+
+ it('should create', () => {
+ expect(component).toBeTruthy()
+ })
+})
+*/
diff --git a/src/app/pages/social-recovery-generate-intro/social-recovery-generate-intro.module.ts b/src/app/pages/social-recovery-generate-intro/social-recovery-generate-intro.module.ts
new file mode 100644
index 00000000..a1adbb2b
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-intro/social-recovery-generate-intro.module.ts
@@ -0,0 +1,20 @@
+import { CommonModule } from '@angular/common'
+import { NgModule } from '@angular/core'
+import { FormsModule, ReactiveFormsModule } from '@angular/forms'
+import { RouterModule, Routes } from '@angular/router'
+import { IonicModule } from '@ionic/angular'
+import { TranslateModule } from '@ngx-translate/core'
+import { SocialRecoveryGenerateIntroPage } from './social-recovery-generate-intro.page'
+
+const routes: Routes = [
+ {
+ path: '',
+ component: SocialRecoveryGenerateIntroPage
+ }
+]
+
+@NgModule({
+ imports: [CommonModule, FormsModule, ReactiveFormsModule, IonicModule, RouterModule.forChild(routes), TranslateModule],
+ declarations: [SocialRecoveryGenerateIntroPage]
+})
+export class SocialRecoveryGenerateIntroPageModule {}
diff --git a/src/app/pages/social-recovery-generate-intro/social-recovery-generate-intro.page.html b/src/app/pages/social-recovery-generate-intro/social-recovery-generate-intro.page.html
new file mode 100644
index 00000000..6699d6d0
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-intro/social-recovery-generate-intro.page.html
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ 'social-recovery-generate.intro-state-1-title' | translate }}
+
{{ 'social-recovery-generate.intro-state-1-text1' | translate }}
+
{{ 'social-recovery-generate.intro-state-1-text2' | translate }}
+
+
+
+
+
+
{{ 'social-recovery-generate.intro-state-2-title' | translate }}
+
{{ 'social-recovery-generate.intro-state-2-text1' | translate }}
+
{{ 'social-recovery-generate.intro-state-2-text2' | translate }}
+
{{ 'social-recovery-generate.intro-state-2-text3' | translate }}
+
+
+
+
+
{{ 'social-recovery-generate.intro-state-3-title1' | translate }}
+
{{ 'social-recovery-generate.intro-state-3-title2' | translate }}
+
{{ 'social-recovery-generate.intro-state-3-text1' | translate }}
+
{{ 'social-recovery-generate.intro-state-3-title3' | translate }}
+
{{ 'social-recovery-generate.intro-state-3-text2' | translate }}
+
+
+
+
+
{{ 'social-recovery-generate.intro-state-3-title4' | translate }}
+
+
{{ 'social-recovery-generate.intro-state-3-text3' | translate }}
+
+
+
+
+
+
+ {{ 'secret-generate.continue_label' | translate }}
+
+
+ {{ 'social-recovery-setup.start_label' | translate }}
+
+
+ {{ 'secret-rules.understood_label' | translate }}
+
+
+
diff --git a/src/app/pages/social-recovery-generate-intro/social-recovery-generate-intro.page.scss b/src/app/pages/social-recovery-generate-intro/social-recovery-generate-intro.page.scss
new file mode 100644
index 00000000..d2b024eb
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-intro/social-recovery-generate-intro.page.scss
@@ -0,0 +1,126 @@
+.button--rounded ion-button {
+ --padding-end: 0;
+ --padding-start: 0;
+ width: 40px;
+ height: 40px;
+}
+
+ion-content {
+ --padding-bottom: var(--ion-padding, 64px);
+}
+
+h1 {
+ font-size: 1.5rem;
+ line-height: 2rem;
+ margin-bottom: 2rem;
+}
+
+p {
+ text-align: center;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 500;
+ margin-block-start: 0px;
+ margin-block-end: 0px;
+ margin-bottom: 0.8rem;
+ max-width: 300px;
+ color: rgba(255, 255, 255, 0.8);
+}
+
+h2 {
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+ margin-bottom: 0.825rem;
+}
+
+h3 {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 500;
+ color: rgba(255, 255, 255, 0.8);
+ margin: 0px;
+}
+
+svg {
+ fill: white;
+ height: 78px;
+}
+
+.content-wrapper {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 80vh;
+}
+
+.button-wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: fixed;
+ bottom: 10px;
+ left: 0;
+ width: 100%;
+}
+
+.triple-svgs {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ width: 200px;
+ height: 160px;
+
+ .svg-1 {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ }
+
+ .svg-3 {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ }
+}
+
+.state3-wrapper {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+
+ h1 {
+ font-weight: bolder;
+ font-size: 1.75rem;
+ line-height: 2.25rem;
+ }
+
+ .divider {
+ flex: 1;
+ }
+
+ .warning-container {
+ border: 1px dashed white;
+ border-radius: 4px;
+ padding: 1rem;
+ margin-bottom: 1rem;
+
+ .warning-title-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ margin-bottom: 1rem;
+
+ svg {
+ height: 24px;
+ }
+
+ h2 {
+ font-size: 1rem;
+ line-height: 1.5rem;
+ margin: 0px;
+ font-weight: bold;
+ }
+ }
+ }
+}
diff --git a/src/app/pages/social-recovery-generate-intro/social-recovery-generate-intro.page.ts b/src/app/pages/social-recovery-generate-intro/social-recovery-generate-intro.page.ts
new file mode 100644
index 00000000..2419b7af
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-intro/social-recovery-generate-intro.page.ts
@@ -0,0 +1,29 @@
+import { Component } from '@angular/core'
+import { MnemonicSecret } from 'src/app/models/secret'
+import { ErrorCategory, handleErrorLocal } from 'src/app/services/error-handler/error-handler.service'
+import { NavigationService } from 'src/app/services/navigation/navigation.service'
+
+@Component({
+ selector: 'airgap-social-recovery-generate-intro',
+ templateUrl: './social-recovery-generate-intro.page.html',
+ styleUrls: ['./social-recovery-generate-intro.page.scss']
+})
+export class SocialRecoveryGenerateIntroPage {
+ public state: 0 | 1 | 2 = 0
+ private readonly secret: MnemonicSecret
+
+ constructor(private readonly navigationService: NavigationService) {
+ this.secret = this.navigationService.getState().secret
+ }
+
+ nextState() {
+ if (this.state < 2) {
+ this.state++
+ return
+ } else if (this.state >= 2) {
+ this.navigationService
+ .routeWithState('/social-recovery-generate-setup', { secret: this.secret })
+ .catch(handleErrorLocal(ErrorCategory.IONIC_NAVIGATION))
+ }
+ }
+}
diff --git a/src/app/pages/social-recovery-generate-intro/social-recovery-generate-intro.spec.ts b/src/app/pages/social-recovery-generate-intro/social-recovery-generate-intro.spec.ts
new file mode 100644
index 00000000..783767ad
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-intro/social-recovery-generate-intro.spec.ts
@@ -0,0 +1,28 @@
+/*
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
+import { async, ComponentFixture, TestBed } from '@angular/core/testing'
+
+import { SocialRecoveryImportPage } from './social-recovery-import.page'
+
+describe('SocialRecoveryImportPage', () => {
+ let component: SocialRecoveryImportPage
+ let fixture: ComponentFixture
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [SocialRecoveryImportPage],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ }).compileComponents()
+ }))
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SocialRecoveryImportPage)
+ component = fixture.componentInstance
+ fixture.detectChanges()
+ })
+
+ it('should create', () => {
+ expect(component).toBeTruthy()
+ })
+})
+*/
diff --git a/src/app/pages/social-recovery-generate-rules/social-recovery-generate-rules.module.ts b/src/app/pages/social-recovery-generate-rules/social-recovery-generate-rules.module.ts
new file mode 100644
index 00000000..6814972d
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-rules/social-recovery-generate-rules.module.ts
@@ -0,0 +1,20 @@
+import { CommonModule } from '@angular/common'
+import { NgModule } from '@angular/core'
+import { FormsModule, ReactiveFormsModule } from '@angular/forms'
+import { RouterModule, Routes } from '@angular/router'
+import { IonicModule } from '@ionic/angular'
+import { TranslateModule } from '@ngx-translate/core'
+import { SocialRecoveryGenerateRulesPage } from './social-recovery-generate-rules.page'
+
+const routes: Routes = [
+ {
+ path: '',
+ component: SocialRecoveryGenerateRulesPage
+ }
+]
+
+@NgModule({
+ imports: [CommonModule, FormsModule, ReactiveFormsModule, IonicModule, RouterModule.forChild(routes), TranslateModule],
+ declarations: [SocialRecoveryGenerateRulesPage]
+})
+export class SocialRecoveryGenerateRulesPageModule {}
diff --git a/src/app/pages/social-recovery-generate-rules/social-recovery-generate-rules.page.html b/src/app/pages/social-recovery-generate-rules/social-recovery-generate-rules.page.html
new file mode 100644
index 00000000..81797901
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-rules/social-recovery-generate-rules.page.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
{{ 'social-recovery-generate.rules-heading' | translate }}
+
+
#1
+ {{ 'social-recovery-generate.rules-text1' | translate }}
+
+
+
+
#2
+ {{ 'social-recovery-generate.rules-text2' | translate }}
+
+
+
+
#3
+ {{ 'social-recovery-generate.rules-text3' | translate }}
+
+
+
+
#4
+ {{ 'social-recovery-generate.rules-text4' | translate }}
+
+
+
+
#5
+ {{ 'social-recovery-generate.rules-text5' | translate }}
+
+
+
+
#6
+ {{ 'social-recovery-generate.rules-text6' | translate }}
+
+
+
+
+
+
+
+ 0" color="light" fill="clear" (click)="prevState()">
+ {{ 'social-recovery-setup.back_label' | translate }}
+
+
+ {{(state == 5 ? 'introduction.continue_label' : 'social-recovery-setup.next_label') | translate }}
+
+
+
+
diff --git a/src/app/pages/social-recovery-generate-rules/social-recovery-generate-rules.page.scss b/src/app/pages/social-recovery-generate-rules/social-recovery-generate-rules.page.scss
new file mode 100644
index 00000000..81028386
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-rules/social-recovery-generate-rules.page.scss
@@ -0,0 +1,82 @@
+.button--rounded ion-button {
+ --padding-end: 0;
+ --padding-start: 0;
+ width: 40px;
+ height: 40px;
+}
+
+ion-content {
+ --padding-bottom: var(--ion-padding, 64px);
+}
+
+h1 {
+ font-size: 3rem;
+ line-height: 2.5rem;
+ margin-bottom: 2rem;
+}
+
+h2 {
+ font-size: 1.5rem;
+ line-height: 2rem;
+ margin-bottom: 2rem;
+}
+
+h3 {
+ font-size: 1rem;
+ line-height: 1.5rem;
+ margin-bottom: 0.825rem;
+ text-align: center;
+ max-width: 400px;
+}
+
+.content-wrapper {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: center;
+ height: 80vh;
+}
+
+.title-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.row-center {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+
+ .row-button {
+ font-size: large;
+ font-weight: bold;
+ color: rgba(255, 255, 255, 0.8);
+ width: 20px;
+ height: 20px;
+ padding: 0px;
+ }
+
+ ion-button {
+ --padding-start: 0px;
+ --padding-end: 0px;
+ }
+}
+
+.buttons-wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+ position: fixed;
+ width: 100%;
+ bottom: 20px;
+
+ ion-button {
+ --padding-top: 24px;
+ --padding-bottom: 24px;
+ --padding-start: 48px;
+ --padding-end: 48px;
+ }
+}
diff --git a/src/app/pages/social-recovery-generate-rules/social-recovery-generate-rules.page.ts b/src/app/pages/social-recovery-generate-rules/social-recovery-generate-rules.page.ts
new file mode 100644
index 00000000..591f5d28
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-rules/social-recovery-generate-rules.page.ts
@@ -0,0 +1,47 @@
+import { Component } from '@angular/core'
+import { MnemonicSecret } from 'src/app/models/secret'
+import { ErrorCategory, handleErrorLocal } from 'src/app/services/error-handler/error-handler.service'
+import { NavigationService } from 'src/app/services/navigation/navigation.service'
+
+@Component({
+ selector: 'airgap-social-recovery-generate-rules',
+ templateUrl: './social-recovery-generate-rules.page.html',
+ styleUrls: ['./social-recovery-generate-rules.page.scss']
+})
+export class SocialRecoveryGenerateRulesPage {
+ public state: 0 | 1 | 2 | 3 | 4 | 5 = 0
+ private readonly required: number
+ private readonly shares: string[]
+ private readonly secret: MnemonicSecret
+
+ constructor(private readonly navigationService: NavigationService) {
+ this.shares = this.navigationService.getState().shares
+ this.secret = this.navigationService.getState().secret
+ this.required = this.navigationService.getState().required
+ }
+
+ changeState(i: 0 | 1 | 2 | 3 | 4 | 5) {
+ this.state = i
+ }
+
+ prevState() {
+ if (this.state <= 0) return
+ this.state--
+ }
+
+ nextState() {
+ if (this.state < 5) {
+ this.state++
+ return
+ } else {
+ this.navigationService
+ .routeWithState('/social-recovery-generate-share-show', {
+ currentShare: 0,
+ shares: this.shares,
+ secret: this.secret,
+ required: this.required
+ })
+ .catch(handleErrorLocal(ErrorCategory.IONIC_NAVIGATION))
+ }
+ }
+}
diff --git a/src/app/pages/social-recovery-generate-rules/social-recovery-generate-rules.spec.ts b/src/app/pages/social-recovery-generate-rules/social-recovery-generate-rules.spec.ts
new file mode 100644
index 00000000..783767ad
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-rules/social-recovery-generate-rules.spec.ts
@@ -0,0 +1,28 @@
+/*
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
+import { async, ComponentFixture, TestBed } from '@angular/core/testing'
+
+import { SocialRecoveryImportPage } from './social-recovery-import.page'
+
+describe('SocialRecoveryImportPage', () => {
+ let component: SocialRecoveryImportPage
+ let fixture: ComponentFixture
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [SocialRecoveryImportPage],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ }).compileComponents()
+ }))
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SocialRecoveryImportPage)
+ component = fixture.componentInstance
+ fixture.detectChanges()
+ })
+
+ it('should create', () => {
+ expect(component).toBeTruthy()
+ })
+})
+*/
diff --git a/src/app/pages/social-recovery-generate-setup/social-recovery-generate-setup.module.ts b/src/app/pages/social-recovery-generate-setup/social-recovery-generate-setup.module.ts
new file mode 100644
index 00000000..6b2f9f38
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-setup/social-recovery-generate-setup.module.ts
@@ -0,0 +1,20 @@
+import { CommonModule } from '@angular/common'
+import { NgModule } from '@angular/core'
+import { FormsModule, ReactiveFormsModule } from '@angular/forms'
+import { RouterModule, Routes } from '@angular/router'
+import { IonicModule } from '@ionic/angular'
+import { TranslateModule } from '@ngx-translate/core'
+import { SocialRecoveryGenerateSetupPage } from './social-recovery-generate-setup.page'
+
+const routes: Routes = [
+ {
+ path: '',
+ component: SocialRecoveryGenerateSetupPage
+ }
+]
+
+@NgModule({
+ imports: [CommonModule, FormsModule, ReactiveFormsModule, IonicModule, RouterModule.forChild(routes), TranslateModule],
+ declarations: [SocialRecoveryGenerateSetupPage]
+})
+export class SocialRecoveryGenerateSetupPageModule {}
diff --git a/src/app/pages/social-recovery-generate-setup/social-recovery-generate-setup.page.html b/src/app/pages/social-recovery-generate-setup/social-recovery-generate-setup.page.html
new file mode 100644
index 00000000..52dbc6a2
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-setup/social-recovery-generate-setup.page.html
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ 'social-recovery-generate.intro-state-2-title' | translate }}
+
+
{{ 'social-recovery-generate.setup-state-1-text1' | translate }}
+
+
+ {{ i }}
+
+
+
+
+
+
+
+
{{ 'social-recovery-generate.setup-state-2-text1' | translate: { numberOfShares: numberOfShares } }}
+
+
+ {{ i }}
+
+
+
+
{{ 'social-recovery-generate.setup-state-2-text2' | translate }}
+
+ numberOfShares"
+ >
+ {{ i }}
+
+
+
+
+
+
+
+ {{ 'secret-edit.confirm_label' | translate }}
+
+
+
diff --git a/src/app/pages/social-recovery-generate-setup/social-recovery-generate-setup.page.scss b/src/app/pages/social-recovery-generate-setup/social-recovery-generate-setup.page.scss
new file mode 100644
index 00000000..665c41fb
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-setup/social-recovery-generate-setup.page.scss
@@ -0,0 +1,105 @@
+.button--rounded ion-button {
+ --padding-end: 0;
+ --padding-start: 0;
+ width: 40px;
+ height: 40px;
+}
+
+ion-content {
+ --padding-bottom: var(--ion-padding, 64px);
+}
+
+h1 {
+ font-size: 1.5rem;
+ line-height: 2rem;
+ margin-bottom: 2rem;
+}
+
+p {
+ text-align: center;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 500;
+ margin-block-start: 0px;
+ margin-block-end: 0px;
+ margin-bottom: 0.8rem;
+ max-width: 300px;
+ color: rgba(255, 255, 255, 0.8);
+}
+
+h2 {
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+ margin-bottom: 0.825rem;
+}
+
+h3 {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 500;
+ color: rgba(255, 255, 255, 0.8);
+ margin: 0px;
+}
+
+svg {
+ fill: white;
+ height: 78px;
+}
+
+.content-wrapper {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 80vh;
+}
+
+.button-wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: fixed;
+ bottom: 10px;
+ left: 0;
+ width: 100%;
+}
+
+.triple-svgs {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ width: 200px;
+ height: 160px;
+
+ .svg-1 {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ }
+
+ .svg-3 {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ }
+}
+
+.row-center {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+
+ .row-button {
+ height: 100%;
+ font-size: large;
+ font-weight: bold;
+ color: rgba(255, 255, 255, 0.8);
+ width: 50px;
+ height: 50px;
+ }
+}
+
+.divider {
+ height: 4rem;
+}
diff --git a/src/app/pages/social-recovery-generate-setup/social-recovery-generate-setup.page.ts b/src/app/pages/social-recovery-generate-setup/social-recovery-generate-setup.page.ts
new file mode 100644
index 00000000..3846bc18
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-setup/social-recovery-generate-setup.page.ts
@@ -0,0 +1,63 @@
+import { Component } from '@angular/core'
+import { MnemonicSecret } from 'src/app/models/secret'
+import { ErrorCategory, handleErrorLocal } from 'src/app/services/error-handler/error-handler.service'
+import { NavigationService } from 'src/app/services/navigation/navigation.service'
+import { SecretsService } from 'src/app/services/secrets/secrets.service'
+import * as bip39 from 'bip39'
+
+@Component({
+ selector: 'airgap-social-recovery-generate-setup',
+ templateUrl: './social-recovery-generate-setup.page.html',
+ styleUrls: ['./social-recovery-generate-setup.page.scss']
+})
+export class SocialRecoveryGenerateSetupPage {
+ public state: 0 | 1 = 0
+ public numberOfShares: number = -1
+ public numberOfRequiredShares: number = -1
+ private readonly secret: MnemonicSecret
+
+ get numberSharesArray(): number[] {
+ const arr = []
+ for (let i = 2; i <= this.numberOfShares; i++) {
+ arr.push(i)
+ }
+ return arr
+ }
+ constructor(private readonly secretService: SecretsService, private readonly navigationService: NavigationService) {
+ this.secret = this.navigationService.getState().secret
+ }
+
+ public setNumberOfShares(i: number): void {
+ this.numberOfShares = i
+ if (this.numberOfRequiredShares > this.numberOfShares) {
+ this.numberOfRequiredShares = this.numberOfShares
+ }
+ }
+
+ public setNumberOfRequiredShares(i: number): void {
+ this.numberOfRequiredShares = i
+ }
+
+ nextState() {
+ if (this.state === 0) {
+ this.state++
+ return
+ } else if (this.state === 1) {
+ this.secretService
+ .retrieveEntropyForSecret(this.secret)
+ .then((entropy) => {
+ const shares: string[] = MnemonicSecret.generateSocialRecover(
+ bip39.entropyToMnemonic(entropy),
+ this.numberOfShares,
+ this.numberOfRequiredShares
+ )
+ this.navigationService
+ .routeWithState('/social-recovery-generate-rules', { shares, secret: this.secret, required: this.numberOfRequiredShares })
+ .catch(handleErrorLocal(ErrorCategory.IONIC_NAVIGATION))
+ })
+ .catch((error) => {
+ console.warn(error)
+ })
+ }
+ }
+}
diff --git a/src/app/pages/social-recovery-generate-setup/social-recovery-generate-setup.spec.ts b/src/app/pages/social-recovery-generate-setup/social-recovery-generate-setup.spec.ts
new file mode 100644
index 00000000..783767ad
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-setup/social-recovery-generate-setup.spec.ts
@@ -0,0 +1,28 @@
+/*
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
+import { async, ComponentFixture, TestBed } from '@angular/core/testing'
+
+import { SocialRecoveryImportPage } from './social-recovery-import.page'
+
+describe('SocialRecoveryImportPage', () => {
+ let component: SocialRecoveryImportPage
+ let fixture: ComponentFixture
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [SocialRecoveryImportPage],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ }).compileComponents()
+ }))
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SocialRecoveryImportPage)
+ component = fixture.componentInstance
+ fixture.detectChanges()
+ })
+
+ it('should create', () => {
+ expect(component).toBeTruthy()
+ })
+})
+*/
diff --git a/src/app/pages/social-recovery-generate-share-show/social-recovery-generate-share-show.module.ts b/src/app/pages/social-recovery-generate-share-show/social-recovery-generate-share-show.module.ts
new file mode 100644
index 00000000..1325adbe
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-share-show/social-recovery-generate-share-show.module.ts
@@ -0,0 +1,20 @@
+import { CommonModule } from '@angular/common'
+import { NgModule } from '@angular/core'
+import { FormsModule, ReactiveFormsModule } from '@angular/forms'
+import { RouterModule, Routes } from '@angular/router'
+import { IonicModule } from '@ionic/angular'
+import { TranslateModule } from '@ngx-translate/core'
+import { SocialRecoveryGenerateShareShowPage } from './social-recovery-generate-share-show.page'
+
+const routes: Routes = [
+ {
+ path: '',
+ component: SocialRecoveryGenerateShareShowPage
+ }
+]
+
+@NgModule({
+ imports: [CommonModule, FormsModule, ReactiveFormsModule, IonicModule, RouterModule.forChild(routes), TranslateModule],
+ declarations: [SocialRecoveryGenerateShareShowPage]
+})
+export class SocialRecoveryGenerateShareShowPageModule {}
diff --git a/src/app/pages/social-recovery-generate-share-show/social-recovery-generate-share-show.page.html b/src/app/pages/social-recovery-generate-share-show/social-recovery-generate-share-show.page.html
new file mode 100644
index 00000000..41200b4e
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-share-show/social-recovery-generate-share-show.page.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{i}}
+
+
+
{{ 'social-recovery-generate.share-show-heading' | translate }}
+
+
+
+ {{ 'social-recovery-setup.back_label' | translate }}
+ {{ 'social-recovery-setup.next_label' | translate }}
+
+
+
diff --git a/src/app/pages/social-recovery-generate-share-show/social-recovery-generate-share-show.page.scss b/src/app/pages/social-recovery-generate-share-show/social-recovery-generate-share-show.page.scss
new file mode 100644
index 00000000..2726bfab
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-share-show/social-recovery-generate-share-show.page.scss
@@ -0,0 +1,121 @@
+.button--rounded ion-button {
+ --padding-end: 0;
+ --padding-start: 0;
+ width: 40px;
+ height: 40px;
+}
+
+ion-content {
+ --padding-bottom: var(--ion-padding, 64px);
+}
+
+.content-wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.center-wrapper {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ max-width: 540px;
+}
+
+h2 {
+ font-size: 1.125rem;
+ line-height: 1.5rem;
+ text-align: center;
+ margin-bottom: 1.5rem;
+ color: rgba(255, 255, 255, 0.9);
+}
+
+.states-wrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+ width: 70%;
+ gap: 4px;
+ margin-bottom: 3rem;
+
+ &::after {
+ content: '';
+ position: absolute;
+ top: 49%;
+ left: 2%;
+ border: 1px dashed rgba(255, 255, 255, 0.591);
+ width: 94%;
+ z-index: -1;
+ }
+
+ .state {
+ height: 100%;
+ font-size: large;
+ font-weight: bold;
+ color: rgba(255, 255, 255, 0.8);
+ width: 30px;
+ height: 30px;
+ padding: 0px;
+ }
+
+ ion-button {
+ --padding-start: 0px;
+ --padding-end: 0px;
+ }
+
+ .selected {
+ margin-right: auto;
+ }
+}
+
+.tags-wrapper {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(95px, 1fr));
+ width: 100%;
+ margin-bottom: 48px;
+
+ .tag {
+ --padding-bottom: 8px;
+ --padding-top: 8px;
+ --padding-start: 18px;
+ --padding-end: 18px;
+ height: fit-content;
+
+ .number {
+ font-size: 0.65rem;
+ line-height: 0.75rem;
+ color: rgb(192, 192, 192);
+ width: 100%;
+ text-align: start;
+ margin: 0px;
+ margin-right: 4px;
+ }
+
+ .text {
+ font-size: 0.65rem;
+ line-height: 0.75rem;
+ text-transform: lowercase;
+ font-weight: 700;
+ margin: 0px;
+ }
+ }
+}
+
+.buttons-wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+ position: fixed;
+ width: 100%;
+ bottom: 0px;
+ padding-bottom: 12px;
+ background-color: rgba(0, 0, 0, 0.5);
+
+ ion-button {
+ --padding-top: 24px;
+ --padding-bottom: 24px;
+ --padding-start: 48px;
+ --padding-end: 48px;
+ }
+}
diff --git a/src/app/pages/social-recovery-generate-share-show/social-recovery-generate-share-show.page.ts b/src/app/pages/social-recovery-generate-share-show/social-recovery-generate-share-show.page.ts
new file mode 100644
index 00000000..7f0dff7e
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-share-show/social-recovery-generate-share-show.page.ts
@@ -0,0 +1,64 @@
+import { Component } from '@angular/core'
+import { ActivatedRoute } from '@angular/router'
+import { MnemonicSecret } from 'src/app/models/secret'
+import { ErrorCategory, handleErrorLocal } from 'src/app/services/error-handler/error-handler.service'
+import { NavigationService } from 'src/app/services/navigation/navigation.service'
+
+@Component({
+ selector: 'airgap-social-recovery-generate-share-show',
+ templateUrl: './social-recovery-generate-share-show.page.html',
+ styleUrls: ['./social-recovery-generate-share-show.page.scss']
+})
+export class SocialRecoveryGenerateShareShowPage {
+ public currentShare: number = 0
+ public shares: string[]
+ private secret: MnemonicSecret
+ private required: number
+
+ get currentShares(): string[] {
+ if (this.shares && this.currentShare < this.shares.length) {
+ const currentShares = this.shares[this.currentShare].split(' ')
+ return currentShares
+ } else return ['No shares generated, something went wrong']
+ }
+
+ constructor(private readonly navigationService: NavigationService, route: ActivatedRoute) {
+ route.params.subscribe((_) => {
+ this.currentShare = this.navigationService.getState().currentShare
+ this.shares = this.navigationService.getState().shares
+ this.secret = this.navigationService.getState().secret
+ this.required = this.navigationService.getState().required
+ })
+ }
+
+ prev() {
+ if (this.currentShare <= 0)
+ this.navigationService
+ .routeWithState('/social-recovery-generate-rules', {
+ shares: this.shares,
+ secret: this.secret,
+ required: this.required
+ })
+ .catch(handleErrorLocal(ErrorCategory.IONIC_NAVIGATION))
+ else
+ this.navigationService
+ .routeWithState('/social-recovery-generate-share-validate', {
+ shares: this.shares,
+ currentShare: this.currentShare - 1,
+ secret: this.secret,
+ required: this.required
+ })
+ .catch(handleErrorLocal(ErrorCategory.IONIC_NAVIGATION))
+ }
+
+ next() {
+ this.navigationService
+ .routeWithState('/social-recovery-generate-share-validate', {
+ shares: this.shares,
+ currentShare: this.currentShare,
+ secret: this.secret,
+ required: this.required
+ })
+ .catch(handleErrorLocal(ErrorCategory.IONIC_NAVIGATION))
+ }
+}
diff --git a/src/app/pages/social-recovery-generate-share-show/social-recovery-generate-share-show.spec.ts b/src/app/pages/social-recovery-generate-share-show/social-recovery-generate-share-show.spec.ts
new file mode 100644
index 00000000..783767ad
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-share-show/social-recovery-generate-share-show.spec.ts
@@ -0,0 +1,28 @@
+/*
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
+import { async, ComponentFixture, TestBed } from '@angular/core/testing'
+
+import { SocialRecoveryImportPage } from './social-recovery-import.page'
+
+describe('SocialRecoveryImportPage', () => {
+ let component: SocialRecoveryImportPage
+ let fixture: ComponentFixture
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [SocialRecoveryImportPage],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ }).compileComponents()
+ }))
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SocialRecoveryImportPage)
+ component = fixture.componentInstance
+ fixture.detectChanges()
+ })
+
+ it('should create', () => {
+ expect(component).toBeTruthy()
+ })
+})
+*/
diff --git a/src/app/pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.module.ts b/src/app/pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.module.ts
new file mode 100644
index 00000000..e1f12842
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.module.ts
@@ -0,0 +1,21 @@
+import { CommonModule } from '@angular/common'
+import { NgModule } from '@angular/core'
+import { FormsModule, ReactiveFormsModule } from '@angular/forms'
+import { RouterModule, Routes } from '@angular/router'
+import { IonicModule } from '@ionic/angular'
+import { TranslateModule } from '@ngx-translate/core'
+import { ComponentsModule } from 'src/app/components/components.module'
+import { SocialRecoveryGenerateShareValidatePage } from './social-recovery-generate-share-validate.page'
+
+const routes: Routes = [
+ {
+ path: '',
+ component: SocialRecoveryGenerateShareValidatePage
+ }
+]
+
+@NgModule({
+ imports: [CommonModule, FormsModule, ReactiveFormsModule, IonicModule, RouterModule.forChild(routes), TranslateModule, ComponentsModule],
+ declarations: [SocialRecoveryGenerateShareValidatePage]
+})
+export class SocialRecoveryGenerateShareValidatePageModule {}
diff --git a/src/app/pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.page.html b/src/app/pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.page.html
new file mode 100644
index 00000000..8422a56f
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.page.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{i}}
+
+
+
Tap each word to match the correct order
+
+
+
+
+ {{ 'social-recovery-setup.back_label' | translate }}
+
+ {{ 'social-recovery-setup.next_label' | translate }}
+
+
+
+
diff --git a/src/app/pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.page.scss b/src/app/pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.page.scss
new file mode 100644
index 00000000..7ce97b26
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.page.scss
@@ -0,0 +1,117 @@
+.button--rounded ion-button {
+ --padding-end: 0;
+ --padding-start: 0;
+ width: 40px;
+ height: 40px;
+}
+
+ion-content {
+ --padding-bottom: var(--ion-padding, 64px);
+}
+
+.content-wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.center-wrapper {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ max-width: 540px;
+}
+
+h2 {
+ font-size: 1.125rem;
+ line-height: 1.5rem;
+ text-align: center;
+ margin-bottom: 1.5rem;
+ color: rgba(255, 255, 255, 0.9);
+}
+
+.states-wrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+ width: 70%;
+ gap: 4px;
+ margin-bottom: 3rem;
+
+ &::after {
+ content: '';
+ position: absolute;
+ top: 49%;
+ left: 2%;
+ border: 1px dashed rgba(255, 255, 255, 0.591);
+ width: 94%;
+ z-index: -1;
+ }
+
+ .state {
+ height: 100%;
+ font-size: large;
+ font-weight: bold;
+ color: rgba(255, 255, 255, 0.8);
+ width: 30px;
+ height: 30px;
+ padding: 0px;
+ }
+
+ ion-button {
+ --padding-start: 0px;
+ --padding-end: 0px;
+ }
+
+ .selected {
+ margin-right: auto;
+ }
+}
+
+.tags-wrapper {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
+ width: 100%;
+
+ .tag {
+ --padding-bottom: 0px;
+ --padding-top: 0px;
+ --padding-start: 18px;
+ --padding-end: 18px;
+
+ .number {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: rgb(192, 192, 192);
+ width: 100%;
+ text-align: start;
+ margin-right: 4px;
+ }
+
+ .text {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ text-transform: lowercase;
+ font-weight: 800;
+ }
+ }
+}
+
+.buttons-wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+ position: fixed;
+ width: 100%;
+ bottom: 0px;
+ padding-bottom: 12px;
+ background-color: rgba(0, 0, 0, 0.4);
+
+ ion-button {
+ --padding-top: 24px;
+ --padding-bottom: 24px;
+ --padding-start: 48px;
+ --padding-end: 48px;
+ }
+}
diff --git a/src/app/pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.page.ts b/src/app/pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.page.ts
new file mode 100644
index 00000000..d73459f6
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.page.ts
@@ -0,0 +1,87 @@
+import { Component, ViewChild } from '@angular/core'
+import { ActivatedRoute } from '@angular/router'
+import { IonContent } from '@ionic/angular'
+import { MnemonicSecret } from 'src/app/models/secret'
+import { ErrorCategory, handleErrorLocal } from 'src/app/services/error-handler/error-handler.service'
+import { NavigationService } from 'src/app/services/navigation/navigation.service'
+import { SecretsService } from 'src/app/services/secrets/secrets.service'
+
+@Component({
+ selector: 'airgap-social-recovery-generate-share-validate',
+ templateUrl: './social-recovery-generate-share-validate.page.html',
+ styleUrls: ['./social-recovery-generate-share-validate.page.scss']
+})
+export class SocialRecoveryGenerateShareValidatePage {
+ @ViewChild(IonContent) content: IonContent
+
+ public validated: boolean = false
+ public currentShare: number = 0
+ public shares: string[]
+
+ public isCompleted: boolean = false
+ public currentWords: string[] = []
+ public promptedWords: string[] = []
+ public selectedWordIndex: number | null = null
+ private secret: MnemonicSecret
+ private required: number
+
+ get currentShares(): string[] {
+ if (this.shares && this.currentShare < this.shares.length) {
+ const currentShares = this.shares[this.currentShare].split(' ')
+ return currentShares
+ } else return ['No shares generated, something went wrong']
+ }
+
+ constructor(
+ private readonly secretsService: SecretsService,
+ private readonly navigationService: NavigationService,
+ route: ActivatedRoute
+ ) {
+ route.params.subscribe((_) => {
+ this.currentShare = this.navigationService.getState().currentShare
+ this.shares = this.navigationService.getState().shares
+ this.secret = this.navigationService.getState().secret
+ this.required = this.navigationService.getState().required
+ })
+ }
+
+ public onComplete(isCorrect: boolean) {
+ this.validated = isCorrect
+ }
+
+ public onNext() {
+ if (this.content) this.content.scrollToBottom(500)
+ }
+
+ prev() {
+ this.navigationService
+ .routeWithState('/social-recovery-generate-share-show', {
+ shares: this.shares,
+ currentShare: this.currentShare,
+ secret: this.secret,
+ required: this.required
+ })
+ .catch(handleErrorLocal(ErrorCategory.IONIC_NAVIGATION))
+ }
+
+ next() {
+ if (this.currentShare < this.shares.length - 1)
+ this.navigationService
+ .routeWithState('/social-recovery-generate-share-show', {
+ shares: this.shares,
+ currentShare: this.currentShare + 1,
+ secret: this.secret,
+ required: this.required
+ })
+ .catch(handleErrorLocal(ErrorCategory.IONIC_NAVIGATION))
+ else {
+ this.secret.hasSocialRecovery = true
+ this.secretsService
+ .addOrUpdateSecret(this.secret)
+ .then(() => {
+ this.navigationService.route('/social-recovery-generate-finish').catch(handleErrorLocal(ErrorCategory.IONIC_NAVIGATION))
+ })
+ .catch(handleErrorLocal(ErrorCategory.SECURE_STORAGE))
+ }
+ }
+}
diff --git a/src/app/pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.spec.ts b/src/app/pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.spec.ts
new file mode 100644
index 00000000..783767ad
--- /dev/null
+++ b/src/app/pages/social-recovery-generate-share-validate/social-recovery-generate-share-validate.spec.ts
@@ -0,0 +1,28 @@
+/*
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
+import { async, ComponentFixture, TestBed } from '@angular/core/testing'
+
+import { SocialRecoveryImportPage } from './social-recovery-import.page'
+
+describe('SocialRecoveryImportPage', () => {
+ let component: SocialRecoveryImportPage
+ let fixture: ComponentFixture
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [SocialRecoveryImportPage],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ }).compileComponents()
+ }))
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SocialRecoveryImportPage)
+ component = fixture.componentInstance
+ fixture.detectChanges()
+ })
+
+ it('should create', () => {
+ expect(component).toBeTruthy()
+ })
+})
+*/
diff --git a/src/app/pages/tab-secrets/tab-secret-popover/tab-secret-popover.component.html b/src/app/pages/tab-secrets/tab-secret-popover/tab-secret-popover.component.html
index 1e07effa..929e3654 100644
--- a/src/app/pages/tab-secrets/tab-secret-popover/tab-secret-popover.component.html
+++ b/src/app/pages/tab-secrets/tab-secret-popover/tab-secret-popover.component.html
@@ -1,9 +1,13 @@
- Settings
+ {{ 'tab-secrets.tab-secret-popover.title' | translate }}
- Add Secret
+ {{ 'tab-secrets.tab-secret-popover.add-secret_label' | translate }}
+
+
+
+ {{ 'tab-secrets.tab-secret-popover.sync-all_label' | translate }}
diff --git a/src/app/pages/tab-secrets/tab-secret-popover/tab-secret-popover.component.ts b/src/app/pages/tab-secrets/tab-secret-popover/tab-secret-popover.component.ts
index 404e0a09..c33cda61 100644
--- a/src/app/pages/tab-secrets/tab-secret-popover/tab-secret-popover.component.ts
+++ b/src/app/pages/tab-secrets/tab-secret-popover/tab-secret-popover.component.ts
@@ -1,4 +1,5 @@
import { Component } from '@angular/core'
+import { NavParams } from '@ionic/angular'
import { ErrorCategory, handleErrorLocal } from 'src/app/services/error-handler/error-handler.service'
import { NavigationService } from 'src/app/services/navigation/navigation.service'
@@ -8,14 +9,21 @@ import { NavigationService } from 'src/app/services/navigation/navigation.servic
styleUrls: ['./tab-secret-popover.component.scss']
})
export class TabSecretPopoverComponent {
- private readonly onClick: Function
+ private readonly onClickNewSecret: Function
+ private readonly onClickSyncWallets: Function
- constructor(public navigationService: NavigationService) {}
+ constructor(public navigationService: NavigationService, protected navParams: NavParams) {}
public goToNewSecret(): void {
- if (this.onClick) {
- this.onClick()
+ if (this.onClickNewSecret) {
+ this.onClickNewSecret()
}
this.navigationService.route('/secret-setup').catch(handleErrorLocal(ErrorCategory.IONIC_NAVIGATION))
}
+
+ public syncWalletsHandler() {
+ if (this.onClickSyncWallets) {
+ this.onClickSyncWallets()
+ }
+ }
}
diff --git a/src/app/pages/tab-secrets/tab-secrets.page.ts b/src/app/pages/tab-secrets/tab-secrets.page.ts
index d59da78a..a15692e2 100644
--- a/src/app/pages/tab-secrets/tab-secrets.page.ts
+++ b/src/app/pages/tab-secrets/tab-secrets.page.ts
@@ -6,6 +6,8 @@ import { SecretsService } from 'src/app/services/secrets/secrets.service'
import { ErrorCategory, handleErrorLocal } from 'src/app/services/error-handler/error-handler.service'
import { NavigationService } from 'src/app/services/navigation/navigation.service'
import { TabSecretPopoverComponent } from './tab-secret-popover/tab-secret-popover.component'
+import { ModeService } from 'src/app/services/mode/mode.service'
+import { ModeStrategy } from 'src/app/services/mode/strategy/ModeStrategy'
@Component({
selector: 'airgap-tab-secrets',
@@ -20,7 +22,8 @@ export class TabSecretsPage {
private readonly popoverCtrl: PopoverController,
public modalController: ModalController,
public navigationService: NavigationService,
- private secretsService: SecretsService
+ private secretsService: SecretsService,
+ private readonly modeService: ModeService
) {
this.secrets = this.secretsService.getSecretsObservable()
}
@@ -47,7 +50,11 @@ export class TabSecretsPage {
const popover = await this.popoverCtrl.create({
component: TabSecretPopoverComponent,
componentProps: {
- onClick: (): void => {
+ onClickNewSecret: (): void => {
+ popover.dismiss().catch(handleErrorLocal(ErrorCategory.IONIC_MODAL))
+ },
+ onClickSyncWallets: (): void => {
+ this.syncWallets()
popover.dismiss().catch(handleErrorLocal(ErrorCategory.IONIC_MODAL))
}
},
@@ -57,4 +64,9 @@ export class TabSecretsPage {
popover.present().catch(handleErrorLocal(ErrorCategory.IONIC_MODAL))
}
+
+ public async syncWallets(): Promise {
+ const strategy: ModeStrategy = await this.modeService.strategy()
+ await strategy.syncAll()
+ }
}
diff --git a/src/app/pages/tab-settings/tab-settings.page.html b/src/app/pages/tab-settings/tab-settings.page.html
index 64158c68..c0bc4ef8 100644
--- a/src/app/pages/tab-settings/tab-settings.page.html
+++ b/src/app/pages/tab-settings/tab-settings.page.html
@@ -5,104 +5,94 @@
-
-
- {{ 'tab-settings.information_label' | translate }}
-
-
-
-
- {{ 'tab-settings.about' | translate }}
-
-
-
-
- {{ 'tab-settings.welcome-onboarding_label' | translate }}
-
-
-
-
-
-
-
-
-
- {{ 'tab-settings.interaction-options_label' | translate }}
-
-
-
- {{ 'tab-settings.paste-from-clipboard_label' | translate }}
-
-
-
-
- {{ 'tab-settings.qr-advanced-settings_label' | translate }}
-
-
-
-
- {{ 'tab-settings.interaction-settings_label' | translate }}
-
-
-
-
- {{ 'tab-settings.preferred-language_label' | translate }}
-
-
-
-
-
- {{ 'tab-settings.advanced-settings_label' | translate }}
-
-
-
-
- {{ 'tab-settings.installation-type_label' | translate }}
-
-
-
-
- {{ 'tab-settings.advanced-mode-type_label' | translate }}
-
-
-
-
- {{ 'tab-settings.bip39_wordlist_label' | translate }}
-
-
-
-
- {{ 'tab-settings.error-history_label' | translate }}
-
-
-
-
- {{ 'tab-settings.danger-zone_label' | translate }}
-
-
-
-
-
- {{ 'tab-settings.advanced-features_label' | translate }}
-
-
-
-
- {{ 'tab-settings.address_book_label' | translate }}
-
-
-
-
- {{ 'tab-settings.configure_address_book_label' | translate }}
-
-
+
+ {{ 'tab-settings.settings_label' | translate }}
+
+
+
+ {{ 'tab-settings.qr-advanced-settings_label' | translate }}
+
+
+
+
+ {{ 'tab-settings.interaction-settings_label' | translate }}
+
+
+
+ {{ 'tab-settings.installation-type_label' | translate }}
+
+
+
+
+ {{ 'tab-settings.preferred-language_label' | translate }}
+
+
+
+
+ {{ 'tab-settings.advanced-mode-type_label' | translate }}
+
+
+
+
+
+ {{ 'tab-settings.address_book_label' | translate }}
+
+
+
+
+ {{ 'tab-settings.address_book_settings_label' | translate }}
+
+
+
+
+
+ {{ 'contact-book.label_enable_address_book' | translate }}
+
+
+
+
+ {{ 'contact-book.label_enable_suggestions' | translate }}
+
+
+
+
+
+
+ {{ 'contact-book.button_delete_entries' | translate }}
+
+
+
+
+
+ {{ 'tab-settings.actions_label' | translate }}
+
+
+
+ {{ 'tab-settings.paste-from-clipboard_label' | translate }}
+
+
+
+ {{ 'danger-zone.wipe_label' | translate }}
+
+
+
+ {{ 'tab-settings.information_label' | translate }}
+
+
+
+
+ {{ 'tab-settings.about' | translate }}
+
+
+
+ {{ 'tab-settings.welcome-onboarding_label' | translate }}
+
+
+
+ {{ 'tab-settings.bip39_wordlist_label' | translate }}
+
+
+
+ {{ 'tab-settings.error-history_label' | translate }}
+
diff --git a/src/app/pages/tab-settings/tab-settings.page.ts b/src/app/pages/tab-settings/tab-settings.page.ts
index d466553b..8e71f35c 100644
--- a/src/app/pages/tab-settings/tab-settings.page.ts
+++ b/src/app/pages/tab-settings/tab-settings.page.ts
@@ -1,5 +1,5 @@
-import { Component } from '@angular/core'
-import { ModalController } from '@ionic/angular'
+import { Component, OnInit } from '@angular/core'
+import { AlertController, ModalController } from '@ionic/angular'
import { Observable } from 'rxjs'
import { MnemonicSecret } from '../../models/secret'
@@ -12,14 +12,19 @@ import { InstallationTypePage } from '../Installation-type/installation-type.pag
import { OnboardingAdvancedModePage } from '../onboarding-advanced-mode/onboarding-advanced-mode.page'
import { OnboardingWelcomePage } from '../onboarding-welcome/onboarding-welcome.page'
import { ContactsService } from 'src/app/services/contacts/contacts.service'
+import { TranslateService } from '@ngx-translate/core'
+import { SecureStorageService } from 'src/app/services/secure-storage/secure-storage.service'
+import { VaultStorageService } from 'src/app/services/storage/storage.service'
@Component({
selector: 'airgap-tab-settings',
templateUrl: './tab-settings.page.html',
styleUrls: ['./tab-settings.page.scss']
})
-export class TabSettingsPage {
+export class TabSettingsPage implements OnInit {
public readonly secrets: Observable
+ public bookEnabled: boolean = true
+ public suggestionsEnabled: boolean
constructor(
public readonly serializerService: SerializerService,
@@ -28,11 +33,20 @@ export class TabSettingsPage {
private readonly iacService: IACService,
private readonly clipboardService: ClipboardService,
private readonly navigationService: NavigationService,
- private readonly contactsService: ContactsService
+ private readonly contactsService: ContactsService,
+ private readonly translateService: TranslateService,
+ private readonly alertCtrl: AlertController,
+ private readonly secureStorage: SecureStorageService,
+ public readonly storageService: VaultStorageService
) {
this.secrets = this.secretsService.getSecretsObservable()
}
+ async ngOnInit(): Promise {
+ this.contactsService.isBookEnabled().then((value: boolean) => (this.bookEnabled = value))
+ this.contactsService.isSuggestionsEnabled().then((value: boolean) => (this.suggestionsEnabled = value))
+ }
+
public goToAbout(): void {
this.navigationService.route('/about').catch(handleErrorLocal(ErrorCategory.IONIC_NAVIGATION))
}
@@ -78,16 +92,6 @@ export class TabSettingsPage {
modal.present().catch(handleErrorLocal(ErrorCategory.IONIC_MODAL))
}
- // public async goToDisclaimer(): Promise {
- // const modal: HTMLIonModalElement = await this.modalController.create({
- // component: WarningModalPage,
- // componentProps: { errorType: Warning.SECURE_STORAGE },
- // backdropDismiss: false
- // })
-
- // modal.present().catch(handleErrorLocal(ErrorCategory.IONIC_MODAL))
- // }
-
public async goToInstallationType(): Promise {
const modal: HTMLIonModalElement = await this.modalController.create({
component: InstallationTypePage,
@@ -112,10 +116,6 @@ export class TabSettingsPage {
this.navigationService.route('/wordlist').catch(handleErrorLocal(ErrorCategory.IONIC_NAVIGATION))
}
- public goToDangerZone(): void {
- this.navigationService.route('/danger-zone').catch(handleErrorLocal(ErrorCategory.IONIC_NAVIGATION))
- }
-
public pasteClipboard(): void {
this.clipboardService.paste().then(
(text: string) => {
@@ -127,4 +127,82 @@ export class TabSettingsPage {
}
)
}
+
+ public async resetVault() {
+ const alert = await this.alertCtrl.create({
+ header: this.translateService.instant('danger-zone.wipe.alert.title'),
+ message: this.translateService.instant('danger-zone.wipe.alert.message'),
+ buttons: [
+ {
+ text: this.translateService.instant('danger-zone.wipe.alert.cancel'),
+ role: 'cancel'
+ },
+ {
+ text: this.translateService.instant('danger-zone.wipe.alert.ok'),
+ handler: async () => {
+ try {
+ await this.secureStorage.wipe()
+ await this.storageService.wipe()
+ } catch (e) {
+ console.error('Wiping failed', e)
+ return this.resetVaultError()
+ }
+
+ this.navigationService.route('/').then(() => {
+ location.reload()
+ })
+ }
+ }
+ ]
+ })
+ alert.present()
+ }
+
+ public async resetVaultError() {
+ const alert = await this.alertCtrl.create({
+ header: this.translateService.instant('danger-zone.wipe-error.alert.title'),
+ message: this.translateService.instant('danger-zone.wipe-error.alert.message'),
+ buttons: [
+ {
+ text: this.translateService.instant('danger-zone.wipe-error.alert.ok')
+ }
+ ]
+ })
+ alert.present()
+ }
+
+ public async toggleAddressBook(event: any) {
+ const value = event.detail.checked
+ await this.contactsService.setBookEnable(value)
+ }
+
+ public async toggleEnableSuggestions(event: any) {
+ const value = event.detail.checked
+ await this.contactsService.setSuggestionsEnable(value)
+ }
+
+ public async onClickDelete() {
+ const alert = await this.alertCtrl.create({
+ header: this.translateService.instant('contacts-delete-popover.title'),
+ message: this.translateService.instant('contacts-delete-popover.text'),
+ buttons: [
+ {
+ text: this.translateService.instant('contacts-delete-popover.cancel_label'),
+ role: 'cancel'
+ },
+ {
+ text: this.translateService.instant('contacts-delete-popover.delete_label'),
+ handler: async () => {
+ try {
+ await this.contactsService.deleteAllContacts()
+ await this.contactsService.setOnboardingEnable(true)
+ } catch (e) {
+ console.error('Deleting Entries failed', e)
+ }
+ }
+ }
+ ]
+ })
+ alert.present()
+ }
}
diff --git a/src/app/services/navigation/navigation.service.ts b/src/app/services/navigation/navigation.service.ts
index f41795e7..21b5a27d 100644
--- a/src/app/services/navigation/navigation.service.ts
+++ b/src/app/services/navigation/navigation.service.ts
@@ -2,6 +2,7 @@ import { Location } from '@angular/common'
import { Injectable } from '@angular/core'
import { NavigationBehaviorOptions, Router } from '@angular/router'
import { NavController } from '@ionic/angular'
+import { App } from '@capacitor/app'
import { Identifiable } from '../../models/identifiable'
@@ -10,6 +11,27 @@ interface State {
[key: string]: any
}
+const rootPath: string = '/tabs/tab-secrets'
+const settingsPath: string = '/tabs/tab-settings'
+const scanPath: string = '/tabs/tab-scan'
+
+const paths: { path: string; prevPath: string }[] = [
+ { path: settingsPath, prevPath: rootPath },
+ { path: '/about', prevPath: settingsPath },
+ { path: '/qr-settings', prevPath: settingsPath },
+ { path: '/languages-selection-settings', prevPath: settingsPath },
+ { path: '/wordlist', prevPath: settingsPath },
+ { path: '/error-history', prevPath: settingsPath },
+ { path: '/contact-book-onboarding', prevPath: settingsPath },
+ { path: '/contact-book-contacts', prevPath: settingsPath },
+ { path: '/contact-book-contacts-detail', prevPath: '/contact-book-contacts' },
+ { path: scanPath, prevPath: rootPath },
+ { path: '/accounts-list', prevPath: rootPath },
+ { path: '/account-address', prevPath: '/accounts-list' },
+ { path: '/secret-setup', prevPath: rootPath },
+ { path: '/contact-book-scan', prevPath: '/contact-book-contacts-detail' }
+]
+
@Injectable({
providedIn: 'root'
})
@@ -45,14 +67,26 @@ export class NavigationService {
}
public routeToSecretsTab(clearStack: boolean = false): Promise {
- return this.router.navigateByUrl('/tabs/tab-secrets', { replaceUrl: clearStack })
+ return this.router.navigateByUrl(rootPath, { replaceUrl: clearStack })
}
public routeToScanTab(clearStack: boolean = false): Promise {
- return this.router.navigateByUrl('/tabs/tab-scan', { replaceUrl: clearStack })
+ return this.router.navigateByUrl(scanPath, { replaceUrl: clearStack })
}
public routeToSettingsTab(clearStack: boolean = false): Promise {
- return this.router.navigateByUrl('/tabs/tab-settings', { replaceUrl: clearStack })
+ return this.router.navigateByUrl(settingsPath, { replaceUrl: clearStack })
+ }
+
+ handleAndroidBackNavigation(currentPath: string) {
+ if (currentPath === rootPath) {
+ App.exitApp()
+ return
+ }
+
+ const prevpath = paths.find((p) => currentPath.includes(p.path))
+ if (prevpath) {
+ this.router.navigateByUrl(prevpath.prevPath).catch((error) => console.error('error related to navigation', error))
+ }
}
}
diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json
index 2e1acf7d..1d42addd 100644
--- a/src/assets/i18n/de.json
+++ b/src/assets/i18n/de.json
@@ -274,6 +274,12 @@
"title": "Soziale Wiederherstellung",
"text": "Ordnen Sie die Wörter in die richtige Reihenfolge, indem Sie jeweils das betreffende Wort anwählen."
},
+ "contacts-delete-popover": {
+ "title": "Einträge zurücksetzen",
+ "text": "Diese Aktion wird alle Ihre gespeicherten Einträge löschen. Möchten Sie fortfahren?",
+ "cancel_label": "Abbrechen",
+ "delete_label": "Ok"
+ },
"tab-scan": {
"title": "QR Code Scanner",
"text": "QR Code von AirGap Wallet einlesen.",
@@ -310,8 +316,13 @@
},
"tab-secrets": {
"title": "Secrets",
- "add-secret_label": "Add Secret",
- "search_placeholder": "Search Secret"
+ "add-secret_label": "Secret hinzufügen",
+ "search_placeholder": "Secrets durchsuchen",
+ "tab-secret-popover":{
+ "title":"Secret Verwaltung",
+ "add-secret_label": "Secret hinzufügen",
+ "sync-all_label": "Alle Accounts mit AirGap Wallet synchronisieren"
+ }
},
"tab-wallets": {
"title": "Konten",
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index d754003f..7faa93bc 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -166,6 +166,38 @@
"retry-button_label": "Retry"
}
},
+ "social-recovery-generate": {
+ "intro-state-1-title": "Social Recovery",
+ "intro-state-1-text1": "With Social Recovery, you can create secret shares and distribute them to social contacts you trust.",
+ "intro-state-1-text2": "You can recover a lost secret if you have a set number of these secret shares.",
+ "intro-state-2-title": "Distribute Your Shares",
+ "intro-state-2-text1": "The idea is to have different shares, which by itself are useless, but together allow you to restore your secret.",
+ "intro-state-2-text2": "You can distribute these shares to different locations or people you trust.",
+ "intro-state-2-text3": "So don't worry, your grandma won't be able to restore your secret with the share that you stored at her place.",
+ "intro-state-3-title1": "How Social Recovery Works",
+ "intro-state-3-title2": "Choose Number of Shares",
+ "intro-state-3-text1": "Begin by specifying how many social shares you'd like to create. You'll alsso need to define how many shares are needed to recover your secret.",
+ "intro-state-3-title3": "Store and Verify Each Share",
+ "intro-state-3-text2": "We will create different social shares for you. Make a note of for each of them. In a next step, you'll be asked to verify each share to make sure that you've copied them correctly.",
+ "intro-state-3-title4": "Not the Same as Your Passphrase",
+ "intro-state-3-text3": "Although they might look the same, social shares are not the same as your passphrase.",
+ "setup-state-1-text1": "Select the number of social shares you wish to create",
+ "setup-state-2-text1": "Creating {{numberOfShares}} shares",
+ "setup-state-2-text2": "Select the number of social shares needed to recover your secret",
+ "rules-heading": "Keep this rule in mind",
+ "rules-text1": "Write these words down on a piece of paper or a metal plate",
+ "rules-text2": "The words need to be in the correct order",
+ "rules-text3": "Check the spelling of each word",
+ "rules-text4": "Store the written down secret in a safe place",
+ "rules-text5": "Do not take a screenshot or photo and do not photocopy your secret",
+ "rules-text6": "Do not upload your secret to the cloud",
+ "share-show-heading": "Write down all words on a piece of paper, remember to follow the rules. We recommend to enumerate the terms.",
+ "finish-title": "Social Recovery Generation Successful!",
+ "finish-header": "You've successfully created social recovery shares for your secret.",
+ "finish-warning-title": "Remember",
+ "finish-warning-paragraph": "You created {{ numberOfShares }} shares, of which you'll need at least {{ sharesRequired }} to recover.",
+ "finish-button": "Finish"
+ },
"secret-service": {
"alert": {
"title": "Duplicate Secret",
@@ -251,7 +283,7 @@
},
"verify-key": {
"incorrect_text": "Your secret does not match the generated one. Make sure you've chosen the correct words.
To correct mistakes, simply select a wrong word and choose the correct one.",
- "success_text": "You have successfully verified this secret.",
+ "success_text": "Share verified successfully!",
"continue_label": "Continue"
},
"social-recovery-import": {
@@ -324,6 +356,18 @@
"label_suggestions": "Suggestions",
"label_note_disable": "Note: You can disable this option later in the settings"
},
+ "contacts-delete-popover": {
+ "title": "Reset Entries",
+ "text": "This action will delete all of your saved entries. Do you want to continue?",
+ "cancel_label": "Cancel",
+ "delete_label": "Ok"
+ },
+ "contact-book-scan": {
+ "title": "Contact Book QR Code Scanner",
+ "text": "Scan a QR code of an address to add it to your contacts",
+ "empty-state_heading": "In order to scan QR codes, AirGap Vault needs the camera permission.",
+ "grant-permission_label": "Grant permission"
+ },
"tab-scan": {
"title": "QR Code Scanner",
"text": "Scan a QR Code from AirGap Wallet",
@@ -359,12 +403,20 @@
"advanced-mode-type_label": "Advanced Mode",
"advanced-features_label": "Advanced Features",
"address_book_label": "Address Book",
- "configure_address_book_label": "Configure Address Book"
+ "address_book_settings_label": "Address Book Settings",
+ "settings_label": "Settings",
+ "actions_label": "Actions"
},
"tab-secrets": {
- "title": "Secrets",
+ "title": "Secret",
"add-secret_label": "Add Secret",
- "search_placeholder": "Search Secret"
+ "search_placeholder": "Search Secret",
+ "tab-secret-popover":{
+ "title":"Secret Management",
+ "add-secret_label": "Add Secret",
+ "sync-all_label": "Sync all accounts with AirGap Wallet"
+
+ }
},
"tab-wallets": {
"title": "Accounts",
diff --git a/src/assets/icons/extension-puzzle.svg b/src/assets/icons/extension-puzzle.svg
new file mode 100644
index 00000000..5c3ad1ce
--- /dev/null
+++ b/src/assets/icons/extension-puzzle.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/shield-checkmark.svg b/src/assets/icons/shield-checkmark.svg
new file mode 100644
index 00000000..44437807
--- /dev/null
+++ b/src/assets/icons/shield-checkmark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/logos/specterwallet.png b/src/assets/logos/specterwallet.png
new file mode 100644
index 00000000..5a684b03
Binary files /dev/null and b/src/assets/logos/specterwallet.png differ
diff --git a/src/theme/variables.scss b/src/theme/variables.scss
index dcdbb2be..c53d9475 100644
--- a/src/theme/variables.scss
+++ b/src/theme/variables.scss
@@ -101,6 +101,12 @@ airgap-transaction-signed,
airgap-contact-book-contacts-detail,
airgap-contact-book-onboarding,
airgap-secret-generate-onboarding,
+airgap-social-recovery-generate-intro,
+airgap-social-recovery-generate-setup,
+airgap-social-recovery-generate-rules,
+airgap-social-recovery-generate-share-show,
+airgap-social-recovery-generate-share-validate,
+airgap-social-recovery-generate-finish,
airgap-social-recovery-setup,
airgap-secret-generate,
airgap-secret-generate-dice,
diff --git a/tsconfig.json b/tsconfig.json
index 9964eb1d..c80dfe16 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,7 +11,7 @@
"experimentalDecorators": true,
"importHelpers": true,
"downlevelIteration": true, // To enable [].entries()
- "target": "es5",
+ "target": "es2016",
"typeRoots": ["node_modules/@types"],
"lib": ["es2018", "dom"],
diff --git a/yarn.lock b/yarn.lock
index f602df6b..eef1ebe6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7,34 +7,34 @@
resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-4.1.0.tgz#33eff662a5c39c0c2061170cc003c5120743fff0"
integrity sha512-AsnBZN3a8/JcNt+KPkGGODaA4c7l3W5+WpeKgGSbstSLxqWtTXqd1ieJGBQ8IFCtRg8DmmKUcSkIkUc0A4p3YA==
-"@airgap/aeternity@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/aeternity/-/aeternity-0.13.12.tgz#dcb8b0040fd6273ba0688ca2af808f70b183cb4b"
- integrity sha512-bzcSQK5tDtHxMLasvQUJOOj6fNUmdF9Uct8xX3X/4GS7ZNlQ4zl2pPyfkA4eUgiKE36pKHyH+hQUBCIoOQ7ciA==
+"@airgap/aeternity@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/aeternity/-/aeternity-0.13.15.tgz#f7eca24d5aeea0b4b075b981830473c59f73beca"
+ integrity sha512-Ooj8XFJZbK0v5tZHsX05yPMQGEVO2KfgycBx3H4asd6TqOiEaj9fE9ZVZia+v/uObHMgo3pWkuRZqfeuLBR4FQ==
dependencies:
"@airgap/coinlib-core" "*"
"@airgap/module-kit" "*"
"@airgap/serializer" "*"
"@stablelib/ed25519" "^1.0.3"
-"@airgap/angular-core@0.0.36":
- version "0.0.36"
- resolved "https://registry.yarnpkg.com/@airgap/angular-core/-/angular-core-0.0.36.tgz#c167bb3ef88915709c984ce0a9f61cf269be2852"
- integrity sha512-iMf8MKB5OTfh+PRZXLY07y8xD4eSvdU9CeWmpSb+eu0utr6sXTOo4vbQNTDfO6PCGyfXcdXOL8ddxsyUSnGz+g==
+"@airgap/angular-core@0.0.37":
+ version "0.0.37"
+ resolved "https://registry.yarnpkg.com/@airgap/angular-core/-/angular-core-0.0.37.tgz#1189ee41165244c8639e74666c5f178829e94a44"
+ integrity sha512-21TmmLmU+31/X/TyNj9pn71KQIJY1jzwDI5dWkYgSw7UjciYs+ch6muBGwK9fZyhoCBqnaQv98jBn+WSvGGV3w==
dependencies:
tslib "^2.2.0"
-"@airgap/angular-ngrx@0.0.36":
- version "0.0.36"
- resolved "https://registry.yarnpkg.com/@airgap/angular-ngrx/-/angular-ngrx-0.0.36.tgz#bc4a0b1a6a37e3a5d663ebc6d5f1c087e5abc2d8"
- integrity sha512-qYa2NUXmsfiAxDxij7inBgyKwv/Gwa/pzBJAXcypdLwt0PSnaHoQpllSTc/jcDJaYKW+sp8xVW46fUilJ9oGMQ==
+"@airgap/angular-ngrx@0.0.37":
+ version "0.0.37"
+ resolved "https://registry.yarnpkg.com/@airgap/angular-ngrx/-/angular-ngrx-0.0.37.tgz#79eca1781efa2e589f74cfdd6a90d0e6771f455d"
+ integrity sha512-eOWmX8O+WCyIvZrrAcDmasZivDIafhP9MITWxQVylHN8TblLCpJpXhD2LzN/qz+5389t4/O3lH/95htBwznM8w==
dependencies:
tslib "^2.2.0"
-"@airgap/astar@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/astar/-/astar-0.13.12.tgz#c0be446ce18a99f440099cba2ecadda09e9a3321"
- integrity sha512-d+yfdqnwbf2h0qSOgRxDVWSTHwuhUZ+EmMXs7Dqet7zkt8YlpqiYFyh+X1g1QuqLmoZKVmbUbWroq3Tv4pLa9w==
+"@airgap/astar@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/astar/-/astar-0.13.15.tgz#e2c8fbb857b2a3db93679f4207784c51e41ea8cc"
+ integrity sha512-19KOTmpEdjrKoE6TNlv1Ba32bmFqTwjJCdHdx/cmtDETU5eKRMEwd5ZtQddgnbv5b3E0oYeePUoTdiMPg0Ucmg==
dependencies:
"@airgap/coinlib-core" "*"
"@airgap/module-kit" "*"
@@ -43,10 +43,10 @@
"@polkadot/util" "2.0.1"
"@polkadot/wasm-crypto" "0.20.1"
-"@airgap/bitcoin@*", "@airgap/bitcoin@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/bitcoin/-/bitcoin-0.13.12.tgz#069f5de8577b9620e191fd1249eef91e85b99c2c"
- integrity sha512-sWT1ohlSuaRWG1qeWSNaN8xia+0TeplLW0Wvg/OvqaUt+tzgdNPgCihL/ezbkTykWlNyFrkT+3J5ARE4pv7gfA==
+"@airgap/bitcoin@*", "@airgap/bitcoin@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/bitcoin/-/bitcoin-0.13.15.tgz#184671ae6c6821309b9e86fac9cb7348f02fb4e2"
+ integrity sha512-UXS25+x+XEw0HMTDEPJSCPlvdc9jSzv5vCnHdUUq8gcT/rvy5KmZPEqfGCL4XwZ5wrtclZhTS0BEvQ2s4EhJ7g==
dependencies:
"@airgap/coinlib-core" "*"
"@airgap/crypto" "*"
@@ -54,10 +54,10 @@
"@airgap/serializer" "*"
bitcoinjs-lib "5.2.0"
-"@airgap/coinlib-core@*", "@airgap/coinlib-core@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/coinlib-core/-/coinlib-core-0.13.12.tgz#49ca6e7e3ddc64c86fc1a31b743412eea3ddd1f1"
- integrity sha512-/GkW2keobLdYcBLJGAstsHIWVzgcAhV6fVabjqq57cQQbSmXa15j509HL9ih+GYfOm/uvuFe5nZo+CTSc5V6vQ==
+"@airgap/coinlib-core@*", "@airgap/coinlib-core@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/coinlib-core/-/coinlib-core-0.13.15.tgz#64bac2c8da2899938641fd7952d2013ad03df439"
+ integrity sha512-JIxBZFanbjJ+WUYlhdgbnNRnHfE40FzVl7rkg6OYD8rJ0bH86GdmCYN7/U93r5mRy3HQ9CrRCREm/c4h0Ois3Q==
dependencies:
"@stablelib/blake2b" "^1.0.1"
"@stablelib/bytes" "^1.0.1"
@@ -67,10 +67,10 @@
long "^5.2.0"
protobufjs "^6.11.2"
-"@airgap/coreum@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/coreum/-/coreum-0.13.12.tgz#4c86e903d151fba4473e53870f3ac35ae8e0e951"
- integrity sha512-wHafjkBQ++0RU8DJPuLNTijZ8ymR3orNmGMVDWabF80nv3BhOUyG8AwJRtuP0G/MGWQjJQbhCOLENNl1CDoHAw==
+"@airgap/coreum@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/coreum/-/coreum-0.13.15.tgz#ec25f363ed7ef8feb14063e60256f7e4ad1ac23c"
+ integrity sha512-KH4HPZ4I/CkiEj2uRvpVaGnk5TVLOq4k3IuNgkawq/py2RVmHlXzMUboY/PlT6/Uqtx3L+kYfJasA4V07byvNg==
dependencies:
"@airgap/coinlib-core" "*"
"@airgap/cosmos-core" "*"
@@ -78,20 +78,20 @@
"@airgap/module-kit" "*"
"@airgap/serializer" "*"
-"@airgap/cosmos-core@*", "@airgap/cosmos-core@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/cosmos-core/-/cosmos-core-0.13.12.tgz#8b13f46f11ef7b8d64fdc6871706375c48c9cb29"
- integrity sha512-9Ts3wqEX6xLggbYMh55DJwy4fupmy/OsXjMPsfuZa3XlbGAVk5XI4QI6TOuBb1fEgtGI/LTFUaPuhNKHbT84/g==
+"@airgap/cosmos-core@*", "@airgap/cosmos-core@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/cosmos-core/-/cosmos-core-0.13.15.tgz#3e53f644c2ecbd39d298de299a13b94ef3bdf6ee"
+ integrity sha512-ptyfJS/rVcP/auipoMfhbCGTpciTlUUKnN+MwIuwNC5SUSCwXkOQYV+t+5fcPbQ3X7tCfjj5QRWi2ZSKWrL0DA==
dependencies:
"@airgap/coinlib-core" "*"
"@airgap/crypto" "*"
"@airgap/module-kit" "*"
"@airgap/serializer" "*"
-"@airgap/cosmos@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/cosmos/-/cosmos-0.13.12.tgz#18ddd88c1d046cd661a6b1da6f011aa00a320e09"
- integrity sha512-Sx2x7pUcqXf2K6Dxbjqz3L33l9VqXQMLKwHraf7IKFeKdf0xcFzdr0uVKUb9waQRTs/gDBY+ldKHJOIEGCiZnA==
+"@airgap/cosmos@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/cosmos/-/cosmos-0.13.15.tgz#6b57a3a6383a517e431142e1c7f41d7dac53e45f"
+ integrity sha512-fyUeMwJvNzlCQgdNT6kA9YG2S8daFN5nvG5RI2EojCs5esWBetD13wvyX4zXSf1riMpq1KpgKQLzqfXsifYxlw==
dependencies:
"@airgap/coinlib-core" "*"
"@airgap/cosmos-core" "*"
@@ -99,10 +99,10 @@
"@airgap/module-kit" "*"
"@airgap/serializer" "*"
-"@airgap/crypto@*", "@airgap/crypto@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/crypto/-/crypto-0.13.12.tgz#87203f9b1677bd808fefd9f66009fed6f4992e22"
- integrity sha512-0nZFkdzSq/8uytJ7spO7uIRgtrBBhaOho98LNhVEcOP6XS2iBE8O0Lt0fAizEETL5U65IB+2vfXd/qErmwzw1g==
+"@airgap/crypto@*", "@airgap/crypto@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/crypto/-/crypto-0.13.15.tgz#484144eafc743c8fc55be951a102402418a2b22a"
+ integrity sha512-CLizaNFFfu7RhmT7wyhhw2D07DuUH6zzmzn5cSYAJlmmhlOTFWbwxHGDUWOonEzINmzrmZFgnZ4tmpANVzeMgg==
dependencies:
"@airgap/coinlib-core" "*"
"@airgap/module-kit" "*"
@@ -110,10 +110,10 @@
"@polkadot/wasm-crypto" "0.20.1"
"@stablelib/hmac" "^1.0.1"
-"@airgap/ethereum@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/ethereum/-/ethereum-0.13.12.tgz#e245aa8154cfd067518c28744a3946769c5dd5b1"
- integrity sha512-LQtiYFZC49imwYosYN2Xvij9tcAH9Tlz4ZXejswz1PTJPHVkM5X8mewzlSz++lPKlwqeokVh3xwI8j8A7nYgLg==
+"@airgap/ethereum@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/ethereum/-/ethereum-0.13.15.tgz#9f507a98f98552213a8f2d10cf1263da3cc2cadc"
+ integrity sha512-vsF5S1HbOZWYR70/I79T7y32A7NJzvh8oLNZYUmdYwvIiZs3YzsyUO45UTpIytv3EAf6peDA9wUSUhOm6zwyiA==
dependencies:
"@airgap/coinlib-core" "*"
"@airgap/crypto" "*"
@@ -123,58 +123,60 @@
"@ethereumjs/tx" "3.4.0"
"@metamask/eth-sig-util" "4.0.0"
-"@airgap/groestlcoin@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/groestlcoin/-/groestlcoin-0.13.12.tgz#482886efa1f5cadb996931b4a5287e2c449fbdb2"
- integrity sha512-vggbH2OnUO47hjx3Z33nd0lgAsZiNEgGQLVTLGDxu63NDpjYIFEvhSNDnlKbXhfZNKUaoCRWSj1OcYykfQT11Q==
+"@airgap/groestlcoin@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/groestlcoin/-/groestlcoin-0.13.15.tgz#db11ed2536f0f013a211745555985ef58c3f6e0a"
+ integrity sha512-btt20YQh1rVHcfixTlHqfMK0v33j82y/l296FASMukQLY2rlK00u4IaGvo6b3QmcjZsgLZ+W8c2lNOtIMWlG1Q==
dependencies:
"@airgap/bitcoin" "*"
"@airgap/coinlib-core" "*"
"@airgap/module-kit" "*"
"@airgap/serializer" "*"
-"@airgap/icp@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/icp/-/icp-0.13.12.tgz#e106e91c698cfc705d7d0dd9f58736deef03f706"
- integrity sha512-gWfSwXoTYBftn6EaArLkMdx1c405MITRJqz7UA9WMz2AAOm3QaFXCOLKYY5whAd9kqpsu51eEEWI9bwZ+LKYbw==
+"@airgap/icp@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/icp/-/icp-0.13.15.tgz#c4579c2f35489a98735b30b7297c432e8ed638bd"
+ integrity sha512-Ku/6Oxlg9DXjKZhbov4ypwQkNpA8SIIk0CVcvA7yPxbumApa+l4svEXMX14nLJ6xF86oKrtW6EMOqOg3wH9Z+g==
dependencies:
"@airgap/coinlib-core" "*"
"@airgap/crypto" "*"
"@airgap/module-kit" "*"
"@airgap/serializer" "*"
+ "@dfinity/agent" "^0.15.4"
+ "@dfinity/identity-secp256k1" "^0.15.4"
+ "@dfinity/nns" "^0.14.0"
"@stablelib/ed25519" "^1.0.3"
base64-arraybuffer "^1.0.2"
bip39 "^3.0.4"
borc "^3.0.0"
- crc "4.3.2"
crc-32 "1.2.2"
isomorphic-fetch "^3.0.0"
js-sha256 "^0.9.0"
secp256k1 "^5.0.0"
simple-cbor "^0.4.1"
-"@airgap/module-kit@*", "@airgap/module-kit@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/module-kit/-/module-kit-0.13.12.tgz#37b2969b890f2af31e0f9b06680b13c4860be76a"
- integrity sha512-CbILYbr9yBlylZB6K754C6LzomAScp/UiXdyi5kYDzD6B0KZVeUoapWmgYsJaV8/qs7TcF/rbn5uqXzOZqf40Q==
+"@airgap/module-kit@*", "@airgap/module-kit@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/module-kit/-/module-kit-0.13.15.tgz#9e9f8f695b99582deaa770116263015f889f67c6"
+ integrity sha512-n2hxPTWpvtJcTL8MRWQNkj0Su9ovg5d43i8H+YaWzcNnFt1Fl2FidmIS3F+JGntqfMQgzTCQnNylI+kWV4zjsg==
dependencies:
"@airgap/coinlib-core" "*"
"@airgap/serializer" "*"
-"@airgap/moonbeam@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/moonbeam/-/moonbeam-0.13.12.tgz#3a43cb06b78e6fa840fae9aa0fa653e7f5d45049"
- integrity sha512-EdowvRfV9lv86rBtLIfAnRlIwgW3Z0mS/KQMCzbUX2vNgrMb8aolerq9dWheE9jsP165RRvHcmYV3nUmyHvbWQ==
+"@airgap/moonbeam@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/moonbeam/-/moonbeam-0.13.15.tgz#95f97b9dc005617f3940a01c82203371199aa5f2"
+ integrity sha512-29BbL6tsi7Fl6M9ks1eqjlbTNrCf92v14nlWkYxJ0O/EDMkAOe7OLRLDlch0+5viltsHRKtUe1xJ6HgFojfVXQ==
dependencies:
"@airgap/coinlib-core" "*"
"@airgap/module-kit" "*"
"@airgap/serializer" "*"
"@airgap/substrate" "*"
-"@airgap/polkadot@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/polkadot/-/polkadot-0.13.12.tgz#c284609f82ea6fbb2f28d919a1b5cff481914023"
- integrity sha512-IeTrhamD4Mko92H9rg2qvmOa3ui7ISedego2LomCW51fFLojhW1QfyZ3izQ5REnutZjw80Grqsi3fUIMnTsjCA==
+"@airgap/polkadot@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/polkadot/-/polkadot-0.13.15.tgz#fcd68ae242474c86010abf1b01980e54c789095a"
+ integrity sha512-QbY0R0r0eABbfFn75shR3cHYP/15yPjNOSweBp0r9gCuVa9azClg0j3CPxH8jMCZqTDIuzrZRKrGpWPwMAAoTg==
dependencies:
"@airgap/coinlib-core" "*"
"@airgap/module-kit" "*"
@@ -190,17 +192,17 @@
resolved "https://registry.yarnpkg.com/@airgap/sapling-wasm/-/sapling-wasm-0.0.9.tgz#641b588822179a1659e248a84ad971e16461fc6b"
integrity sha512-rJjV7JIDxoardnZMgk4Uor5Z6OVsAE4I5uDlkGAb0tQ5NN0gmz9Bu2LKMPSCv7UqDpZowDX4BbMDuSqdO/IsbQ==
-"@airgap/serializer@*", "@airgap/serializer@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/serializer/-/serializer-0.13.12.tgz#b7bceee57f763cb66450a6bf72d53e7a6b2b4570"
- integrity sha512-R0QjWn9YAGUL/N03eILRdRAryFcOGB8t6B0BrxFOvGemPELlI7CyDvIlCvsUA9KUhrVEJkp0kotsjwHLZyB92w==
+"@airgap/serializer@*", "@airgap/serializer@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/serializer/-/serializer-0.13.15.tgz#c0c602d4127e2d52ce31c12e276253be17d4a9a4"
+ integrity sha512-5vOthazh7KFYpRO2i9lZI/D5LOCGxOvaTYIIv+b4DZxgKcOzOinnL/+t2HS8L1H1MF3fIdy90yJBYwnVyMKq2w==
dependencies:
"@airgap/coinlib-core" "*"
-"@airgap/substrate@*", "@airgap/substrate@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/substrate/-/substrate-0.13.12.tgz#ba998beec2835239f1d65b91c7f7806c86073f65"
- integrity sha512-x2oxuP0e6Bw8oTgRu9NnjiewIdV/SHklY98ZauZeldFl8ItQ2sEAPFW+DEPVhkJUk08SFFA4vlffVsHuinHH3w==
+"@airgap/substrate@*", "@airgap/substrate@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/substrate/-/substrate-0.13.15.tgz#4b086016c5c490cf676e7083ff5efae3bd9fb5d3"
+ integrity sha512-DBXfXmw8YasjgkPN3O2dlagzQe6GCcL9TrydbTeKHBdENOjN7/pqpWkqDPk/kFL3++MM6DoQ2PRhXnjPI44hAA==
dependencies:
"@airgap/coinlib-core" "*"
"@airgap/crypto" "*"
@@ -209,10 +211,10 @@
"@polkadot/util" "2.0.1"
"@polkadot/wasm-crypto" "0.20.1"
-"@airgap/tezos@0.13.12":
- version "0.13.12"
- resolved "https://registry.yarnpkg.com/@airgap/tezos/-/tezos-0.13.12.tgz#83750e2119a0f7c78be84cd0898125c053fef4a3"
- integrity sha512-Jl1sZtAFPxhNvSjwDzLLUkpF6vuYVwaGWI8CX/8EBnXx1+075SDRK2oJVwsMI0t63P1Q5v6EGnPfAGYhm7yiOQ==
+"@airgap/tezos@0.13.15":
+ version "0.13.15"
+ resolved "https://registry.yarnpkg.com/@airgap/tezos/-/tezos-0.13.15.tgz#3664ae4bc772cd4b7e812b4f327763e2933785dd"
+ integrity sha512-VSYvJZ2lk6BoRcY4UeOCrMHlvT5TirEE3hFyInPUtCtluoNcs5vmD1Txb5j2XivG9osuA8ZY08Hlsyj+e8meJQ==
dependencies:
"@airgap/coinlib-core" "*"
"@airgap/crypto" "*"
@@ -1537,6 +1539,13 @@
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
+"@cspotcode/source-map-support@^0.8.0":
+ version "0.8.1"
+ resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
+ integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
+ dependencies:
+ "@jridgewell/trace-mapping" "0.3.9"
+
"@csstools/postcss-progressive-custom-properties@^1.1.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz#542292558384361776b45c85226b9a3a34f276fa"
@@ -1549,6 +1558,39 @@
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.1.1.tgz#c9c61d9fe5ca5ac664e1153bb0aa0eba1c6d6308"
integrity sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==
+"@dfinity/agent@^0.15.4", "@dfinity/agent@^0.15.5":
+ version "0.15.5"
+ resolved "https://registry.yarnpkg.com/@dfinity/agent/-/agent-0.15.5.tgz#56a46fe86ffe9dbb98e3aa8ee3200091ace2d925"
+ integrity sha512-Ytn8eo0Gk8QkaCX2X+CS1NYOdCr0ZjanqFyKbR5rY7LGQoVaNzdVHJufzheuTFMhTZs6i/OtaG2BQug4cNIocw==
+ dependencies:
+ base64-arraybuffer "^0.2.0"
+ bignumber.js "^9.0.0"
+ borc "^2.1.1"
+ js-sha256 "0.9.0"
+ simple-cbor "^0.4.1"
+ ts-node "^10.8.2"
+
+"@dfinity/identity-secp256k1@^0.15.4":
+ version "0.15.5"
+ resolved "https://registry.yarnpkg.com/@dfinity/identity-secp256k1/-/identity-secp256k1-0.15.5.tgz#47b86c0988e9548e4aac8908c22c28309fc3a9b4"
+ integrity sha512-W0Y1bpuC6Nkl3zZlsFhYpLlaF6yw6uCZOEbLPqNfdUB5jv1fOS8SSsl557L3cG5d9utDLJOA9ejZ8cWxcTEMSw==
+ dependencies:
+ "@dfinity/agent" "^0.15.5"
+ bip39 "^3.0.4"
+ bs58check "^2.1.2"
+ secp256k1 "^4.0.3"
+
+"@dfinity/nns@^0.14.0":
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/@dfinity/nns/-/nns-0.14.0.tgz#a73ca5b9db776c6ee753180dd67c92208843154b"
+ integrity sha512-qQ3SzihD9hL2Zo0pAC+Ip2snbEtLDL1Aq2JTnDMLur9DEpiCajzhjXfsOy+U1zeSTOc+RUBbbWeLbS5Edsxeig==
+ dependencies:
+ crc "^4.3.2"
+ crc-32 "^1.2.2"
+ google-protobuf "^3.21.2"
+ js-sha256 "^0.9.0"
+ randombytes "^2.1.0"
+
"@discoveryjs/json-ext@0.5.6":
version "0.5.6"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f"
@@ -1936,6 +1978,14 @@
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
+"@jridgewell/trace-mapping@0.3.9":
+ version "0.3.9"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
+ integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.0.3"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+
"@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.17"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985"
@@ -2405,6 +2455,26 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
+"@tsconfig/node10@^1.0.7":
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
+ integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==
+
+"@tsconfig/node12@^1.0.7":
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d"
+ integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
+
+"@tsconfig/node14@^1.0.0":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1"
+ integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
+
+"@tsconfig/node16@^1.0.2":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e"
+ integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==
+
"@types/bn.js@^4.11.3", "@types/bn.js@^4.11.6":
version "4.11.6"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c"
@@ -2900,7 +2970,7 @@ acorn-walk@^7.0.0:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
-acorn-walk@^8.2.0:
+acorn-walk@^8.1.1, acorn-walk@^8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
@@ -3398,6 +3468,11 @@ base58check@^2.0.0:
dependencies:
bs58 "^3.0.0"
+base64-arraybuffer@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz#4b944fac0191aa5907afe2d8c999ccc57ce80f45"
+ integrity sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==
+
base64-arraybuffer@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
@@ -3622,6 +3697,19 @@ boolbase@^1.0.0:
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
+borc@^2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/borc/-/borc-2.1.2.tgz#6ce75e7da5ce711b963755117dd1b187f6f8cf19"
+ integrity sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w==
+ dependencies:
+ bignumber.js "^9.0.0"
+ buffer "^5.5.0"
+ commander "^2.15.0"
+ ieee754 "^1.1.13"
+ iso-url "~0.4.7"
+ json-text-sequence "~0.1.0"
+ readable-stream "^3.6.0"
+
borc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/borc/-/borc-3.0.0.tgz#49ada1be84de86f57bb1bb89789f34c186dfa4fe"
@@ -4602,16 +4690,11 @@ cosmiconfig@^7.0.0:
path-type "^4.0.0"
yaml "^1.10.0"
-crc-32@1.2.2, crc-32@^1.2.0:
+crc-32@1.2.2, crc-32@^1.2.0, crc-32@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff"
integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
-crc@4.3.2:
- version "4.3.2"
- resolved "https://registry.yarnpkg.com/crc/-/crc-4.3.2.tgz#49b7821cbf2cf61dfd079ed93863bbebd5469b9a"
- integrity sha512-uGDHf4KLLh2zsHa8D8hIQ1H/HtFQhyHrc0uhHBcoKGol/Xnb+MPYfUMw7cvON6ze/GUESTudKayDcJC5HnJv1A==
-
crc@^3.8.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6"
@@ -4619,6 +4702,11 @@ crc@^3.8.0:
dependencies:
buffer "^5.1.0"
+crc@^4.3.2:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/crc/-/crc-4.3.2.tgz#49b7821cbf2cf61dfd079ed93863bbebd5469b9a"
+ integrity sha512-uGDHf4KLLh2zsHa8D8hIQ1H/HtFQhyHrc0uhHBcoKGol/Xnb+MPYfUMw7cvON6ze/GUESTudKayDcJC5HnJv1A==
+
create-ecdh@^4.0.0:
version "4.0.4"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
@@ -5035,6 +5123,11 @@ delegates@^1.0.0:
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==
+delimit-stream@0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/delimit-stream/-/delimit-stream-0.1.0.tgz#9b8319477c0e5f8aeb3ce357ae305fc25ea1cd2b"
+ integrity sha512-a02fiQ7poS5CnjiJBAsjGLPp5EwVoGHNeu9sziBd9huppRfsAFIpv5zNLv0V1gbop53ilngAf5Kf331AwcoRBQ==
+
depd@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
@@ -6434,6 +6527,11 @@ globby@^5.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
+google-protobuf@^3.21.2:
+ version "3.21.2"
+ resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.2.tgz#4580a2bea8bbb291ee579d1fefb14d6fa3070ea4"
+ integrity sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==
+
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
@@ -7303,6 +7401,11 @@ iso-url@^1.1.5:
resolved "https://registry.yarnpkg.com/iso-url/-/iso-url-1.2.1.tgz#db96a49d8d9a64a1c889fc07cc525d093afb1811"
integrity sha512-9JPDgCN4B7QPkLtYAAOrEuAWvP9rWvR5offAr0/SeF046wIkglqH3VXgYYP6NcsKslH80UIVgmPqNe3j7tG2ng==
+iso-url@~0.4.7:
+ version "0.4.7"
+ resolved "https://registry.yarnpkg.com/iso-url/-/iso-url-0.4.7.tgz#de7e48120dae46921079fe78f325ac9e9217a385"
+ integrity sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==
+
isobject@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
@@ -7426,7 +7529,7 @@ jetifier@^1.6.6:
resolved "https://registry.yarnpkg.com/jetifier/-/jetifier-1.6.8.tgz#e88068697875cbda98c32472902c4d3756247798"
integrity sha512-3Zi16h6L5tXDRQJTb221cnRoVG9/9OvreLdLU2/ZjRv/GILL+2Cemt0IKvkowwkDpvouAU1DQPOJ7qaiHeIdrw==
-js-sha256@^0.9.0:
+js-sha256@0.9.0, js-sha256@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966"
integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==
@@ -7504,6 +7607,13 @@ json-stringify-safe@~5.0.1:
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
+json-text-sequence@~0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/json-text-sequence/-/json-text-sequence-0.1.1.tgz#a72f217dc4afc4629fff5feb304dc1bd51a2f3d2"
+ integrity sha512-L3mEegEWHRekSHjc7+sc8eJhba9Clq1PZ8kMkzf8OxElhXc8O4TS5MwcVlj9aEbm5dr81N90WHC5nAz3UO971w==
+ dependencies:
+ delimit-stream "0.1.0"
+
json-text-sequence@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/json-text-sequence/-/json-text-sequence-0.3.0.tgz#6603e0ee45da41f949669fd18744b97fb209e6ce"
@@ -10301,7 +10411,7 @@ sdp@^1.5.0:
resolved "https://registry.yarnpkg.com/sdp/-/sdp-1.5.4.tgz#8e038f6ddb14bd765ae1f4b5216e12094511e0d0"
integrity sha512-9x0+fpJHW2bbqtIktmL+H9m+BswgurPVPhWrur4cCZPDWDSsdGYvRBaKhDXlnNCD1b/Xi8cOTG2u703CTQuwgg==
-secp256k1@^4.0.0, secp256k1@^4.0.1:
+secp256k1@^4.0.0, secp256k1@^4.0.1, secp256k1@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303"
integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==
@@ -11372,6 +11482,25 @@ ts-node@9.1.1:
source-map-support "^0.5.17"
yn "3.1.1"
+ts-node@^10.8.2:
+ version "10.9.1"
+ resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b"
+ integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==
+ dependencies:
+ "@cspotcode/source-map-support" "^0.8.0"
+ "@tsconfig/node10" "^1.0.7"
+ "@tsconfig/node12" "^1.0.7"
+ "@tsconfig/node14" "^1.0.0"
+ "@tsconfig/node16" "^1.0.2"
+ acorn "^8.4.1"
+ acorn-walk "^8.1.1"
+ arg "^4.1.0"
+ create-require "^1.1.0"
+ diff "^4.0.1"
+ make-error "^1.1.1"
+ v8-compile-cache-lib "^3.0.1"
+ yn "3.1.1"
+
tslib@2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
@@ -11734,6 +11863,11 @@ uuid@^3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
+v8-compile-cache-lib@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
+ integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
+
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"