web worker

check if already initialized

more progress, zap feed not loading?

request send receive

fix setup

profile editing and show zaps

wallet connections

kitchen sink

mutiny plus and misc

get rid of swap

backup / restore, nostr stuff

get rid of gifts

channels stuff

manage federations and profile fixes

cleanup

fix build

fix chrome android

update to cap 6

bump to actual 6.0.0

update xcode version

fix interpolation again (regression)

move all static methods to the worker

add doc strings

get rid of window.nostr, make parse params async

fight load flicker

use a "-test" bundle for debug builds so they don't clobber

add back swaps and do some cleanup

fix activity flicker
This commit is contained in:
Paul Miller
2024-04-18 10:24:13 -05:00
committed by Tony Giorgio
parent f34b4b8e02
commit e01b8465d5
84 changed files with 3210 additions and 2379 deletions

View File

@@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
android { android {
namespace "com.mutinywallet.mutinywallet" namespace "com.mutinywallet.mutinywallet"
compileSdkVersion rootProject.ext.compileSdkVersion compileSdk rootProject.ext.compileSdkVersion
defaultConfig { defaultConfig {
applicationId "com.mutinywallet.mutinywallet" applicationId "com.mutinywallet.mutinywallet"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion

View File

@@ -15,6 +15,7 @@ dependencies {
implementation project(':capacitor-clipboard') implementation project(':capacitor-clipboard')
implementation project(':capacitor-filesystem') implementation project(':capacitor-filesystem')
implementation project(':capacitor-haptics') implementation project(':capacitor-haptics')
implementation project(':capacitor-network')
implementation project(':capacitor-share') implementation project(':capacitor-share')
implementation project(':capacitor-status-bar') implementation project(':capacitor-status-bar')
implementation project(':capacitor-toast') implementation project(':capacitor-toast')

View File

@@ -7,8 +7,8 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.0.2' classpath 'com.android.tools.build:gradle:8.2.2'
classpath 'com.google.gms:google-services:4.3.15' classpath 'com.google.gms:google-services:4.4.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

View File

@@ -1,33 +1,36 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android' include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/.pnpm/@capacitor+android@5.7.4_@capacitor+core@5.7.4/node_modules/@capacitor/android/capacitor') project(':capacitor-android').projectDir = new File('../node_modules/.pnpm/@capacitor+android@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/android/capacitor')
include ':capacitor-mlkit-barcode-scanning' include ':capacitor-mlkit-barcode-scanning'
project(':capacitor-mlkit-barcode-scanning').projectDir = new File('../node_modules/.pnpm/@capacitor-mlkit+barcode-scanning@5.4.0_@capacitor+core@5.7.4/node_modules/@capacitor-mlkit/barcode-scanning/android') project(':capacitor-mlkit-barcode-scanning').projectDir = new File('../node_modules/.pnpm/@capacitor-mlkit+barcode-scanning@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor-mlkit/barcode-scanning/android')
include ':capacitor-app' include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/.pnpm/@capacitor+app@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/app/android') project(':capacitor-app').projectDir = new File('../node_modules/.pnpm/@capacitor+app@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/app/android')
include ':capacitor-app-launcher' include ':capacitor-app-launcher'
project(':capacitor-app-launcher').projectDir = new File('../node_modules/.pnpm/@capacitor+app-launcher@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/app-launcher/android') project(':capacitor-app-launcher').projectDir = new File('../node_modules/.pnpm/@capacitor+app-launcher@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/app-launcher/android')
include ':capacitor-clipboard' include ':capacitor-clipboard'
project(':capacitor-clipboard').projectDir = new File('../node_modules/.pnpm/@capacitor+clipboard@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/clipboard/android') project(':capacitor-clipboard').projectDir = new File('../node_modules/.pnpm/@capacitor+clipboard@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/clipboard/android')
include ':capacitor-filesystem' include ':capacitor-filesystem'
project(':capacitor-filesystem').projectDir = new File('../node_modules/.pnpm/@capacitor+filesystem@5.2.1_@capacitor+core@5.7.4/node_modules/@capacitor/filesystem/android') project(':capacitor-filesystem').projectDir = new File('../node_modules/.pnpm/@capacitor+filesystem@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/filesystem/android')
include ':capacitor-haptics' include ':capacitor-haptics'
project(':capacitor-haptics').projectDir = new File('../node_modules/.pnpm/@capacitor+haptics@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/haptics/android') project(':capacitor-haptics').projectDir = new File('../node_modules/.pnpm/@capacitor+haptics@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/haptics/android')
include ':capacitor-network'
project(':capacitor-network').projectDir = new File('../node_modules/.pnpm/@capacitor+network@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/network/android')
include ':capacitor-share' include ':capacitor-share'
project(':capacitor-share').projectDir = new File('../node_modules/.pnpm/@capacitor+share@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/share/android') project(':capacitor-share').projectDir = new File('../node_modules/.pnpm/@capacitor+share@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/share/android')
include ':capacitor-status-bar' include ':capacitor-status-bar'
project(':capacitor-status-bar').projectDir = new File('../node_modules/.pnpm/@capacitor+status-bar@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/status-bar/android') project(':capacitor-status-bar').projectDir = new File('../node_modules/.pnpm/@capacitor+status-bar@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/status-bar/android')
include ':capacitor-toast' include ':capacitor-toast'
project(':capacitor-toast').projectDir = new File('../node_modules/.pnpm/@capacitor+toast@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/toast/android') project(':capacitor-toast').projectDir = new File('../node_modules/.pnpm/@capacitor+toast@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/toast/android')
include ':capacitor-secure-storage-plugin' include ':capacitor-secure-storage-plugin'
project(':capacitor-secure-storage-plugin').projectDir = new File('../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@5.7.4/node_modules/capacitor-secure-storage-plugin/android') project(':capacitor-secure-storage-plugin').projectDir = new File('../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@6.0.0/node_modules/capacitor-secure-storage-plugin/android')

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip
networkTimeout=10000 networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -1,14 +1,14 @@
ext { ext {
minSdkVersion = 22 minSdkVersion = 22
compileSdkVersion = 33 compileSdkVersion = 34
targetSdkVersion = 33 targetSdkVersion = 34
androidxActivityVersion = '1.7.0' androidxActivityVersion = '1.8.0'
androidxAppCompatVersion = '1.6.1' androidxAppCompatVersion = '1.6.1'
androidxCoordinatorLayoutVersion = '1.2.0' androidxCoordinatorLayoutVersion = '1.2.0'
androidxCoreVersion = '1.10.0' androidxCoreVersion = '1.12.0'
androidxFragmentVersion = '1.5.6' androidxFragmentVersion = '1.6.2'
coreSplashScreenVersion = '1.0.0' coreSplashScreenVersion = '1.0.1'
androidxWebkitVersion = '1.6.1' androidxWebkitVersion = '1.9.0'
junitVersion = '4.13.2' junitVersion = '4.13.2'
androidxJunitVersion = '1.1.5' androidxJunitVersion = '1.1.5'
androidxEspressoCoreVersion = '3.5.1' androidxEspressoCoreVersion = '3.5.1'

View File

@@ -61,7 +61,7 @@ test("test local encrypt", async ({ page }) => {
// The "Encrypt" button should not be disabled // The "Encrypt" button should not be disabled
const encryptButton = await page.locator("button", { hasText: "Encrypt" }); const encryptButton = await page.locator("button", { hasText: "Encrypt" });
await expect(encryptButton).not.toBeDisabled(); await expect(encryptButton).toBeEnabled();
// wait 5 seconds for no reason (SADLY THIS IS IMPORTANT FOR THE TEST TO PASS) // wait 5 seconds for no reason (SADLY THIS IS IMPORTANT FOR THE TEST TO PASS)
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
@@ -69,11 +69,8 @@ test("test local encrypt", async ({ page }) => {
// Click the "Encrypt" button // Click the "Encrypt" button
await encryptButton.click(); await encryptButton.click();
// wait for a while just to see what happens
// await page.waitForTimeout(10000);
// Wait for a modal with the text "Enter your password" // Wait for a modal with the text "Enter your password"
await page.waitForSelector("text=Enter your password"); await page.getByText("Enter your password").waitFor();
// Find the input field with the name "password" // Find the input field with the name "password"
const passwordInput2 = await page.locator(`input[name='password']`); const passwordInput2 = await page.locator(`input[name='password']`);
@@ -85,5 +82,5 @@ test("test local encrypt", async ({ page }) => {
await page.click("text=Decrypt Wallet"); await page.click("text=Decrypt Wallet");
// Wait for an element matching the selector to appear in DOM. // Wait for an element matching the selector to appear in DOM.
await page.locator(`text=0 sats`).first(); await page.locator(`text=0 sats`).first().waitFor();
}); });

View File

@@ -124,8 +124,8 @@ test("rountrip receive and send", async ({ page }) => {
await page.click("text=Online Channels"); await page.click("text=Online Channels");
// Give it just a second to settle down // Idk why the node isn't ready to close channels right away
await page.waitForTimeout(2000); await page.waitForTimeout(5000);
await page.click("text=Close"); await page.click("text=Close");

View File

@@ -9,7 +9,6 @@ const routes = [
"/scanner", "/scanner",
"/search", "/search",
"/send", "/send",
"/swap",
"/settings" "/settings"
]; ];
@@ -156,13 +155,6 @@ test("visit each route", async ({ page }) => {
"Add Connection" "Add Connection"
); );
// Swap
await page.goto("http://localhost:3420/swap");
await expect(
page.getByRole("heading", { name: "Swap to Lightning" })
).toBeVisible();
checklist.set("/swap", true);
// print how many routes we've visited // print how many routes we've visited
checklist.forEach((value, key) => { checklist.forEach((value, key) => {
console.log(`${key}: ${value}`); console.log(`${key}: ${value}`);

View File

@@ -362,7 +362,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.6.8; MARKETING_VERSION = 1.6.8;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = com.mutinywallet.mutiny; PRODUCT_BUNDLE_IDENTIFIER = "com.mutinywallet.mutiny-test";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1530"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "504EC3031FED79650016851F"
BuildableName = "App.app"
BlueprintName = "App"
ReferencedContainer = "container:App.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "504EC3031FED79650016851F"
BuildableName = "App.app"
BlueprintName = "App"
ReferencedContainer = "container:App.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = ""
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "504EC3031FED79650016851F"
BuildableName = "App.app"
BlueprintName = "App"
ReferencedContainer = "container:App.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,4 +1,4 @@
require_relative '../../node_modules/.pnpm/@capacitor+ios@5.7.4_@capacitor+core@5.7.4/node_modules/@capacitor/ios/scripts/pods_helpers' require_relative '../../node_modules/.pnpm/@capacitor+ios@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/ios/scripts/pods_helpers'
platform :ios, '13.0' platform :ios, '13.0'
use_frameworks! use_frameworks!
@@ -9,18 +9,19 @@ use_frameworks!
install! 'cocoapods', :disable_input_output_paths => true install! 'cocoapods', :disable_input_output_paths => true
def capacitor_pods def capacitor_pods
pod 'Capacitor', :path => '../../node_modules/.pnpm/@capacitor+ios@5.7.4_@capacitor+core@5.7.4/node_modules/@capacitor/ios' pod 'Capacitor', :path => '../../node_modules/.pnpm/@capacitor+ios@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../node_modules/.pnpm/@capacitor+ios@5.7.4_@capacitor+core@5.7.4/node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../node_modules/.pnpm/@capacitor+ios@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/ios'
pod 'CapacitorMlkitBarcodeScanning', :path => '../../node_modules/.pnpm/@capacitor-mlkit+barcode-scanning@5.4.0_@capacitor+core@5.7.4/node_modules/@capacitor-mlkit/barcode-scanning' pod 'CapacitorMlkitBarcodeScanning', :path => '../../node_modules/.pnpm/@capacitor-mlkit+barcode-scanning@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor-mlkit/barcode-scanning'
pod 'CapacitorApp', :path => '../../node_modules/.pnpm/@capacitor+app@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/app' pod 'CapacitorApp', :path => '../../node_modules/.pnpm/@capacitor+app@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/app'
pod 'CapacitorAppLauncher', :path => '../../node_modules/.pnpm/@capacitor+app-launcher@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/app-launcher' pod 'CapacitorAppLauncher', :path => '../../node_modules/.pnpm/@capacitor+app-launcher@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/app-launcher'
pod 'CapacitorClipboard', :path => '../../node_modules/.pnpm/@capacitor+clipboard@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/clipboard' pod 'CapacitorClipboard', :path => '../../node_modules/.pnpm/@capacitor+clipboard@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/clipboard'
pod 'CapacitorFilesystem', :path => '../../node_modules/.pnpm/@capacitor+filesystem@5.2.1_@capacitor+core@5.7.4/node_modules/@capacitor/filesystem' pod 'CapacitorFilesystem', :path => '../../node_modules/.pnpm/@capacitor+filesystem@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/filesystem'
pod 'CapacitorHaptics', :path => '../../node_modules/.pnpm/@capacitor+haptics@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/haptics' pod 'CapacitorHaptics', :path => '../../node_modules/.pnpm/@capacitor+haptics@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/haptics'
pod 'CapacitorShare', :path => '../../node_modules/.pnpm/@capacitor+share@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/share' pod 'CapacitorNetwork', :path => '../../node_modules/.pnpm/@capacitor+network@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/network'
pod 'CapacitorStatusBar', :path => '../../node_modules/.pnpm/@capacitor+status-bar@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/status-bar' pod 'CapacitorShare', :path => '../../node_modules/.pnpm/@capacitor+share@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/share'
pod 'CapacitorToast', :path => '../../node_modules/.pnpm/@capacitor+toast@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/toast' pod 'CapacitorStatusBar', :path => '../../node_modules/.pnpm/@capacitor+status-bar@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/status-bar'
pod 'CapacitorSecureStoragePlugin', :path => '../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@5.7.4/node_modules/capacitor-secure-storage-plugin' pod 'CapacitorToast', :path => '../../node_modules/.pnpm/@capacitor+toast@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/toast'
pod 'CapacitorSecureStoragePlugin', :path => '../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@6.0.0/node_modules/capacitor-secure-storage-plugin'
end end
target 'App' do target 'App' do

View File

@@ -1,28 +1,30 @@
PODS: PODS:
- Capacitor (5.7.4): - Capacitor (6.0.0):
- CapacitorCordova - CapacitorCordova
- CapacitorApp (5.0.7): - CapacitorApp (6.0.0):
- Capacitor - Capacitor
- CapacitorAppLauncher (5.0.7): - CapacitorAppLauncher (6.0.0):
- Capacitor - Capacitor
- CapacitorClipboard (5.0.7): - CapacitorClipboard (6.0.0):
- Capacitor - Capacitor
- CapacitorCordova (5.7.4) - CapacitorCordova (6.0.0)
- CapacitorFilesystem (5.2.1): - CapacitorFilesystem (6.0.0):
- Capacitor - Capacitor
- CapacitorHaptics (5.0.7): - CapacitorHaptics (6.0.0):
- Capacitor - Capacitor
- CapacitorMlkitBarcodeScanning (5.4.0): - CapacitorMlkitBarcodeScanning (6.0.0):
- Capacitor - Capacitor
- GoogleMLKit/BarcodeScanning (= 4.0.0) - GoogleMLKit/BarcodeScanning (= 4.0.0)
- CapacitorNetwork (6.0.0):
- Capacitor
- CapacitorSecureStoragePlugin (0.9.0): - CapacitorSecureStoragePlugin (0.9.0):
- Capacitor - Capacitor
- SwiftKeychainWrapper - SwiftKeychainWrapper
- CapacitorShare (5.0.7): - CapacitorShare (6.0.0):
- Capacitor - Capacitor
- CapacitorStatusBar (5.0.7): - CapacitorStatusBar (6.0.0):
- Capacitor - Capacitor
- CapacitorToast (5.0.7): - CapacitorToast (6.0.0):
- Capacitor - Capacitor
- GoogleDataTransport (9.2.5): - GoogleDataTransport (9.2.5):
- GoogleUtilities/Environment (~> 7.7) - GoogleUtilities/Environment (~> 7.7)
@@ -81,18 +83,19 @@ PODS:
- SwiftKeychainWrapper (4.0.1) - SwiftKeychainWrapper (4.0.1)
DEPENDENCIES: DEPENDENCIES:
- "Capacitor (from `../../node_modules/.pnpm/@capacitor+ios@5.7.4_@capacitor+core@5.7.4/node_modules/@capacitor/ios`)" - "Capacitor (from `../../node_modules/.pnpm/@capacitor+ios@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/ios`)"
- "CapacitorApp (from `../../node_modules/.pnpm/@capacitor+app@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/app`)" - "CapacitorApp (from `../../node_modules/.pnpm/@capacitor+app@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/app`)"
- "CapacitorAppLauncher (from `../../node_modules/.pnpm/@capacitor+app-launcher@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/app-launcher`)" - "CapacitorAppLauncher (from `../../node_modules/.pnpm/@capacitor+app-launcher@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/app-launcher`)"
- "CapacitorClipboard (from `../../node_modules/.pnpm/@capacitor+clipboard@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/clipboard`)" - "CapacitorClipboard (from `../../node_modules/.pnpm/@capacitor+clipboard@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/clipboard`)"
- "CapacitorCordova (from `../../node_modules/.pnpm/@capacitor+ios@5.7.4_@capacitor+core@5.7.4/node_modules/@capacitor/ios`)" - "CapacitorCordova (from `../../node_modules/.pnpm/@capacitor+ios@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/ios`)"
- "CapacitorFilesystem (from `../../node_modules/.pnpm/@capacitor+filesystem@5.2.1_@capacitor+core@5.7.4/node_modules/@capacitor/filesystem`)" - "CapacitorFilesystem (from `../../node_modules/.pnpm/@capacitor+filesystem@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/filesystem`)"
- "CapacitorHaptics (from `../../node_modules/.pnpm/@capacitor+haptics@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/haptics`)" - "CapacitorHaptics (from `../../node_modules/.pnpm/@capacitor+haptics@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/haptics`)"
- "CapacitorMlkitBarcodeScanning (from `../../node_modules/.pnpm/@capacitor-mlkit+barcode-scanning@5.4.0_@capacitor+core@5.7.4/node_modules/@capacitor-mlkit/barcode-scanning`)" - "CapacitorMlkitBarcodeScanning (from `../../node_modules/.pnpm/@capacitor-mlkit+barcode-scanning@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor-mlkit/barcode-scanning`)"
- "CapacitorSecureStoragePlugin (from `../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@5.7.4/node_modules/capacitor-secure-storage-plugin`)" - "CapacitorNetwork (from `../../node_modules/.pnpm/@capacitor+network@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/network`)"
- "CapacitorShare (from `../../node_modules/.pnpm/@capacitor+share@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/share`)" - "CapacitorSecureStoragePlugin (from `../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@6.0.0/node_modules/capacitor-secure-storage-plugin`)"
- "CapacitorStatusBar (from `../../node_modules/.pnpm/@capacitor+status-bar@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/status-bar`)" - "CapacitorShare (from `../../node_modules/.pnpm/@capacitor+share@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/share`)"
- "CapacitorToast (from `../../node_modules/.pnpm/@capacitor+toast@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/toast`)" - "CapacitorStatusBar (from `../../node_modules/.pnpm/@capacitor+status-bar@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/status-bar`)"
- "CapacitorToast (from `../../node_modules/.pnpm/@capacitor+toast@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/toast`)"
SPEC REPOS: SPEC REPOS:
trunk: trunk:
@@ -112,43 +115,46 @@ SPEC REPOS:
EXTERNAL SOURCES: EXTERNAL SOURCES:
Capacitor: Capacitor:
:path: "../../node_modules/.pnpm/@capacitor+ios@5.7.4_@capacitor+core@5.7.4/node_modules/@capacitor/ios" :path: "../../node_modules/.pnpm/@capacitor+ios@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/ios"
CapacitorApp: CapacitorApp:
:path: "../../node_modules/.pnpm/@capacitor+app@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/app" :path: "../../node_modules/.pnpm/@capacitor+app@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/app"
CapacitorAppLauncher: CapacitorAppLauncher:
:path: "../../node_modules/.pnpm/@capacitor+app-launcher@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/app-launcher" :path: "../../node_modules/.pnpm/@capacitor+app-launcher@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/app-launcher"
CapacitorClipboard: CapacitorClipboard:
:path: "../../node_modules/.pnpm/@capacitor+clipboard@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/clipboard" :path: "../../node_modules/.pnpm/@capacitor+clipboard@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/clipboard"
CapacitorCordova: CapacitorCordova:
:path: "../../node_modules/.pnpm/@capacitor+ios@5.7.4_@capacitor+core@5.7.4/node_modules/@capacitor/ios" :path: "../../node_modules/.pnpm/@capacitor+ios@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/ios"
CapacitorFilesystem: CapacitorFilesystem:
:path: "../../node_modules/.pnpm/@capacitor+filesystem@5.2.1_@capacitor+core@5.7.4/node_modules/@capacitor/filesystem" :path: "../../node_modules/.pnpm/@capacitor+filesystem@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/filesystem"
CapacitorHaptics: CapacitorHaptics:
:path: "../../node_modules/.pnpm/@capacitor+haptics@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/haptics" :path: "../../node_modules/.pnpm/@capacitor+haptics@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/haptics"
CapacitorMlkitBarcodeScanning: CapacitorMlkitBarcodeScanning:
:path: "../../node_modules/.pnpm/@capacitor-mlkit+barcode-scanning@5.4.0_@capacitor+core@5.7.4/node_modules/@capacitor-mlkit/barcode-scanning" :path: "../../node_modules/.pnpm/@capacitor-mlkit+barcode-scanning@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor-mlkit/barcode-scanning"
CapacitorNetwork:
:path: "../../node_modules/.pnpm/@capacitor+network@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/network"
CapacitorSecureStoragePlugin: CapacitorSecureStoragePlugin:
:path: "../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@5.7.4/node_modules/capacitor-secure-storage-plugin" :path: "../../node_modules/.pnpm/capacitor-secure-storage-plugin@0.9.0_@capacitor+core@6.0.0/node_modules/capacitor-secure-storage-plugin"
CapacitorShare: CapacitorShare:
:path: "../../node_modules/.pnpm/@capacitor+share@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/share" :path: "../../node_modules/.pnpm/@capacitor+share@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/share"
CapacitorStatusBar: CapacitorStatusBar:
:path: "../../node_modules/.pnpm/@capacitor+status-bar@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/status-bar" :path: "../../node_modules/.pnpm/@capacitor+status-bar@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/status-bar"
CapacitorToast: CapacitorToast:
:path: "../../node_modules/.pnpm/@capacitor+toast@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/toast" :path: "../../node_modules/.pnpm/@capacitor+toast@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/toast"
SPEC CHECKSUMS: SPEC CHECKSUMS:
Capacitor: 4fe9adf012caceb4c71ffea2f1f4d005cdcbeea7 Capacitor: 559d073c4ca6c27f8e7002c807eea94c3ba435a9
CapacitorApp: 17fecd0e6cb23feafac7eb0939417389038b0979 CapacitorApp: 9d53aec7101f7b030a950c5bdc4df8612576b279
CapacitorAppLauncher: 7b2705481a74cbe322441bd374f56194de59ab1a CapacitorAppLauncher: 24ce4be152a84883378d22114ffe6c492056ced2
CapacitorClipboard: 45e5e25f2271f98712985d422776cdc5a779cca1 CapacitorClipboard: 80282f684154124b9019ebf401235b70b0cf4994
CapacitorCordova: a6e87fccc0307dee7aec1560ec9398485f2b0ce7 CapacitorCordova: 8c4bfdf69368512e85b1d8b724dd7546abeb30af
CapacitorFilesystem: 9f3e3c7fea2fff12f46dd5b07a2914f2103e4cfc CapacitorFilesystem: 60e59ba274c234a979e7a3be2552feaadcee4263
CapacitorHaptics: 7c7c206f0c96a628fed073830c96d28c4b2e772e CapacitorHaptics: 9ebc9363f0e9b8eb4295088a0b474530acf1859b
CapacitorMlkitBarcodeScanning: 8fb81cbef3c6ffe0c0e2dbd15ed6dca889a5a062 CapacitorMlkitBarcodeScanning: 1cc47dc2163628b44be5400864de32696362045b
CapacitorNetwork: f15a94c16a33cba7c47a17814cb6bcfe3ea34ded
CapacitorSecureStoragePlugin: e91d7df060f2495a1acff9583641a6953e3aacba CapacitorSecureStoragePlugin: e91d7df060f2495a1acff9583641a6953e3aacba
CapacitorShare: c6a1ebbf0114ff9e863b966cd6052678fa25d480 CapacitorShare: a771200d3b924a5d7ad9d9fecbac517e4c0aa74f
CapacitorStatusBar: f390fbb49b82ffb754ea4b3cf71dc8b048baf3e7 CapacitorStatusBar: 2e4369f99166125435641b1908d05f561eaba6f6
CapacitorToast: c8bb89eeb59a23c1fc298f138cc06c8ff4d90ac1 CapacitorToast: ba573a7bc5dfd622e78d5be126a84ee221da4180
GoogleDataTransport: 54dee9d48d14580407f8f5fbf2f496e92437a2f2 GoogleDataTransport: 54dee9d48d14580407f8f5fbf2f496e92437a2f2
GoogleMLKit: 2bd0dc6253c4d4f227aad460f69215a504b2980e GoogleMLKit: 2bd0dc6253c4d4f227aad460f69215a504b2980e
GoogleToolboxForMac: 8bef7c7c5cf7291c687cf5354f39f9db6399ad34 GoogleToolboxForMac: 8bef7c7c5cf7291c687cf5354f39f9db6399ad34
@@ -163,6 +169,6 @@ SPEC CHECKSUMS:
PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4
SwiftKeychainWrapper: 807ba1d63c33a7d0613288512399cd1eda1e470c SwiftKeychainWrapper: 807ba1d63c33a7d0613288512399cd1eda1e470c
PODFILE CHECKSUM: 3864776b838da083701f08e4d80ef59ac02798db PODFILE CHECKSUM: 7d4dccfabb68790e47370312014de5fc49a89668
COCOAPODS: 1.14.3 COCOAPODS: 1.14.3

View File

@@ -15,43 +15,44 @@
}, },
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@capacitor/assets": "^2.0.4", "@capacitor/assets": "^3.0.5",
"@capacitor/cli": "^5.5.1", "@capacitor/cli": "6.0.0",
"@ianvs/prettier-plugin-sort-imports": "^4.1.1", "@ianvs/prettier-plugin-sort-imports": "^4.2.1",
"@playwright/test": "^1.39.0", "@playwright/test": "^1.42.1",
"@typescript-eslint/eslint-plugin": "^7.0.1", "@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.0.1", "@typescript-eslint/parser": "^7.4.0",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.19",
"eslint": "^8.56.0", "eslint": "^8.57.0",
"eslint-import-resolver-typescript": "3.6.1", "eslint-import-resolver-typescript": "3.6.1",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",
"eslint-plugin-prettier": "5.1.3", "eslint-plugin-prettier": "5.1.3",
"eslint-plugin-solid": "0.13.1", "eslint-plugin-solid": "0.13.1",
"postcss": "^8.4.35", "postcss": "^8.4.38",
"prettier": "^3.0.3", "prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.12", "prettier-plugin-tailwindcss": "^0.5.12",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "^5.3.3", "typescript": "^5.4.5",
"vite": "^5.1.1", "vite": "^5.2.10",
"vite-plugin-pwa": "^0.16.7", "vite-plugin-pwa": "^0.19.8",
"vite-plugin-solid": "^2.10.1", "vite-plugin-solid": "^2.10.2",
"vite-plugin-wasm": "^3.3.0", "vite-plugin-wasm": "^3.3.0",
"workbox-window": "^7.0.0" "workbox-window": "^7.0.0",
"vite-plugin-comlink": "^4.0.3"
}, },
"dependencies": { "dependencies": {
"i18next-http-backend": "^2.5.0", "@capacitor-mlkit/barcode-scanning": "^6.0.0",
"@capacitor-mlkit/barcode-scanning": "^5.3.0", "@capacitor/android": "^6.0.0",
"@capacitor/android": "^5.5.1", "@capacitor/app": "^6.0.0",
"@capacitor/app": "^5.0.6", "@capacitor/app-launcher": "^6.0.0",
"@capacitor/app-launcher": "^5.0.6", "@capacitor/clipboard": "^6.0.0",
"@capacitor/clipboard": "^5.0.6", "@capacitor/core": "^6.0.0",
"@capacitor/core": "^5.5.1", "@capacitor/filesystem": "^6.0.0",
"@capacitor/filesystem": "^5.1.4", "@capacitor/haptics": "^6.0.0",
"@capacitor/haptics": "^5.0.6", "@capacitor/ios": "^6.0.0",
"@capacitor/ios": "^5.5.1", "@capacitor/network": "^6.0.0",
"@capacitor/share": "^5.0.6", "@capacitor/share": "^6.0.0",
"@capacitor/status-bar": "^5.0.6", "@capacitor/status-bar": "^6.0.0",
"@capacitor/toast": "^5.0.6", "@capacitor/toast": "^6.0.0",
"@kobalte/core": "^0.12.6", "@kobalte/core": "^0.12.6",
"@kobalte/tailwindcss": "^0.9.0", "@kobalte/tailwindcss": "^0.9.0",
"@mutinywallet/mutiny-wasm": "0.6.7", "@mutinywallet/mutiny-wasm": "0.6.7",
@@ -61,12 +62,14 @@
"@solidjs/router": "^0.13.1", "@solidjs/router": "^0.13.1",
"capacitor-secure-storage-plugin": "^0.9.0", "capacitor-secure-storage-plugin": "^0.9.0",
"i18next": "^23.10.1", "i18next": "^23.10.1",
"i18next-browser-languagedetector": "^7.1.0", "i18next-browser-languagedetector": "^7.2.0",
"i18next-http-backend": "^2.5.0",
"lucide-solid": "^0.363.0", "lucide-solid": "^0.363.0",
"qr-scanner": "^1.4.2", "qr-scanner": "^1.4.2",
"solid-js": "^1.8.16", "solid-js": "^1.8.16",
"solid-qr-code": "^0.0.8", "solid-qr-code": "^0.0.8",
"solid-transition-group": "^0.2.3" "solid-transition-group": "^0.2.3",
"comlink": "^4.4.1"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"

813
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -400,7 +400,8 @@
"import_state": "Import State From File", "import_state": "Import State From File",
"confirm_replace": "Do you want to replace your state with", "confirm_replace": "Do you want to replace your state with",
"password": "Enter your password to decrypt", "password": "Enter your password to decrypt",
"decrypt_wallet": "Decrypt Wallet" "decrypt_wallet": "Decrypt Wallet",
"decrypt_export": "Enter your password to save"
}, },
"logs": { "logs": {
"title": "Download debug logs", "title": "Download debug logs",

View File

@@ -18,12 +18,13 @@ import {
Button, Button,
ButtonCard, ButtonCard,
ContactButton, ContactButton,
LoadingShimmer,
NiceP, NiceP,
SimpleDialog SimpleDialog
} from "~/components"; } from "~/components";
import { useI18n } from "~/i18n/context"; import { useI18n } from "~/i18n/context";
import { PrivacyLevel } from "~/routes"; import { PrivacyLevel } from "~/routes";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore, WalletWorker } from "~/state/megaStore";
import { import {
actuallyFetchNostrProfile, actuallyFetchNostrProfile,
createDeepSignal, createDeepSignal,
@@ -56,9 +57,11 @@ export interface IActivityItem {
} }
async function fetchContactForNpub( async function fetchContactForNpub(
sw: WalletWorker,
npub: string npub: string
): Promise<PseudoContact | undefined> { ): Promise<PseudoContact | undefined> {
const hexpub = await hexpubFromNpub(npub); const hexpub = await hexpubFromNpub(sw, npub);
console.log("fetchContactForNpub", hexpub);
if (!hexpub) { if (!hexpub) {
return undefined; return undefined;
} }
@@ -75,6 +78,7 @@ export function UnifiedActivityItem(props: {
onClick: (id: string, kind: HackActivityType) => void; onClick: (id: string, kind: HackActivityType) => void;
onNewContactClick: (profile: PseudoContact) => void; onNewContactClick: (profile: PseudoContact) => void;
}) { }) {
const [_state, _actions, sw] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
const click = () => { const click = () => {
@@ -97,10 +101,12 @@ export function UnifiedActivityItem(props: {
}); });
const getContact = cache(async (npub) => { const getContact = cache(async (npub) => {
return await fetchContactForNpub(npub); return await fetchContactForNpub(sw, npub);
}, "profile"); }, "profile");
const profileFromNostr = createAsync(async () => { // Complaining about a "tracked scope" but I think we're good
// eslint-disable-next-line solid/reactivity
const getProfileFromNostr = cache(async () => {
if (props.item.contacts.length === 0) { if (props.item.contacts.length === 0) {
if (props.item.labels) { if (props.item.labels) {
const npub = props.item.labels.find((l) => const npub = props.item.labels.find((l) =>
@@ -137,6 +143,10 @@ export function UnifiedActivityItem(props: {
} }
} }
return undefined; return undefined;
}, "profileFromNostr");
const profileFromNostr = createAsync(async () => {
return await getProfileFromNostr();
}); });
// TODO: figure out what other shit we should filter out // TODO: figure out what other shit we should filter out
@@ -300,12 +310,11 @@ function NewContactModal(props: { profile: PseudoContact; close: () => void }) {
const i18n = useI18n(); const i18n = useI18n();
const navigate = useNavigate(); const navigate = useNavigate();
const [state, _actions] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
async function createContact() { async function createContact() {
try { try {
const existingContact = const existingContact = await sw.get_contact_for_npub(
await state.mutiny_wallet?.get_contact_for_npub(
props.profile.hexpub props.profile.hexpub
); );
@@ -314,7 +323,7 @@ function NewContactModal(props: { profile: PseudoContact; close: () => void }) {
return; return;
} }
const contactId = await state.mutiny_wallet?.create_new_contact( const contactId = await sw.create_new_contact(
props.profile.name, props.profile.name,
props.profile.hexpub, props.profile.hexpub,
props.profile.ln_address, props.profile.ln_address,
@@ -326,7 +335,7 @@ function NewContactModal(props: { profile: PseudoContact; close: () => void }) {
throw new Error("no contact id returned"); throw new Error("no contact id returned");
} }
const tagItem = await state.mutiny_wallet?.get_tag_item(contactId); const tagItem = await sw.get_tag_item(contactId);
if (!tagItem) { if (!tagItem) {
throw new Error("no contact returned"); throw new Error("no contact returned");
@@ -365,7 +374,7 @@ function NewContactModal(props: { profile: PseudoContact; close: () => void }) {
} }
export function CombinedActivity() { export function CombinedActivity() {
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const i18n = useI18n(); const i18n = useI18n();
const [detailsOpen, setDetailsOpen] = createSignal(false); const [detailsOpen, setDetailsOpen] = createSignal(false);
@@ -386,25 +395,16 @@ export function CombinedActivity() {
setDetailsOpen(true); setDetailsOpen(true);
} }
async function getActivity() { async function fetchActivity() {
try { try {
console.log("refetching activity"); return await sw.get_activity(50, undefined);
const activity = await state.mutiny_wallet?.get_activity(
50,
undefined
);
if (!activity) return [];
return activity as IActivityItem[];
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return [] as IActivityItem[]; return [] as IActivityItem[];
} }
} }
const [activity, { refetch }] = createResource(getActivity, { const [activity, { refetch }] = createResource(fetchActivity, {
initialValue: [],
storage: createDeepSignal storage: createDeepSignal
}); });
@@ -433,11 +433,14 @@ export function CombinedActivity() {
close={() => setNewContact(undefined)} close={() => setNewContact(undefined)}
/> />
</Show> </Show>
<Suspense fallback={<LoadingShimmer />}>
<Switch> <Switch>
<Match when={activity.latest.length === 0}> <Match when={activity.latest?.length === 0}>
<Show when={state.federations?.length === 0}> <Show when={state.federations?.length === 0}>
<ButtonCard <ButtonCard
onClick={() => navigate("/settings/federations")} onClick={() =>
navigate("/settings/federations")
}
> >
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Users class="inline-block text-m-red" /> <Users class="inline-block text-m-red" />
@@ -457,28 +460,10 @@ export function CombinedActivity() {
<NiceP>{i18n.t("home.find")}</NiceP> <NiceP>{i18n.t("home.find")}</NiceP>
</div> </div>
</ButtonCard> </ButtonCard>
<Show when={!state.has_backed_up}>
<ButtonCard
onClick={() => navigate("/settings/backup")}
>
<div class="flex items-center gap-2">
<Save class="inline-block text-m-red" />
<NiceP>{i18n.t("home.backup")}</NiceP>
</div>
</ButtonCard>
</Show>
</Match> </Match>
<Match when={activity.latest.length >= 0}> <Match
<Show when={!state.has_backed_up}> when={activity.latest && activity.latest!.length >= 0}
<ButtonCard
onClick={() => navigate("/settings/backup")}
> >
<div class="flex items-center gap-2">
<Save class="inline-block text-m-red" />
<NiceP>{i18n.t("home.backup")}</NiceP>
</div>
</ButtonCard>
</Show>
<div class="flex w-full flex-col divide-y divide-m-grey-800 overflow-x-clip"> <div class="flex w-full flex-col divide-y divide-m-grey-800 overflow-x-clip">
<For each={activity.latest}> <For each={activity.latest}>
{(activityItem) => ( {(activityItem) => (
@@ -492,6 +477,18 @@ export function CombinedActivity() {
</div> </div>
</Match> </Match>
</Switch> </Switch>
<Show when={!state.has_backed_up}>
<ButtonCard
red
onClick={() => navigate("/settings/backup")}
>
<div class="flex items-center gap-2">
<Save class="inline-block text-neutral-200" />
<NiceP>{i18n.t("home.backup")}</NiceP>
</div>
</ButtonCard>
</Show>
</Suspense>
</> </>
); );
} }

View File

@@ -1,13 +1,13 @@
import { Dialog } from "@kobalte/core"; import { Dialog } from "@kobalte/core";
import { import {
MutinyChannel, ActivityItem,
MutinyInvoice, MutinyInvoice,
TagItem TagItem
} from "@mutinywallet/mutiny-wasm"; } from "@mutinywallet/mutiny-wasm";
import { createAsync } from "@solidjs/router";
import { Copy, Link, Shuffle, Zap } from "lucide-solid"; import { Copy, Link, Shuffle, Zap } from "lucide-solid";
import { import {
createEffect, createEffect,
createMemo,
createResource, createResource,
Match, Match,
ParentComponent, ParentComponent,
@@ -30,7 +30,6 @@ import {
VStack VStack
} from "~/components"; } from "~/components";
import { useI18n } from "~/i18n/context"; import { useI18n } from "~/i18n/context";
import { Network } from "~/logic/mutinyWalletSetup";
import { BalanceBar } from "~/routes/settings/Channels"; import { BalanceBar } from "~/routes/settings/Channels";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
import { mempoolTxUrl, prettyPrintTime, useCopy } from "~/utils"; import { mempoolTxUrl, prettyPrintTime, useCopy } from "~/utils";
@@ -301,24 +300,21 @@ function OnchainDetails(props: {
tags?: TagItem; tags?: TagItem;
}) { }) {
const i18n = useI18n(); const i18n = useI18n();
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const [copy, copied] = useCopy({ copiedTimeout: 1000 }); const [copy, copied] = useCopy({ copiedTimeout: 1000 });
const confirmationTime = () => { const confirmationTime = () => {
return props.info.confirmation_time?.Confirmed?.time; return props.info.confirmation_time?.Confirmed?.time;
}; };
const network = state.mutiny_wallet?.get_network() as Network; const network = state.network || "signet";
// Can return nothing if the channel is already closed // Can return nothing if the channel is already closed
const [channelInfo] = createResource(async () => { const [channelInfo] = createResource(async () => {
if (props.kind === "ChannelOpen") { if (props.kind === "ChannelOpen") {
try { try {
const channels = const channels = await sw.list_channels();
await (state.mutiny_wallet?.list_channels() as Promise< const channel = channels?.find((channel) =>
MutinyChannel[]
>);
const channel = channels.find((channel) =>
channel.outpoint?.startsWith(props.info.txid) channel.outpoint?.startsWith(props.info.txid)
); );
return channel; return channel;
@@ -500,7 +496,7 @@ export function ActivityDetailsModal(props: {
id: string; id: string;
setOpen: (open: boolean) => void; setOpen: (open: boolean) => void;
}) { }) {
const [state, _actions] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const id = () => props.id; const id = () => props.id;
const kind = () => props.kind; const kind = () => props.kind;
@@ -508,18 +504,16 @@ export function ActivityDetailsModal(props: {
try { try {
if (kind() === "Lightning") { if (kind() === "Lightning") {
console.debug("reading invoice: ", id()); console.debug("reading invoice: ", id());
const invoice = const invoice = await sw.get_invoice_by_hash(id());
await state.mutiny_wallet?.get_invoice_by_hash(id());
return invoice; return invoice;
} else if (kind() === "ChannelClose") { } else if (kind() === "ChannelClose") {
console.debug("reading channel close: ", id()); console.debug("reading channel close: ", id());
const closeItem = const closeItem = await sw.get_channel_closure(id());
await state.mutiny_wallet?.get_channel_closure(id());
return closeItem; return closeItem;
} else { } else {
console.debug("reading tx: ", id()); console.debug("reading tx: ", id());
const tx = await state.mutiny_wallet?.get_transaction(id()); const tx = await sw.get_transaction(id());
return tx; return tx;
} }
@@ -528,17 +522,18 @@ export function ActivityDetailsModal(props: {
return undefined; return undefined;
} }
}); });
const tags = createMemo(() => { const tags = createAsync(async () => {
if ( if (
!!data() && !!data() &&
// @ts-expect-error we're narrowing the type here
data()?.labels !== undefined && data()?.labels !== undefined &&
// @ts-expect-error we're narrowing the type here
typeof data()?.labels[0] === "string" typeof data()?.labels[0] === "string"
) { ) {
const typedData = data() as MutinyInvoice | ActivityItem;
try { try {
// find if there's just one for now // find if there's just one for now
const tags = state.mutiny_wallet?.get_tag_item( const tags = await sw.get_tag_item(typedData.labels[0]);
data().labels[0]
);
if (tags) { if (tags) {
return tags; return tags;
} else { } else {
@@ -598,7 +593,7 @@ export function ActivityDetailsModal(props: {
> >
<OnchainHeader <OnchainHeader
info={ info={
data() as OnChainTx data() as unknown as OnChainTx
} }
kind={kind()} kind={kind()}
/> />
@@ -612,7 +607,9 @@ export function ActivityDetailsModal(props: {
<Switch> <Switch>
<Match when={kind() === "Lightning"}> <Match when={kind() === "Lightning"}>
<LightningDetails <LightningDetails
info={data() as MutinyInvoice} info={
data() as unknown as MutinyInvoice
}
tags={tags()} tags={tags()}
/> />
</Match> </Match>
@@ -623,14 +620,18 @@ export function ActivityDetailsModal(props: {
} }
> >
<OnchainDetails <OnchainDetails
info={data() as OnChainTx} info={
data() as unknown as OnChainTx
}
kind={kind()} kind={kind()}
tags={tags()} tags={tags()}
/> />
</Match> </Match>
<Match when={kind() === "ChannelClose"}> <Match when={kind() === "ChannelClose"}>
<ChannelCloseDetails <ChannelCloseDetails
info={data() as ChannelClosure} info={
data() as unknown as ChannelClosure
}
/> />
</Match> </Match>
</Switch> </Switch>

View File

@@ -1,3 +1,4 @@
import { createAsync } from "@solidjs/router";
import { Link, Users, Zap } from "lucide-solid"; import { Link, Users, Zap } from "lucide-solid";
import { Show } from "solid-js"; import { Show } from "solid-js";
@@ -76,16 +77,19 @@ export function AmountFiat(props: {
amountSats: bigint | number | undefined; amountSats: bigint | number | undefined;
denominationSize?: "sm" | "lg" | "xl"; denominationSize?: "sm" | "lg" | "xl";
}) { }) {
const [state, _] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const amountInFiat = () => const amountInFiat = createAsync(async () => {
(state.fiat.value === "BTC" ? "" : "~") + const formattedFiat = await satsToFormattedFiat(
satsToFormattedFiat(
state.price, state.price,
Number(props.amountSats) || 0, Number(props.amountSats) || 0,
state.fiat state.fiat,
sw
); );
return (state.fiat.value === "BTC" ? "" : "~") + formattedFiat;
});
return ( return (
<h2 class="whitespace-nowrap font-light"> <h2 class="whitespace-nowrap font-light">
{amountInFiat()} {amountInFiat()}

View File

@@ -35,25 +35,35 @@ function methodToIcon(method: MethodChoice["method"]) {
export const AmountEditable: ParentComponent<{ export const AmountEditable: ParentComponent<{
initialAmountSats: string | bigint; initialAmountSats: string | bigint;
setAmountSats: (s: bigint) => void; setAmountSats: (s: bigint) => void;
fee?: string;
frozenAmount?: boolean; frozenAmount?: boolean;
onSubmit?: () => void; onSubmit?: () => void;
activeMethod?: MethodChoice; activeMethod?: MethodChoice;
methods?: MethodChoice[]; methods?: MethodChoice[];
setChosenMethod?: (method: MethodChoice) => void; setChosenMethod?: (method: MethodChoice) => void;
}> = (props) => { }> = (props) => {
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const [mode, setMode] = createSignal<"fiat" | "sats">("sats"); const [mode, setMode] = createSignal<"fiat" | "sats">("sats");
const [localSats, setLocalSats] = createSignal( const [localSats, setLocalSats] = createSignal(
props.initialAmountSats.toString() || "0" props.initialAmountSats.toString() || "0"
); );
const [localFiat, setLocalFiat] = createSignal( const [rawFiatAmount, setRawFiatAmount] = createSignal(
props.initialAmountSats.toString() || "0"
);
const [localFiat, setLocalFiat] = createSignal("0");
createEffect(() => {
if (rawFiatAmount()) {
satsToFiat( satsToFiat(
state.price, state.price,
parseInt(props.initialAmountSats.toString() || "0") || 0, Number(rawFiatAmount()) || 0,
state.fiat state.fiat,
) sw
); ).then((sats) => {
console.log("sats", sats);
setLocalFiat(sats);
});
}
});
const displaySats = () => toDisplayHandleNaN(localSats()); const displaySats = () => toDisplayHandleNaN(localSats());
const displayFiat = () => const displayFiat = () =>
@@ -72,7 +82,7 @@ export const AmountEditable: ParentComponent<{
const { value } = e.target as HTMLInputElement; const { value } = e.target as HTMLInputElement;
const sane = satsInputSanitizer(value); const sane = satsInputSanitizer(value);
setLocalSats(sane); setLocalSats(sane);
setLocalFiat(satsToFiat(state.price, Number(sane) || 0, state.fiat)); setRawFiatAmount(Number(sane).toString() || "0");
} }
/** This behaves the same as handleCharacterInput but allows for the keyboard to be used instead of the virtual keypad /** This behaves the same as handleCharacterInput but allows for the keyboard to be used instead of the virtual keypad
@@ -88,7 +98,7 @@ export const AmountEditable: ParentComponent<{
* result - 123.45 * result - 123.45
*/ */
function handleFiatInput(e: InputEvent) { async function handleFiatInput(e: InputEvent) {
const { value } = e.currentTarget as HTMLInputElement; const { value } = e.currentTarget as HTMLInputElement;
let sane; let sane;
@@ -101,7 +111,7 @@ export const AmountEditable: ParentComponent<{
} else { } else {
sane = fiatInputSanitizer( sane = fiatInputSanitizer(
// This allows us to handle the backspace key and fight float rounding // This allows us to handle the backspace key and fight float rounding
btcFloatRounding(localFiat()), btcFloatRounding(localFiat() || "0"),
state.fiat.maxFractionalDigits state.fiat.maxFractionalDigits
); );
} }
@@ -112,9 +122,13 @@ export const AmountEditable: ParentComponent<{
); );
} }
setLocalFiat(sane); setLocalFiat(sane);
setLocalSats( const sats = await fiatToSats(
fiatToSats(state.price, parseFloat(sane || "0") || 0, false) state.price,
Number(sane) || 0,
false,
sw
); );
setLocalSats(sats);
} }
function toggle(disabled: boolean) { function toggle(disabled: boolean) {

View File

@@ -1,6 +1,6 @@
import { A, useNavigate } from "@solidjs/router"; import { A, useNavigate } from "@solidjs/router";
import { Shuffle, Users } from "lucide-solid"; import { Shuffle, Users } from "lucide-solid";
import { Match, Show, Switch } from "solid-js"; import { createMemo, Match, Show, Suspense, Switch } from "solid-js";
import { import {
AmountFiat, AmountFiat,
@@ -51,13 +51,18 @@ export function BalanceBox(props: { loading?: boolean; small?: boolean }) {
const navigate = useNavigate(); const navigate = useNavigate();
const i18n = useI18n(); const i18n = useI18n();
const totalOnchain = () => const totalOnchain = createMemo(
() =>
(state.balance?.confirmed || 0n) + (state.balance?.confirmed || 0n) +
(state.balance?.unconfirmed || 0n) + (state.balance?.unconfirmed || 0n) +
(state.balance?.force_close || 0n); (state.balance?.force_close || 0n)
);
const usableOnchain = () => const usableOnchain = createMemo(
(state.balance?.confirmed || 0n) + (state.balance?.unconfirmed || 0n); () =>
(state.balance?.confirmed || 0n) +
(state.balance?.unconfirmed || 0n)
);
return ( return (
<VStack> <VStack>
@@ -82,12 +87,15 @@ export function BalanceBox(props: { loading?: boolean; small?: boolean }) {
/> />
</div> </div>
<div class="text-lg text-white/70"> <div class="text-lg text-white/70">
<Suspense>
<AmountFiat <AmountFiat
amountSats={ amountSats={
state.balance?.federation || 0n state.balance?.federation ||
0n
} }
denominationSize="sm" denominationSize="sm"
/> />
</Suspense>
</div> </div>
</div> </div>
<Show <Show
@@ -145,12 +153,14 @@ export function BalanceBox(props: { loading?: boolean; small?: boolean }) {
/> />
</div> </div>
<div class="text-lg text-white/70"> <div class="text-lg text-white/70">
<Suspense>
<AmountFiat <AmountFiat
amountSats={ amountSats={
state.balance?.lightning || 0 state.balance?.lightning || 0
} }
denominationSize="sm" denominationSize="sm"
/> />
</Suspense>
</div> </div>
</div> </div>
</Match> </Match>
@@ -168,10 +178,12 @@ export function BalanceBox(props: { loading?: boolean; small?: boolean }) {
/> />
</div> </div>
<div class="text-lg text-white/70"> <div class="text-lg text-white/70">
<Suspense>
<AmountFiat <AmountFiat
amountSats={totalOnchain()} amountSats={totalOnchain()}
denominationSize="sm" denominationSize="sm"
/> />
</Suspense>
</div> </div>
</div> </div>
<div class="flex flex-col items-end justify-between gap-1"> <div class="flex flex-col items-end justify-between gap-1">

View File

@@ -8,14 +8,15 @@ import {
import { Button, ContactFormValues, TextField, VStack } from "~/components"; import { Button, ContactFormValues, TextField, VStack } from "~/components";
import { useI18n } from "~/i18n/context"; import { useI18n } from "~/i18n/context";
import { useMegaStore, WalletWorker } from "~/state/megaStore";
import { hexpubFromNpub } from "~/utils"; import { hexpubFromNpub } from "~/utils";
const validateNpub = async (value?: string) => { const validateNpub = async (sw: WalletWorker, value?: string) => {
if (!value) { if (!value) {
return false; return false;
} }
try { try {
const hexpub = await hexpubFromNpub(value); const hexpub = await hexpubFromNpub(sw, value);
if (!hexpub) { if (!hexpub) {
return false; return false;
} }
@@ -30,6 +31,7 @@ export function ContactForm(props: {
initialValues?: ContactFormValues; initialValues?: ContactFormValues;
cta: string; cta: string;
}) { }) {
const [_state, _actions, sw] = useMegaStore();
const i18n = useI18n(); const i18n = useI18n();
const [_contactForm, { Form, Field }] = createForm<ContactFormValues>({ const [_contactForm, { Form, Field }] = createForm<ContactFormValues>({
initialValues: props.initialValues initialValues: props.initialValues
@@ -78,7 +80,10 @@ export function ContactForm(props: {
<Field <Field
name="npub" name="npub"
validate={[ validate={[
custom(validateNpub, i18n.t("contacts.npub_error")) custom(
(v) => validateNpub(sw, v),
i18n.t("contacts.npub_error")
)
]} ]}
> >
{(field, props) => ( {(field, props) => (

View File

@@ -32,7 +32,7 @@ export function ContactViewer(props: {
const i18n = useI18n(); const i18n = useI18n();
const [isOpen, setIsOpen] = createSignal(false); const [isOpen, setIsOpen] = createSignal(false);
const [isEditing, setIsEditing] = createSignal(false); const [isEditing, setIsEditing] = createSignal(false);
const [state, actions] = useMegaStore(); const [state, actions, sw] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
const [confirmOpen, setConfirmOpen] = createSignal(false); const [confirmOpen, setConfirmOpen] = createSignal(false);
@@ -52,13 +52,13 @@ export function ContactViewer(props: {
setIsOpen(false); setIsOpen(false);
}; };
const handlePay = () => { const handlePay = async () => {
const network = state.mutiny_wallet?.get_network() || "signet"; const network = state.network || "signet";
const lnurl = props.contact.lnurl || props.contact.ln_address || ""; const lnurl = props.contact.lnurl || props.contact.ln_address || "";
if (lnurl) { if (lnurl) {
const result = toParsedParams(lnurl, network); const result = await toParsedParams(lnurl, network, sw);
if (!result.ok) { if (!result.ok) {
showToast(result.error); showToast(result.error);
return; return;

View File

@@ -1,4 +1,3 @@
import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { SecureStoragePlugin } from "capacitor-secure-storage-plugin"; import { SecureStoragePlugin } from "capacitor-secure-storage-plugin";
import { createSignal } from "solid-js"; import { createSignal } from "solid-js";
@@ -9,7 +8,7 @@ import { eify } from "~/utils";
export function DeleteEverything(props: { emergency?: boolean }) { export function DeleteEverything(props: { emergency?: boolean }) {
const i18n = useI18n(); const i18n = useI18n();
const [state, actions] = useMegaStore(); const [state, actions, sw] = useMegaStore();
async function confirmReset() { async function confirmReset() {
setConfirmOpen(true); setConfirmOpen(true);
@@ -29,7 +28,7 @@ export function DeleteEverything(props: { emergency?: boolean }) {
// If we're in a context where the wallet is loaded we want to use the regular action to delete it // If we're in a context where the wallet is loaded we want to use the regular action to delete it
// Otherwise we just call the import_json method directly // Otherwise we just call the import_json method directly
if (state.mutiny_wallet && !props.emergency) { if (state.load_stage === "done" && !props.emergency) {
try { try {
await actions.deleteMutinyWallet(); await actions.deleteMutinyWallet();
} catch (e) { } catch (e) {
@@ -37,9 +36,7 @@ export function DeleteEverything(props: { emergency?: boolean }) {
console.error(e); console.error(e);
} }
} else { } else {
// If there's no mutiny_wallet loaded we might need to initialize WASM await sw.import_json("{}");
await initMutinyWallet();
await MutinyWallet.import_json("{}");
} }
showToast({ showToast({

View File

@@ -20,7 +20,7 @@ export function EditProfileForm(props: {
saving: boolean; saving: boolean;
cta: string; cta: string;
}) { }) {
const [state] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const [uploading, setUploading] = createSignal(false); const [uploading, setUploading] = createSignal(false);
const [uploadError, setUploadError] = createSignal<Error>(); const [uploadError, setUploadError] = createSignal<Error>();
@@ -51,8 +51,7 @@ export function EditProfileForm(props: {
if (files() && files().length) { if (files() && files().length) {
const base64 = await blobToBase64(files()[0].file); const base64 = await blobToBase64(files()[0].file);
if (base64) { if (base64) {
imageUrl = imageUrl = await sw.upload_profile_pic(base64);
await state.mutiny_wallet?.upload_profile_pic(base64);
} }
} }
await props.onSave({ await props.onSave({

View File

@@ -1,4 +1,5 @@
import { createMemo, ParentComponent, Show } from "solid-js"; import { createAsync } from "@solidjs/router";
import { createMemo, ParentComponent, Show, Suspense } from "solid-js";
import { VStack } from "~/components"; import { VStack } from "~/components";
import { useI18n } from "~/i18n/context"; import { useI18n } from "~/i18n/context";
@@ -20,20 +21,22 @@ const AmountKeyValue: ParentComponent<{ key: string; gray?: boolean }> = (
}; };
function USDShower(props: { amountSats: string; fee?: string }) { function USDShower(props: { amountSats: string; fee?: string }) {
const [state, _] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const amountInFiat = () => const amountInFiat = createAsync(async () => {
(state.fiat.value === "BTC" ? "" : "~") + const formattedFiat = await satsToFormattedFiat(
satsToFormattedFiat(
state.price, state.price,
add(props.amountSats, props.fee), add(props.amountSats, props.fee),
state.fiat state.fiat,
sw
); );
return (state.fiat.value === "BTC" ? "" : "~") + formattedFiat;
});
return ( return (
<Show when={!(props.amountSats === "0")}> <Show when={!(props.amountSats === "0")}>
<AmountKeyValue gray key=""> <AmountKeyValue gray key="">
<div class="self-end whitespace-nowrap"> <div class="self-end whitespace-nowrap">
{`${amountInFiat()} `} <Suspense>{`${amountInFiat()} `}</Suspense>
<span class="text-sm">{state.fiat.value}</span> <span class="text-sm">{state.fiat.value}</span>
</div> </div>
</AmountKeyValue> </AmountKeyValue>

View File

@@ -114,7 +114,10 @@ export function GenericItem(props: {
<div class="flex w-full items-center gap-1 text-m-grey-400"> <div class="flex w-full items-center gap-1 text-m-grey-400">
<Clock4 class="w-3" /> <Clock4 class="w-3" />
<span class="text-xs text-m-grey-400"> <span class="text-xs text-m-grey-400">
{i18n.t("common.expires", { time: props.due })} {i18n.t("common.expires", {
time: props.due,
interpolation: { escapeValue: false }
})}
</span> </span>
</div> </div>
</Show> </Show>

View File

@@ -1,22 +0,0 @@
import { A, useLocation } from "@solidjs/router";
import { Gift } from "lucide-solid";
import { useI18n } from "~/i18n/context";
export function GiftLink() {
const i18n = useI18n();
const location = useLocation();
return (
<A
class="flex items-center gap-2 font-semibold text-m-red no-underline"
href="/settings/gift"
state={{
previous: location.pathname
}}
>
{i18n.t("settings.gift.give_sats_link")}
<Gift class="h-5 w-5" />
</A>
);
}

View File

@@ -1,4 +1,4 @@
import { Match, Switch } from "solid-js"; import { createMemo, Match, Suspense, Switch } from "solid-js";
import { AmountFiat, AmountSats } from "~/components/Amount"; import { AmountFiat, AmountSats } from "~/components/Amount";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
@@ -6,11 +6,13 @@ import { useMegaStore } from "~/state/megaStore";
export function HomeBalance() { export function HomeBalance() {
const [state, actions] = useMegaStore(); const [state, actions] = useMegaStore();
const combinedBalance = () => const combinedBalance = createMemo(
() =>
(state.balance?.federation || 0n) + (state.balance?.federation || 0n) +
(state.balance?.lightning || 0n) + (state.balance?.lightning || 0n) +
(state.balance?.confirmed || 0n) + (state.balance?.confirmed || 0n) +
(state.balance?.unconfirmed || 0n); (state.balance?.unconfirmed || 0n)
);
// TODO: do some sort of status indicator // TODO: do some sort of status indicator
// const fullyReady = () => state.load_stage === "done" && state.price !== 0; // const fullyReady = () => state.load_stage === "done" && state.price !== 0;
@@ -29,10 +31,12 @@ export function HomeBalance() {
/> />
</Match> </Match>
<Match when={state.balanceView === "fiat"}> <Match when={state.balanceView === "fiat"}>
<Suspense>
<AmountFiat <AmountFiat
amountSats={combinedBalance()} amountSats={combinedBalance()}
denominationSize="lg" denominationSize="lg"
/> />
</Suspense>
</Match> </Match>
<Match when={state.balanceView === "hidden"}> <Match when={state.balanceView === "hidden"}>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">

View File

@@ -35,7 +35,7 @@ const ImageWithFallback = (props: { src: string; alt: string }) => {
}; };
export function HomePrompt() { export function HomePrompt() {
const [state, _actions] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const i18n = useI18n(); const i18n = useI18n();
const [params, setParams] = useSearchParams(); const [params, setParams] = useSearchParams();
@@ -68,7 +68,7 @@ export function HomePrompt() {
lsps_token: params.token lsps_token: params.token
}; };
try { try {
await state.mutiny_wallet?.change_lsp( await sw.change_lsp(
values.lsp ? values.lsp : undefined, values.lsp ? values.lsp : undefined,
values.lsps_connection_string values.lsps_connection_string
? values.lsps_connection_string ? values.lsps_connection_string
@@ -100,10 +100,9 @@ export function HomePrompt() {
async function handleLnurlAuth() { async function handleLnurlAuth() {
setAuthLoading(true); setAuthLoading(true);
try { try {
await state.mutiny_wallet?.lnurl_auth(lnurlauthResult()!); await sw.lnurl_auth(lnurlauthResult()!);
setIsAuthenticated(true); setIsAuthenticated(true);
} catch (e) { } catch (e) {
// lnurl1dp68gurn8ghj7um5v93kketj9ehx2amn9ashq6f0d3hxzat5dqlhgct884kx7emfdcnxkvfavvurwdtrvgmkyd3489skgcfexqckxd3svg6xgwr98q6nsd3c893kzcfkvc6nsdr9xpjxvc3jvejrxwpevyurqvfev3nxvvnxx5ergdc8g6gzl
console.error(e); console.error(e);
setAuthError(eify(e)); setAuthError(eify(e));
} finally { } finally {

View File

@@ -18,7 +18,7 @@ import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
export function HomeSubnav() { export function HomeSubnav() {
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const i18n = useI18n(); const i18n = useI18n();
@@ -32,8 +32,7 @@ export function HomeSubnav() {
const [pending, { refetch }] = createResource(async () => { const [pending, { refetch }] = createResource(async () => {
try { try {
const pending = const pending = await sw.get_pending_nwc_invoices();
await state.mutiny_wallet?.get_pending_nwc_invoices();
return pending?.length || 0; return pending?.length || 0;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@@ -90,7 +89,7 @@ export function HomeSubnav() {
"text-white": !!((pending.latest || 0) > 0) "text-white": !!((pending.latest || 0) > 0)
}} }}
> >
{pending()} {pending.latest}
</span> </span>
</Show> </Show>
</Suspense> </Suspense>

View File

@@ -1,4 +1,3 @@
import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { createFileUploader } from "@solid-primitives/upload"; import { createFileUploader } from "@solid-primitives/upload";
import { createSignal, Show } from "solid-js"; import { createSignal, Show } from "solid-js";
@@ -19,7 +18,7 @@ import { downloadTextFile, eify } from "~/utils";
export function ImportExport(props: { emergency?: boolean }) { export function ImportExport(props: { emergency?: boolean }) {
const i18n = useI18n(); const i18n = useI18n();
const [state, _] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const [error, setError] = createSignal<Error>(); const [error, setError] = createSignal<Error>();
const [exportDecrypt, setExportDecrypt] = createSignal(false); const [exportDecrypt, setExportDecrypt] = createSignal(false);
@@ -28,7 +27,7 @@ export function ImportExport(props: { emergency?: boolean }) {
async function handleSave() { async function handleSave() {
try { try {
setError(undefined); setError(undefined);
const json = await MutinyWallet.export_json(); const json = await sw.export_json();
await downloadTextFile(json || "", "mutiny-state.json"); await downloadTextFile(json || "", "mutiny-state.json");
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@@ -52,7 +51,7 @@ export function ImportExport(props: { emergency?: boolean }) {
) )
); );
} }
const json = await MutinyWallet.export_json(password()); const json = await sw.export_json(password());
await downloadTextFile(json || "", "mutiny-state.json"); await downloadTextFile(json || "", "mutiny-state.json");
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@@ -103,24 +102,22 @@ export function ImportExport(props: { emergency?: boolean }) {
fileReader.readAsText(file, "UTF-8"); fileReader.readAsText(file, "UTF-8");
}); });
if (state.mutiny_wallet && !props.emergency) { if (state.load_stage === "done" && !props.emergency) {
console.log("Mutiny wallet loaded, stopping"); console.log("Mutiny wallet loaded, stopping");
try { try {
await state.mutiny_wallet.stop(); await sw.stop();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
setError(eify(e)); setError(eify(e));
} }
} else { } else {
// If there's no mutiny wallet loaded we need to initialize WASM await sw.initializeWasm();
console.log("Initializing WASM");
await initMutinyWallet();
} }
// This should throw if there's a parse error, so we won't end up clearing // This should throw if there's a parse error, so we won't end up clearing
if (text) { if (text) {
JSON.parse(text); JSON.parse(text);
await MutinyWallet.import_json(text); await sw.import_json(text);
} }
setTimeout(() => { setTimeout(() => {
@@ -191,9 +188,10 @@ export function ImportExport(props: { emergency?: boolean }) {
{/* TODO: this is pretty redundant with the DecryptDialog, could make a shared component */} {/* TODO: this is pretty redundant with the DecryptDialog, could make a shared component */}
<SimpleDialog <SimpleDialog
title={i18n.t( title={i18n.t(
"settings.emergency_kit.import_export.confirm_replace" "settings.emergency_kit.import_export.decrypt_export"
)} )}
open={exportDecrypt()} open={exportDecrypt()}
setOpen={() => setExportDecrypt(false)}
> >
<form onSubmit={savePassword}> <form onSubmit={savePassword}>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">

View File

@@ -1,4 +1,3 @@
import { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { useNavigate } from "@solidjs/router"; import { useNavigate } from "@solidjs/router";
import { SecureStoragePlugin } from "capacitor-secure-storage-plugin"; import { SecureStoragePlugin } from "capacitor-secure-storage-plugin";
import { createSignal, Show } from "solid-js"; import { createSignal, Show } from "solid-js";
@@ -7,7 +6,7 @@ import { Button, InfoBox, SimpleInput } from "~/components";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
export function ImportNsecForm(props: { setup?: boolean }) { export function ImportNsecForm(props: { setup?: boolean }) {
const [state, _actions] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
const [nsec, setNsec] = createSignal(""); const [nsec, setNsec] = createSignal("");
const [saving, setSaving] = createSignal(false); const [saving, setSaving] = createSignal(false);
@@ -18,16 +17,13 @@ export function ImportNsecForm(props: { setup?: boolean }) {
setError(undefined); setError(undefined);
const trimmedNsec = nsec().trim(); const trimmedNsec = nsec().trim();
try { try {
const npub = await MutinyWallet.nsec_to_npub(trimmedNsec); const npub = await sw.nsec_to_npub(trimmedNsec);
if (!npub) { if (!npub) {
throw new Error("Invalid nsec"); throw new Error("Invalid nsec");
} }
await SecureStoragePlugin.set({ key: "nsec", value: trimmedNsec }); await SecureStoragePlugin.set({ key: "nsec", value: trimmedNsec });
const new_npub = await state.mutiny_wallet?.change_nostr_keys( const new_npub = await sw.change_nostr_keys(trimmedNsec, undefined);
trimmedNsec,
undefined
);
console.log("Changed to new npub: ", new_npub); console.log("Changed to new npub: ", new_npub);
if (props.setup) { if (props.setup) {
navigate("/addfederation"); navigate("/addfederation");

View File

@@ -43,13 +43,13 @@ type RefetchPeersType = (
function PeerItem(props: { peer: MutinyPeer; refetchPeers: RefetchPeersType }) { function PeerItem(props: { peer: MutinyPeer; refetchPeers: RefetchPeersType }) {
const i18n = useI18n(); const i18n = useI18n();
const [state, _] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const handleDisconnectPeer = async () => { const handleDisconnectPeer = async () => {
if (props.peer.is_connected) { if (props.peer.is_connected) {
await state.mutiny_wallet?.disconnect_peer(props.peer.pubkey); await sw.disconnect_peer(props.peer.pubkey);
} else { } else {
await state.mutiny_wallet?.delete_peer(props.peer.pubkey); await sw.delete_peer(props.peer.pubkey);
} }
await props.refetchPeers(); await props.refetchPeers();
}; };
@@ -82,12 +82,10 @@ function PeerItem(props: { peer: MutinyPeer; refetchPeers: RefetchPeersType }) {
function PeersList() { function PeersList() {
const i18n = useI18n(); const i18n = useI18n();
const [state, _] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const getPeers = async () => { const getPeers = async () => {
return (await state.mutiny_wallet?.list_peers()) as Promise< return await sw.list_peers();
MutinyPeer[]
>;
}; };
const [peers, { refetch }] = createResource(getPeers); const [peers, { refetch }] = createResource(getPeers);
@@ -125,7 +123,7 @@ function PeersList() {
function ConnectPeer(props: { refetchPeers: RefetchPeersType }) { function ConnectPeer(props: { refetchPeers: RefetchPeersType }) {
const i18n = useI18n(); const i18n = useI18n();
const [state, _] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const [value, setValue] = createSignal(""); const [value, setValue] = createSignal("");
@@ -134,7 +132,7 @@ function ConnectPeer(props: { refetchPeers: RefetchPeersType }) {
const peerConnectString = value().trim(); const peerConnectString = value().trim();
await state.mutiny_wallet?.connect_to_peer(peerConnectString); await sw.connect_to_peer(peerConnectString);
await props.refetchPeers(); await props.refetchPeers();
@@ -176,7 +174,7 @@ type PendingChannelAction = "close" | "force_close" | "abandon";
function ChannelItem(props: { channel: MutinyChannel; network?: Network }) { function ChannelItem(props: { channel: MutinyChannel; network?: Network }) {
const i18n = useI18n(); const i18n = useI18n();
const [state, _] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const [pendingChannelAction, setPendingChannelAction] = const [pendingChannelAction, setPendingChannelAction] =
createSignal<PendingChannelAction>(); createSignal<PendingChannelAction>();
@@ -187,7 +185,7 @@ function ChannelItem(props: { channel: MutinyChannel; network?: Network }) {
if (!action) return; if (!action) return;
setConfirmLoading(true); setConfirmLoading(true);
try { try {
await state.mutiny_wallet?.close_channel( await sw.close_channel(
props.channel.outpoint as string, props.channel.outpoint as string,
action === "force_close", action === "force_close",
action === "abandon" action === "abandon"
@@ -279,17 +277,15 @@ function ChannelItem(props: { channel: MutinyChannel; network?: Network }) {
function ChannelsList() { function ChannelsList() {
const i18n = useI18n(); const i18n = useI18n();
const [state, _] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const getChannels = async () => { const getChannels = async () => {
return (await state.mutiny_wallet?.list_channels()) as Promise< return sw.list_channels();
MutinyChannel[]
>;
}; };
const [channels, { refetch }] = createResource(getChannels); const [channels, { refetch }] = createResource(getChannels);
const network = state.mutiny_wallet?.get_network() as Network; const network = state.network;
return ( return (
<> <>
@@ -329,7 +325,7 @@ function ChannelsList() {
function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) { function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) {
const i18n = useI18n(); const i18n = useI18n();
const [state, _] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const [creationError, setCreationError] = createSignal<Error>(); const [creationError, setCreationError] = createSignal<Error>();
@@ -347,11 +343,11 @@ function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) {
try { try {
const pubkey = peerPubkey().trim(); const pubkey = peerPubkey().trim();
const bigAmount = BigInt(amount()); const bigAmount = BigInt(amount());
console.log("pubkey", pubkey);
console.log("bigAmount", bigAmount);
const new_channel = await state.mutiny_wallet?.open_channel( const new_channel = await sw.open_channel(pubkey, bigAmount);
pubkey, console.log("new_channel", new_channel);
bigAmount
);
setNewChannel(new_channel); setNewChannel(new_channel);
@@ -364,7 +360,7 @@ function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) {
} }
}; };
const network = state.mutiny_wallet?.get_network() as Network; const network = state.network;
return ( return (
<> <>
@@ -421,10 +417,10 @@ function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) {
function ListNodes() { function ListNodes() {
const i18n = useI18n(); const i18n = useI18n();
const [state, _] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const getNodeIds = async () => { const getNodeIds = async () => {
const nodes = await state.mutiny_wallet?.list_nodes(); const nodes = await sw.list_nodes();
return nodes as string[]; return nodes as string[];
}; };
@@ -450,7 +446,7 @@ function ListNodes() {
function LSPS(props: { initialSettings: MutinyWalletSettingStrings }) { function LSPS(props: { initialSettings: MutinyWalletSettingStrings }) {
const i18n = useI18n(); const i18n = useI18n();
const [state, _] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const [lspSettingsForm, { Form, Field }] = const [lspSettingsForm, { Form, Field }] =
createForm<MutinyWalletSettingStrings>({ createForm<MutinyWalletSettingStrings>({
validate: (values) => { validate: (values) => {
@@ -469,7 +465,7 @@ function LSPS(props: { initialSettings: MutinyWalletSettingStrings }) {
async function handleSubmit(values: MutinyWalletSettingStrings) { async function handleSubmit(values: MutinyWalletSettingStrings) {
console.log("values", values); console.log("values", values);
try { try {
await state.mutiny_wallet?.change_lsp( await sw.change_lsp(
values.lsp ? values.lsp : undefined, values.lsp ? values.lsp : undefined,
values.lsps_connection_string values.lsps_connection_string
? values.lsps_connection_string ? values.lsps_connection_string

View File

@@ -43,7 +43,7 @@ function LoadingBar(props: { value: number; max: number }) {
} }
export function LoadingIndicator() { export function LoadingIndicator() {
const [state, _actions] = useMegaStore(); const [state] = useMegaStore();
const loadStageValue = () => { const loadStageValue = () => {
switch (state.load_stage) { switch (state.load_stage) {

View File

@@ -1,12 +1,13 @@
import { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { createSignal, Show } from "solid-js"; import { createSignal, Show } from "solid-js";
import { Button, InfoBox, InnerCard, NiceP, VStack } from "~/components"; import { Button, InfoBox, InnerCard, NiceP, VStack } from "~/components";
import { useI18n } from "~/i18n/context"; import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore";
import { downloadTextFile, eify } from "~/utils"; import { downloadTextFile, eify } from "~/utils";
export function Logs() { export function Logs() {
const i18n = useI18n(); const i18n = useI18n();
const [_state, _actions, sw] = useMegaStore();
// Create state for errors, password and dialog visibility // Create state for errors, password and dialog visibility
const [error, setError] = createSignal<Error>(); const [error, setError] = createSignal<Error>();
@@ -14,7 +15,7 @@ export function Logs() {
async function handleSave() { async function handleSave() {
try { try {
setError(undefined); setError(undefined);
const logs = await MutinyWallet.get_logs(); const logs = await sw.get_logs();
await downloadTextFile( await downloadTextFile(
logs.join("") || "", logs.join("") || "",
"mutiny-logs.txt", "mutiny-logs.txt",

View File

@@ -104,7 +104,7 @@ export function NWCEditor(props: {
initialNWA?: string; initialNWA?: string;
onSave: (indexToOpen?: number, nwcUriForCallback?: string) => Promise<void>; onSave: (indexToOpen?: number, nwcUriForCallback?: string) => Promise<void>;
}) { }) {
const [state] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const i18n = useI18n(); const i18n = useI18n();
const nwa = createMemo(() => parseNWA(props.initialNWA)); const nwa = createMemo(() => parseNWA(props.initialNWA));
@@ -152,7 +152,7 @@ export function NWCEditor(props: {
async function createNwa(f: BudgetForm) { async function createNwa(f: BudgetForm) {
if (!f.nwaString) throw new Error("We lost the NWA string!"); if (!f.nwaString) throw new Error("We lost the NWA string!");
try { try {
await state.mutiny_wallet?.approve_nostr_wallet_auth( await sw.approve_nostr_wallet_auth(
f.connection_name || "Nostr Wallet Auth", f.connection_name || "Nostr Wallet Auth",
// can we do better than ! here? // can we do better than ! here?
f.nwaString f.nwaString
@@ -177,7 +177,7 @@ export function NWCEditor(props: {
try { try {
const profile: NwcProfile | undefined = const profile: NwcProfile | undefined =
await state.mutiny_wallet?.get_nwc_profile(index); await sw.get_nwc_profile(index);
console.log(profile); console.log(profile);
return profile; return profile;
} catch (e) { } catch (e) {
@@ -200,8 +200,7 @@ export function NWCEditor(props: {
if (!label) return undefined; if (!label) return undefined;
try { try {
const contact: TagItem | undefined = const contact: TagItem | undefined = await sw.get_tag_item(label);
await state.mutiny_wallet?.get_tag_item(label);
return contact; return contact;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@@ -215,12 +214,11 @@ export function NWCEditor(props: {
let newProfile: NwcProfile | undefined = undefined; let newProfile: NwcProfile | undefined = undefined;
if (!f.profileIndex) throw new Error("No profile index!"); if (!f.profileIndex) throw new Error("No profile index!");
if (!f.auto_approve || f.budget_amount === "0") { if (!f.auto_approve || f.budget_amount === "0") {
newProfile = newProfile = await sw.set_nwc_profile_require_approval(
await state.mutiny_wallet?.set_nwc_profile_require_approval(
f.profileIndex f.profileIndex
); );
} else { } else {
newProfile = await state.mutiny_wallet?.set_nwc_profile_budget( newProfile = await sw.set_nwc_profile_budget(
f.profileIndex, f.profileIndex,
BigInt(f.budget_amount), BigInt(f.budget_amount),
mapIntervalToBudgetPeriod(f.interval) mapIntervalToBudgetPeriod(f.interval)
@@ -240,11 +238,9 @@ export function NWCEditor(props: {
let newProfile: NwcProfile | undefined = undefined; let newProfile: NwcProfile | undefined = undefined;
if (!f.auto_approve || f.budget_amount === "0") { if (!f.auto_approve || f.budget_amount === "0") {
newProfile = await state.mutiny_wallet?.create_nwc_profile( newProfile = await sw.create_nwc_profile(f.connection_name);
f.connection_name
);
} else { } else {
newProfile = await state.mutiny_wallet?.create_budget_nwc_profile( newProfile = await sw.create_budget_nwc_profile(
f.connection_name, f.connection_name,
BigInt(f.budget_amount), BigInt(f.budget_amount),
mapIntervalToBudgetPeriod(f.interval), mapIntervalToBudgetPeriod(f.interval),

View File

@@ -1,4 +1,3 @@
import { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { createAsync, useNavigate } from "@solidjs/router"; import { createAsync, useNavigate } from "@solidjs/router";
import { Search } from "lucide-solid"; import { Search } from "lucide-solid";
import { import {
@@ -38,9 +37,9 @@ export function Avatar(props: { image_url?: string; large?: boolean }) {
export function NostrActivity() { export function NostrActivity() {
const i18n = useI18n(); const i18n = useI18n();
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const npub = createAsync(async () => state.mutiny_wallet?.get_npub()); const npub = createAsync(async () => await sw.get_npub());
const [data, { refetch }] = createResource(npub, fetchZaps); const [data, { refetch }] = createResource(npub, fetchZaps);
@@ -72,14 +71,14 @@ export function NostrActivity() {
// TODO: can this be part of mutiny wallet? // TODO: can this be part of mutiny wallet?
async function newContactFromHexpub(hexpub: string) { async function newContactFromHexpub(hexpub: string) {
try { try {
const npub = await MutinyWallet.hexpub_to_npub(hexpub); const npub = await sw.hexpub_to_npub(hexpub);
console.log("newContactFromHexpub", npub);
if (!npub) { if (!npub) {
throw new Error("No npub for that hexpub"); throw new Error("No npub for that hexpub");
} }
const existingContact = const existingContact = await sw.get_contact_for_npub(npub);
await state.mutiny_wallet?.get_contact_for_npub(npub);
if (existingContact) { if (existingContact) {
navigate(`/chat/${existingContact.id}`); navigate(`/chat/${existingContact.id}`);
@@ -94,7 +93,7 @@ export function NostrActivity() {
const ln_address = parsed.lud16 || undefined; const ln_address = parsed.lud16 || undefined;
const lnurl = parsed.lud06 || undefined; const lnurl = parsed.lud06 || undefined;
const contactId = await state.mutiny_wallet?.create_new_contact( const contactId = await sw.create_new_contact(
name, name,
npub, npub,
ln_address, ln_address,
@@ -106,7 +105,7 @@ export function NostrActivity() {
throw new Error("no contact id returned"); throw new Error("no contact id returned");
} }
const tagItem = await state.mutiny_wallet?.get_tag_item(contactId); const tagItem = await sw.get_tag_item(contactId);
if (!tagItem) { if (!tagItem) {
throw new Error("no contact returned"); throw new Error("no contact returned");

View File

@@ -31,21 +31,20 @@ type PendingItem = {
export function PendingNwc() { export function PendingNwc() {
const i18n = useI18n(); const i18n = useI18n();
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const [error, setError] = createSignal<Error>(); const [error, setError] = createSignal<Error>();
const navigate = useNavigate(); const navigate = useNavigate();
async function fetchPendingRequests() { async function fetchPendingRequests() {
const profiles = await state.mutiny_wallet?.get_nwc_profiles(); const profiles = await sw.get_nwc_profiles();
if (!profiles) return []; if (!profiles) return [];
const contacts: TagItem[] | undefined = const contacts: TagItem[] | undefined = await sw.get_contacts_sorted();
await state.mutiny_wallet?.get_contacts_sorted();
if (!contacts) return []; if (!contacts) return [];
const pending = await state.mutiny_wallet?.get_pending_nwc_invoices(); const pending = await sw.get_pending_nwc_invoices();
if (!pending) return []; if (!pending) return [];
const pendingItems: PendingItem[] = []; const pendingItems: PendingItem[] = [];
@@ -88,7 +87,7 @@ export function PendingNwc() {
try { try {
// setPaying(item.id); // setPaying(item.id);
setPayList([...payList(), item.id]); setPayList([...payList(), item.id]);
await state.mutiny_wallet?.approve_invoice(item.id); await sw.approve_invoice(item.id);
await vibrateSuccess(); await vibrateSuccess();
} catch (e) { } catch (e) {
const err = eify(e); const err = eify(e);
@@ -97,7 +96,7 @@ export function PendingNwc() {
if (err.message === "An invoice must not get payed twice.") { if (err.message === "An invoice must not get payed twice.") {
// wrap in try/catch so we don't crash if the invoice is already gone // wrap in try/catch so we don't crash if the invoice is already gone
try { try {
await state.mutiny_wallet?.deny_invoice(item.id); await sw.deny_invoice(item.id);
} catch (_e) { } catch (_e) {
// do nothing // do nothing
} }
@@ -121,7 +120,7 @@ export function PendingNwc() {
async function denyAll() { async function denyAll() {
try { try {
await state.mutiny_wallet?.deny_all_pending_nwc(); await sw.deny_all_pending_nwc();
} catch (e) { } catch (e) {
setError(eify(e)); setError(eify(e));
console.error(e); console.error(e);
@@ -133,7 +132,7 @@ export function PendingNwc() {
async function rejectItem(item: PendingItem) { async function rejectItem(item: PendingItem) {
try { try {
setPayList([...payList(), item.id]); setPayList([...payList(), item.id]);
await state.mutiny_wallet?.deny_invoice(item.id); await sw.deny_invoice(item.id);
} catch (e) { } catch (e) {
setError(eify(e)); setError(eify(e));
console.error(e); console.error(e);
@@ -161,7 +160,11 @@ export function PendingNwc() {
return ( return (
<Switch> <Switch>
<Match when={pendingRequests() && pendingRequests()!.length > 0}> <Match
when={
pendingRequests.latest && pendingRequests.latest!.length > 0
}
>
<ButtonCard onClick={() => navigate("/settings/connections")}> <ButtonCard onClick={() => navigate("/settings/connections")}>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<PlugZap class="inline-block text-m-red" /> <PlugZap class="inline-block text-m-red" />
@@ -197,7 +200,7 @@ export function PendingNwc() {
<InfoBox accent="red">{error()?.message}</InfoBox> <InfoBox accent="red">{error()?.message}</InfoBox>
</Show> </Show>
<For each={pendingRequests()}> <For each={pendingRequests.latest}>
{(pendingItem) => ( {(pendingItem) => (
<GenericItem <GenericItem
primaryAvatarUrl={pendingItem.image || ""} primaryAvatarUrl={pendingItem.image || ""}

View File

@@ -6,25 +6,30 @@ import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
export function ReceiveWarnings(props: { export function ReceiveWarnings(props: {
amountSats: string | bigint; amountSats: bigint;
from_fedi_to_ln?: boolean; from_fedi_to_ln?: boolean;
}) { }) {
const i18n = useI18n(); const i18n = useI18n();
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const [inboundCapacity] = createResource(async () => { const [inboundCapacity] = createResource(async () => {
try { try {
const channels = await state.mutiny_wallet?.list_channels(); const channels = await sw.list_channels();
let inbound = 0; if (!channels) return 0n;
let inbound = 0n;
// PAIN: mutiny-wasm types say these are bigints, but they're actually numbers
for (const channel of channels) { for (const channel of channels) {
inbound += channel.size - (channel.balance + channel.reserve); inbound +=
BigInt(channel.size) -
BigInt(channel.balance + channel.reserve);
} }
return inbound; return inbound;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return 0; return 0n;
} }
}); });
@@ -41,12 +46,7 @@ export function ReceiveWarnings(props: {
}); });
} }
const parsed = Number(props.amountSats); if (props.amountSats > (inboundCapacity() || 0n)) {
if (isNaN(parsed)) {
return undefined;
}
if (parsed > (inboundCapacity() || 0)) {
return i18n.t("receive.amount_editable.setup_fee_lightning"); return i18n.t("receive.amount_editable.setup_fee_lightning");
} }

View File

@@ -6,16 +6,16 @@ import { useMegaStore } from "~/state/megaStore";
export function Restart() { export function Restart() {
const i18n = useI18n(); const i18n = useI18n();
const [state, _] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const [hasStopped, setHasStopped] = createSignal(false); const [hasStopped, setHasStopped] = createSignal(false);
async function toggle() { async function toggle() {
try { try {
if (hasStopped()) { if (hasStopped()) {
await state.mutiny_wallet?.start(); await sw.start();
setHasStopped(false); setHasStopped(false);
} else { } else {
await state.mutiny_wallet?.stop(); await sw.stop();
setHasStopped(true); setHasStopped(true);
} }
} catch (e) { } catch (e) {

View File

@@ -4,11 +4,11 @@ import { useMegaStore } from "~/state/megaStore";
export function ResyncOnchain() { export function ResyncOnchain() {
const i18n = useI18n(); const i18n = useI18n();
const [state, _] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
async function reset() { async function reset() {
try { try {
await state.mutiny_wallet?.reset_onchain_tracker(); await sw.reset_onchain_tracker();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }

View File

@@ -1,4 +1,3 @@
import { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { Title } from "@solidjs/meta"; import { Title } from "@solidjs/meta";
import { MonitorSmartphone } from "lucide-solid"; import { MonitorSmartphone } from "lucide-solid";
import { createResource, Match, Switch } from "solid-js"; import { createResource, Match, Switch } from "solid-js";
@@ -21,6 +20,7 @@ import {
MutinyWalletSettingStrings MutinyWalletSettingStrings
} from "~/logic/mutinyWalletSetup"; } from "~/logic/mutinyWalletSetup";
import { FeedbackLink } from "~/routes/Feedback"; import { FeedbackLink } from "~/routes/Feedback";
import { useMegaStore } from "~/state/megaStore";
function ErrorFooter() { function ErrorFooter() {
return ( return (
@@ -38,6 +38,7 @@ export function SetupErrorDisplay(props: {
password?: string; password?: string;
}) { }) {
// Error shouldn't be reactive, so we assign to it so it just gets rendered with the first value // Error shouldn't be reactive, so we assign to it so it just gets rendered with the first value
const [_state, _actions, sw] = useMegaStore();
const i18n = useI18n(); const i18n = useI18n();
const error = props.initialError; const error = props.initialError;
@@ -45,7 +46,7 @@ export function SetupErrorDisplay(props: {
if (error.message.startsWith("Mutiny is already running")) { if (error.message.startsWith("Mutiny is already running")) {
const settings: MutinyWalletSettingStrings = await getSettings(); const settings: MutinyWalletSettingStrings = await getSettings();
try { try {
const secs = await MutinyWallet.get_device_lock_remaining_secs( const secs = await sw.get_device_lock_remaining_secs(
props.password, props.password,
settings.auth, settings.auth,
settings.storage settings.storage

View File

@@ -1,7 +1,7 @@
import { TagItem } from "@mutinywallet/mutiny-wasm"; import { TagItem } from "@mutinywallet/mutiny-wasm";
import { cache, createAsync, useNavigate } from "@solidjs/router"; import { cache, createAsync, useNavigate } from "@solidjs/router";
import { Scan, Search } from "lucide-solid"; import { Scan, Search } from "lucide-solid";
import { createMemo, For, Suspense } from "solid-js"; import { For, Suspense } from "solid-js";
import { Circle, LabelCircle, showToast } from "~/components"; import { Circle, LabelCircle, showToast } from "~/components";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
@@ -10,13 +10,12 @@ export function SocialActionRow(props: {
onSearch: () => void; onSearch: () => void;
onScan: () => void; onScan: () => void;
}) { }) {
const [state, actions] = useMegaStore(); const [_state, actions, sw] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
const getContacts = cache(async () => { const getContacts = cache(async () => {
try { try {
const contacts: TagItem[] = const contacts: TagItem[] = (await sw.get_contacts_sorted()) || [];
(await state.mutiny_wallet?.get_contacts_sorted()) || [];
// contact must have a npub, ln_address, or lnurl // contact must have a npub, ln_address, or lnurl
return contacts.filter( return contacts.filter(
@@ -33,16 +32,17 @@ export function SocialActionRow(props: {
const contacts = createAsync(() => getContacts(), { initialValue: [] }); const contacts = createAsync(() => getContacts(), { initialValue: [] });
const profileDeleted = createMemo( const profileDeleted = createAsync(async () => {
() => state.mutiny_wallet?.get_nostr_profile().deleted const profile = await sw.get_nostr_profile();
); return profile?.deleted;
});
// TODO this is mostly copy pasted from chat, could be a shared util maybe // TODO this is mostly copy pasted from chat, could be a shared util maybe
function sendToContact(contact?: TagItem) { async function sendToContact(contact?: TagItem) {
if (!contact) return; if (!contact) return;
const address = contact.ln_address || contact.lnurl; const address = contact.ln_address || contact.lnurl;
if (address) { if (address) {
actions.handleIncomingString( await actions.handleIncomingString(
(address || "").trim(), (address || "").trim(),
(error) => { (error) => {
showToast(error); showToast(error);
@@ -60,6 +60,14 @@ export function SocialActionRow(props: {
} }
} }
async function handleClick(contact: TagItem) {
if (profileDeleted() || !contact.npub) {
sendToContact(contact);
} else {
navigate(`/chat/${contact.id}`);
}
}
return ( return (
<div class="-ml-4 -mr-4 flex gap-2 overflow-x-scroll py-[1px] pl-4"> <div class="-ml-4 -mr-4 flex gap-2 overflow-x-scroll py-[1px] pl-4">
<Circle color="red" onClick={props.onSearch}> <Circle color="red" onClick={props.onSearch}>
@@ -76,13 +84,7 @@ export function SocialActionRow(props: {
label={false} label={false}
name={contact.name} name={contact.name}
image_url={contact.primal_image_url} image_url={contact.primal_image_url}
onClick={() => { onClick={() => handleClick(contact)}
if (profileDeleted() || !contact.npub) {
sendToContact(contact);
} else {
navigate(`/chat/${contact.id}`);
}
}}
/> />
)} )}
</For> </For>

View File

@@ -1,77 +0,0 @@
import { createForm, required, SubmitHandler } from "@modular-forms/solid";
import { createSignal, Show } from "solid-js";
import { Button, VStack } from "~/components";
import { InfoBox } from "~/components/InfoBox";
import { TextField } from "~/components/layout/TextField";
import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore";
import { eify } from "~/utils";
type NostrContactsForm = {
npub: string;
};
export function SyncContactsForm() {
const i18n = useI18n();
const [state, _actions] = useMegaStore();
const [error, setError] = createSignal<Error>();
const [feedbackForm, { Form, Field }] = createForm<NostrContactsForm>({
initialValues: {
npub: ""
}
});
const handleSubmit: SubmitHandler<NostrContactsForm> = async (
f: NostrContactsForm
) => {
try {
const npub = f.npub.trim();
await state.mutiny_wallet?.sync_nostr_contacts(npub);
} catch (e) {
console.error(e);
setError(eify(e));
}
};
return (
<Form onSubmit={handleSubmit}>
<VStack>
<Field
name="npub"
validate={[
required(
i18n.t("settings.nostr_contacts.npub_required")
)
]}
>
{(field, props) => (
<TextField
{...props}
value={field.value}
error={field.error}
label={i18n.t("settings.nostr_contacts.npub_label")}
placeholder="npub..."
/>
)}
</Field>
<Show when={error()}>
<InfoBox accent="red">{error()?.message}</InfoBox>
</Show>
<Button
loading={feedbackForm.submitting}
disabled={
!feedbackForm.dirty ||
feedbackForm.submitting ||
feedbackForm.invalid
}
intent="blue"
type="submit"
>
{i18n.t("settings.nostr_contacts.sync")}
</Button>
</VStack>
</Form>
);
}

View File

@@ -37,8 +37,6 @@ export * from "./Failure";
export * from "./ShareCard"; export * from "./ShareCard";
export * from "./Toaster"; export * from "./Toaster";
export * from "./NostrActivity"; export * from "./NostrActivity";
export * from "./SyncContactsForm";
export * from "./GiftLink";
export * from "./MutinyPlusCta"; export * from "./MutinyPlusCta";
export * from "./ToggleHodl"; export * from "./ToggleHodl";
export * from "./IOSbanner"; export * from "./IOSbanner";

View File

@@ -51,11 +51,16 @@ export const Card: ParentComponent<{
export const ButtonCard: ParentComponent<{ export const ButtonCard: ParentComponent<{
onClick: () => void; onClick: () => void;
red?: boolean;
}> = (props) => { }> = (props) => {
return ( return (
<button <button
onClick={() => props.onClick()} onClick={() => props.onClick()}
class="flex w-full rounded-xl border border-white/10 bg-neutral-900 p-4 active:-mb-[1px] active:mt-[1px] active:opacity-70" class="flex w-full rounded-xl border border-white/10 p-4 active:-mb-[1px] active:mt-[1px] active:opacity-70"
classList={{
"bg-neutral-900": !props.red,
"bg-m-red": props.red
}}
> >
{props.children} {props.children}
</button> </button>
@@ -172,12 +177,14 @@ const FullscreenLoader = () => {
}; };
export const MutinyWalletGuard: ParentComponent = (props) => { export const MutinyWalletGuard: ParentComponent = (props) => {
const [state, _] = useMegaStore(); const [state] = useMegaStore();
return ( return (
<Suspense fallback={<FullscreenLoader />}> <Suspense fallback={<FullscreenLoader />}>
<Switch> <Switch>
<Match when={state.mutiny_wallet && !state.wallet_loading}> <Match
when={!state.wallet_loading && state.load_stage === "done"}
>
{props.children} {props.children}
</Match> </Match>
<Match when={true}> <Match when={true}>

View File

@@ -1,6 +1,3 @@
import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { SecureStoragePlugin } from "capacitor-secure-storage-plugin";
export type Network = "bitcoin" | "testnet" | "regtest" | "signet"; export type Network = "bitcoin" | "testnet" | "regtest" | "signet";
export type MutinyWalletSettingStrings = { export type MutinyWalletSettingStrings = {
@@ -139,7 +136,7 @@ export async function getSettings() {
// Expect urls like /_services/proxy and /_services/storage // Expect urls like /_services/proxy and /_services/storage
if (selfhosted) { if (selfhosted) {
let base = window.location.origin; let base = location.origin;
console.log("Self-hosted mode enabled, using base URL", base); console.log("Self-hosted mode enabled, using base URL", base);
const storage = settings.storage; const storage = settings.storage;
if (storage && storage.startsWith("/")) { if (storage && storage.startsWith("/")) {
@@ -178,26 +175,6 @@ export async function setSettings(newSettings: MutinyWalletSettingStrings) {
}); });
} }
export async function checkForWasm() {
try {
if (
typeof WebAssembly === "object" &&
typeof WebAssembly.instantiate === "function"
) {
const module = new WebAssembly.Module(
Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)
);
if (!(module instanceof WebAssembly.Module)) {
throw new Error("Couldn't instantiate WASM Module");
}
} else {
throw new Error("No WebAssembly global object found");
}
} catch (e) {
console.error(e);
}
}
export async function doubleInitDefense() { export async function doubleInitDefense() {
console.log("Starting init..."); console.log("Starting init...");
// Ultimate defense against getting multiple instances of the wallet running. // Ultimate defense against getting multiple instances of the wallet running.
@@ -213,146 +190,3 @@ export async function doubleInitDefense() {
window.location.reload(); window.location.reload();
} }
} }
export async function initializeWasm() {
// Actually intialize the WASM, this should be the first thing that requires the WASM blob to be downloaded
// If WASM is already initialized, don't init twice
try {
const _sats_the_standard = MutinyWallet.convert_btc_to_sats(1);
console.debug("MutinyWallet WASM already initialized, skipping init");
return;
} catch (e) {
console.debug("MutinyWallet WASM about to be initialized");
await initMutinyWallet();
}
}
export async function setupMutinyWallet(
settings: MutinyWalletSettingStrings,
password?: string,
safeMode?: boolean,
shouldZapHodl?: boolean
): Promise<MutinyWallet | undefined> {
console.log("Starting setup...");
// https://developer.mozilla.org/en-US/docs/Web/API/Storage_API
// Ask the browser to not clear storage
if (navigator.storage && navigator.storage.persist) {
navigator.storage.persist().then((persistent) => {
if (persistent) {
console.log(
"Storage will not be cleared except by explicit user action"
);
} else {
console.log(
"Storage may be cleared by the UA under storage pressure."
);
}
});
}
const {
network,
proxy,
esplora,
rgs,
lsp,
lsps_connection_string,
lsps_token,
auth,
subscriptions,
storage,
scorer,
primal_api,
blind_auth,
hermes
} = settings;
let nsec;
// get nsec from secure storage
try {
const value = await SecureStoragePlugin.get({ key: "nsec" });
nsec = value.value;
} catch (e) {
console.log("No nsec stored");
}
// if we didn't get an nsec from storage, try to use extension
let extension_key;
if (!nsec && Object.prototype.hasOwnProperty.call(window, "nostr")) {
try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore ignore nostr not existing, only does if they have extension
extension_key = await window.nostr.getPublicKey();
} catch (_) {
console.log("No NIP-07 extension");
}
}
console.log("Initializing Mutiny Manager");
console.log("Using network", network);
console.log("Using proxy", proxy);
console.log("Using esplora address", esplora);
console.log("Using rgs address", rgs);
console.log("Using lsp address", lsp);
console.log("Using lsp connection string", lsps_connection_string);
console.log("Using lsp token", lsps_token);
console.log("Using auth address", auth);
console.log("Using subscriptions address", subscriptions);
console.log("Using storage address", storage);
console.log("Using scorer address", scorer);
console.log("Using primal api", primal_api);
console.log("Using blind auth", blind_auth);
console.log("Using hermes", hermes);
console.log(safeMode ? "Safe mode enabled" : "Safe mode disabled");
console.log(shouldZapHodl ? "Hodl zaps enabled" : "Hodl zaps disabled");
// Only use lsps if there's no lsp set
const shouldUseLSPS = !lsp && lsps_connection_string && lsps_token;
const mutinyWallet = await new MutinyWallet(
// Password
password ? password : undefined,
// Mnemonic
undefined,
proxy,
network,
esplora,
rgs,
shouldUseLSPS ? undefined : lsp,
shouldUseLSPS ? lsps_connection_string : undefined,
shouldUseLSPS ? lsps_token : undefined,
auth,
subscriptions,
storage,
scorer,
// Do not connect peers
undefined,
// Do not skip device lock
undefined,
// Safe mode
safeMode || undefined,
// Skip hodl invoices? (defaults to true, so if shouldZapHodl is true that's when we pass false)
shouldZapHodl ? false : undefined,
// Nsec override
nsec,
// Nip7
extension_key ? extension_key : undefined,
// primal URL
primal_api || "https://primal-cache.mutinywallet.com/api",
/// blind auth url
blind_auth,
/// hermes url
hermes
);
sessionStorage.setItem("MUTINY_WALLET_INITIALIZED", Date.now().toString());
if (mutinyWallet) {
return mutinyWallet;
} else {
return undefined;
}
}

View File

@@ -1,5 +1,4 @@
import { PaymentParams } from "@mutinywallet/mutiny-wasm"; import { WalletWorker } from "~/state/megaStore";
import { Result } from "~/utils"; import { Result } from "~/utils";
export type ParsedParams = { export type ParsedParams = {
@@ -20,13 +19,14 @@ export type ParsedParams = {
contact_id?: string; contact_id?: string;
}; };
export function toParsedParams( export async function toParsedParams(
str: string, str: string,
ourNetwork: string ourNetwork: string,
): Result<ParsedParams> { sw: WalletWorker
): Promise<Result<ParsedParams>> {
let params; let params;
try { try {
params = new PaymentParams(str || ""); params = await sw.parse_params(str || "");
} catch (e) { } catch (e) {
return { ok: false, error: new Error("Invalid payment request") }; return { ok: false, error: new Error("Invalid payment request") };
} }

View File

@@ -8,6 +8,7 @@ import {
JSX, JSX,
Match, Match,
onCleanup, onCleanup,
onMount,
Suspense, Suspense,
Switch Switch
} from "solid-js"; } from "solid-js";
@@ -22,7 +23,6 @@ import {
Chat, Chat,
EditProfile, EditProfile,
Feedback, Feedback,
Gift as GiftReceive,
Main, Main,
NotFound, NotFound,
Profile, Profile,
@@ -43,7 +43,6 @@ import {
Currency, Currency,
EmergencyKit, EmergencyKit,
Encrypt, Encrypt,
Gift,
ImportProfileSettings, ImportProfileSettings,
Language, Language,
LightningAddress, LightningAddress,
@@ -63,11 +62,36 @@ import {
} from "~/routes/setup"; } from "~/routes/setup";
import { Provider as MegaStoreProvider, useMegaStore } from "~/state/megaStore"; import { Provider as MegaStoreProvider, useMegaStore } from "~/state/megaStore";
function GlobalListeners() { const setStatusBarStyleDark = async () => {
await StatusBar.setStyle({ style: Style.Dark });
};
if (Capacitor.isNativePlatform()) {
await setStatusBarStyleDark();
}
function ChildrenOrError(props: { children: JSX.Element }) {
const [state] = useMegaStore();
return (
<Switch>
<Match when={state.setup_error}>
<SetupErrorDisplay
initialError={state.setup_error!}
password={state.password}
/>
</Match>
<Match when={true}>{props.children}</Match>
</Switch>
);
}
export function Router() {
// listeners for native navigation handling // listeners for native navigation handling
// Check if the platform is Android to handle back // Check if the platform is Android to handle back
onMount(async () => {
if (Capacitor.getPlatform() === "android") { if (Capacitor.getPlatform() === "android") {
const { remove } = CapacitorApp.addListener( const { remove } = await CapacitorApp.addListener(
"backButton", "backButton",
({ canGoBack }) => { ({ canGoBack }) => {
if (!canGoBack) { if (!canGoBack) {
@@ -88,56 +112,32 @@ function GlobalListeners() {
// Handle app links on native platforms // Handle app links on native platforms
if (Capacitor.isNativePlatform()) { if (Capacitor.isNativePlatform()) {
const navigate = useNavigate(); const navigate = useNavigate();
const { remove } = CapacitorApp.addListener("appUrlOpen", (data) => { const { remove } = await CapacitorApp.addListener(
"appUrlOpen",
(data) => {
const url = new URL(data.url); const url = new URL(data.url);
const path = url.pathname; const path = url.pathname;
const urlParams = new URLSearchParams(url.search); const urlParams = new URLSearchParams(url.search);
console.log(`Navigating to ${path}?${urlParams.toString()}`); console.log(
`Navigating to ${path}?${urlParams.toString()}`
);
navigate(`${path}?${urlParams.toString()}`); navigate(`${path}?${urlParams.toString()}`);
}); }
);
onCleanup(() => { onCleanup(() => {
console.debug("cleaning up appUrlOpen listener"); console.debug("cleaning up appUrlOpen listener");
remove(); remove();
}); });
} }
});
return null;
}
const setStatusBarStyleDark = async () => {
await StatusBar.setStyle({ style: Style.Dark });
};
if (Capacitor.isNativePlatform()) {
await setStatusBarStyleDark();
}
function ChildrenOrError(props: { children: JSX.Element }) {
const [state, _] = useMegaStore();
return (
<Switch>
<Match when={state.setup_error}>
<SetupErrorDisplay
initialError={state.setup_error!}
password={state.password}
/>
</Match>
<Match when={true}>{props.children}</Match>
</Switch>
);
}
export function Router() {
return ( return (
<SolidRouter <SolidRouter
root={(props) => ( root={(props) => (
<MetaProvider> <MetaProvider>
<Title>Mutiny Wallet</Title> <Title>Mutiny Wallet</Title>
<ErrorBoundary fallback={(e) => <ErrorDisplay error={e} />}> <ErrorBoundary fallback={(e) => <ErrorDisplay error={e} />}>
<GlobalListeners />
<Suspense> <Suspense>
<ErrorBoundary <ErrorBoundary
fallback={(e) => <ErrorDisplay error={e} />} fallback={(e) => <ErrorDisplay error={e} />}
@@ -172,7 +172,6 @@ export function Router() {
<Route path="/profile" component={Profile} /> <Route path="/profile" component={Profile} />
<Route path="/chat/:id" component={Chat} /> <Route path="/chat/:id" component={Chat} />
<Route path="/feedback" component={Feedback} /> <Route path="/feedback" component={Feedback} />
<Route path="/gift" component={GiftReceive} />
<Route path="/receive" component={Receive} /> <Route path="/receive" component={Receive} />
<Route path="/redeem" component={Redeem} /> <Route path="/redeem" component={Redeem} />
<Route path="/request/:id" component={RequestRoute} /> <Route path="/request/:id" component={RequestRoute} />
@@ -191,7 +190,6 @@ export function Router() {
<Route path="/language" component={Language} /> <Route path="/language" component={Language} />
<Route path="/emergencykit" component={EmergencyKit} /> <Route path="/emergencykit" component={EmergencyKit} />
<Route path="/encrypt" component={Encrypt} /> <Route path="/encrypt" component={Encrypt} />
<Route path="/gift" component={Gift} />
<Route path="/plus" component={Plus} /> <Route path="/plus" component={Plus} />
<Route path="/restore" component={Restore} /> <Route path="/restore" component={Restore} />
<Route path="/servers" component={Servers} /> <Route path="/servers" component={Servers} />

View File

@@ -43,14 +43,14 @@ import { MiniFab } from "~/components/Fab";
import { useI18n } from "~/i18n/context"; import { useI18n } from "~/i18n/context";
import { ParsedParams, toParsedParams } from "~/logic/waila"; import { ParsedParams, toParsedParams } from "~/logic/waila";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
import { eify, hexpubFromNpub, timeAgo } from "~/utils"; import { createDeepSignal, eify, hexpubFromNpub, timeAgo } from "~/utils";
type CombinedMessagesAndActivity = type CombinedMessagesAndActivity =
| { kind: "message"; content: FakeDirectMessage } | { kind: "message"; content: FakeDirectMessage }
| { kind: "activity"; content: IActivityItem }; | { kind: "activity"; content: IActivityItem };
// TODO: Use the actual type from MutinyWallet // TODO: Use the actual type from MutinyWallet
type FakeDirectMessage = { export type FakeDirectMessage = {
from: string; from: string;
to: string; to: string;
message: string; message: string;
@@ -70,8 +70,8 @@ function SingleMessage(props: {
counterPartyNpub: string; counterPartyNpub: string;
counterPartyContactId: string; counterPartyContactId: string;
}) { }) {
const [state, actions] = useMegaStore(); const [state, actions, sw] = useMegaStore();
const network = state.mutiny_wallet?.get_network() || "signet"; const network = state.network || "signet";
const navigate = useNavigate(); const navigate = useNavigate();
const parsed = createAsync( const parsed = createAsync(
@@ -82,7 +82,7 @@ function SingleMessage(props: {
const split_message_by_whitespace = props.dm.message.split(/\s+/g); const split_message_by_whitespace = props.dm.message.split(/\s+/g);
for (const word of split_message_by_whitespace) { for (const word of split_message_by_whitespace) {
if (word.length > 15) { if (word.length > 15) {
result = toParsedParams(word, network); result = await toParsedParams(word, network, sw);
if (result.ok) { if (result.ok) {
break; break;
} }
@@ -94,9 +94,8 @@ function SingleMessage(props: {
} }
if (result.value?.invoice) { if (result.value?.invoice) {
console.log("about to get invoice");
try { try {
const alreadyPaid = await state.mutiny_wallet?.get_invoice( const alreadyPaid = await sw.get_invoice(
result.value.invoice result.value.invoice
); );
if (alreadyPaid?.paid) { if (alreadyPaid?.paid) {
@@ -149,9 +148,10 @@ function SingleMessage(props: {
navWithContactId(); navWithContactId();
} }
function handlePay(invoice: string) { async function handlePay() {
actions.handleIncomingString( if (!parsed()) return;
invoice, await actions.handleIncomingString(
parsed()!.value,
(error) => { (error) => {
showToast(error); showToast(error);
}, },
@@ -191,7 +191,7 @@ function SingleMessage(props: {
<Button <Button
intent="blue" intent="blue"
layout="xs" layout="xs"
onClick={() => handlePay(parsed()?.value || "")} onClick={handlePay}
> >
Pay Pay
</Button> </Button>
@@ -308,17 +308,17 @@ function FixedChatHeader(props: {
sendToContact: (contact: TagItem) => void; sendToContact: (contact: TagItem) => void;
requestFromContact: (contact: TagItem) => void; requestFromContact: (contact: TagItem) => void;
}) { }) {
const [state, _actions] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
async function saveContact(id: string, contact: ContactFormValues) { async function saveContact(id: string, contact: ContactFormValues) {
console.log("saving contact", id, contact); console.log("saving contact", id, contact);
const hexpub = await hexpubFromNpub(contact.npub?.trim()); const hexpub = await hexpubFromNpub(sw, contact.npub?.trim());
try { try {
const existing = state.mutiny_wallet?.get_tag_item(id); const existing = await sw.get_tag_item(id);
// This shouldn't happen // This shouldn't happen
if (!existing) throw new Error("No existing contact"); if (!existing) throw new Error("No existing contact");
await state.mutiny_wallet?.edit_contact( await sw.edit_contact(
id, id,
contact.name, contact.name,
hexpub ? hexpub : undefined, hexpub ? hexpub : undefined,
@@ -337,7 +337,7 @@ function FixedChatHeader(props: {
async function deleteContact(id: string) { async function deleteContact(id: string) {
try { try {
await state.mutiny_wallet?.delete_contact(id); await sw.delete_contact(id);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
showToast(eify(e)); showToast(eify(e));
@@ -352,7 +352,7 @@ function FixedChatHeader(props: {
try { try {
if (!props.contact.npub) throw new Error("No npub"); if (!props.contact.npub) throw new Error("No npub");
await state.mutiny_wallet?.follow_npub(props.contact.npub); await sw.follow_npub(props.contact.npub);
props.refetch(); props.refetch();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@@ -366,7 +366,7 @@ function FixedChatHeader(props: {
try { try {
if (!props.contact.npub) throw new Error("No npub"); if (!props.contact.npub) throw new Error("No npub");
await state.mutiny_wallet?.unfollow_npub(props.contact.npub); await sw.unfollow_npub(props.contact.npub);
props.refetch(); props.refetch();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@@ -447,26 +447,16 @@ function FixedChatHeader(props: {
export function Chat() { export function Chat() {
const params = useParams(); const params = useParams();
const [state, actions] = useMegaStore(); const [_state, actions, sw] = useMegaStore();
const [messageValue, setMessageValue] = createSignal(""); const [messageValue, setMessageValue] = createSignal("");
const [sending, setSending] = createSignal(false); const [sending, setSending] = createSignal(false);
const i18n = useI18n(); const i18n = useI18n();
// const contact = createAsync(async () => {
// try {
// return state.mutiny_wallet?.get_tag_item(params.id);
// } catch (e) {
// console.error("couldn't find contact");
// console.error(e);
// return undefined;
// }
// });
const [contact, { refetch: refetchContact }] = createResource(async () => { const [contact, { refetch: refetchContact }] = createResource(async () => {
try { try {
return state.mutiny_wallet?.get_tag_item(params.id); return await sw.get_tag_item(params.id);
} catch (e) { } catch (e) {
console.error("couldn't find contact"); console.error("couldn't find contact");
console.error(e); console.error(e);
@@ -474,6 +464,7 @@ export function Chat() {
} }
}); });
// TODO: could probably move this to the web worker and make it even snappier
const [convo, { refetch }] = createResource( const [convo, { refetch }] = createResource(
contact, contact,
async (contact?: TagItem) => { async (contact?: TagItem) => {
@@ -484,7 +475,7 @@ export function Chat() {
let dms = [] as FakeDirectMessage[]; let dms = [] as FakeDirectMessage[];
try { try {
acts = (await state.mutiny_wallet?.get_label_activity( acts = (await sw.get_label_activity(
params.id params.id
)) as IActivityItem[]; )) as IActivityItem[];
} catch (e) { } catch (e) {
@@ -492,7 +483,7 @@ export function Chat() {
} }
try { try {
dms = (await state.mutiny_wallet?.get_dm_conversation( dms = (await sw.get_dm_conversation(
contact.npub, contact.npub,
20n, 20n,
undefined, undefined,
@@ -529,13 +520,14 @@ export function Chat() {
return b_time - a_time; // Descending order return b_time - a_time; // Descending order
}); });
console.log("combined activity", combined);
return combined as CombinedMessagesAndActivity[]; return combined as CombinedMessagesAndActivity[];
} catch (e) { } catch (e) {
console.error("error getting convo:", e); console.error("error getting convo:", e);
return [] as CombinedMessagesAndActivity[]; return [] as CombinedMessagesAndActivity[];
} }
},
{
storage: createDeepSignal
} }
); );
@@ -546,10 +538,7 @@ export function Chat() {
const rememberedValue = messageValue(); const rememberedValue = messageValue();
setMessageValue(""); setMessageValue("");
try { try {
const dmResult = await state.mutiny_wallet?.send_dm( const dmResult = await sw.send_dm(npub, rememberedValue);
npub,
rememberedValue
);
console.log("dmResult:", dmResult); console.log("dmResult:", dmResult);
refetch(); refetch();
} catch (e) { } catch (e) {
@@ -567,11 +556,11 @@ export function Chat() {
}); });
}); });
function sendToContact(contact?: TagItem) { async function sendToContact(contact?: TagItem) {
if (!contact) return; if (!contact) return;
const address = contact.ln_address || contact.lnurl; const address = contact.ln_address || contact.lnurl;
if (address) { if (address) {
actions.handleIncomingString( await actions.handleIncomingString(
(address || "").trim(), (address || "").trim(),
(error) => { (error) => {
showToast(error); showToast(error);

View File

@@ -1,5 +1,5 @@
import { useNavigate } from "@solidjs/router"; import { createAsync, useNavigate } from "@solidjs/router";
import { createMemo, createSignal, Show } from "solid-js"; import { createSignal, Show } from "solid-js";
import { import {
BackLink, BackLink,
@@ -14,14 +14,14 @@ import { useMegaStore } from "~/state/megaStore";
import { DEFAULT_NOSTR_NAME } from "~/utils"; import { DEFAULT_NOSTR_NAME } from "~/utils";
export function EditProfile() { export function EditProfile() {
const [state, _actions] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
// const i18n = useI18n(); // const i18n = useI18n();
const navigate = useNavigate(); const navigate = useNavigate();
const [saving, setSaving] = createSignal(false); const [saving, setSaving] = createSignal(false);
const originalProfile = createMemo(() => { const originalProfile = createAsync(async () => {
const profile = state.mutiny_wallet?.get_nostr_profile(); const profile = await sw.get_nostr_profile();
return { return {
name: profile?.display_name || profile?.name || DEFAULT_NOSTR_NAME, name: profile?.display_name || profile?.name || DEFAULT_NOSTR_NAME,
@@ -37,11 +37,11 @@ export function EditProfile() {
console.log("new profile", profile); console.log("new profile", profile);
const newProfile = await state.mutiny_wallet?.edit_nostr_profile( const newProfile = await sw.edit_nostr_profile(
profile.nym ? profile.nym : undefined, profile.nym ? profile.nym : undefined,
profile.imageUrl ? profile.imageUrl : undefined, profile.imageUrl ? profile.imageUrl : undefined,
profile.lightningAddress ? profile.lightningAddress : undefined, profile.lightningAddress ? profile.lightningAddress : undefined,
originalProfile().nip05 ? originalProfile().nip05 : undefined originalProfile()?.nip05 ? originalProfile()?.nip05 : undefined
); );
console.log("newProfile", newProfile); console.log("newProfile", newProfile);
@@ -62,9 +62,9 @@ export function EditProfile() {
<Show when={originalProfile()}> <Show when={originalProfile()}>
<EditProfileForm <EditProfileForm
initialProfile={{ initialProfile={{
nym: originalProfile().name, nym: originalProfile()?.name,
lightningAddress: originalProfile().lud16, lightningAddress: originalProfile()?.lud16,
imageUrl: originalProfile().picture imageUrl: originalProfile()?.picture
}} }}
onSave={saveProfile} onSave={saveProfile}
saving={saving()} saving={saving()}

View File

@@ -1,270 +0,0 @@
import { useSearchParams } from "@solidjs/router";
import {
createMemo,
createResource,
createSignal,
Match,
Show,
Suspense,
Switch
} from "solid-js";
import treasureClosed from "~/assets/treasure-closed.png";
import treasure from "~/assets/treasure.gif";
import {
AmountFiat,
AmountSats,
BackLink,
Button,
ButtonLink,
DefaultMain,
FancyCard,
FeesModal,
InfoBox,
Logo,
MutinyWalletGuard,
NavBar,
NiceP,
VStack
} from "~/components";
import { useI18n } from "~/i18n/context";
import { Network } from "~/logic/mutinyWalletSetup";
import { useMegaStore } from "~/state/megaStore";
import { eify } from "~/utils";
function InboundWarning() {
const [state, _] = useMegaStore();
const i18n = useI18n();
const [searchParams] = useSearchParams();
const [inboundCapacity] = createResource(async () => {
try {
const channels = await state.mutiny_wallet?.list_channels();
let inbound = 0n;
for (const channel of channels) {
inbound =
inbound +
BigInt(channel.size) -
BigInt(channel.balance + channel.reserve);
}
return inbound;
} catch (e) {
console.error(e);
return 0n;
}
});
const warningText = createMemo(() => {
if (isNaN(Number(searchParams.amount))) {
return undefined;
}
const amountNumber = Number(searchParams.amount);
const amount = BigInt(amountNumber);
const network = state.mutiny_wallet?.get_network() as Network;
const threshold = network === "bitcoin" ? 100000 : 10000;
const balance =
(state.balance?.lightning || 0n) +
(state.balance?.federation || 0n);
if (balance === 0n && amount < threshold) {
return i18n.t("settings.gift.receive_too_small", {
amount: network === "bitcoin" ? "100,000" : "10,000"
});
}
if (inboundCapacity() && inboundCapacity()! > amount) {
return undefined;
} else {
return i18n.t("settings.gift.setup_fee_lightning");
}
});
return (
<Show when={warningText()}>
<InfoBox accent="blue">
{warningText()} <FeesModal />
</InfoBox>
</Show>
);
}
export function Gift() {
const [state, _] = useMegaStore();
const i18n = useI18n();
const [claimSuccess, setClaimSuccess] = createSignal(false);
const [error, setError] = createSignal<Error>();
const [loading, setLoading] = createSignal(false);
const [searchParams] = useSearchParams();
async function claim() {
const amount = Number(searchParams.amount);
const nwc = searchParams.nwc_uri;
setLoading(true);
if (!nwc) {
throw new Error(i18n.t("settings.gift.something_went_wrong"));
}
try {
const claimResult = await state.mutiny_wallet?.claim_single_use_nwc(
BigInt(amount),
nwc
);
if (claimResult === "Already Claimed") {
throw new Error(i18n.t("settings.gift.already_claimed"));
}
if (
claimResult ===
"Failed to pay invoice: We do not have enough balance to pay the given amount."
) {
throw new Error(i18n.t("settings.gift.sender_is_poor"));
}
// Fallback for any other errors
if (claimResult) {
throw new Error(
i18n.t("settings.gift.sender_generic_error", {
error: claimResult
})
);
}
setClaimSuccess(true);
} catch (e) {
console.error(e);
const err = eify(e);
if (err.message === "Payment timed out.") {
setError(new Error(i18n.t("settings.gift.sender_timed_out")));
} else {
setError(err);
}
} finally {
setLoading(false);
}
}
async function tryAgain() {
setError(undefined);
await claim();
}
return (
<MutinyWalletGuard>
<DefaultMain>
<BackLink />
<Show when={searchParams.nwc_uri && searchParams.amount}>
<VStack>
<FancyCard>
<VStack>
<div class="flex items-start justify-between">
<VStack smallgap>
<span class="text-3xl">
<AmountSats
denominationSize="xl"
amountSats={Number(
searchParams.amount
)}
/>
</span>
<span class="text-xl text-white/70">
<AmountFiat
denominationSize="xl"
amountSats={Number(
searchParams.amount
)}
/>
</span>
</VStack>
<Logo />
</div>
<div
class="relative transition-all duration-500"
classList={{
"grayscale filter opacity-75":
!claimSuccess()
}}
>
<img
src={treasureClosed}
fetchpriority="high"
class="mx-auto w-1/2"
classList={{
hidden: !!claimSuccess()
}}
/>
<img
src={treasure}
fetchpriority="high"
class="mx-auto w-1/2"
classList={{
hidden: !claimSuccess()
}}
/>
</div>
<h2 class="text-center text-3xl font-semibold">
{i18n.t("settings.gift.receive_header")}
</h2>
<NiceP>
{i18n.t(
"settings.gift.receive_description"
)}
</NiceP>
<Show when={!claimSuccess()}>
<Suspense>
<InboundWarning />
</Suspense>
</Show>
<Switch>
<Match when={error()}>
<InfoBox accent="red">
{error()?.message}
</InfoBox>
<ButtonLink href="/" intent="red">
{i18n.t("common.dangit")}
</ButtonLink>
<Button
intent="inactive"
onClick={tryAgain}
loading={loading()}
>
{i18n.t(
"settings.gift.receive_try_again"
)}
</Button>
</Match>
<Match when={claimSuccess()}>
<InfoBox accent="green">
{i18n.t(
"settings.gift.receive_claimed"
)}
</InfoBox>
<ButtonLink href="/" intent="inactive">
{i18n.t("common.nice")}
</ButtonLink>
</Match>
<Match when={true}>
<Button
intent="inactive"
onClick={claim}
loading={loading()}
>
{i18n.t(
"settings.gift.receive_cta"
)}
</Button>
</Match>
</Switch>
</VStack>
</FancyCard>
</VStack>
</Show>
</DefaultMain>
<NavBar activeTab="none" />
</MutinyWalletGuard>
);
}

View File

@@ -1,5 +1,5 @@
import { createAsync, useNavigate } from "@solidjs/router"; import { createAsync, useNavigate } from "@solidjs/router";
import { createMemo, Show, Suspense } from "solid-js"; import { Show, Suspense } from "solid-js";
import { import {
Circle, Circle,
@@ -16,45 +16,37 @@ import {
} from "~/components"; } from "~/components";
import { Fab } from "~/components/Fab"; import { Fab } from "~/components/Fab";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
import { DEFAULT_NOSTR_NAME } from "~/utils";
export function WalletHeader(props: { loading: boolean }) { export function WalletHeader(props: { loading: boolean }) {
const navigate = useNavigate(); const navigate = useNavigate();
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
async function getProfile() { const profile = createAsync(async () => {
const profile = state.mutiny_wallet?.get_nostr_profile();
return {
name: profile?.display_name || profile?.name || DEFAULT_NOSTR_NAME,
picture: profile?.picture || undefined,
// TODO: this but for real
lud16: profile?.lud16 || undefined
};
}
const profile = createAsync(() => getProfile());
const profileImage = createMemo(() => {
if (props.loading) { if (props.loading) {
return undefined; return undefined;
} }
return await sw.get_nostr_profile();
if (profile() && profile()!.picture) {
return profile()!.picture;
}
return undefined;
}); });
return ( return (
<header class="grid grid-cols-[auto_minmax(0,_1fr)_auto] items-center gap-4"> <header class="grid grid-cols-[auto_minmax(0,_1fr)_auto] items-center gap-4">
<Suspense
fallback={
<LabelCircle <LabelCircle
contact contact
label={false} label={false}
image_url={profileImage()} image_url={undefined}
onClick={() => navigate("/profile")} onClick={() => navigate("/profile")}
/> />
}
>
<LabelCircle
contact
label={false}
image_url={profile()?.picture}
onClick={() => navigate("/profile")}
/>
</Suspense>
<HomeBalance /> <HomeBalance />
<Circle onClick={() => navigate("/settings")}> <Circle onClick={() => navigate("/settings")}>
<img <img
@@ -76,7 +68,7 @@ export function WalletHeader(props: { loading: boolean }) {
} }
export function Main() { export function Main() {
const [state, _actions] = useMegaStore(); const [state] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -90,9 +82,7 @@ export function Main() {
<div class="flex-1" /> <div class="flex-1" />
</Show> </Show>
<Show when={state.load_stage === "done"}> <Show when={state.load_stage === "done"}>
<Suspense>
<WalletHeader loading={false} /> <WalletHeader loading={false} />
</Suspense>
<Show when={!state.wallet_loading && !state.safe_mode}> <Show when={!state.wallet_loading && !state.safe_mode}>
<SocialActionRow <SocialActionRow

View File

@@ -1,6 +1,6 @@
import { useNavigate } from "@solidjs/router"; import { createAsync, useNavigate } from "@solidjs/router";
import { AtSign, Edit, Import } from "lucide-solid"; import { AtSign, Edit, Import } from "lucide-solid";
import { createMemo, Show } from "solid-js"; import { createMemo, Show, Suspense } from "solid-js";
import { import {
BackLink, BackLink,
@@ -9,6 +9,7 @@ import {
DefaultMain, DefaultMain,
LabelCircle, LabelCircle,
LightningAddressShower, LightningAddressShower,
LoadingShimmer,
MutinyWalletGuard, MutinyWalletGuard,
NavBar, NavBar,
NiceP NiceP
@@ -25,13 +26,13 @@ export type UserProfile = {
}; };
export function Profile() { export function Profile() {
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const i18n = useI18n(); const i18n = useI18n();
const navigate = useNavigate(); const navigate = useNavigate();
const profile = createMemo(() => { const profile = createAsync(async () => {
const profile = state.mutiny_wallet?.get_nostr_profile(); const profile = await sw.get_nostr_profile();
const userProfile: UserProfile = { const userProfile: UserProfile = {
name: profile?.display_name || profile?.name || DEFAULT_NOSTR_NAME, name: profile?.display_name || profile?.name || DEFAULT_NOSTR_NAME,
@@ -43,17 +44,17 @@ export function Profile() {
}); });
const profileDeleted = createMemo(() => { const profileDeleted = createMemo(() => {
return profile().deleted === true || profile().deleted === "true"; return profile()?.deleted === true || profile()?.deleted === "true";
}); });
const hasMutinyAddress = createMemo(() => { const hasMutinyAddress = createMemo(() => {
if (profile().lud16) { if (profile()?.lud16) {
const hermes = import.meta.env.VITE_HERMES; const hermes = import.meta.env.VITE_HERMES;
if (!hermes) { if (!hermes) {
return false; return false;
} }
const hermesDomain = new URL(hermes).hostname; const hermesDomain = new URL(hermes).hostname;
const afterAt = profile().lud16!.split("@")[1]; const afterAt = profile()?.lud16!.split("@")[1];
if (afterAt && afterAt.includes(hermesDomain)) { if (afterAt && afterAt.includes(hermesDomain)) {
return true; return true;
} }
@@ -70,11 +71,13 @@ export function Profile() {
<LabelCircle <LabelCircle
contact contact
label={false} label={false}
image_url={profile().picture} image_url={profile()?.picture}
size="xl" size="xl"
/> />
<h1 class="text-3xl font-semibold"> <h1 class="text-3xl font-semibold">
<Show when={profile().name}>{profile().name}</Show> <Show when={profile()?.name}>
{profile()?.name}
</Show>
</h1> </h1>
<LightningAddressShower profile={profile()} /> <LightningAddressShower profile={profile()} />
@@ -107,7 +110,7 @@ export function Profile() {
</ButtonCard> </ButtonCard>
</Show> </Show>
</Show> </Show>
<Show when={profile() && profile().deleted}> <Show when={profile() && profile()?.deleted}>
<ButtonCard <ButtonCard
onClick={() => navigate("/settings/importprofile")} onClick={() => navigate("/settings/importprofile")}
> >
@@ -119,7 +122,9 @@ export function Profile() {
</div> </div>
</ButtonCard> </ButtonCard>
</Show> </Show>
<Suspense fallback={<LoadingShimmer />}>
<BalanceBox loading={state.wallet_loading} /> <BalanceBox loading={state.wallet_loading} />
</Suspense>
<NavBar activeTab="profile" /> <NavBar activeTab="profile" />
</DefaultMain> </DefaultMain>
</MutinyWalletGuard> </MutinyWalletGuard>

View File

@@ -47,7 +47,7 @@ import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
import { eify, objectToSearchParams, vibrateSuccess } from "~/utils"; import { eify, objectToSearchParams, vibrateSuccess } from "~/utils";
type OnChainTx = { export type OnChainTx = {
transaction: { transaction: {
version: number; version: number;
lock_time: number; lock_time: number;
@@ -123,7 +123,7 @@ function ReceiveMethodHelp() {
} }
export function Receive() { export function Receive() {
const [state, actions] = useMegaStore(); const [state, actions, sw] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
const i18n = useI18n(); const i18n = useI18n();
@@ -242,10 +242,7 @@ export function Receive() {
// First we try to get both an invoice and an address // First we try to get both an invoice and an address
try { try {
console.log("big amount", bigAmount); console.log("big amount", bigAmount);
const raw = await state.mutiny_wallet?.create_bip21( const raw = await sw.create_bip21(bigAmount, tags);
bigAmount,
tags
);
// Save the raw info so we can watch the address and invoice // Save the raw info so we can watch the address and invoice
setBip21Raw(raw); setBip21Raw(raw);
@@ -270,7 +267,7 @@ export function Receive() {
// If we didn't return before this, that means create_bip21 failed // If we didn't return before this, that means create_bip21 failed
// So now we'll just try and get an address without the invoice // So now we'll just try and get an address without the invoice
try { try {
const raw = await state.mutiny_wallet?.get_new_address(tags); const raw = await sw.get_new_address(tags);
// Save the raw info so we can watch the address // Save the raw info so we can watch the address
setBip21Raw(raw); setBip21Raw(raw);
@@ -314,8 +311,7 @@ export function Receive() {
try { try {
// Lightning invoice might be blank // Lightning invoice might be blank
if (lightning) { if (lightning) {
const invoice = const invoice = await sw.get_invoice(lightning);
await state.mutiny_wallet?.get_invoice(lightning);
// If the invoice has a fees amount that's probably the LSP fee // If the invoice has a fees amount that's probably the LSP fee
if (invoice?.fees_paid) { if (invoice?.fees_paid) {
@@ -330,9 +326,9 @@ export function Receive() {
} }
} }
const tx = (await state.mutiny_wallet?.check_address( const tx = (await sw.check_address(address)) as
address | OnChainTx
)) as OnChainTx | undefined; | undefined;
if (tx) { if (tx) {
setReceiveState("paid"); setReceiveState("paid");
@@ -396,7 +392,7 @@ export function Receive() {
onSubmit={getQr} onSubmit={getQr}
/> />
<ReceiveWarnings <ReceiveWarnings
amountSats={amount() || "0"} amountSats={amount() || 0n}
from_fedi_to_ln={false} from_fedi_to_ln={false}
/> />
</VStack> </VStack>

View File

@@ -36,7 +36,7 @@ import { eify, vibrateSuccess } from "~/utils";
type RedeemState = "edit" | "paid"; type RedeemState = "edit" | "paid";
export function Redeem() { export function Redeem() {
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
const i18n = useI18n(); const i18n = useI18n();
@@ -52,8 +52,9 @@ export function Redeem() {
const [loading, setLoading] = createSignal(false); const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal<string>(""); const [error, setError] = createSignal<string>("");
function mSatsToSats(mSats: bigint) { function mSatsToSats(mSats: bigint | number) {
return mSats / 1000n; const bigMsats = BigInt(mSats);
return bigMsats / 1000n;
} }
function clearAll() { function clearAll() {
@@ -69,9 +70,7 @@ export function Redeem() {
const [decodedLnurl] = createResource(async () => { const [decodedLnurl] = createResource(async () => {
if (state.scan_result) { if (state.scan_result) {
if (state.scan_result.lnurl) { if (state.scan_result.lnurl) {
const decoded = await state.mutiny_wallet?.decode_lnurl( const decoded = await sw.decode_lnurl(state.scan_result.lnurl);
state.scan_result.lnurl
);
return decoded; return decoded;
} }
} }
@@ -126,10 +125,7 @@ export function Redeem() {
setLoading(true); setLoading(true);
try { try {
const success = await state.mutiny_wallet?.lnurl_withdraw( const success = await sw.lnurl_withdraw(lnurlString(), amount());
lnurlString(),
amount()
);
if (!success) { if (!success) {
setError(i18n.t("redeem.lnurl_redeem_failed")); setError(i18n.t("redeem.lnurl_redeem_failed"));
} else { } else {
@@ -170,7 +166,7 @@ export function Redeem() {
</Show> </Show>
</Suspense> </Suspense>
<ReceiveWarnings <ReceiveWarnings
amountSats={amount() || "0"} amountSats={amount() || 0n}
from_fedi_to_ln={false} from_fedi_to_ln={false}
/> />
<Show when={lnurlAmountText() && !fixedAmount()}> <Show when={lnurlAmountText() && !fixedAmount()}>

View File

@@ -22,7 +22,7 @@ import { eify } from "~/utils";
import { DestinationItem } from "./Send"; import { DestinationItem } from "./Send";
export function RequestRoute() { export function RequestRoute() {
const [state, _actions] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
const i18n = useI18n(); const i18n = useI18n();
@@ -46,12 +46,12 @@ export function RequestRoute() {
tags.push(whatForInput().trim()); tags.push(whatForInput().trim());
} }
const raw = await state.mutiny_wallet?.create_bip21(amount(), tags); const raw = await sw.create_bip21(amount(), tags);
if (!raw || !raw.invoice) if (!raw || !raw.invoice)
throw new Error("Invoice creation failed"); throw new Error("Invoice creation failed");
await state.mutiny_wallet?.send_dm(npub, raw.invoice); await sw.send_dm(npub, raw.invoice);
navigate("/chat/" + params.id); navigate("/chat/" + params.id);
} catch (e) { } catch (e) {
@@ -64,7 +64,7 @@ export function RequestRoute() {
async function getContact(id: string) { async function getContact(id: string) {
console.log("fetching contact", id); console.log("fetching contact", id);
try { try {
const contact = state.mutiny_wallet?.get_tag_item(id); const contact = await sw.get_tag_item(id);
console.log("fetching contact", contact); console.log("fetching contact", contact);
// This shouldn't happen // This shouldn't happen
if (!contact) throw new Error("Contact not found"); if (!contact) throw new Error("Contact not found");
@@ -103,7 +103,7 @@ export function RequestRoute() {
setAmountSats={setAmount} setAmountSats={setAmount}
onSubmit={handleSubmit} onSubmit={handleSubmit}
/> />
<ReceiveWarnings amountSats={amount() || "0"} /> <ReceiveWarnings amountSats={amount() || 0n} />
</VStack> </VStack>
<div class="flex-1" /> <div class="flex-1" />
<VStack> <VStack>

View File

@@ -74,7 +74,7 @@ export function Search() {
function ActualSearch(props: { initialValue?: string }) { function ActualSearch(props: { initialValue?: string }) {
const [searchValue, setSearchValue] = createSignal(""); const [searchValue, setSearchValue] = createSignal("");
const [debouncedSearchValue, setDebouncedSearchValue] = createSignal(""); const [debouncedSearchValue, setDebouncedSearchValue] = createSignal("");
const [state, actions] = useMegaStore(); const [_state, actions, sw] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
const i18n = useI18n(); const i18n = useI18n();
@@ -88,7 +88,7 @@ function ActualSearch(props: { initialValue?: string }) {
const getContacts = cache(async () => { const getContacts = cache(async () => {
try { try {
const contacts = await state.mutiny_wallet?.get_contacts_sorted(); const contacts = await sw.get_contacts_sorted();
return contacts || ([] as TagItem[]); return contacts || ([] as TagItem[]);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@@ -124,7 +124,7 @@ function ActualSearch(props: { initialValue?: string }) {
type SearchState = "notsendable" | "sendable" | "sendableWithContact"; type SearchState = "notsendable" | "sendable" | "sendableWithContact";
const searchState = createMemo<SearchState>(() => { const searchState = createAsync<SearchState>(async () => {
if (debouncedSearchValue() === "") { if (debouncedSearchValue() === "") {
return "notsendable"; return "notsendable";
} }
@@ -134,7 +134,7 @@ function ActualSearch(props: { initialValue?: string }) {
return "notsendable"; return "notsendable";
} }
let state: SearchState = "notsendable"; let state: SearchState = "notsendable";
actions.handleIncomingString( await actions.handleIncomingString(
text, text,
(_error) => { (_error) => {
// noop // noop
@@ -147,6 +147,7 @@ function ActualSearch(props: { initialValue?: string }) {
} }
} }
); );
console.log("params searchState", state);
return state; return state;
}); });
@@ -160,8 +161,8 @@ function ActualSearch(props: { initialValue?: string }) {
}); });
} }
function handleContinue() { async function handleContinue() {
actions.handleIncomingString( await actions.handleIncomingString(
debouncedSearchValue().trim(), debouncedSearchValue().trim(),
(error) => { (error) => {
showToast(error); showToast(error);
@@ -181,16 +182,17 @@ function ActualSearch(props: { initialValue?: string }) {
); );
} }
const profileDeleted = createMemo( const profileDeleted = createAsync(async () => {
() => state.mutiny_wallet?.get_nostr_profile().deleted const profile = await sw.get_nostr_profile();
); return profile?.deleted;
});
// TODO this is mostly copy pasted from chat, could be a shared util maybe // TODO this is mostly copy pasted from chat, could be a shared util maybe
function navToSend(contact?: TagItem) { async function navToSend(contact?: TagItem) {
if (!contact) return; if (!contact) return;
const address = contact.ln_address || contact.lnurl; const address = contact.ln_address || contact.lnurl;
if (address) { if (address) {
actions.handleIncomingString( await actions.handleIncomingString(
(address || "").trim(), (address || "").trim(),
(error) => { (error) => {
showToast(error); showToast(error);
@@ -208,9 +210,9 @@ function ActualSearch(props: { initialValue?: string }) {
} }
} }
function sendToContact(contact: TagItem) { async function sendToContact(contact: TagItem) {
if (profileDeleted()) { if (profileDeleted()) {
navToSend(contact); await navToSend(contact);
} else { } else {
navWithSearchValue(`/chat/${contact.id}`); navWithSearchValue(`/chat/${contact.id}`);
} }
@@ -224,11 +226,11 @@ function ActualSearch(props: { initialValue?: string }) {
); );
if (existingContact) { if (existingContact) {
sendToContact(existingContact); await sendToContact(existingContact);
return; return;
} }
const contactId = await state.mutiny_wallet?.create_new_contact( const contactId = await sw.create_new_contact(
contact.name, contact.name,
contact.npub ? contact.npub.trim() : undefined, contact.npub ? contact.npub.trim() : undefined,
contact.ln_address ? contact.ln_address.trim() : undefined, contact.ln_address ? contact.ln_address.trim() : undefined,
@@ -240,7 +242,7 @@ function ActualSearch(props: { initialValue?: string }) {
throw new Error("no contact id returned"); throw new Error("no contact id returned");
} }
const tagItem = await state.mutiny_wallet?.get_tag_item(contactId); const tagItem = await sw.get_tag_item(contactId);
if (!tagItem) { if (!tagItem) {
throw new Error("no contact returned"); throw new Error("no contact returned");
@@ -249,9 +251,9 @@ function ActualSearch(props: { initialValue?: string }) {
// if the new contact has an npub, send to chat // if the new contact has an npub, send to chat
// otherwise, send to send page // otherwise, send to send page
if (tagItem.npub) { if (tagItem.npub) {
sendToContact(tagItem); await sendToContact(tagItem);
} else if (tagItem.ln_address) { } else if (tagItem.ln_address) {
actions.handleIncomingString( await actions.handleIncomingString(
tagItem.ln_address, tagItem.ln_address,
() => {}, () => {},
() => { () => {
@@ -259,7 +261,7 @@ function ActualSearch(props: { initialValue?: string }) {
} }
); );
} else if (tagItem.lnurl) { } else if (tagItem.lnurl) {
actions.handleIncomingString( await actions.handleIncomingString(
tagItem.lnurl, tagItem.lnurl,
() => {}, () => {},
() => { () => {
@@ -293,14 +295,14 @@ function ActualSearch(props: { initialValue?: string }) {
const trimText = text.trim(); const trimText = text.trim();
setSearchValue(trimText); setSearchValue(trimText);
parsePaste(trimText); await parsePaste(trimText);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
} }
function parsePaste(text: string) { async function parsePaste(text: string) {
actions.handleIncomingString( await actions.handleIncomingString(
text, text,
(error) => { (error) => {
showToast(error); showToast(error);
@@ -354,6 +356,7 @@ function ActualSearch(props: { initialValue?: string }) {
</button> </button>
</Show> </Show>
</div> </div>
<Suspense>
<div class="flex-0 flex w-full"> <div class="flex-0 flex w-full">
<Show when={searchState() !== "notsendable"}> <Show when={searchState() !== "notsendable"}>
<Button intent="green" onClick={handleContinue}> <Button intent="green" onClick={handleContinue}>
@@ -370,7 +373,9 @@ function ActualSearch(props: { initialValue?: string }) {
{(contact) => ( {(contact) => (
<ContactButton <ContactButton
contact={contact} contact={contact}
onClick={() => sendToContact(contact)} onClick={() =>
sendToContact(contact)
}
/> />
)} )}
</For> </For>
@@ -393,6 +398,7 @@ function ActualSearch(props: { initialValue?: string }) {
<div class="h-4" /> <div class="h-4" />
</VStack> </VStack>
</Show> </Show>
</Suspense>
</> </>
); );
} }
@@ -402,10 +408,11 @@ function GlobalSearch(props: {
sendToContact: (contact: TagItem) => void; sendToContact: (contact: TagItem) => void;
foundNpubs: (string | undefined)[]; foundNpubs: (string | undefined)[];
}) { }) {
const [_state, _actions, sw] = useMegaStore();
const hexpubs = createMemo(() => { const hexpubs = createMemo(() => {
const hexpubs: Set<string> = new Set(); const hexpubs: Set<string> = new Set();
for (const npub of props.foundNpubs) { for (const npub of props.foundNpubs) {
hexpubFromNpub(npub) hexpubFromNpub(sw, npub)
.then((h) => { .then((h) => {
if (h) { if (h) {
hexpubs.add(h); hexpubs.add(h);
@@ -425,7 +432,7 @@ function GlobalSearch(props: {
try { try {
// Handling case when value starts with "npub" // Handling case when value starts with "npub"
if (args.value?.toLowerCase().startsWith("npub")) { if (args.value?.toLowerCase().startsWith("npub")) {
const hexpub = await hexpubFromNpub(args.value); const hexpub = await hexpubFromNpub(sw, args.value);
if (!hexpub) return []; if (!hexpub) return [];
const profile = await actuallyFetchNostrProfile(hexpub); const profile = await actuallyFetchNostrProfile(hexpub);
@@ -491,23 +498,25 @@ function SingleContact(props: {
contact: PseudoContact; contact: PseudoContact;
sendToContact: (contact: TagItem) => void; sendToContact: (contact: TagItem) => void;
}) { }) {
const [state, _actions] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
async function createContactFromSearchResult(contact: PseudoContact) { async function createContactFromSearchResult(contact: PseudoContact) {
try { try {
const contactId = await state.mutiny_wallet?.create_new_contact( const contactId = await sw.create_new_contact(
contact.name, contact.name,
contact.hexpub ? contact.hexpub : undefined, contact.hexpub ? contact.hexpub : "",
contact.ln_address ? contact.ln_address : undefined, contact.ln_address ? contact.ln_address : undefined,
undefined, undefined,
contact.image_url ? contact.image_url : undefined contact.image_url ? contact.image_url : undefined
); );
console.log("contactId", contactId);
if (!contactId) { if (!contactId) {
throw new Error("no contact id returned"); throw new Error("no contact id returned");
} }
const tagItem = await state.mutiny_wallet?.get_tag_item(contactId); const tagItem = await sw.get_tag_item(contactId);
if (!tagItem) { if (!tagItem) {
throw new Error("no contact returned"); throw new Error("no contact returned");

View File

@@ -1,5 +1,10 @@
import { MutinyInvoice, TagItem } from "@mutinywallet/mutiny-wasm"; import { MutinyInvoice, TagItem } from "@mutinywallet/mutiny-wasm";
import { useLocation, useNavigate, useSearchParams } from "@solidjs/router"; import {
createAsync,
useLocation,
useNavigate,
useSearchParams
} from "@solidjs/router";
import { Eye, EyeOff, Link, X, Zap } from "lucide-solid"; import { Eye, EyeOff, Link, X, Zap } from "lucide-solid";
import { import {
createEffect, createEffect,
@@ -158,7 +163,7 @@ export function DestinationItem(props: {
} }
export function Send() { export function Send() {
const [state, actions] = useMegaStore(); const [state, actions, sw] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
const [params, setParams] = useSearchParams(); const [params, setParams] = useSearchParams();
const i18n = useI18n(); const i18n = useI18n();
@@ -220,8 +225,8 @@ export function Send() {
} }
// TODO: can I dedupe this from the search page? // TODO: can I dedupe this from the search page?
function parsePaste(text: string) { async function parsePaste(text: string) {
actions.handleIncomingString( await actions.handleIncomingString(
text, text,
(error) => { (error) => {
showToast(error); showToast(error);
@@ -233,9 +238,10 @@ export function Send() {
); );
} }
// TODO: do we actually use this anywhere?
// send?invoice=... need to check for wallet because we can't parse until we have the wallet // send?invoice=... need to check for wallet because we can't parse until we have the wallet
createEffect(() => { createEffect(() => {
if (params.invoice && state.mutiny_wallet) { if (params.invoice && state.load_stage === "done") {
parsePaste(params.invoice); parsePaste(params.invoice);
setParams({ invoice: undefined }); setParams({ invoice: undefined });
} }
@@ -300,7 +306,7 @@ export function Send() {
}); });
// Rerun every time the amount changes if we're onchain // Rerun every time the amount changes if we're onchain
const feeEstimate = createMemo(() => { const feeEstimate = createAsync(async () => {
if ( if (
source() === "onchain" && source() === "onchain" &&
amountSats() && amountSats() &&
@@ -310,16 +316,16 @@ export function Send() {
try { try {
// If max we want to use the sweep fee estimator // If max we want to use the sweep fee estimator
if (isMax()) { if (isMax()) {
return state.mutiny_wallet?.estimate_sweep_tx_fee( return await sw.estimate_sweep_tx_fee(address()!);
address()!
);
} }
return state.mutiny_wallet?.estimate_tx_fee( const estimate = await sw.estimate_tx_fee(
address()!, address()!,
amountSats(), amountSats(),
undefined undefined
); );
console.log("estimate", estimate);
return estimate;
} catch (e) { } catch (e) {
setError(eify(e).message); setError(eify(e).message);
} }
@@ -367,15 +373,15 @@ export function Send() {
// A ParsedParams with an invoice in it // A ParsedParams with an invoice in it
function processInvoice(source: ParsedParams & { invoice: string }) { function processInvoice(source: ParsedParams & { invoice: string }) {
state.mutiny_wallet sw.decode_invoice(source.invoice!)
?.decode_invoice(source.invoice!)
.then((invoice) => { .then((invoice) => {
if (!invoice) return;
if (invoice.expire <= Date.now() / 1000) { if (invoice.expire <= Date.now() / 1000) {
navigate("/search"); navigate("/search");
throw new Error(i18n.t("send.error_expired")); throw new Error(i18n.t("send.error_expired"));
} }
if (invoice?.amount_sats) { if (invoice.amount_sats) {
setAmountSats(invoice.amount_sats); setAmountSats(invoice.amount_sats);
setIsAmtEditable(false); setIsAmtEditable(false);
} }
@@ -396,8 +402,7 @@ export function Send() {
// A ParsedParams with an lnurl in it // A ParsedParams with an lnurl in it
function processLnurl(source: ParsedParams & { lnurl: string }) { function processLnurl(source: ParsedParams & { lnurl: string }) {
setDecodingLnUrl(true); setDecodingLnUrl(true);
state.mutiny_wallet sw.decode_lnurl(source.lnurl)
?.decode_lnurl(source.lnurl)
.then((lnurlParams) => { .then((lnurlParams) => {
setDecodingLnUrl(false); setDecodingLnUrl(false);
if (lnurlParams.tag === "payRequest") { if (lnurlParams.tag === "payRequest") {
@@ -470,7 +475,7 @@ export function Send() {
sentDetails.destination = bolt11; sentDetails.destination = bolt11;
// If the invoice has sats use that, otherwise we pass the user-defined amount // If the invoice has sats use that, otherwise we pass the user-defined amount
if (invoice()?.amount_sats) { if (invoice()?.amount_sats) {
const payment = await state.mutiny_wallet?.pay_invoice( const payment = await sw.pay_invoice(
bolt11, bolt11,
undefined, undefined,
tags tags
@@ -479,7 +484,7 @@ export function Send() {
sentDetails.payment_hash = payment?.payment_hash; sentDetails.payment_hash = payment?.payment_hash;
sentDetails.fee_estimate = payment?.fees_paid || 0; sentDetails.fee_estimate = payment?.fees_paid || 0;
} else { } else {
const payment = await state.mutiny_wallet?.pay_invoice( const payment = await sw.pay_invoice(
bolt11, bolt11,
amountSats(), amountSats(),
tags tags
@@ -489,7 +494,7 @@ export function Send() {
sentDetails.fee_estimate = payment?.fees_paid || 0; sentDetails.fee_estimate = payment?.fees_paid || 0;
} }
} else if (source() === "lightning" && nodePubkey()) { } else if (source() === "lightning" && nodePubkey()) {
const payment = await state.mutiny_wallet?.keysend( const payment = await sw.keysend(
nodePubkey()!, nodePubkey()!,
amountSats(), amountSats(),
undefined, // todo add optional keysend message undefined, // todo add optional keysend message
@@ -509,7 +514,7 @@ export function Send() {
visibility() !== "Not Available" && contact()?.npub visibility() !== "Not Available" && contact()?.npub
? contact()?.npub ? contact()?.npub
: undefined; : undefined;
const payment = await state.mutiny_wallet?.lnurl_pay( const payment = await sw.lnurl_pay(
lnurlp()!, lnurlp()!,
amountSats(), amountSats(),
zapNpub, // zap_npub zapNpub, // zap_npub
@@ -530,17 +535,14 @@ export function Send() {
if (isMax()) { if (isMax()) {
// If we're trying to send the max amount, use the sweep method instead of regular send // If we're trying to send the max amount, use the sweep method instead of regular send
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const txid = await state.mutiny_wallet?.sweep_wallet( const txid = await sw.sweep_wallet(address()!, tags);
address()!,
tags
);
sentDetails.amount = amountSats(); sentDetails.amount = amountSats();
sentDetails.destination = address(); sentDetails.destination = address();
sentDetails.txid = txid; sentDetails.txid = txid;
sentDetails.fee_estimate = feeEstimate() ?? 0; sentDetails.fee_estimate = feeEstimate() ?? 0;
} else if (payjoinEnabled()) { } else if (payjoinEnabled()) {
const txid = await state.mutiny_wallet?.send_payjoin( const txid = await sw.send_payjoin(
originalScan()!, originalScan()!,
amountSats(), amountSats(),
tags tags
@@ -551,7 +553,7 @@ export function Send() {
sentDetails.fee_estimate = feeEstimate() ?? 0; sentDetails.fee_estimate = feeEstimate() ?? 0;
} else { } else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const txid = await state.mutiny_wallet?.send_to_address( const txid = await sw.send_to_address(
address()!, address()!,
amountSats(), amountSats(),
tags tags
@@ -665,7 +667,7 @@ export function Send() {
async function getContact(id: string) { async function getContact(id: string) {
console.log("fetching contact", id); console.log("fetching contact", id);
try { try {
const contact = state.mutiny_wallet?.get_tag_item(id); const contact = await sw.get_tag_item(id);
console.log("fetching contact", contact); console.log("fetching contact", contact);
// This shouldn't happen // This shouldn't happen
if (!contact) throw new Error("Contact not found"); if (!contact) throw new Error("Contact not found");
@@ -773,7 +775,6 @@ export function Send() {
<AmountEditable <AmountEditable
initialAmountSats={amountSats()} initialAmountSats={amountSats()}
setAmountSats={setAmountInput} setAmountSats={setAmountInput}
fee={feeEstimate()?.toString()}
onSubmit={() => onSubmit={() =>
sendButtonDisabled() ? undefined : handleSend() sendButtonDisabled() ? undefined : handleSend()
} }
@@ -791,7 +792,6 @@ export function Send() {
<AmountEditable <AmountEditable
initialAmountSats={amountSats()} initialAmountSats={amountSats()}
setAmountSats={setAmountInput} setAmountSats={setAmountInput}
fee={feeEstimate()?.toString()}
frozenAmount={true} frozenAmount={true}
onSubmit={() => onSubmit={() =>
sendButtonDisabled() ? undefined : handleSend() sendButtonDisabled() ? undefined : handleSend()
@@ -801,6 +801,7 @@ export function Send() {
setChosenMethod={setSourceFromMethod} setChosenMethod={setSourceFromMethod}
/> />
</Show> </Show>
<Suspense>
<Show when={feeEstimate()}> <Show when={feeEstimate()}>
<FeeDisplay <FeeDisplay
amountSats={amountSats().toString()} amountSats={amountSats().toString()}
@@ -808,6 +809,7 @@ export function Send() {
maxAmountSats={maxAmountSats()} maxAmountSats={maxAmountSats()}
/> />
</Show> </Show>
</Suspense>
<Show when={isHodlInvoice()}> <Show when={isHodlInvoice()}>
<InfoBox accent="red"> <InfoBox accent="red">
<p>{i18n.t("send.hodl_invoice_warning")}</p> <p>{i18n.t("send.hodl_invoice_warning")}</p>

View File

@@ -1,6 +1,6 @@
import { createForm, required } from "@modular-forms/solid"; import { createForm, required } from "@modular-forms/solid";
import { MutinyChannel, MutinyPeer } from "@mutinywallet/mutiny-wasm"; import { MutinyChannel } from "@mutinywallet/mutiny-wasm";
import { useNavigate } from "@solidjs/router"; import { createAsync, useNavigate } from "@solidjs/router";
import { import {
createMemo, createMemo,
createResource, createResource,
@@ -8,6 +8,7 @@ import {
For, For,
Match, Match,
Show, Show,
Suspense,
Switch Switch
} from "solid-js"; } from "solid-js";
@@ -33,7 +34,6 @@ import {
VStack VStack
} from "~/components"; } from "~/components";
import { useI18n } from "~/i18n/context"; import { useI18n } from "~/i18n/context";
import { Network } from "~/logic/mutinyWalletSetup";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
import { eify, vibrateSuccess } from "~/utils"; import { eify, vibrateSuccess } from "~/utils";
@@ -50,7 +50,7 @@ type ChannelOpenDetails = {
}; };
export function Swap() { export function Swap() {
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
const i18n = useI18n(); const i18n = useI18n();
@@ -100,9 +100,7 @@ export function Swap() {
}; };
const getPeers = async () => { const getPeers = async () => {
return (await state.mutiny_wallet?.list_peers()) as Promise< return await sw?.list_peers();
MutinyPeer[]
>;
}; };
const [peers, { refetch }] = createResource(getPeers); const [peers, { refetch }] = createResource(getPeers);
@@ -114,7 +112,7 @@ export function Swap() {
try { try {
const peerConnectString = values.peer.trim(); const peerConnectString = values.peer.trim();
await state.mutiny_wallet?.connect_to_peer(peerConnectString); await sw.connect_to_peer(peerConnectString);
await refetch(); await refetch();
@@ -156,12 +154,11 @@ export function Swap() {
} }
if (isMax()) { if (isMax()) {
const new_channel = const new_channel = await sw.sweep_all_to_channel(peer);
await state.mutiny_wallet?.sweep_all_to_channel(peer);
setChannelOpenResult({ channel: new_channel }); setChannelOpenResult({ channel: new_channel });
} else { } else {
const new_channel = await state.mutiny_wallet?.open_channel( const new_channel = await sw.open_channel(
peer, peer,
amountSats() amountSats()
); );
@@ -182,7 +179,7 @@ export function Swap() {
const balance = const balance =
(state.balance?.confirmed || 0n) + (state.balance?.confirmed || 0n) +
(state.balance?.unconfirmed || 0n); (state.balance?.unconfirmed || 0n);
const network = state.mutiny_wallet?.get_network() as Network; const network = state.network || "signet";
if (network === "bitcoin") { if (network === "bitcoin") {
return ( return (
@@ -199,12 +196,12 @@ export function Swap() {
} }
}; };
const amountWarning = () => { const amountWarning = createAsync(async () => {
if (amountSats() === 0n || !!channelOpenResult()) { if (amountSats() === 0n || !!channelOpenResult()) {
return undefined; return undefined;
} }
const network = state.mutiny_wallet?.get_network() as Network; const network = state.network || "signet";
if (network === "bitcoin" && amountSats() < 100000n) { if (network === "bitcoin" && amountSats() < 100000n) {
return i18n.t("swap.channel_too_small", { amount: "100,000" }); return i18n.t("swap.channel_too_small", { amount: "100,000" });
@@ -224,7 +221,7 @@ export function Swap() {
} }
return undefined; return undefined;
}; });
function calculateMaxOnchain() { function calculateMaxOnchain() {
return ( return (
@@ -241,12 +238,12 @@ export function Swap() {
return amountSats() === calculateMaxOnchain(); return amountSats() === calculateMaxOnchain();
}); });
const feeEstimate = createMemo(() => { const feeEstimate = createAsync(async () => {
const max = calculateMaxOnchain(); const max = maxOnchain();
// If max we want to use the sweep fee estimator // If max we want to use the sweep fee estimator
if (amountSats() > 0n && amountSats() === max) { if (amountSats() > 0n && amountSats() === max) {
try { try {
return state.mutiny_wallet?.estimate_sweep_channel_open_fee(); return await sw.estimate_sweep_channel_open_fee();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return undefined; return undefined;
@@ -255,7 +252,7 @@ export function Swap() {
if (amountSats() > 0n) { if (amountSats() > 0n) {
try { try {
return state.mutiny_wallet?.estimate_tx_fee( return await sw.estimate_tx_fee(
CHANNEL_FEE_ESTIMATE_ADDRESS, CHANNEL_FEE_ESTIMATE_ADDRESS,
amountSats(), amountSats(),
undefined undefined
@@ -328,6 +325,7 @@ export function Swap() {
})} })}
</p> </p>
<div class="text-center text-sm text-white/70"> <div class="text-center text-sm text-white/70">
<Suspense>
<AmountFiat <AmountFiat
amountSats={ amountSats={
Number( Number(
@@ -340,6 +338,7 @@ export function Swap() {
) )
} }
/> />
</Suspense>
</div> </div>
</div> </div>
<hr class="w-16 bg-m-grey-400" /> <hr class="w-16 bg-m-grey-400" />
@@ -425,7 +424,6 @@ export function Swap() {
<AmountEditable <AmountEditable
initialAmountSats={amountSats()} initialAmountSats={amountSats()}
setAmountSats={setAmountSats} setAmountSats={setAmountSats}
fee={feeEstimate()?.toString()}
activeMethod={{ activeMethod={{
method: "onchain", method: "onchain",
maxAmountSats: maxOnchain() maxAmountSats: maxOnchain()
@@ -437,6 +435,7 @@ export function Swap() {
} }
]} ]}
/> />
<Suspense>
<Show when={feeEstimate() && amountSats() > 0n}> <Show when={feeEstimate() && amountSats() > 0n}>
<FeeDisplay <FeeDisplay
amountSats={amountSats().toString()} amountSats={amountSats().toString()}
@@ -444,9 +443,14 @@ export function Swap() {
maxAmountSats={maxOnchain()} maxAmountSats={maxOnchain()}
/> />
</Show> </Show>
</Suspense>
<Suspense>
<Show when={amountWarning() && amountSats() > 0n}> <Show when={amountWarning() && amountSats() > 0n}>
<InfoBox accent={"red"}>{amountWarning()}</InfoBox> <InfoBox accent={"red"}>
{amountWarning()}
</InfoBox>
</Show> </Show>
</Suspense>
</VStack> </VStack>
<div class="flex-1" /> <div class="flex-1" />
<VStack> <VStack>

View File

@@ -1,6 +1,13 @@
import { FedimintSweepResult } from "@mutinywallet/mutiny-wasm"; import { FedimintSweepResult } from "@mutinywallet/mutiny-wasm";
import { useNavigate } from "@solidjs/router"; import { useNavigate } from "@solidjs/router";
import { createMemo, createSignal, Match, Show, Switch } from "solid-js"; import {
createMemo,
createSignal,
Match,
Show,
Suspense,
Switch
} from "solid-js";
import { import {
AmountEditable, AmountEditable,
@@ -31,7 +38,7 @@ type SweepResultDetails = {
}; };
export function SwapLightning() { export function SwapLightning() {
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
const i18n = useI18n(); const i18n = useI18n();
@@ -63,17 +70,11 @@ export function SwapLightning() {
setFeeEstimateWarning(undefined); setFeeEstimateWarning(undefined);
if (isMax()) { if (isMax()) {
const result = const result = await sw.sweep_federation_balance(undefined);
await state.mutiny_wallet?.sweep_federation_balance(
undefined
);
setSweepResult({ result: result }); setSweepResult({ result: result });
} else { } else {
const result = const result = await sw.sweep_federation_balance(amountSats());
await state.mutiny_wallet?.sweep_federation_balance(
amountSats()
);
setSweepResult({ result: result }); setSweepResult({ result: result });
} }
@@ -128,8 +129,7 @@ export function SwapLightning() {
setLoading(true); setLoading(true);
setFeeEstimateWarning(undefined); setFeeEstimateWarning(undefined);
const fee = const fee = await sw.estimate_sweep_federation_fee(
await state.mutiny_wallet?.estimate_sweep_federation_fee(
isMax() ? undefined : amountSats() isMax() ? undefined : amountSats()
); );
@@ -201,11 +201,13 @@ export function SwapLightning() {
})} })}
</p> </p>
<div class="text-center text-sm text-white/70"> <div class="text-center text-sm text-white/70">
<Suspense>
<AmountFiat <AmountFiat
amountSats={Number( amountSats={Number(
sweepResult()?.result?.amount sweepResult()?.result?.amount
)} )}
/> />
</Suspense>
</div> </div>
</div> </div>
<hr class="w-16 bg-m-grey-400" /> <hr class="w-16 bg-m-grey-400" />
@@ -236,7 +238,7 @@ export function SwapLightning() {
]} ]}
/> />
<ReceiveWarnings <ReceiveWarnings
amountSats={amountSats() || "0"} amountSats={amountSats() || 0n}
from_fedi_to_ln={true} from_fedi_to_ln={true}
/> />
<Show <Show
@@ -246,11 +248,13 @@ export function SwapLightning() {
{amountWarning()} {amountWarning()}
</InfoBox> </InfoBox>
</Show> </Show>
<Suspense>
<Show when={feeEstimateWarning()}> <Show when={feeEstimateWarning()}>
<InfoBox accent={"red"}> <InfoBox accent={"red"}>
{feeEstimateWarning()} {feeEstimateWarning()}
</InfoBox> </InfoBox>
</Show> </Show>
</Suspense>
</VStack> </VStack>
<div class="flex-1" /> <div class="flex-1" />
<VStack> <VStack>

View File

@@ -1,15 +1,14 @@
export * from "./[...404]"; export * from "./[...404]";
export * from "./Feedback"; export * from "./Feedback";
export * from "./Gift";
export * from "./Main"; export * from "./Main";
export * from "./Receive"; export * from "./Receive";
export * from "./Scanner"; export * from "./Scanner";
export * from "./Send"; export * from "./Send";
export * from "./Swap";
export * from "./SwapLightning";
export * from "./Search"; export * from "./Search";
export * from "./Redeem"; export * from "./Redeem";
export * from "./Profile"; export * from "./Profile";
export * from "./Chat"; export * from "./Chat";
export * from "./Request"; export * from "./Request";
export * from "./EditProfile"; export * from "./EditProfile";
export * from "./Swap";
export * from "./SwapLightning";

View File

@@ -1,4 +1,4 @@
import { useNavigate } from "@solidjs/router"; import { createAsync, useNavigate } from "@solidjs/router";
import { createEffect, createSignal, Show } from "solid-js"; import { createEffect, createSignal, Show } from "solid-js";
import { import {
@@ -53,7 +53,7 @@ function Quiz(props: { setHasCheckedAll: (hasChecked: boolean) => void }) {
export function Backup() { export function Backup() {
const i18n = useI18n(); const i18n = useI18n();
const [store, actions] = useMegaStore(); const [_store, actions, sw] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
const [hasSeenBackup, setHasSeenBackup] = createSignal(false); const [hasSeenBackup, setHasSeenBackup] = createSignal(false);
@@ -67,6 +67,8 @@ export function Backup() {
setLoading(false); setLoading(false);
} }
const words = createAsync(async () => await sw.show_seed());
return ( return (
<MutinyWalletGuard> <MutinyWalletGuard>
<DefaultMain> <DefaultMain>
@@ -79,7 +81,7 @@ export function Backup() {
<NiceP>{i18n.t("settings.backup.warning_one")}</NiceP> <NiceP>{i18n.t("settings.backup.warning_one")}</NiceP>
<NiceP>{i18n.t("settings.backup.warning_two")}</NiceP> <NiceP>{i18n.t("settings.backup.warning_two")}</NiceP>
<SeedWords <SeedWords
words={store.mutiny_wallet?.show_seed() || ""} words={words() || ""}
setHasSeen={setHasSeenBackup} setHasSeen={setHasSeenBackup}
/> />
<Show when={hasSeenBackup()}> <Show when={hasSeenBackup()}>

View File

@@ -30,7 +30,6 @@ import {
VStack VStack
} from "~/components"; } from "~/components";
import { useI18n } from "~/i18n/context"; import { useI18n } from "~/i18n/context";
import { Network } from "~/logic/mutinyWalletSetup";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
import { createDeepSignal, eify, mempoolTxUrl } from "~/utils"; import { createDeepSignal, eify, mempoolTxUrl } from "~/utils";
@@ -100,8 +99,8 @@ function splitChannelNumbers(channel: MutinyChannel): {
function SingleChannelItem(props: { channel: MutinyChannel; online: boolean }) { function SingleChannelItem(props: { channel: MutinyChannel; online: boolean }) {
const i18n = useI18n(); const i18n = useI18n();
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const network = state.mutiny_wallet?.get_network() as Network; const network = state.network;
const [confirmOpen, setConfirmOpen] = createSignal(false); const [confirmOpen, setConfirmOpen] = createSignal(false);
const [confirmLoading, setConfirmLoading] = createSignal(false); const [confirmLoading, setConfirmLoading] = createSignal(false);
@@ -115,11 +114,7 @@ function SingleChannelItem(props: { channel: MutinyChannel; online: boolean }) {
if (!props.channel.outpoint) return; if (!props.channel.outpoint) return;
setConfirmLoading(true); setConfirmLoading(true);
const forceClose = !props.online; const forceClose = !props.online;
await state.mutiny_wallet?.close_channel( await sw.close_channel(props.channel.outpoint, forceClose, false);
props.channel.outpoint,
forceClose,
false
);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
showToast(eify(e)); showToast(eify(e));
@@ -180,12 +175,11 @@ function SingleChannelItem(props: { channel: MutinyChannel; online: boolean }) {
function LiquidityMonitor() { function LiquidityMonitor() {
const i18n = useI18n(); const i18n = useI18n();
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
async function listChannels() { async function listChannels() {
try { try {
const channels: MutinyChannel[] | undefined = const channels = await sw.list_channels();
await state.mutiny_wallet?.list_channels();
if (!channels) if (!channels)
return { return {
@@ -232,22 +226,24 @@ function LiquidityMonitor() {
return ( return (
<Switch> <Switch>
<Match when={channelInfo()?.channelCount}> <Match
when={channelInfo.latest && channelInfo.latest?.channelCount}
>
<VStack> <VStack>
<Card> <Card>
<NiceP> <NiceP>
{i18n.t("settings.channels.have_channels")}{" "} {i18n.t("settings.channels.have_channels")}{" "}
{channelInfo()?.channelCount}{" "} {channelInfo.latest?.channelCount}{" "}
{channelInfo()?.channelCount === 1 {channelInfo.latest?.channelCount === 1
? i18n.t("settings.channels.have_channels_one") ? i18n.t("settings.channels.have_channels_one")
: i18n.t( : i18n.t(
"settings.channels.have_channels_many" "settings.channels.have_channels_many"
)} )}
</NiceP>{" "} </NiceP>{" "}
<BalanceBar <BalanceBar
inbound={Number(channelInfo()?.inbound) || 0} inbound={Number(channelInfo.latest?.inbound) || 0}
reserve={Number(channelInfo()?.reserve) || 0} reserve={Number(channelInfo.latest?.reserve) || 0}
outbound={Number(channelInfo()?.outbound) || 0} outbound={Number(channelInfo.latest?.outbound) || 0}
/> />
<TinyText> <TinyText>
{i18n.t("settings.channels.inbound_outbound_tip")} {i18n.t("settings.channels.inbound_outbound_tip")}
@@ -256,7 +252,7 @@ function LiquidityMonitor() {
{i18n.t("settings.channels.reserve_tip")} {i18n.t("settings.channels.reserve_tip")}
</TinyText> </TinyText>
</Card> </Card>
<Show when={channelInfo()?.online?.length}> <Show when={channelInfo.latest?.online?.length}>
<SettingsCard> <SettingsCard>
<Collapser <Collapser
title={i18n.t( title={i18n.t(
@@ -265,7 +261,7 @@ function LiquidityMonitor() {
activityLight="on" activityLight="on"
> >
<VStack> <VStack>
<For each={channelInfo()?.online}> <For each={channelInfo.latest?.online}>
{(channel) => ( {(channel) => (
<SingleChannelItem <SingleChannelItem
channel={channel} channel={channel}
@@ -277,7 +273,7 @@ function LiquidityMonitor() {
</Collapser> </Collapser>
</SettingsCard> </SettingsCard>
</Show> </Show>
<Show when={channelInfo()?.offline?.length}> <Show when={channelInfo.latest?.offline?.length}>
<SettingsCard> <SettingsCard>
<Collapser <Collapser
title={i18n.t( title={i18n.t(
@@ -286,7 +282,7 @@ function LiquidityMonitor() {
activityLight="off" activityLight="off"
> >
<VStack> <VStack>
<For each={channelInfo()?.offline}> <For each={channelInfo.latest?.offline}>
{(channel) => ( {(channel) => (
<SingleChannelItem <SingleChannelItem
channel={channel} channel={channel}

View File

@@ -77,7 +77,7 @@ function NwcDetails(props: {
onEdit?: () => void; onEdit?: () => void;
}) { }) {
const i18n = useI18n(); const i18n = useI18n();
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const [confirmOpen, setConfirmOpen] = createSignal(false); const [confirmOpen, setConfirmOpen] = createSignal(false);
@@ -87,7 +87,7 @@ function NwcDetails(props: {
async function deleteProfile() { async function deleteProfile() {
try { try {
await state.mutiny_wallet?.delete_nwc_profile(props.profile.index); await sw.delete_nwc_profile(props.profile.index);
setConfirmOpen(false); setConfirmOpen(false);
props.refetch(); props.refetch();
} catch (e) { } catch (e) {
@@ -187,11 +187,12 @@ function NwcDetails(props: {
function Nwc() { function Nwc() {
const i18n = useI18n(); const i18n = useI18n();
const [state, _actions] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
async function fetchNwcProfiles() { async function fetchNwcProfiles() {
try { try {
const profiles = await state.mutiny_wallet?.get_nwc_profiles(); const profiles = await sw.get_nwc_profiles();
console.log("profiles", profiles);
if (!profiles) return []; if (!profiles) return [];
return profiles; return profiles;

View File

@@ -26,7 +26,7 @@ type EncryptPasswordForm = {
export function Encrypt() { export function Encrypt() {
const i18n = useI18n(); const i18n = useI18n();
const [store, _actions] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const [error, setError] = createSignal<Error>(); const [error, setError] = createSignal<Error>();
const [loading, setLoading] = createSignal(false); const [loading, setLoading] = createSignal(false);
@@ -56,7 +56,7 @@ export function Encrypt() {
const handleFormSubmit = async (f: EncryptPasswordForm) => { const handleFormSubmit = async (f: EncryptPasswordForm) => {
setLoading(true); setLoading(true);
try { try {
await store.mutiny_wallet?.change_password( await sw.change_password(
f.existingPassword === "" ? undefined : f.existingPassword, f.existingPassword === "" ? undefined : f.existingPassword,
f.password === "" ? undefined : f.password f.password === "" ? undefined : f.password
); );

View File

@@ -1,323 +0,0 @@
import {
createForm,
getValue,
required,
reset,
setValue,
SubmitHandler
} from "@modular-forms/solid";
import { NwcProfile } from "@mutinywallet/mutiny-wasm";
import {
createEffect,
createResource,
createSignal,
For,
Match,
Show,
Suspense,
Switch
} from "solid-js";
import {
AmountEditable,
BackPop,
Button,
Collapser,
ConfirmDialog,
DefaultMain,
InfoBox,
IntegratedQr,
LargeHeader,
LoadingSpinner,
MutinyPlusCta,
MutinyWalletGuard,
NavBar,
NiceP,
SettingsCard,
TextField,
VStack
} from "~/components";
import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore";
import { eify, isFreeGiftingDay } from "~/utils";
import { baseUrlAccountingForNative } from "~/utils/baseUrl";
import { createDeepSignal } from "~/utils/deepSignal";
type CreateGiftForm = {
name: string;
amount: string;
};
function SingleGift(props: { profile: NwcProfile; onDelete?: () => void }) {
const i18n = useI18n();
const [state, _actions] = useMegaStore();
const network = state.mutiny_wallet?.get_network();
const baseUrl = baseUrlAccountingForNative(network);
const sharableUrl = () => baseUrl.concat(props.profile.url_suffix || "");
const amount = () => props.profile.budget_amount?.toString() || "0";
const [confirmOpen, setConfirmOpen] = createSignal(false);
const handleConfirmDelete = async () => {
try {
await state.mutiny_wallet?.delete_nwc_profile(props.profile.index);
setConfirmOpen(false);
props.onDelete && props.onDelete();
} catch (e) {
console.error(e);
}
};
return (
<VStack>
<IntegratedQr
amountSats={amount()}
value={sharableUrl()}
kind="gift"
/>
<Button intent="red" onClick={() => setConfirmOpen(true)}>
{i18n.t("settings.gift.send_delete_button")}
</Button>
<ConfirmDialog
loading={false}
open={confirmOpen()}
onConfirm={handleConfirmDelete}
onCancel={() => setConfirmOpen(false)}
>
{i18n.t("settings.gift.send_delete_confirm")}
</ConfirmDialog>
</VStack>
);
}
function ExistingGifts() {
const [state, _actions] = useMegaStore();
const [giftNWCProfiles, { refetch }] = createResource(async () => {
try {
const profiles = await state.mutiny_wallet?.get_nwc_profiles();
if (!profiles) return [];
const filteredForGifts = profiles.filter((p) => p.tag === "Gift");
return filteredForGifts;
} catch (e) {
console.error(e);
}
});
return (
<Show when={giftNWCProfiles() && giftNWCProfiles()!.length > 0}>
<SettingsCard title={"Existing Gifts"}>
<For each={giftNWCProfiles()}>
{(profile) => (
<Collapser title={profile.name} activityLight={"on"}>
<SingleGift profile={profile} onDelete={refetch} />
</Collapser>
)}
</For>
</SettingsCard>
</Show>
);
}
export function Gift() {
const i18n = useI18n();
const [state, _actions] = useMegaStore();
const [_error, setError] = createSignal<Error>();
const [giftResult, setGiftResult] = createSignal<NwcProfile>();
const [giftForm, { Form, Field }] = createForm<CreateGiftForm>({
initialValues: {
name: "",
amount: "100000"
}
});
function resetGifting() {
reset(giftForm);
setGiftResult(undefined);
}
const handleSubmit: SubmitHandler<CreateGiftForm> = async (
f: CreateGiftForm
) => {
const nwc_name = f.name.trim();
const amount = Number(f.amount);
try {
const profile = await state.mutiny_wallet?.create_single_use_nwc(
nwc_name,
BigInt(amount)
);
setGiftResult(profile);
} catch (e) {
console.error(e);
setError(eify(e));
}
};
async function fetchProfile(gift?: NwcProfile) {
if (!gift) return;
try {
const fresh = await state.mutiny_wallet?.get_nwc_profile(
gift.index
);
return fresh;
} catch (e) {
console.error(e);
// If the gift is not found it means it's been deleted because it was redeemed
return undefined;
}
}
const [freshProfile, { refetch }] = createResource(
() => giftResult(),
fetchProfile,
{
storage: createDeepSignal
}
);
createEffect(() => {
// Should re-run after every sync
if (!state.is_syncing) {
refetch();
}
});
const lessThanMinChannelSize = () => {
return Number(getValue(giftForm, "amount")) < 100000;
};
const selfHosted = state.settings?.selfhosted === "true";
const freeDay = isFreeGiftingDay();
const canGift = state.mutiny_plus || selfHosted || freeDay;
return (
<MutinyWalletGuard>
<DefaultMain>
<BackPop default="/settings" />
<Show when={!canGift}>
<VStack>
<LargeHeader>
{i18n.t("settings.gift.send_header")}
</LargeHeader>
<NiceP>{i18n.t("settings.gift.need_plus")}</NiceP>
<MutinyPlusCta />
</VStack>
</Show>
<Show when={giftResult()}>
<VStack>
<Switch>
<Match when={!freshProfile()}>
<LargeHeader>
{i18n.t(
"settings.gift.send_header_claimed"
)}
</LargeHeader>
<NiceP>
{i18n.t("settings.gift.send_claimed")}
</NiceP>
</Match>
<Match when={true}>
<LargeHeader>
{i18n.t(
"settings.gift.send_sharable_header"
)}
</LargeHeader>
<NiceP>
{i18n.t("settings.gift.send_instructions")}
</NiceP>
<InfoBox accent="green">
{i18n.t("settings.gift.send_tip")}
</InfoBox>
<SingleGift
profile={freshProfile()!}
onDelete={resetGifting}
/>
</Match>
</Switch>
<Button intent="green" onClick={resetGifting}>
{i18n.t("settings.gift.send_another")}
</Button>
</VStack>
</Show>
<Show when={!giftResult() && canGift}>
<LargeHeader>
{i18n.t("settings.gift.send_header")}
</LargeHeader>
<Form onSubmit={handleSubmit}>
<VStack>
<NiceP>
{i18n.t("settings.gift.send_explainer")}
</NiceP>
<Field name="amount">
{(field) => (
<AmountEditable
initialAmountSats={field.value || "0"}
setAmountSats={(newAmount) =>
setValue(
giftForm,
"amount",
newAmount.toString()
)
}
/>
)}
</Field>
<Field
name="name"
validate={[
required(
i18n.t(
"settings.gift.send_name_required"
)
)
]}
>
{(field, props) => (
<TextField
{...props}
value={field.value}
error={field.error}
label={i18n.t(
"settings.gift.send_name_label"
)}
placeholder="Satoshi Nakamoto"
/>
)}
</Field>
<Show when={lessThanMinChannelSize()}>
<InfoBox accent="green">
{i18n.t("settings.gift.send_small_warning")}
</InfoBox>
</Show>
<Button
intent="blue"
type="submit"
loading={giftForm.submitting}
>
{i18n.t("settings.gift.send_cta")}
</Button>
</VStack>
</Form>
<Suspense fallback={<LoadingSpinner />}>
<ExistingGifts />
</Suspense>
</Show>
</DefaultMain>
<NavBar activeTab="settings" />
</MutinyWalletGuard>
);
}

View File

@@ -1,7 +1,13 @@
import { BackLink, DefaultMain, ImportNsecForm } from "~/components"; import {
BackLink,
DefaultMain,
ImportNsecForm,
MutinyWalletGuard
} from "~/components";
export function ImportProfileSettings() { export function ImportProfileSettings() {
return ( return (
<MutinyWalletGuard>
<DefaultMain> <DefaultMain>
<BackLink title="Back" href="/settings/nostrkeys" /> <BackLink title="Back" href="/settings/nostrkeys" />
<div class="mx-auto flex max-w-[20rem] flex-1 flex-col items-center gap-4"> <div class="mx-auto flex max-w-[20rem] flex-1 flex-col items-center gap-4">
@@ -16,5 +22,6 @@ export function ImportProfileSettings() {
<div class="flex-1" /> <div class="flex-1" />
</div> </div>
</DefaultMain> </DefaultMain>
</MutinyWalletGuard>
); );
} }

View File

@@ -6,7 +6,7 @@ import {
reset, reset,
SubmitHandler SubmitHandler
} from "@modular-forms/solid"; } from "@modular-forms/solid";
import { useNavigate } from "@solidjs/router"; import { createAsync, useNavigate } from "@solidjs/router";
import { Users } from "lucide-solid"; import { Users } from "lucide-solid";
import { import {
createMemo, createMemo,
@@ -49,7 +49,7 @@ const validateLowerCase = (value?: string) => {
// todo(paul) put this somewhere else // todo(paul) put this somewhere else
function HermesForm(props: { onSubmit: (name: string) => void }) { function HermesForm(props: { onSubmit: (name: string) => void }) {
const [state, _] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const [error, setError] = createSignal<Error>(); const [error, setError] = createSignal<Error>();
const [success, setSuccess] = createSignal(""); const [success, setSuccess] = createSignal("");
@@ -70,17 +70,16 @@ function HermesForm(props: { onSubmit: (name: string) => void }) {
setError(undefined); setError(undefined);
try { try {
const name = f.name.trim().toLowerCase(); const name = f.name.trim().toLowerCase();
const available = const available = await sw.check_available_lnurl_name(name);
await state.mutiny_wallet?.check_available_lnurl_name(name);
if (!available) { if (!available) {
throw new Error("Name already taken"); throw new Error("Name already taken");
} }
await state.mutiny_wallet?.reserve_lnurl_name(name); await sw.reserve_lnurl_name(name);
console.log("lnurl name reserved:", name); console.log("lnurl name reserved:", name);
const formattedName = `${name}@${hermesDomain}`; const formattedName = `${name}@${hermesDomain}`;
const _ = await state.mutiny_wallet?.edit_nostr_profile( const _ = await sw.edit_nostr_profile(
undefined, undefined,
undefined, undefined,
// lnurl // lnurl
@@ -144,14 +143,14 @@ function HermesForm(props: { onSubmit: (name: string) => void }) {
export function LightningAddress() { export function LightningAddress() {
const i18n = useI18n(); const i18n = useI18n();
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
const [error, setError] = createSignal<Error>(); const [error, setError] = createSignal<Error>();
const [settingLnAddress, setSettingLnAddress] = createSignal(false); const [settingLnAddress, setSettingLnAddress] = createSignal(false);
const [lnurlName] = createResource(async () => { const [lnurlName] = createResource(async () => {
try { try {
const name = await state.mutiny_wallet?.check_lnurl_name(); const name = await sw.check_lnurl_name();
return name; return name;
} catch (e) { } catch (e) {
setError(eify(e)); setError(eify(e));
@@ -172,10 +171,10 @@ export function LightningAddress() {
} }
}); });
const profileLnAddress = createMemo(() => { const profileLnAddress = createAsync(async () => {
if (lnurlName()) { if (lnurlName()) {
const profile = state.mutiny_wallet?.get_nostr_profile(); const profile = await sw.get_nostr_profile();
if (profile?.lud16) { if (profile && profile.lud16) {
return profile.lud16; return profile.lud16;
} }
} }
@@ -187,7 +186,7 @@ export function LightningAddress() {
setSettingLnAddress(true); setSettingLnAddress(true);
setError(undefined); setError(undefined);
const _ = await state.mutiny_wallet?.edit_nostr_profile( const _ = await sw.edit_nostr_profile(
undefined, undefined,
undefined, undefined,
newAddress, newAddress,

View File

@@ -89,7 +89,7 @@ export function AddFederationForm(props: {
browseOnly?: boolean; browseOnly?: boolean;
}) { }) {
const i18n = useI18n(); const i18n = useI18n();
const [state, actions] = useMegaStore(); const [_state, actions, sw] = useMegaStore();
const navigate = useNavigate(); const navigate = useNavigate();
const [error, setError] = createSignal<Error>(); const [error, setError] = createSignal<Error>();
const [success, setSuccess] = createSignal(""); const [success, setSuccess] = createSignal("");
@@ -122,8 +122,7 @@ export function AddFederationForm(props: {
const [federations] = createResource(async () => { const [federations] = createResource(async () => {
try { try {
const federations: DiscoveredFederation[] = const federations = await sw.discover_federations();
await state.mutiny_wallet?.discover_federations();
return federations; return federations;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@@ -139,8 +138,7 @@ export function AddFederationForm(props: {
try { try {
console.log("Adding federation:", inviteCode); console.log("Adding federation:", inviteCode);
setLoadingFederation(inviteCode); setLoadingFederation(inviteCode);
const newFederation = const newFederation = await sw.new_federation(inviteCode);
await state.mutiny_wallet?.new_federation(inviteCode);
console.log("New federation added:", newFederation); console.log("New federation added:", newFederation);
break; break;
} catch (e) { } catch (e) {
@@ -345,7 +343,7 @@ export function AddFederationForm(props: {
} }
function RecommendButton(props: { fed: MutinyFederationIdentity }) { function RecommendButton(props: { fed: MutinyFederationIdentity }) {
const [state] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const i18n = useI18n(); const i18n = useI18n();
const [recommendLoading, setRecommendLoading] = createSignal(false); const [recommendLoading, setRecommendLoading] = createSignal(false);
// This is just some local state that makes it feel like they've recommended it // This is just some local state that makes it feel like they've recommended it
@@ -354,8 +352,7 @@ function RecommendButton(props: { fed: MutinyFederationIdentity }) {
const [recommendedByMe, { refetch }] = createResource(async () => { const [recommendedByMe, { refetch }] = createResource(async () => {
try { try {
const hasRecommended = const hasRecommended = await sw.has_recommended_federation(
await state.mutiny_wallet?.has_recommended_federation(
props.fed.federation_id props.fed.federation_id
); );
return hasRecommended; return hasRecommended;
@@ -368,7 +365,7 @@ function RecommendButton(props: { fed: MutinyFederationIdentity }) {
async function recommendFederation() { async function recommendFederation() {
setRecommendLoading(true); setRecommendLoading(true);
try { try {
const event_id = await state.mutiny_wallet?.recommend_federation( const event_id = await sw.recommend_federation(
props.fed.invite_code props.fed.invite_code
); );
console.log("Recommended federation: ", event_id); console.log("Recommended federation: ", event_id);
@@ -383,9 +380,7 @@ function RecommendButton(props: { fed: MutinyFederationIdentity }) {
async function deleteRecommendation() { async function deleteRecommendation() {
setRecommendLoading(true); setRecommendLoading(true);
try { try {
await state.mutiny_wallet?.delete_federation_recommendation( await sw.delete_federation_recommendation(props.fed.federation_id);
props.fed.federation_id
);
setRecommended(false); setRecommended(false);
refetch(); refetch();
} catch (e) { } catch (e) {
@@ -430,14 +425,12 @@ function FederationListItem(props: {
balance?: bigint; balance?: bigint;
}) { }) {
const i18n = useI18n(); const i18n = useI18n();
const [state, actions] = useMegaStore(); const [_state, actions, sw] = useMegaStore();
async function removeFederation() { async function removeFederation() {
setConfirmLoading(true); setConfirmLoading(true);
try { try {
await state.mutiny_wallet?.remove_federation( await sw.remove_federation(props.fed.federation_id);
props.fed.federation_id
);
await actions.refreshFederations(); await actions.refreshFederations();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@@ -522,12 +515,11 @@ function FederationListItem(props: {
export function ManageFederations() { export function ManageFederations() {
const i18n = useI18n(); const i18n = useI18n();
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
const [balances, { refetch }] = createResource(async () => { const [balances, { refetch }] = createResource(async () => {
try { try {
const balances = const balances = await sw.get_federation_balances();
await state.mutiny_wallet?.get_federation_balances();
return balances?.balances || []; return balances?.balances || [];
} catch (e) { } catch (e) {
console.error(e); console.error(e);

View File

@@ -23,7 +23,7 @@ import { useMegaStore } from "~/state/megaStore";
function DeleteAccount() { function DeleteAccount() {
const i18n = useI18n(); const i18n = useI18n();
const [state, _actions] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
async function confirmDelete() { async function confirmDelete() {
setConfirmOpen(true); setConfirmOpen(true);
@@ -35,7 +35,7 @@ function DeleteAccount() {
async function deleteNostrAccount() { async function deleteNostrAccount() {
setConfirmLoading(true); setConfirmLoading(true);
try { try {
await state.mutiny_wallet?.delete_profile(); await sw.delete_profile();
// Remove the nsec from secure storage if it exists // Remove the nsec from secure storage if it exists
await SecureStoragePlugin.clear(); await SecureStoragePlugin.clear();
window.location.href = "/"; window.location.href = "/";
@@ -113,14 +113,11 @@ function UnlinkAccount() {
export function NostrKeys() { export function NostrKeys() {
const i18n = useI18n(); const i18n = useI18n();
const [state, _actions] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const npub = createAsync(async () => state.mutiny_wallet?.get_npub()); const npub = createAsync(async () => await sw.get_npub());
const nsec = createAsync(async () => state.mutiny_wallet?.export_nsec()); const nsec = createAsync(async () => await sw.export_nsec());
const profile = () => state.mutiny_wallet?.get_nostr_profile(); const profile = createAsync(async () => await sw.get_nostr_profile());
// @ts-expect-error we're checking for an extension
const windowHasNostr = window.nostr && window.nostr.getPublicKey;
const [nsecInSecureStorage] = createResource(async () => { const [nsecInSecureStorage] = createResource(async () => {
try { try {
@@ -144,7 +141,7 @@ export function NostrKeys() {
</ExternalLink> </ExternalLink>
</NiceP> </NiceP>
<Switch> <Switch>
<Match when={profile() && !profile().deleted}> <Match when={profile() && !profile()?.deleted}>
<FancyCard> <FancyCard>
<VStack> <VStack>
<div class="w-[10rem] self-center rounded bg-white p-[1rem]"> <div class="w-[10rem] self-center rounded bg-white p-[1rem]">
@@ -169,7 +166,6 @@ export function NostrKeys() {
</Show> </Show>
</VStack> </VStack>
</FancyCard> </FancyCard>
<Show when={!windowHasNostr}>
<A <A
href="/settings/importprofile" href="/settings/importprofile"
class="flex w-full items-center justify-center gap-2 rounded-xl border border-white/10 bg-neutral-900 p-2 text-m-grey-350 no-underline active:-mb-[1px] active:mt-[1px] active:opacity-70" class="flex w-full items-center justify-center gap-2 rounded-xl border border-white/10 bg-neutral-900 p-2 text-m-grey-350 no-underline active:-mb-[1px] active:mt-[1px] active:opacity-70"
@@ -177,19 +173,16 @@ export function NostrKeys() {
<Import class="w-4" /> <Import class="w-4" />
{i18n.t("settings.nostr_keys.import_profile")} {i18n.t("settings.nostr_keys.import_profile")}
</A> </A>
</Show>
<Switch> <Switch>
<Match when={nsecInSecureStorage()}> <Match when={nsecInSecureStorage()}>
<UnlinkAccount /> <UnlinkAccount />
</Match> </Match>
<Match <Match when={!nsecInSecureStorage()}>
when={!nsecInSecureStorage() && !windowHasNostr}
>
<DeleteAccount /> <DeleteAccount />
</Match> </Match>
</Switch> </Switch>
</Match> </Match>
<Match when={profile() && profile().deleted}> <Match when={profile() && profile()?.deleted}>
<A <A
href="/settings/importprofile" href="/settings/importprofile"
class="flex w-full items-center justify-center gap-2 rounded-xl border border-white/10 bg-neutral-900 p-2 text-m-grey-350 no-underline active:-mb-[1px] active:mt-[1px] active:opacity-70" class="flex w-full items-center justify-center gap-2 rounded-xl border border-white/10 bg-neutral-900 p-2 text-m-grey-350 no-underline active:-mb-[1px] active:mt-[1px] active:opacity-70"

View File

@@ -56,7 +56,7 @@ function Perks(props: { alreadySubbed?: boolean }) {
function PlusCTA() { function PlusCTA() {
const i18n = useI18n(); const i18n = useI18n();
const [state, actions] = useMegaStore(); const [state, actions, sw] = useMegaStore();
const [subbing, setSubbing] = createSignal(false); const [subbing, setSubbing] = createSignal(false);
const [confirmOpen, setConfirmOpen] = createSignal(false); const [confirmOpen, setConfirmOpen] = createSignal(false);
@@ -65,7 +65,7 @@ function PlusCTA() {
const [planDetails] = createResource(async () => { const [planDetails] = createResource(async () => {
try { try {
const plans = await state.mutiny_wallet?.get_subscription_plans(); const plans = await sw.get_subscription_plans();
console.log("plans:", plans); console.log("plans:", plans);
if (!plans) return undefined; if (!plans) return undefined;
return plans[0]; return plans[0];
@@ -82,17 +82,12 @@ function PlusCTA() {
if (planDetails()?.id === undefined || planDetails()?.id === null) if (planDetails()?.id === undefined || planDetails()?.id === null)
throw new Error(i18n.t("settings.plus.error_no_plan")); throw new Error(i18n.t("settings.plus.error_no_plan"));
const invoice = await state.mutiny_wallet?.subscribe_to_plan( const invoice = await sw.subscribe_to_plan(planDetails()!.id);
planDetails().id
);
if (!invoice?.bolt11) if (!invoice?.bolt11)
throw new Error(i18n.t("settings.plus.error_failure")); throw new Error(i18n.t("settings.plus.error_failure"));
await state.mutiny_wallet?.pay_subscription_invoice( await sw.pay_subscription_invoice(invoice?.bolt11, true);
invoice?.bolt11,
true
);
await vibrateSuccess(); await vibrateSuccess();
@@ -112,7 +107,7 @@ function PlusCTA() {
return ( return (
(state.balance?.lightning || 0n) + (state.balance?.lightning || 0n) +
(state.balance?.federation || 0n) > (state.balance?.federation || 0n) >
planDetails().amount_sat planDetails()!.amount_sat
); );
}; };
@@ -126,7 +121,7 @@ function PlusCTA() {
</strong>{" "} </strong>{" "}
{i18n.t("settings.plus.sats_per_month", { {i18n.t("settings.plus.sats_per_month", {
amount: Number( amount: Number(
planDetails().amount_sat planDetails()!.amount_sat
).toLocaleString() ).toLocaleString()
})} })}
</NiceP> </NiceP>
@@ -137,7 +132,7 @@ function PlusCTA() {
<TinyText> <TinyText>
{i18n.t("settings.plus.lightning_balance", { {i18n.t("settings.plus.lightning_balance", {
amount: Number( amount: Number(
planDetails().amount_sat planDetails()!.amount_sat
).toLocaleString() ).toLocaleString()
})} })}
</TinyText> </TinyText>

View File

@@ -9,7 +9,6 @@ import {
SubmitHandler, SubmitHandler,
validate validate
} from "@modular-forms/solid"; } from "@modular-forms/solid";
import { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { LucideClipboard } from "lucide-solid"; import { LucideClipboard } from "lucide-solid";
import { createSignal, For, Show, splitProps } from "solid-js"; import { createSignal, For, Show, splitProps } from "solid-js";
@@ -79,7 +78,7 @@ function SeedTextField(props: TextFieldProps) {
export function TwelveWordsEntry() { export function TwelveWordsEntry() {
const i18n = useI18n(); const i18n = useI18n();
const [state, actions] = useMegaStore(); const [state, actions, sw] = useMegaStore();
const [error, setError] = createSignal<Error>(); const [error, setError] = createSignal<Error>();
const [mnemnoic, setMnemonic] = createSignal<string>(); const [mnemnoic, setMnemonic] = createSignal<string>();
@@ -129,17 +128,16 @@ export function TwelveWordsEntry() {
try { try {
setConfirmLoading(true); setConfirmLoading(true);
if (state.mutiny_wallet) { if (state.load_stage === "done") {
console.log("Mutiny wallet loaded, stopping"); console.log("Mutiny wallet loaded, stopping");
try { try {
await state.mutiny_wallet.stop(); await sw.stop();
actions.dropMutinyWallet();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
} }
await MutinyWallet.restore_mnemonic(mnemnoic() || "", ""); await sw.restore_mnemonic(mnemnoic() || "", "");
actions.setHasBackedUp(); actions.setHasBackedUp();

View File

@@ -7,7 +7,6 @@ export * from "./Currency";
export * from "./Language"; export * from "./Language";
export * from "./EmergencyKit"; export * from "./EmergencyKit";
export * from "./Encrypt"; export * from "./Encrypt";
export * from "./Gift";
export * from "./Plus"; export * from "./Plus";
export * from "./Restore"; export * from "./Restore";
export * from "./Servers"; export * from "./Servers";

View File

@@ -12,7 +12,7 @@ import { useMegaStore } from "~/state/megaStore";
import { DEFAULT_NOSTR_NAME } from "~/utils"; import { DEFAULT_NOSTR_NAME } from "~/utils";
export function NewProfile() { export function NewProfile() {
const [state, _actions] = useMegaStore(); const [_state, _actions, sw] = useMegaStore();
const i18n = useI18n(); const i18n = useI18n();
const [creating, setCreating] = createSignal(false); const [creating, setCreating] = createSignal(false);
@@ -23,7 +23,7 @@ export function NewProfile() {
async function handleSkip() { async function handleSkip() {
setSkipping(true); setSkipping(true);
// set up an empty profile so we at least have some kind0 event // set up an empty profile so we at least have some kind0 event
const profile = await state.mutiny_wallet?.setup_new_profile( const profile = await sw.setup_new_profile(
DEFAULT_NOSTR_NAME, DEFAULT_NOSTR_NAME,
undefined, undefined,
undefined, undefined,
@@ -38,7 +38,7 @@ export function NewProfile() {
async function createProfile(p: EditableProfile) { async function createProfile(p: EditableProfile) {
setCreating(true); setCreating(true);
try { try {
const profile = await state.mutiny_wallet?.setup_new_profile( const profile = await sw.setup_new_profile(
p.nym ? p.nym : DEFAULT_NOSTR_NAME, p.nym ? p.nym : DEFAULT_NOSTR_NAME,
p.imageUrl ? p.imageUrl : undefined, p.imageUrl ? p.imageUrl : undefined,
undefined, undefined,

View File

@@ -39,9 +39,8 @@ export function Setup() {
return ( return (
<DefaultMain> <DefaultMain>
{/* <LargeHeader>Setup</LargeHeader> */}
<div class="flex flex-1 flex-col items-center justify-between gap-4"> <div class="flex flex-1 flex-col items-center justify-between gap-4">
<div class="flex-[2]" /> <div class="flex-1" />
<div class="flex flex-col items-center gap-4"> <div class="flex flex-col items-center gap-4">
<img <img
id="mutiny-logo" id="mutiny-logo"

View File

@@ -1,12 +1,8 @@
/* @refresh reload */
// Inspired by https://github.com/solidjs/solid-realworld/blob/main/src/store/index.js // Inspired by https://github.com/solidjs/solid-realworld/blob/main/src/store/index.js
import { import { MutinyBalance, TagItem } from "@mutinywallet/mutiny-wasm";
MutinyBalance,
MutinyWallet,
TagItem
} from "@mutinywallet/mutiny-wasm";
import { useNavigate, useSearchParams } from "@solidjs/router"; import { useNavigate, useSearchParams } from "@solidjs/router";
import { SecureStoragePlugin } from "capacitor-secure-storage-plugin";
import { Remote } from "comlink";
import { import {
createContext, createContext,
onCleanup, onCleanup,
@@ -20,10 +16,10 @@ import { checkBrowserCompatibility } from "~/logic/browserCompatibility";
import { import {
doubleInitDefense, doubleInitDefense,
getSettings, getSettings,
initializeWasm,
MutinyWalletSettingStrings, MutinyWalletSettingStrings,
setSettings, Network,
setupMutinyWallet setSettings
// setupMutinyWallet
} from "~/logic/mutinyWalletSetup"; } from "~/logic/mutinyWalletSetup";
import { ParsedParams, toParsedParams } from "~/logic/waila"; import { ParsedParams, toParsedParams } from "~/logic/waila";
import { MutinyFederationIdentity } from "~/routes/settings"; import { MutinyFederationIdentity } from "~/routes/settings";
@@ -35,8 +31,6 @@ import {
USD_OPTION USD_OPTION
} from "~/utils"; } from "~/utils";
const MegaStoreContext = createContext<MegaStore>();
type LoadStage = type LoadStage =
| "fresh" | "fresh"
| "checking_double_init" | "checking_double_init"
@@ -45,69 +39,22 @@ type LoadStage =
| "setup" | "setup"
| "done"; | "done";
type MegaStore = [ export type WalletWorker = Remote<typeof import("../workers/walletWorker")>;
{
mutiny_wallet?: MutinyWallet;
deleting: boolean;
scan_result?: ParsedParams;
balance?: MutinyBalance;
is_syncing?: boolean;
last_sync?: number;
price_sync_backoff_multiple?: number;
price: number;
fiat: Currency;
lang?: string;
has_backed_up: boolean;
wallet_loading: boolean;
setup_error?: Error;
is_pwa: boolean;
existing_tab_detected: boolean;
subscription_timestamp?: number;
readonly mutiny_plus: boolean;
needs_password: boolean;
password?: string;
load_stage: LoadStage;
settings?: MutinyWalletSettingStrings;
safe_mode?: boolean;
preferredInvoiceType: "unified" | "lightning" | "onchain";
testflightPromptDismissed: boolean;
should_zap_hodl: boolean;
federations?: MutinyFederationIdentity[];
balanceView: "sats" | "fiat" | "hidden";
},
{
setup(password?: string): Promise<void>;
deleteMutinyWallet(): Promise<void>;
setScanResult(scan_result: ParsedParams | undefined): void;
sync(): Promise<void>;
setHasBackedUp(): void;
listTags(): Promise<TagItem[]>;
checkForSubscription(justPaid?: boolean): Promise<void>;
fetchPrice(fiat: Currency): Promise<number>;
saveFiat(fiat: Currency): void;
saveLanguage(lang: string): void;
setPreferredInvoiceType(
type: "unified" | "lightning" | "onchain"
): void;
handleIncomingString(
str: string,
onError: (e: Error) => void,
onSuccess: (value: ParsedParams) => void
): void;
setTestFlightPromptDismissed(): void;
toggleHodl(): void;
dropMutinyWallet(): void;
refreshFederations(): Promise<void>;
cycleBalanceView(): void;
}
];
export const Provider: ParentComponent = (props) => { export const makeMegaStoreContext = () => {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const navigate = useNavigate(); const navigate = useNavigate();
// Not actually a shared worker, but it's the same code
const sw = new ComlinkWorker<typeof import("../workers/walletWorker")>(
new URL("../workers/walletWorker", import.meta.url),
{
type: "module"
}
);
const [state, setState] = createStore({ const [state, setState] = createStore({
mutiny_wallet: undefined as MutinyWallet | undefined, network: undefined as Network | undefined,
deleting: false, deleting: false,
scan_result: undefined as ParsedParams | undefined, scan_result: undefined as ParsedParams | undefined,
price: 0, price: 0,
@@ -115,7 +62,7 @@ export const Provider: ParentComponent = (props) => {
? (JSON.parse(localStorage.getItem("fiat_currency")!) as Currency) ? (JSON.parse(localStorage.getItem("fiat_currency")!) as Currency)
: USD_OPTION, : USD_OPTION,
has_backed_up: localStorage.getItem("has_backed_up") === "true", has_backed_up: localStorage.getItem("has_backed_up") === "true",
balance: undefined as MutinyBalance | undefined, balance: undefined as Partial<MutinyBalance> | undefined,
last_sync: undefined as number | undefined, last_sync: undefined as number | undefined,
price_sync_backoff_multiple: 1, price_sync_backoff_multiple: 1,
is_syncing: false, is_syncing: false,
@@ -146,7 +93,7 @@ export const Provider: ParentComponent = (props) => {
const actions = { const actions = {
async checkForSubscription(justPaid?: boolean): Promise<void> { async checkForSubscription(justPaid?: boolean): Promise<void> {
try { try {
const timestamp = await state.mutiny_wallet?.check_subscribed(); const timestamp = await sw.check_subscribed();
// Check that timestamp is a number // Check that timestamp is a number
if (timestamp && !isNaN(Number(timestamp))) { if (timestamp && !isNaN(Number(timestamp))) {
@@ -160,21 +107,27 @@ export const Provider: ParentComponent = (props) => {
console.error(e); console.error(e);
} }
}, },
async preSetup(): Promise<void> { async preSetup(): Promise<boolean> {
try { try {
// If we're already in an error state there should be no reason to continue // If we're already in an error state there should be no reason to continue
if (state.setup_error) { if (state.setup_error) {
throw state.setup_error; throw state.setup_error;
} }
// If there's already a mutiny wallet in state abort! setState({ wallet_loading: true });
if (state.mutiny_wallet) {
setState({ await this.checkForExistingTab();
setup_error: new Error(
"Existing Mutiny Wallet already running, aborting setup" if (state.existing_tab_detected) {
) return false;
}); }
return;
console.log("checking for browser compatibility");
try {
await checkBrowserCompatibility();
} catch (e) {
setState({ setup_error: eify(e) });
return false;
} }
setState({ setState({
@@ -183,11 +136,22 @@ export const Provider: ParentComponent = (props) => {
}); });
await doubleInitDefense(); await doubleInitDefense();
setState({ load_stage: "downloading" }); setState({ load_stage: "downloading" });
await initializeWasm(); await sw.initializeWasm();
setState({ load_stage: "checking_for_existing_wallet" });
const existing = await sw.has_node_manager();
if (!existing && !searchParams.skip_setup) {
navigate("/setup");
return false;
}
return true;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
setState({ setup_error: eify(e) }); setState({ setup_error: eify(e) });
return false;
} }
}, },
async setup(password?: string): Promise<void> { async setup(password?: string): Promise<void> {
@@ -224,24 +188,29 @@ export const Provider: ParentComponent = (props) => {
} }
}, 1000); }, 1000);
const mutinyWallet = await setupMutinyWallet( let nsec;
// get nsec from secure storage
try {
const value = await SecureStoragePlugin.get({
key: "nsec"
});
nsec = value.value;
} catch (e) {
console.log("No nsec stored");
}
const success = await sw.setupMutinyWallet(
settings, settings,
password, password,
state.safe_mode, state.safe_mode,
state.should_zap_hodl state.should_zap_hodl,
nsec
); );
// Done with the timeout shenanigans
clearInterval(interval); clearInterval(interval);
// I've never managed to trigger this but it's just some extra safety I guess if (!success) {
if (!mutinyWallet) { throw new Error("Failed to initialize mutiny wallet");
setState({
setup_error: new Error(
"Failed to initialize Mutiny Wallet"
)
});
return;
} }
// Give other components access to settings via the store // Give other components access to settings via the store
@@ -250,21 +219,32 @@ export const Provider: ParentComponent = (props) => {
// If we get this far then we don't need the password anymore // If we get this far then we don't need the password anymore
setState({ needs_password: false }); setState({ needs_password: false });
// Get network
const network = await sw.get_network();
// Get balance // Get balance
const balance = await mutinyWallet.get_balance(); const balance = await sw.get_balance();
// Get federations // Get federations
const federations = const federations =
(await mutinyWallet.list_federations()) as MutinyFederationIdentity[]; (await sw.list_federations()) as MutinyFederationIdentity[];
setState({ setState({
mutiny_wallet: mutinyWallet,
wallet_loading: false, wallet_loading: false,
load_stage: "done", load_stage: "done",
balance, balance,
federations federations,
network: network as Network
}); });
// Timestamp our initialization for double init defense
sessionStorage.setItem(
"MUTINY_WALLET_INITIALIZED",
Date.now().toString()
);
console.log("Wallet initialized");
await actions.postSetup(); await actions.postSetup();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@@ -277,7 +257,7 @@ export const Provider: ParentComponent = (props) => {
} }
}, },
async postSetup(): Promise<void> { async postSetup(): Promise<void> {
if (!state.mutiny_wallet) { if (!sw) {
console.error( console.error(
"Unable to run post setup, no mutiny_wallet is set" "Unable to run post setup, no mutiny_wallet is set"
); );
@@ -286,8 +266,7 @@ export const Provider: ParentComponent = (props) => {
// Check if we're subscribed and update the timestamp // Check if we're subscribed and update the timestamp
try { try {
const timestamp = await state.mutiny_wallet.check_subscribed(); const timestamp = await sw.check_subscribed();
// Check that timestamp is a number // Check that timestamp is a number
if (timestamp && !isNaN(Number(timestamp))) { if (timestamp && !isNaN(Number(timestamp))) {
setState({ subscription_timestamp: Number(timestamp) }); setState({ subscription_timestamp: Number(timestamp) });
@@ -319,9 +298,9 @@ export const Provider: ParentComponent = (props) => {
...prevState, ...prevState,
deleting: true deleting: true
})); }));
if (state.mutiny_wallet) { if (sw) {
await state.mutiny_wallet?.stop(); await sw.stop();
await state.mutiny_wallet?.delete_all(); await sw.delete_all();
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@@ -346,9 +325,9 @@ export const Provider: ParentComponent = (props) => {
}, },
async sync(): Promise<void> { async sync(): Promise<void> {
try { try {
if (state.mutiny_wallet && !state.is_syncing) { if (sw && !state.is_syncing) {
setState({ is_syncing: true }); setState({ is_syncing: true });
const newBalance = await state.mutiny_wallet?.get_balance(); const newBalance = await sw.get_balance();
try { try {
setState({ setState({
balance: newBalance, balance: newBalance,
@@ -376,7 +355,7 @@ export const Provider: ParentComponent = (props) => {
return price; return price;
} else { } else {
try { try {
price = await state.mutiny_wallet?.get_bitcoin_price( price = await sw.get_bitcoin_price(
fiat.value.toLowerCase() || "usd" fiat.value.toLowerCase() || "usd"
); );
return price; return price;
@@ -395,7 +374,7 @@ export const Provider: ParentComponent = (props) => {
}, },
async listTags(): Promise<TagItem[] | undefined> { async listTags(): Promise<TagItem[] | undefined> {
try { try {
return state.mutiny_wallet?.get_tag_items(); return sw.get_tag_items();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return []; return [];
@@ -416,18 +395,13 @@ export const Provider: ParentComponent = (props) => {
setPreferredInvoiceType(type: "unified" | "lightning" | "onchain") { setPreferredInvoiceType(type: "unified" | "lightning" | "onchain") {
setState({ preferredInvoiceType: type }); setState({ preferredInvoiceType: type });
}, },
handleIncomingString( async handleIncomingString(
str: string, str: string,
onError: (e: Error) => void, onError: (e: Error) => void,
onSuccess: (value: ParsedParams) => void onSuccess: (value: ParsedParams) => void
): void { ): Promise<void> {
try { try {
const url = new URL(str); const url = new URL(str);
if (url && url.pathname.startsWith("/gift")) {
navigate(url.pathname + url.search);
return;
}
if (url && url.pathname.startsWith("/settings/plus")) { if (url && url.pathname.startsWith("/settings/plus")) {
navigate(url.pathname + url.search); navigate(url.pathname + url.search);
return; return;
@@ -436,9 +410,10 @@ export const Provider: ParentComponent = (props) => {
// If it's not a URL, we'll just continue with normal parsing // If it's not a URL, we'll just continue with normal parsing
} }
const network = state.mutiny_wallet?.get_network() || "signet"; const network = state.network || "signet";
const result = toParsedParams(str || "", network); const result = await toParsedParams(str || "", network, sw);
if (!result.ok) {
if (!result || !result.ok) {
if (onError) { if (onError) {
onError(result.error); onError(result.error);
} }
@@ -489,12 +464,8 @@ export const Provider: ParentComponent = (props) => {
localStorage.setItem("should_zap_hodl", should_zap_hodl.toString()); localStorage.setItem("should_zap_hodl", should_zap_hodl.toString());
setState({ should_zap_hodl }); setState({ should_zap_hodl });
}, },
dropMutinyWallet() {
setState({ mutiny_wallet: undefined });
},
async refreshFederations() { async refreshFederations() {
const federations = const federations = await sw.list_federations();
(await state.mutiny_wallet?.list_federations()) as MutinyFederationIdentity[];
setState({ federations }); setState({ federations });
}, },
cycleBalanceView() { cycleBalanceView() {
@@ -508,23 +479,8 @@ export const Provider: ParentComponent = (props) => {
localStorage.setItem("balanceView", "sats"); localStorage.setItem("balanceView", "sats");
setState({ balanceView: "sats" }); setState({ balanceView: "sats" });
} }
} },
}; async checkForExistingTab() {
onCleanup(() => {
console.warn("Parent Component is being unmounted!!!");
state.mutiny_wallet
?.stop()
.then(() => {
console.warn("Successfully stopped mutiny wallet");
sessionStorage.removeItem("MUTINY_WALLET_INITIALIZED");
})
.catch((e) => {
console.error("Error stopping mutiny wallet", e);
});
});
async function checkForExistingTab() {
// Set up existing tab detector // Set up existing tab detector
const channel = new BroadcastChannel("tab-detector"); const channel = new BroadcastChannel("tab-detector");
@@ -551,67 +507,43 @@ export const Provider: ParentComponent = (props) => {
} }
}; };
} }
};
const [params, _] = useSearchParams(); return [state, actions, sw] as const;
};
type MegaStoreContextType = ReturnType<typeof makeMegaStoreContext>;
export const MegaStoreContext = createContext<MegaStoreContextType>();
export const useMegaStore = () => useContext(MegaStoreContext)!;
export const Provider: ParentComponent = (props) => {
const [state, actions, sw] = makeMegaStoreContext();
onMount(async () => { onMount(async () => {
await checkForExistingTab(); const shouldSetup = await actions.preSetup();
if (state.existing_tab_detected) { console.log("Should run setup?", shouldSetup);
return;
}
console.log("checking for browser compatibility");
try {
await checkBrowserCompatibility();
} catch (e) {
setState({ setup_error: eify(e) });
return;
}
await actions.preSetup();
setState({ load_stage: "checking_for_existing_wallet" });
const existing = await MutinyWallet.has_node_manager();
if (!existing && !params.skip_setup) {
navigate("/setup");
return;
}
// Setup catches its own errors and sets state itself
console.log("running setup node manager");
if ( if (
!state.mutiny_wallet && shouldSetup &&
sw &&
!state.existing_tab_detected &&
!state.deleting && !state.deleting &&
!state.setup_error && !state.setup_error
!state.existing_tab_detected
) { ) {
await actions.setup(); await actions.setup();
} else {
console.warn("setup aborted");
return;
} }
// After we have the mutiny wallet we still need to check for subscription and sync nostr
// await actions.postSetup();
console.log("node manager setup done");
}); });
const store = [state, actions] as MegaStore; onCleanup(async () => {
console.warn("Parent Component is being unmounted!!!");
await sw.stop();
console.warn("Successfully stopped mutiny wallet");
sessionStorage.removeItem("MUTINY_WALLET_INITIALIZED");
});
return ( return (
<MegaStoreContext.Provider value={store}> <MegaStoreContext.Provider value={[state, actions, sw]}>
{props.children} {props.children}
</MegaStoreContext.Provider> </MegaStoreContext.Provider>
); );
}; };
export function useMegaStore() {
// This is a trick to narrow the typescript types: https://docs.solidjs.com/references/api-reference/component-apis/createContext
const context = useContext(MegaStoreContext);
if (!context) {
throw new Error("useMegaStore: cannot find a MegaStoreContext");
}
return context;
}

View File

@@ -1,4 +1,4 @@
import { MutinyWallet } from "@mutinywallet/mutiny-wasm"; import { WalletWorker } from "~/state/megaStore";
import { Currency } from "./currencies"; import { Currency } from "./currencies";
@@ -9,18 +9,17 @@ import { Currency } from "./currencies";
* @param {Currency} fiat - Takes {@link Currency} object options to determine how to format the amount input * @param {Currency} fiat - Takes {@link Currency} object options to determine how to format the amount input
*/ */
export function satsToFiat( export async function satsToFiat(
amount: number | undefined, amount: number | undefined,
price: number, price: number,
fiat: Currency fiat: Currency,
): string { sw: WalletWorker
): Promise<string> {
if (typeof amount !== "number" || isNaN(amount)) { if (typeof amount !== "number" || isNaN(amount)) {
return ""; return "";
} }
try { try {
const btc = MutinyWallet.convert_sats_to_btc( const btc = await sw.convert_sats_to_btc(BigInt(Math.floor(amount)));
BigInt(Math.floor(amount))
);
const fiatPrice = btc * price; const fiatPrice = btc * price;
const roundedFiat = Math.round(fiatPrice); const roundedFiat = Math.round(fiatPrice);
if ( if (
@@ -45,18 +44,17 @@ export function satsToFiat(
* @param {Currency} fiat - Takes {@link Currency} object options to determine how to format the amount input * @param {Currency} fiat - Takes {@link Currency} object options to determine how to format the amount input
*/ */
export function satsToFormattedFiat( export async function satsToFormattedFiat(
amount: number | undefined, amount: number | undefined,
price: number, price: number,
fiat: Currency fiat: Currency,
): string { sw: WalletWorker
): Promise<string> {
if (typeof amount !== "number" || isNaN(amount)) { if (typeof amount !== "number" || isNaN(amount)) {
return ""; return "";
} }
try { try {
const btc = MutinyWallet.convert_sats_to_btc( const btc = await sw.convert_sats_to_btc(BigInt(Math.floor(amount)));
BigInt(Math.floor(amount))
);
const fiatPrice = btc * price; const fiatPrice = btc * price;
//Handles currencies not supported by .toLocaleString() like BTC //Handles currencies not supported by .toLocaleString() like BTC
//Returns a string with a currency symbol and a number with decimals equal to the maxFractionalDigits //Returns a string with a currency symbol and a number with decimals equal to the maxFractionalDigits
@@ -82,17 +80,18 @@ export function satsToFormattedFiat(
} }
} }
export function fiatToSats( export async function fiatToSats(
amount: number | undefined, amount: number | undefined,
price: number, price: number,
formatted: boolean formatted: boolean,
): string { sw: WalletWorker
): Promise<string> {
if (typeof amount !== "number" || isNaN(amount)) { if (typeof amount !== "number" || isNaN(amount)) {
return ""; return "";
} }
try { try {
const btc = price / amount; const btc = price / amount;
const sats = MutinyWallet.convert_btc_to_sats(btc); const sats = await sw.convert_btc_to_sats(btc);
if (formatted) { if (formatted) {
return parseInt(sats.toString()).toLocaleString(); return parseInt(sats.toString()).toLocaleString();
} else { } else {

View File

@@ -1,7 +1,6 @@
import { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { ResourceFetcher } from "solid-js"; import { ResourceFetcher } from "solid-js";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore, WalletWorker } from "~/state/megaStore";
import { import {
getPrimalImageUrl, getPrimalImageUrl,
hexpubFromNpub, hexpubFromNpub,
@@ -71,7 +70,7 @@ function getZapKind(event: NostrEvent): "public" | "private" | "anonymous" {
async function simpleZapFromEvent( async function simpleZapFromEvent(
event: NostrEvent, event: NostrEvent,
wallet: MutinyWallet sw: WalletWorker
): Promise<SimpleZapItem | undefined> { ): Promise<SimpleZapItem | undefined> {
if (event.kind === 9735 && event.tags?.length > 0) { if (event.kind === 9735 && event.tags?.length > 0) {
const to = findByTag(event.tags, "p") || ""; const to = findByTag(event.tags, "p") || "";
@@ -94,8 +93,8 @@ async function simpleZapFromEvent(
if (bolt11) { if (bolt11) {
try { try {
// We hardcode the "bitcoin" network because we don't have a good source of mutinynet zaps // We hardcode the "bitcoin" network because we don't have a good source of mutinynet zaps
const decoded = await wallet.decode_invoice(bolt11, "bitcoin"); const decoded = await sw.decode_invoice(bolt11, "bitcoin");
if (decoded.amount_sats) { if (decoded?.amount_sats) {
amount = decoded.amount_sats; amount = decoded.amount_sats;
} else { } else {
console.log("no amount in decoded invoice"); console.log("no amount in decoded invoice");
@@ -181,7 +180,7 @@ export const fetchZaps: ResourceFetcher<
until?: number; until?: number;
} }
> = async (npub, info) => { > = async (npub, info) => {
const [state, _actions] = useMegaStore(); const [state, _actions, sw] = useMegaStore();
try { try {
console.log("fetching zaps for:", npub); console.log("fetching zaps for:", npub);
@@ -197,16 +196,18 @@ export const fetchZaps: ResourceFetcher<
// Only have to ask the relays for follows one time // Only have to ask the relays for follows one time
if (follows.length === 0) { if (follows.length === 0) {
const contacts = await state.mutiny_wallet?.get_contacts_sorted(); const contacts = await sw.get_contacts_sorted();
const hexpubs = []; const hexpubs = [];
if (contacts) {
for (const contact of contacts) { for (const contact of contacts) {
if (contact.npub) { if (contact.npub) {
const hexpub = await hexpubFromNpub(contact.npub); const hexpub = await hexpubFromNpub(sw, contact.npub);
if (hexpub) { if (hexpub) {
hexpubs.push(hexpub); hexpubs.push(hexpub);
} }
} }
} }
}
follows = hexpubs; follows = hexpubs;
} }
@@ -239,10 +240,7 @@ export const fetchZaps: ResourceFetcher<
if (object.kind === 9735) { if (object.kind === 9735) {
// console.log("got a 9735 object", object); // console.log("got a 9735 object", object);
try { try {
const event = await simpleZapFromEvent( const event = await simpleZapFromEvent(object, sw);
object,
state.mutiny_wallet!
);
// Only add it if it's a valid zap (not undefined) // Only add it if it's a valid zap (not undefined)
if (event) { if (event) {

View File

@@ -1,4 +1,4 @@
import { MutinyWallet } from "@mutinywallet/mutiny-wasm"; import { WalletWorker } from "~/state/megaStore";
export type NostrTag = string[]; export type NostrTag = string[];
export declare enum NostrKind { export declare enum NostrKind {
@@ -45,6 +45,7 @@ export declare enum NostrKind {
} }
export async function hexpubFromNpub( export async function hexpubFromNpub(
sw: WalletWorker,
npub?: string npub?: string
): Promise<string | undefined> { ): Promise<string | undefined> {
if (!npub) { if (!npub) {
@@ -55,7 +56,7 @@ export async function hexpubFromNpub(
} }
try { try {
const hexpub = await MutinyWallet.npub_to_hexpub(npub); const hexpub = await sw?.npub_to_hexpub(npub);
return hexpub; return hexpub;
} catch (err) { } catch (err) {
console.error(err); console.error(err);

1
src/vite-env.d.ts vendored
View File

@@ -1 +1,2 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
/// <reference types="vite-plugin-comlink/client" />

1574
src/workers/walletWorker.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
import child from "node:child_process"; import child from "node:child_process";
import path from "node:path"; import path from "node:path";
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import { comlink } from "vite-plugin-comlink";
import { VitePWA, VitePWAOptions } from "vite-plugin-pwa"; import { VitePWA, VitePWAOptions } from "vite-plugin-pwa";
import solid from "vite-plugin-solid"; import solid from "vite-plugin-solid";
import wasm from "vite-plugin-wasm"; import wasm from "vite-plugin-wasm";
@@ -40,7 +41,11 @@ export default defineConfig({
allow: [".."] allow: [".."]
} }
}, },
plugins: [wasm(), solid(), VitePWA(pwaOptions)], plugins: [comlink(), wasm(), solid(), VitePWA(pwaOptions)],
worker: {
plugins: () => [comlink(), wasm()],
format: "es"
},
define: { define: {
"import.meta.env.__COMMIT_HASH__": JSON.stringify(commitHash), "import.meta.env.__COMMIT_HASH__": JSON.stringify(commitHash),
"import.meta.env.__RELEASE_VERSION__": JSON.stringify( "import.meta.env.__RELEASE_VERSION__": JSON.stringify(
@@ -69,6 +74,9 @@ export default defineConfig({
"@capacitor/toast" "@capacitor/toast"
], ],
// This is necessary because otherwise `vite dev` can't find the wasm // This is necessary because otherwise `vite dev` can't find the wasm
exclude: ["@mutinywallet/mutiny-wasm"] exclude: ["@mutinywallet/mutiny-wasm"],
esbuildOptions: {
target: "esnext"
}
} }
}); });