diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 000000000..48354a3df
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1,101 @@
+# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
+
+# Built application files
+*.apk
+*.aar
+*.ap_
+*.aab
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+# Uncomment the following line in case you need and you don't have the release build type files in your app
+# release/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+# Android Studio 3 in .gitignore file.
+.idea/caches
+.idea/modules.xml
+# Comment next line if keeping position of elements in Navigation Editor is relevant for you
+.idea/navEditor.xml
+
+# Keystore files
+# Uncomment the following lines if you do not want to check your keystore files in.
+#*.jks
+#*.keystore
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+.cxx/
+
+# Google Services (e.g. APIs or Firebase)
+# google-services.json
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+# Version control
+vcs.xml
+
+# lint
+lint/intermediates/
+lint/generated/
+lint/outputs/
+lint/tmp/
+# lint/reports/
+
+# Android Profiling
+*.hprof
+
+# Cordova plugins for Capacitor
+capacitor-cordova-android-plugins
+
+# Copied web assets
+app/src/main/assets/public
+
+# Generated Config files
+app/src/main/assets/capacitor.config.json
+app/src/main/assets/capacitor.plugins.json
+app/src/main/res/xml/config.xml
diff --git a/android/app/.gitignore b/android/app/.gitignore
new file mode 100644
index 000000000..043df802a
--- /dev/null
+++ b/android/app/.gitignore
@@ -0,0 +1,2 @@
+/build/*
+!/build/.npmkeep
diff --git a/android/app/build.gradle b/android/app/build.gradle
new file mode 100644
index 000000000..6936b9be2
--- /dev/null
+++ b/android/app/build.gradle
@@ -0,0 +1,59 @@
+apply plugin: 'com.android.application'
+
+android {
+ namespace = "com.peanut.app"
+ compileSdk = rootProject.ext.compileSdkVersion
+ defaultConfig {
+ applicationId "com.peanut.app"
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ aaptOptions {
+ // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
+ // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
+ ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
+ }
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+repositories {
+ flatDir{
+ dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
+ }
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
+ implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
+ implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
+ implementation project(':capacitor-android')
+
+ // credential manager provider — required for capacitor-webauthn passkeys
+ implementation "androidx.credentials:credentials:1.5.0"
+ implementation "androidx.credentials:credentials-play-services-auth:1.5.0"
+
+ testImplementation "junit:junit:$junitVersion"
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
+ implementation project(':capacitor-cordova-android-plugins')
+}
+
+apply from: 'capacitor.build.gradle'
+
+try {
+ def servicesJSON = file('google-services.json')
+ if (servicesJSON.text) {
+ apply plugin: 'com.google.gms.google-services'
+ }
+} catch(Exception e) {
+ logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
+}
diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle
new file mode 100644
index 000000000..ddda33877
--- /dev/null
+++ b/android/app/capacitor.build.gradle
@@ -0,0 +1,19 @@
+// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
+
+android {
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_21
+ targetCompatibility JavaVersion.VERSION_21
+ }
+}
+
+apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
+dependencies {
+ implementation project(':capacitor-webauthn')
+
+}
+
+
+if (hasProperty('postBuildExtras')) {
+ postBuildExtras()
+}
diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro
new file mode 100644
index 000000000..f1b424510
--- /dev/null
+++ b/android/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java b/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java
new file mode 100644
index 000000000..570457a31
--- /dev/null
+++ b/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.getcapacitor.myapp;
+
+import static org.junit.Assert.*;
+
+import android.content.Context;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ assertEquals("com.peanut.app", appContext.getPackageName());
+ }
+}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..90c21fa76
--- /dev/null
+++ b/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/assets/capacitor.config.json b/android/app/src/main/assets/capacitor.config.json
new file mode 100644
index 000000000..a08e4541d
--- /dev/null
+++ b/android/app/src/main/assets/capacitor.config.json
@@ -0,0 +1,14 @@
+{
+ "appId": "com.peanut.app",
+ "appName": "Peanut",
+ "webDir": "out",
+ "server": {
+ "url": "https://peanutdev.site",
+ "cleartext": false
+ },
+ "android": {
+ "allowMixedContent": true,
+ "webContentsDebuggingEnabled": true
+ },
+ "plugins": {}
+}
diff --git a/android/app/src/main/assets/capacitor.plugins.json b/android/app/src/main/assets/capacitor.plugins.json
new file mode 100644
index 000000000..7f5490a59
--- /dev/null
+++ b/android/app/src/main/assets/capacitor.plugins.json
@@ -0,0 +1,6 @@
+[
+ {
+ "pkg": "capacitor-webauthn",
+ "classpath": "com.headcount.plugins.webauthn.WebauthnPlugin"
+ }
+]
diff --git a/android/app/src/main/assets/public/cordova.js b/android/app/src/main/assets/public/cordova.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/android/app/src/main/assets/public/cordova_plugins.js b/android/app/src/main/assets/public/cordova_plugins.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/android/app/src/main/java/com/peanut/app/MainActivity.java b/android/app/src/main/java/com/peanut/app/MainActivity.java
new file mode 100644
index 000000000..07b6a9422
--- /dev/null
+++ b/android/app/src/main/java/com/peanut/app/MainActivity.java
@@ -0,0 +1,5 @@
+package com.peanut.app;
+
+import com.getcapacitor.BridgeActivity;
+
+public class MainActivity extends BridgeActivity {}
diff --git a/android/app/src/main/res/drawable-land-hdpi/splash.png b/android/app/src/main/res/drawable-land-hdpi/splash.png
new file mode 100644
index 000000000..e31573b4f
Binary files /dev/null and b/android/app/src/main/res/drawable-land-hdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-land-mdpi/splash.png b/android/app/src/main/res/drawable-land-mdpi/splash.png
new file mode 100644
index 000000000..f7a64923e
Binary files /dev/null and b/android/app/src/main/res/drawable-land-mdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-land-xhdpi/splash.png b/android/app/src/main/res/drawable-land-xhdpi/splash.png
new file mode 100644
index 000000000..807725501
Binary files /dev/null and b/android/app/src/main/res/drawable-land-xhdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-land-xxhdpi/splash.png b/android/app/src/main/res/drawable-land-xxhdpi/splash.png
new file mode 100644
index 000000000..14c6c8fe3
Binary files /dev/null and b/android/app/src/main/res/drawable-land-xxhdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-land-xxxhdpi/splash.png b/android/app/src/main/res/drawable-land-xxxhdpi/splash.png
new file mode 100644
index 000000000..244ca2506
Binary files /dev/null and b/android/app/src/main/res/drawable-land-xxxhdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-port-hdpi/splash.png b/android/app/src/main/res/drawable-port-hdpi/splash.png
new file mode 100644
index 000000000..74faaa583
Binary files /dev/null and b/android/app/src/main/res/drawable-port-hdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-port-mdpi/splash.png b/android/app/src/main/res/drawable-port-mdpi/splash.png
new file mode 100644
index 000000000..e944f4ad4
Binary files /dev/null and b/android/app/src/main/res/drawable-port-mdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-port-xhdpi/splash.png b/android/app/src/main/res/drawable-port-xhdpi/splash.png
new file mode 100644
index 000000000..564a82ff9
Binary files /dev/null and b/android/app/src/main/res/drawable-port-xhdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-port-xxhdpi/splash.png b/android/app/src/main/res/drawable-port-xxhdpi/splash.png
new file mode 100644
index 000000000..bfabe6871
Binary files /dev/null and b/android/app/src/main/res/drawable-port-xxhdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-port-xxxhdpi/splash.png b/android/app/src/main/res/drawable-port-xxxhdpi/splash.png
new file mode 100644
index 000000000..692907126
Binary files /dev/null and b/android/app/src/main/res/drawable-port-xxxhdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 000000000..c7bd21dbd
--- /dev/null
+++ b/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 000000000..d5fccc538
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/splash.png b/android/app/src/main/res/drawable/splash.png
new file mode 100644
index 000000000..f7a64923e
Binary files /dev/null and b/android/app/src/main/res/drawable/splash.png differ
diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 000000000..b5ad13870
--- /dev/null
+++ b/android/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..036d09bc5
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 000000000..036d09bc5
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..c023e5059
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..2127973b2
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 000000000..b441f37d6
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..72905b854
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..8ed0605c2
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 000000000..9502e47a2
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..4d1e07710
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..df0f15880
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 000000000..853db043d
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..6cdf97c11
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..2960cbb61
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 000000000..8e3093a86
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..46de6e255
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..d2ea9abed
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 000000000..a40d73e9c
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 000000000..c5d5899fd
--- /dev/null
+++ b/android/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..6a21ae8eb
--- /dev/null
+++ b/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+
+ Peanut
+ Peanut
+ com.peanut.app
+ com.peanut.app
+
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
new file mode 100644
index 000000000..be874e54a
--- /dev/null
+++ b/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/xml/config.xml b/android/app/src/main/res/xml/config.xml
new file mode 100644
index 000000000..1b1b0e0dc
--- /dev/null
+++ b/android/app/src/main/res/xml/config.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/xml/file_paths.xml b/android/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 000000000..bd0c4d80d
--- /dev/null
+++ b/android/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java b/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java
new file mode 100644
index 000000000..029732784
--- /dev/null
+++ b/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java
@@ -0,0 +1,18 @@
+package com.getcapacitor.myapp;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+}
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 000000000..f8f0e43b6
--- /dev/null
+++ b/android/build.gradle
@@ -0,0 +1,29 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:8.13.0'
+ classpath 'com.google.gms:google-services:4.4.4'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+apply from: "variables.gradle"
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/android/capacitor-cordova-android-plugins/build.gradle b/android/capacitor-cordova-android-plugins/build.gradle
new file mode 100644
index 000000000..b2e25ddd6
--- /dev/null
+++ b/android/capacitor-cordova-android-plugins/build.gradle
@@ -0,0 +1,59 @@
+ext {
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
+ cordovaAndroidVersion = project.hasProperty('cordovaAndroidVersion') ? rootProject.ext.cordovaAndroidVersion : '14.0.1'
+}
+
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:8.13.0'
+ }
+}
+
+apply plugin: 'com.android.library'
+
+android {
+ namespace = "capacitor.cordova.android.plugins"
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
+ defaultConfig {
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
+ versionCode 1
+ versionName "1.0"
+ }
+ lintOptions {
+ abortOnError = false
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_21
+ targetCompatibility JavaVersion.VERSION_21
+ }
+}
+
+repositories {
+ google()
+ mavenCentral()
+ flatDir{
+ dirs 'src/main/libs', 'libs'
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'src/main/libs', include: ['*.jar'])
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
+ implementation "org.apache.cordova:framework:$cordovaAndroidVersion"
+ // SUB-PROJECT DEPENDENCIES START
+
+ // SUB-PROJECT DEPENDENCIES END
+}
+
+// PLUGIN GRADLE EXTENSIONS START
+apply from: "cordova.variables.gradle"
+// PLUGIN GRADLE EXTENSIONS END
+
+for (def func : cdvPluginPostBuildExtras) {
+ func()
+}
\ No newline at end of file
diff --git a/android/capacitor-cordova-android-plugins/cordova.variables.gradle b/android/capacitor-cordova-android-plugins/cordova.variables.gradle
new file mode 100644
index 000000000..b806d8ad6
--- /dev/null
+++ b/android/capacitor-cordova-android-plugins/cordova.variables.gradle
@@ -0,0 +1,7 @@
+// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
+ext {
+ cdvMinSdkVersion = project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
+ // Plugin gradle extensions can append to this to have code run at the end.
+ cdvPluginPostBuildExtras = []
+ cordovaConfig = [:]
+}
\ No newline at end of file
diff --git a/android/capacitor-cordova-android-plugins/src/main/AndroidManifest.xml b/android/capacitor-cordova-android-plugins/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..cb9c8aa35
--- /dev/null
+++ b/android/capacitor-cordova-android-plugins/src/main/AndroidManifest.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/capacitor-cordova-android-plugins/src/main/java/.gitkeep b/android/capacitor-cordova-android-plugins/src/main/java/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/android/capacitor-cordova-android-plugins/src/main/res/.gitkeep b/android/capacitor-cordova-android-plugins/src/main/res/.gitkeep
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/android/capacitor-cordova-android-plugins/src/main/res/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle
new file mode 100644
index 000000000..da0a63ec1
--- /dev/null
+++ b/android/capacitor.settings.gradle
@@ -0,0 +1,6 @@
+// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
+include ':capacitor-android'
+project(':capacitor-android').projectDir = new File('../node_modules/.pnpm/@capacitor+android@8.2.0_@capacitor+core@8.2.0/node_modules/@capacitor/android/capacitor')
+
+include ':capacitor-webauthn'
+project(':capacitor-webauthn').projectDir = new File('../node_modules/.pnpm/capacitor-webauthn@0.0.12_@capacitor+core@8.2.0/node_modules/capacitor-webauthn/android')
diff --git a/android/gradle.properties b/android/gradle.properties
new file mode 100644
index 000000000..2e87c52f8
--- /dev/null
+++ b/android/gradle.properties
@@ -0,0 +1,22 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..1b33c55ba
Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..7705927e9
--- /dev/null
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/android/gradlew b/android/gradlew
new file mode 100755
index 000000000..23d15a936
--- /dev/null
+++ b/android/gradlew
@@ -0,0 +1,251 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH="\\\"\\\""
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/android/gradlew.bat b/android/gradlew.bat
new file mode 100644
index 000000000..5eed7ee84
--- /dev/null
+++ b/android/gradlew.bat
@@ -0,0 +1,94 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/android/settings.gradle b/android/settings.gradle
new file mode 100644
index 000000000..3b4431d77
--- /dev/null
+++ b/android/settings.gradle
@@ -0,0 +1,5 @@
+include ':app'
+include ':capacitor-cordova-android-plugins'
+project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
+
+apply from: 'capacitor.settings.gradle'
\ No newline at end of file
diff --git a/android/variables.gradle b/android/variables.gradle
new file mode 100644
index 000000000..ee4ba41c4
--- /dev/null
+++ b/android/variables.gradle
@@ -0,0 +1,16 @@
+ext {
+ minSdkVersion = 24
+ compileSdkVersion = 36
+ targetSdkVersion = 36
+ androidxActivityVersion = '1.11.0'
+ androidxAppCompatVersion = '1.7.1'
+ androidxCoordinatorLayoutVersion = '1.3.0'
+ androidxCoreVersion = '1.17.0'
+ androidxFragmentVersion = '1.8.9'
+ coreSplashScreenVersion = '1.2.0'
+ androidxWebkitVersion = '1.14.0'
+ junitVersion = '4.13.2'
+ androidxJunitVersion = '1.3.0'
+ androidxEspressoCoreVersion = '3.7.0'
+ cordovaAndroidVersion = '14.0.1'
+}
\ No newline at end of file
diff --git a/capacitor.config.ts b/capacitor.config.ts
new file mode 100644
index 000000000..455dc7573
--- /dev/null
+++ b/capacitor.config.ts
@@ -0,0 +1,23 @@
+import type { CapacitorConfig } from '@capacitor/cli'
+
+const config: CapacitorConfig = {
+ appId: 'com.peanut.app',
+ appName: 'Peanut',
+ webDir: 'out',
+
+ // cloudflare tunnel for dev testing (passkeys need HTTPS + stable domain)
+ // TODO: remove server.url for production (static export loads from local out/ directory)
+ server: {
+ url: 'https://peanutdev.site',
+ cleartext: false,
+ },
+
+ android: {
+ allowMixedContent: true,
+ webContentsDebuggingEnabled: true,
+ },
+
+ plugins: {},
+}
+
+export default config
diff --git a/next.config.js b/next.config.js
index d212a939d..92ad0d8e9 100644
--- a/next.config.js
+++ b/next.config.js
@@ -107,10 +107,13 @@ let nextConfig = {
source: '/.well-known/apple-app-site-association',
destination: '/api/apple-app-site-association',
},
- {
- source: '/.well-known/assetLinks.json',
- destination: '/api/assetLinks',
- },
+ // disabled for native dev testing — serve local public/.well-known/assetlinks.json
+ // which has the debug signing key fingerprint
+ // TODO: re-enable for production (backend should have the release key)
+ // {
+ // source: '/.well-known/assetLinks.json',
+ // destination: '/api/assetLinks',
+ // },
],
afterFiles: [
// PostHog reverse proxy — bypasses ad blockers
diff --git a/next.config.native.js b/next.config.native.js
new file mode 100644
index 000000000..2b7180c2b
--- /dev/null
+++ b/next.config.native.js
@@ -0,0 +1,73 @@
+const os = require('os')
+const { execSync } = require('child_process')
+const withBundleAnalyzer = require('@next/bundle-analyzer')({
+ enabled: false, // Disable for native builds
+})
+
+// Get git commit hash at build time
+let gitCommitHash = 'unknown'
+try {
+ gitCommitHash = execSync('git rev-parse --short=7 HEAD').toString().trim()
+} catch (error) {
+ console.warn('Could not get git commit hash:', error.message)
+}
+
+/** @type {import('next').NextConfig} */
+let nextConfig = {
+ // STATIC EXPORT FOR CAPACITOR
+ output: 'export',
+
+ // Disable image optimization (requires server)
+ images: {
+ unoptimized: true,
+ },
+
+ // Required for Capacitor - assets must use relative paths
+ assetPrefix: '',
+
+ // Trailing slashes help with static file serving
+ trailingSlash: true,
+
+ env: {
+ NEXT_PUBLIC_GIT_COMMIT_HASH: gitCommitHash,
+ // Flag to detect native context in code
+ NEXT_PUBLIC_IS_NATIVE_BUILD: 'true',
+ },
+
+ // Transpile packages for better compatibility
+ transpilePackages: ['@squirrel-labs/peanut-sdk'],
+
+ // Experimental features for optimization
+ experimental: {
+ optimizePackageImports: [
+ '@chakra-ui/react',
+ 'framer-motion',
+ '@headlessui/react',
+ '@radix-ui/react-accordion',
+ '@radix-ui/react-select',
+ '@radix-ui/react-slider',
+ '@reduxjs/toolkit',
+ 'react-redux',
+ 'lodash',
+ 'date-fns',
+ 'react-hook-form',
+ '@mui/icons-material',
+ ],
+ },
+
+ webpack: (config, { isServer, dev }) => {
+ if (!dev) {
+ if (isServer) {
+ config.ignoreWarnings = [{ module: /@opentelemetry\/instrumentation/, message: /Critical dependency/ }]
+ }
+ }
+ return config
+ },
+
+ reactStrictMode: false,
+
+ // Note: rewrites, redirects, and headers don't work with static export
+ // These would need to be handled by your backend or Capacitor plugins
+}
+
+module.exports = withBundleAnalyzer(nextConfig)
diff --git a/package.json b/package.json
index a7308b244..79eaaea10 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,9 @@
"verify-content": "tsx scripts/verify-content.ts"
},
"dependencies": {
+ "@capacitor/android": "8.2.0",
+ "@capacitor/cli": "8.2.0",
+ "@capacitor/core": "8.2.0",
"@dicebear/collection": "^9.2.2",
"@dicebear/core": "^9.2.2",
"@emotion/react": "^11.14.0",
@@ -39,6 +42,7 @@
"@justaname.id/sdk": "0.2.177",
"@mui/icons-material": "^7.3.6",
"@mui/material": "^7.3.6",
+ "@noble/curves": "1.9.7",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slider": "^1.3.5",
@@ -59,6 +63,7 @@
"@zerodev/sdk": "5.5.7",
"autoprefixer": "^10.4.20",
"canvas-confetti": "^1.9.3",
+ "capacitor-webauthn": "0.0.12",
"classnames": "^2.5.1",
"d3-force": "^3.0.0",
"embla-carousel-react": "^8.6.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 671ed5bda..e297172a2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -13,6 +13,15 @@ importers:
.:
dependencies:
+ '@capacitor/android':
+ specifier: 8.2.0
+ version: 8.2.0(@capacitor/core@8.2.0)
+ '@capacitor/cli':
+ specifier: 8.2.0
+ version: 8.2.0
+ '@capacitor/core':
+ specifier: 8.2.0
+ version: 8.2.0
'@dicebear/collection':
specifier: ^9.2.2
version: 9.3.1(@dicebear/core@9.3.1)
@@ -43,6 +52,9 @@ importers:
'@mui/material':
specifier: ^7.3.6
version: 7.3.7(@emotion/react@11.14.0(@types/react@18.3.27)(react@19.2.4))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.27)(react@19.2.4))(@types/react@18.3.27)(react@19.2.4))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@noble/curves':
+ specifier: 1.9.7
+ version: 1.9.7
'@radix-ui/react-accordion':
specifier: ^1.2.12
version: 1.2.12(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -69,7 +81,7 @@ importers:
version: 9.1.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.6)
'@sentry/nextjs':
specifier: ^8.39.0
- version: 8.55.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(next@16.0.10(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.104.1)
+ version: 8.55.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@16.0.10(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.104.1)
'@serwist/next':
specifier: ^9.0.10
version: 9.5.0(next@16.0.10(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(webpack@5.104.1)
@@ -103,6 +115,9 @@ importers:
canvas-confetti:
specifier: ^1.9.3
version: 1.9.4
+ capacitor-webauthn:
+ specifier: 0.0.12
+ version: 0.0.12(@capacitor/core@8.2.0)
classnames:
specifier: ^2.5.1
version: 2.5.1
@@ -490,6 +505,19 @@ packages:
'@bcoe/v8-coverage@0.2.3':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
+ '@capacitor/android@8.2.0':
+ resolution: {integrity: sha512-XLm5OsWLPfXQxDxzFS7SOdMEgGvW+2c7TGLXkTR2cSKdkWK5Abns4imlT5qghKYhjM9r74IrDkBWg/9ALUGNKQ==}
+ peerDependencies:
+ '@capacitor/core': ^8.2.0
+
+ '@capacitor/cli@8.2.0':
+ resolution: {integrity: sha512-1cMEk0d/I6tl1U+v/lnJR5Oylpx8ZBIHrvQxD5zK0MkjYOUyQAAGJgh97rkhGJqjAUvrGpa8H4BmyhNQN9a17A==}
+ engines: {node: '>=22.0.0'}
+ hasBin: true
+
+ '@capacitor/core@8.2.0':
+ resolution: {integrity: sha512-oKaoNeNtH2iIZMDFVrb1atoyRECDGHcfLMunJ5KWN8DtvpVBeeA4c41e20NTuhMxw1cSYbpq2PV2hb+/9CJxlQ==}
+
'@coinbase/wallet-sdk@3.9.3':
resolution: {integrity: sha512-N/A2DRIf0Y3PHc1XAMvbBUu4zisna6qAdqABMZwBMNEfWrXpAwx16pZGkYCLGE+Rvv1edbcB2LYDRnACNcmCiw==}
@@ -1226,10 +1254,46 @@ packages:
cpu: [x64]
os: [win32]
+ '@ionic/cli-framework-output@2.2.8':
+ resolution: {integrity: sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==}
+ engines: {node: '>=16.0.0'}
+
+ '@ionic/utils-array@2.1.6':
+ resolution: {integrity: sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==}
+ engines: {node: '>=16.0.0'}
+
+ '@ionic/utils-fs@3.1.7':
+ resolution: {integrity: sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==}
+ engines: {node: '>=16.0.0'}
+
+ '@ionic/utils-object@2.1.6':
+ resolution: {integrity: sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==}
+ engines: {node: '>=16.0.0'}
+
+ '@ionic/utils-process@2.1.12':
+ resolution: {integrity: sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==}
+ engines: {node: '>=16.0.0'}
+
+ '@ionic/utils-stream@3.1.7':
+ resolution: {integrity: sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==}
+ engines: {node: '>=16.0.0'}
+
+ '@ionic/utils-subprocess@3.0.1':
+ resolution: {integrity: sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==}
+ engines: {node: '>=16.0.0'}
+
+ '@ionic/utils-terminal@2.3.5':
+ resolution: {integrity: sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==}
+ engines: {node: '>=16.0.0'}
+
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
+ '@isaacs/fs-minipass@4.0.1':
+ resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
+ engines: {node: '>=18.0.0'}
+
'@istanbuljs/load-nyc-config@1.1.0':
resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
engines: {node: '>=8'}
@@ -2958,6 +3022,9 @@ packages:
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+ '@types/fs-extra@8.1.5':
+ resolution: {integrity: sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==}
+
'@types/graceful-fs@4.1.9':
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
@@ -3037,6 +3104,9 @@ packages:
'@types/shimmer@1.2.0':
resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==}
+ '@types/slice-ansi@4.0.0':
+ resolution: {integrity: sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==}
+
'@types/stack-utils@2.0.3':
resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
@@ -3311,6 +3381,10 @@ packages:
'@webassemblyjs/wast-printer@1.14.1':
resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==}
+ '@xmldom/xmldom@0.8.12':
+ resolution: {integrity: sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==}
+ engines: {node: '>=10.0.0'}
+
'@xtuc/ieee754@1.2.0':
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
@@ -3466,6 +3540,10 @@ packages:
resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
engines: {node: '>=4'}
+ astral-regex@2.0.0:
+ resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
+ engines: {node: '>=8'}
+
astring@1.9.0:
resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==}
hasBin: true
@@ -3476,6 +3554,10 @@ packages:
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+ at-least-node@1.0.0:
+ resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
+ engines: {node: '>= 4.0.0'}
+
atomic-sleep@1.0.0:
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
engines: {node: '>=8.0.0'}
@@ -3537,6 +3619,10 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ balanced-match@4.0.4:
+ resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
+ engines: {node: 18 || 20 || >=22}
+
bare-events@2.8.2:
resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==}
peerDependencies:
@@ -3595,6 +3681,10 @@ packages:
bezier-js@6.1.4:
resolution: {integrity: sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==}
+ big-integer@1.6.52:
+ resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==}
+ engines: {node: '>=0.6'}
+
big.js@6.2.2:
resolution: {integrity: sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==}
@@ -3611,12 +3701,20 @@ packages:
bowser@2.13.1:
resolution: {integrity: sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==}
+ bplist-parser@0.3.2:
+ resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==}
+ engines: {node: '>= 5.10.0'}
+
brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
brace-expansion@2.0.2:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
+ brace-expansion@5.0.5:
+ resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==}
+ engines: {node: 18 || 20 || >=22}
+
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
@@ -3694,6 +3792,11 @@ packages:
canvas-confetti@1.9.4:
resolution: {integrity: sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw==}
+ capacitor-webauthn@0.0.12:
+ resolution: {integrity: sha512-sOgk2fyeSgzB+mCd+81J+dXpoqgLBuqjyX6Now9ujjAYQehjPwWvSSlJUxDUhosz6I2yFcXnyunAQK3NkWz6/g==}
+ peerDependencies:
+ '@capacitor/core': '>=7.0.0'
+
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
@@ -3733,6 +3836,10 @@ packages:
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
engines: {node: '>= 20.19.0'}
+ chownr@3.0.0:
+ resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
+ engines: {node: '>=18'}
+
chrome-trace-event@1.0.4:
resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==}
engines: {node: '>=6.0'}
@@ -3798,6 +3905,10 @@ packages:
resolution: {integrity: sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==}
engines: {node: '>=18'}
+ commander@12.1.0:
+ resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
+ engines: {node: '>=18'}
+
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -4022,6 +4133,10 @@ packages:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'}
+ define-lazy-prop@2.0.0:
+ resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
+ engines: {node: '>=8'}
+
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
@@ -4127,6 +4242,10 @@ packages:
electron-to-chromium@1.5.282:
resolution: {integrity: sha512-FCPkJtpst28UmFzd903iU7PdeVTfY0KAeJy+Lk0GLZRwgwYHn/irRcaCbQQOmr5Vytc/7rcavsYLvTM8RiHYhQ==}
+ elementtree@0.1.7:
+ resolution: {integrity: sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==}
+ engines: {node: '>= 0.4.0'}
+
elliptic@6.5.4:
resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==}
@@ -4177,6 +4296,10 @@ packages:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
+ env-paths@2.2.1:
+ resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
+ engines: {node: '>=6'}
+
error-ex@1.3.4:
resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
@@ -4482,6 +4605,14 @@ packages:
react-dom:
optional: true
+ fs-extra@11.3.4:
+ resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==}
+ engines: {node: '>=14.14'}
+
+ fs-extra@9.1.0:
+ resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
+ engines: {node: '>=10'}
+
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
@@ -4556,6 +4687,10 @@ packages:
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
hasBin: true
+ glob@13.0.6:
+ resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
+ engines: {node: 18 || 20 || >=22}
+
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
@@ -4726,6 +4861,10 @@ packages:
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+ ini@4.1.3:
+ resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
inline-style-parser@0.2.7:
resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==}
@@ -4768,6 +4907,11 @@ packages:
is-decimal@2.0.1:
resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
+ is-docker@2.2.1:
+ resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
+ engines: {node: '>=8'}
+ hasBin: true
+
is-extendable@0.1.1:
resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
engines: {node: '>=0.10.0'}
@@ -4825,6 +4969,10 @@ packages:
resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
engines: {node: '>= 0.4'}
+ is-wsl@2.2.0:
+ resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
+ engines: {node: '>=8'}
+
isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
@@ -5073,6 +5221,9 @@ packages:
engines: {node: '>=6'}
hasBin: true
+ jsonfile@6.2.0:
+ resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
+
jsqr@1.4.0:
resolution: {integrity: sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==}
@@ -5095,6 +5246,10 @@ packages:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'}
+ kleur@4.1.5:
+ resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
+ engines: {node: '>=6'}
+
knip@5.82.1:
resolution: {integrity: sha512-1nQk+5AcnkqL40kGQXfouzAEXkTR+eSrgo/8m1d0BMei4eAzFwghoXC4gOKbACgBiCof7hE8wkBVDsEvznf85w==}
engines: {node: '>=18.18.0'}
@@ -5410,6 +5565,10 @@ packages:
minimalistic-crypto-utils@1.0.1:
resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==}
+ minimatch@10.2.5:
+ resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
+ engines: {node: 18 || 20 || >=22}
+
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -5432,6 +5591,14 @@ packages:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'}
+ minipass@7.1.3:
+ resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minizlib@3.1.0:
+ resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
+ engines: {node: '>= 18'}
+
mipd@0.0.7:
resolution: {integrity: sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==}
peerDependencies:
@@ -5478,6 +5645,11 @@ packages:
nanospinner@1.2.2:
resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==}
+ native-run@2.0.3:
+ resolution: {integrity: sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q==}
+ engines: {node: '>=16.0.0'}
+ hasBin: true
+
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
@@ -5609,6 +5781,10 @@ packages:
oniguruma-to-es@4.3.4:
resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==}
+ open@8.4.2:
+ resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
+ engines: {node: '>=12'}
+
opener@1.5.2:
resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
hasBin: true
@@ -5699,6 +5875,10 @@ packages:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
+ path-scurry@2.0.2:
+ resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
+ engines: {node: 18 || 20 || >=22}
+
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
@@ -5772,6 +5952,10 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ plist@3.1.0:
+ resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
+ engines: {node: '>=10.4.0'}
+
pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
@@ -6271,6 +6455,11 @@ packages:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ rimraf@6.1.3:
+ resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==}
+ engines: {node: 20 || >=22}
+ hasBin: true
+
rollup@3.29.5:
resolution: {integrity: sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==}
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
@@ -6296,6 +6485,13 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+ sax@1.1.4:
+ resolution: {integrity: sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==}
+
+ sax@1.6.0:
+ resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==}
+ engines: {node: '>=11.0.0'}
+
saxes@6.0.0:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
@@ -6408,6 +6604,10 @@ packages:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'}
+ slice-ansi@4.0.0:
+ resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}
+ engines: {node: '>=10'}
+
smart-buffer@4.2.0:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
@@ -6639,6 +6839,10 @@ packages:
tar-stream@3.1.7:
resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
+ tar@7.5.13:
+ resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==}
+ engines: {node: '>=18'}
+
terser-webpack-plugin@5.3.16:
resolution: {integrity: sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==}
engines: {node: '>= 10.13.0'}
@@ -6677,6 +6881,9 @@ packages:
thread-stream@0.15.2:
resolution: {integrity: sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==}
+ through2@4.0.2:
+ resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
+
tinycolor2@1.6.0:
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
@@ -6713,6 +6920,10 @@ packages:
resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
engines: {node: '>=12'}
+ tree-kill@1.2.2:
+ resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
+ hasBin: true
+
trim-lines@3.0.1:
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
@@ -6836,6 +7047,10 @@ packages:
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
engines: {node: '>= 4.0.0'}
+ universalify@2.0.1:
+ resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
+ engines: {node: '>= 10.0.0'}
+
unplugin@1.0.1:
resolution: {integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==}
@@ -6901,6 +7116,10 @@ packages:
uploadthing:
optional: true
+ untildify@4.0.0:
+ resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
+ engines: {node: '>=8'}
+
update-browserslist-db@1.2.3:
resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
hasBin: true
@@ -7187,6 +7406,18 @@ packages:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'}
+ xml2js@0.6.2:
+ resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
+ engines: {node: '>=4.0.0'}
+
+ xmlbuilder@11.0.1:
+ resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
+ engines: {node: '>=4.0'}
+
+ xmlbuilder@15.1.1:
+ resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==}
+ engines: {node: '>=8.0'}
+
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
@@ -7208,6 +7439,10 @@ packages:
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+ yallist@5.0.0:
+ resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
+ engines: {node: '>=18'}
+
yaml@1.10.2:
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
engines: {node: '>= 6'}
@@ -7512,6 +7747,36 @@ snapshots:
'@bcoe/v8-coverage@0.2.3': {}
+ '@capacitor/android@8.2.0(@capacitor/core@8.2.0)':
+ dependencies:
+ '@capacitor/core': 8.2.0
+
+ '@capacitor/cli@8.2.0':
+ dependencies:
+ '@ionic/cli-framework-output': 2.2.8
+ '@ionic/utils-subprocess': 3.0.1
+ '@ionic/utils-terminal': 2.3.5
+ commander: 12.1.0
+ debug: 4.4.3
+ env-paths: 2.2.1
+ fs-extra: 11.3.4
+ kleur: 4.1.5
+ native-run: 2.0.3
+ open: 8.4.2
+ plist: 3.1.0
+ prompts: 2.4.2
+ rimraf: 6.1.3
+ semver: 7.7.3
+ tar: 7.5.13
+ tslib: 2.8.1
+ xml2js: 0.6.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@capacitor/core@8.2.0':
+ dependencies:
+ tslib: 2.8.1
+
'@coinbase/wallet-sdk@3.9.3':
dependencies:
bn.js: 5.2.2
@@ -8357,6 +8622,82 @@ snapshots:
'@img/sharp-win32-x64@0.34.5':
optional: true
+ '@ionic/cli-framework-output@2.2.8':
+ dependencies:
+ '@ionic/utils-terminal': 2.3.5
+ debug: 4.4.3
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@ionic/utils-array@2.1.6':
+ dependencies:
+ debug: 4.4.3
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@ionic/utils-fs@3.1.7':
+ dependencies:
+ '@types/fs-extra': 8.1.5
+ debug: 4.4.3
+ fs-extra: 9.1.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@ionic/utils-object@2.1.6':
+ dependencies:
+ debug: 4.4.3
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@ionic/utils-process@2.1.12':
+ dependencies:
+ '@ionic/utils-object': 2.1.6
+ '@ionic/utils-terminal': 2.3.5
+ debug: 4.4.3
+ signal-exit: 3.0.7
+ tree-kill: 1.2.2
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@ionic/utils-stream@3.1.7':
+ dependencies:
+ debug: 4.4.3
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@ionic/utils-subprocess@3.0.1':
+ dependencies:
+ '@ionic/utils-array': 2.1.6
+ '@ionic/utils-fs': 3.1.7
+ '@ionic/utils-process': 2.1.12
+ '@ionic/utils-stream': 3.1.7
+ '@ionic/utils-terminal': 2.3.5
+ cross-spawn: 7.0.6
+ debug: 4.4.3
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@ionic/utils-terminal@2.3.5':
+ dependencies:
+ '@types/slice-ansi': 4.0.0
+ debug: 4.4.3
+ signal-exit: 3.0.7
+ slice-ansi: 4.0.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ tslib: 2.8.1
+ untildify: 4.0.0
+ wrap-ansi: 7.0.0
+ transitivePeerDependencies:
+ - supports-color
+
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
@@ -8366,6 +8707,10 @@ snapshots:
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
+ '@isaacs/fs-minipass@4.0.1':
+ dependencies:
+ minipass: 7.1.2
+
'@istanbuljs/load-nyc-config@1.1.0':
dependencies:
camelcase: 5.3.1
@@ -10442,7 +10787,7 @@ snapshots:
'@scure/bip32@1.7.0':
dependencies:
- '@noble/curves': 1.9.1
+ '@noble/curves': 1.9.7
'@noble/hashes': 1.8.0
'@scure/base': 1.2.6
@@ -10540,7 +10885,7 @@ snapshots:
'@sentry/core@8.55.0': {}
- '@sentry/nextjs@8.55.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(next@16.0.10(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.104.1)':
+ '@sentry/nextjs@8.55.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@16.0.10(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.104.1)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.39.0
@@ -10548,7 +10893,7 @@ snapshots:
'@sentry-internal/browser-utils': 8.55.0
'@sentry/core': 8.55.0
'@sentry/node': 8.55.0
- '@sentry/opentelemetry': 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)
+ '@sentry/opentelemetry': 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)
'@sentry/react': 8.55.0(react@19.2.4)
'@sentry/vercel-edge': 8.55.0
'@sentry/webpack-plugin': 2.22.7(webpack@5.104.1)
@@ -10617,16 +10962,6 @@ snapshots:
'@opentelemetry/semantic-conventions': 1.39.0
'@sentry/core': 8.55.0
- '@sentry/opentelemetry@8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)':
- dependencies:
- '@opentelemetry/api': 1.9.0
- '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0)
- '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
- '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.39.0
- '@sentry/core': 8.55.0
-
'@sentry/react@8.55.0(react@19.2.4)':
dependencies:
'@sentry/browser': 8.55.0
@@ -10942,6 +11277,10 @@ snapshots:
'@types/estree@1.0.8': {}
+ '@types/fs-extra@8.1.5':
+ dependencies:
+ '@types/node': 20.4.2
+
'@types/graceful-fs@4.1.9':
dependencies:
'@types/node': 20.4.2
@@ -11024,6 +11363,8 @@ snapshots:
'@types/shimmer@1.2.0': {}
+ '@types/slice-ansi@4.0.0': {}
+
'@types/stack-utils@2.0.3': {}
'@types/tedious@4.0.14':
@@ -11927,6 +12268,8 @@ snapshots:
'@webassemblyjs/ast': 1.14.1
'@xtuc/long': 4.2.2
+ '@xmldom/xmldom@0.8.12': {}
+
'@xtuc/ieee754@1.2.0': {}
'@xtuc/long@4.2.2': {}
@@ -12061,6 +12404,8 @@ snapshots:
dependencies:
tslib: 2.8.1
+ astral-regex@2.0.0: {}
+
astring@1.9.0: {}
async-mutex@0.2.6:
@@ -12069,6 +12414,8 @@ snapshots:
asynckit@0.4.0: {}
+ at-least-node@1.0.0: {}
+
atomic-sleep@1.0.0: {}
autoprefixer@10.4.23(postcss@8.5.6):
@@ -12159,6 +12506,8 @@ snapshots:
balanced-match@1.0.2: {}
+ balanced-match@4.0.4: {}
+
bare-events@2.8.2: {}
bare-fs@4.5.3:
@@ -12208,6 +12557,8 @@ snapshots:
bezier-js@6.1.4: {}
+ big-integer@1.6.52: {}
+
big.js@6.2.2: {}
binary-extensions@2.3.0: {}
@@ -12218,6 +12569,10 @@ snapshots:
bowser@2.13.1: {}
+ bplist-parser@0.3.2:
+ dependencies:
+ big-integer: 1.6.52
+
brace-expansion@1.1.12:
dependencies:
balanced-match: 1.0.2
@@ -12227,6 +12582,10 @@ snapshots:
dependencies:
balanced-match: 1.0.2
+ brace-expansion@5.0.5:
+ dependencies:
+ balanced-match: 4.0.4
+
braces@3.0.3:
dependencies:
fill-range: 7.1.1
@@ -12301,6 +12660,10 @@ snapshots:
canvas-confetti@1.9.4: {}
+ capacitor-webauthn@0.0.12(@capacitor/core@8.2.0):
+ dependencies:
+ '@capacitor/core': 8.2.0
+
ccount@2.0.1: {}
chalk@3.0.0:
@@ -12343,6 +12706,8 @@ snapshots:
dependencies:
readdirp: 5.0.0
+ chownr@3.0.0: {}
+
chrome-trace-event@1.0.4: {}
chromium-bidi@8.0.0(devtools-protocol@0.0.1495869):
@@ -12395,6 +12760,8 @@ snapshots:
commander@12.0.0: {}
+ commander@12.1.0: {}
+
commander@2.20.3: {}
commander@4.1.1: {}
@@ -12605,6 +12972,8 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
+ define-lazy-prop@2.0.0: {}
+
defu@6.1.4: {}
degenerator@5.0.1:
@@ -12697,6 +13066,10 @@ snapshots:
electron-to-chromium@1.5.282: {}
+ elementtree@0.1.7:
+ dependencies:
+ sax: 1.1.4
+
elliptic@6.5.4:
dependencies:
bn.js: 4.12.2
@@ -12762,6 +13135,8 @@ snapshots:
entities@6.0.1: {}
+ env-paths@2.2.1: {}
+
error-ex@1.3.4:
dependencies:
is-arrayish: 0.2.1
@@ -13174,6 +13549,19 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
+ fs-extra@11.3.4:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.2.0
+ universalify: 2.0.1
+
+ fs-extra@9.1.0:
+ dependencies:
+ at-least-node: 1.0.0
+ graceful-fs: 4.2.11
+ jsonfile: 6.2.0
+ universalify: 2.0.1
+
fs.realpath@1.0.0: {}
fsevents@2.3.2:
@@ -13249,6 +13637,12 @@ snapshots:
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
+ glob@13.0.6:
+ dependencies:
+ minimatch: 10.2.5
+ minipass: 7.1.3
+ path-scurry: 2.0.2
+
glob@7.2.3:
dependencies:
fs.realpath: 1.0.0
@@ -13494,6 +13888,8 @@ snapshots:
inherits@2.0.4: {}
+ ini@4.1.3: {}
+
inline-style-parser@0.2.7: {}
internmap@2.0.3: {}
@@ -13528,6 +13924,8 @@ snapshots:
is-decimal@2.0.1: {}
+ is-docker@2.2.1: {}
+
is-extendable@0.1.1: {}
is-extglob@2.1.1: {}
@@ -13575,6 +13973,10 @@ snapshots:
dependencies:
which-typed-array: 1.1.20
+ is-wsl@2.2.0:
+ dependencies:
+ is-docker: 2.2.1
+
isarray@1.0.0: {}
isarray@2.0.5: {}
@@ -14032,6 +14434,12 @@ snapshots:
json5@2.2.3: {}
+ jsonfile@6.2.0:
+ dependencies:
+ universalify: 2.0.1
+ optionalDependencies:
+ graceful-fs: 4.2.11
+
jsqr@1.4.0: {}
kapsule@1.16.3:
@@ -14050,6 +14458,8 @@ snapshots:
kleur@3.0.3: {}
+ kleur@4.1.5: {}
+
knip@5.82.1(@types/node@20.4.2)(typescript@5.9.3):
dependencies:
'@nodelib/fs.walk': 1.2.8
@@ -14617,6 +15027,10 @@ snapshots:
minimalistic-crypto-utils@1.0.1: {}
+ minimatch@10.2.5:
+ dependencies:
+ brace-expansion: 5.0.5
+
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.12
@@ -14635,6 +15049,12 @@ snapshots:
minipass@7.1.2: {}
+ minipass@7.1.3: {}
+
+ minizlib@3.1.0:
+ dependencies:
+ minipass: 7.1.2
+
mipd@0.0.7(typescript@5.9.3):
optionalDependencies:
typescript: 5.9.3
@@ -14669,6 +15089,22 @@ snapshots:
dependencies:
picocolors: 1.1.1
+ native-run@2.0.3:
+ dependencies:
+ '@ionic/utils-fs': 3.1.7
+ '@ionic/utils-terminal': 2.3.5
+ bplist-parser: 0.3.2
+ debug: 4.4.3
+ elementtree: 0.1.7
+ ini: 4.1.3
+ plist: 3.1.0
+ split2: 4.2.0
+ through2: 4.0.2
+ tslib: 2.8.1
+ yauzl: 2.10.0
+ transitivePeerDependencies:
+ - supports-color
+
natural-compare@1.4.0: {}
neo-async@2.6.2: {}
@@ -14781,6 +15217,12 @@ snapshots:
regex: 6.1.0
regex-recursion: 6.0.2
+ open@8.4.2:
+ dependencies:
+ define-lazy-prop: 2.0.0
+ is-docker: 2.2.1
+ is-wsl: 2.2.0
+
opener@1.5.2: {}
ox@0.11.3(typescript@5.9.3)(zod@3.22.4):
@@ -14928,6 +15370,11 @@ snapshots:
lru-cache: 10.4.3
minipass: 7.1.2
+ path-scurry@2.0.2:
+ dependencies:
+ lru-cache: 11.2.5
+ minipass: 7.1.3
+
path-type@4.0.0: {}
pend@1.2.0: {}
@@ -14999,6 +15446,12 @@ snapshots:
optionalDependencies:
fsevents: 2.3.2
+ plist@3.1.0:
+ dependencies:
+ '@xmldom/xmldom': 0.8.12
+ base64-js: 1.5.1
+ xmlbuilder: 15.1.1
+
pngjs@5.0.0: {}
pony-cause@2.1.11: {}
@@ -15503,6 +15956,11 @@ snapshots:
reusify@1.1.0: {}
+ rimraf@6.1.3:
+ dependencies:
+ glob: 13.0.6
+ package-json-from-dist: 1.0.1
+
rollup@3.29.5:
optionalDependencies:
fsevents: 2.3.3
@@ -15525,6 +15983,10 @@ snapshots:
safer-buffer@2.1.2: {}
+ sax@1.1.4: {}
+
+ sax@1.6.0: {}
+
saxes@6.0.0:
dependencies:
xmlchars: 2.2.0
@@ -15688,6 +16150,12 @@ snapshots:
slash@3.0.0: {}
+ slice-ansi@4.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ astral-regex: 2.0.0
+ is-fullwidth-code-point: 3.0.0
+
smart-buffer@4.2.0: {}
smol-toml@1.6.0: {}
@@ -15952,6 +16420,14 @@ snapshots:
- bare-abort-controller
- react-native-b4a
+ tar@7.5.13:
+ dependencies:
+ '@isaacs/fs-minipass': 4.0.1
+ chownr: 3.0.0
+ minipass: 7.1.2
+ minizlib: 3.1.0
+ yallist: 5.0.0
+
terser-webpack-plugin@5.3.16(webpack@5.104.1):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
@@ -15992,6 +16468,10 @@ snapshots:
dependencies:
real-require: 0.1.0
+ through2@4.0.2:
+ dependencies:
+ readable-stream: 3.6.2
+
tinycolor2@1.6.0: {}
tinyglobby@0.2.15:
@@ -16030,6 +16510,8 @@ snapshots:
dependencies:
punycode: 2.3.1
+ tree-kill@1.2.2: {}
+
trim-lines@3.0.1: {}
trough@2.2.0: {}
@@ -16147,6 +16629,8 @@ snapshots:
universalify@0.2.0: {}
+ universalify@2.0.1: {}
+
unplugin@1.0.1:
dependencies:
acorn: 8.15.0
@@ -16167,6 +16651,8 @@ snapshots:
optionalDependencies:
idb-keyval: 6.2.2
+ untildify@4.0.0: {}
+
update-browserslist-db@1.2.3(browserslist@4.28.1):
dependencies:
browserslist: 4.28.1
@@ -16517,6 +17003,15 @@ snapshots:
xml-name-validator@4.0.0: {}
+ xml2js@0.6.2:
+ dependencies:
+ sax: 1.6.0
+ xmlbuilder: 11.0.1
+
+ xmlbuilder@11.0.1: {}
+
+ xmlbuilder@15.1.1: {}
+
xmlchars@2.2.0: {}
xmlhttprequest-ssl@2.1.2: {}
@@ -16529,6 +17024,8 @@ snapshots:
yallist@3.1.1: {}
+ yallist@5.0.0: {}
+
yaml@1.10.2: {}
yaml@2.8.2: {}
diff --git a/public/.well-known/assetlinks.json b/public/.well-known/assetlinks.json
index 4c36b4165..040d0e389 100644
--- a/public/.well-known/assetlinks.json
+++ b/public/.well-known/assetlinks.json
@@ -1,16 +1,13 @@
[
{
- "relation": ["delegate_permission/common.query_webapk"],
+ "relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
"target": {
- "namespace": "web",
- "site": "https://peanut.me/manifest.webmanifest"
- }
- },
- {
- "relation": ["delegate_permission/common.handle_all_urls"],
- "target": {
- "namespace": "web",
- "site": "https://peanut.me"
+ "namespace": "android_app",
+ "package_name": "com.peanut.app",
+ "sha256_cert_fingerprints": [
+ "11:94:75:B7:2F:74:28:DC:D8:B2:FF:FF:A9:A0:6B:2D:78:13:17:F1:15:F6:24:BD:BE:F3:C8:16:38:92:0E:98",
+ "29:10:BA:ED:60:53:6D:60:A9:B2:D5:DB:7D:AD:9B:04:A5:49:FE:17:6E:89:CF:A4:85:17:19:D1:14:99:F1:EB"
+ ]
}
}
]
diff --git a/scripts/native-build.js b/scripts/native-build.js
new file mode 100644
index 000000000..639bcc873
--- /dev/null
+++ b/scripts/native-build.js
@@ -0,0 +1,232 @@
+#!/usr/bin/env node
+
+/**
+ * Native Build Script
+ *
+ * Temporarily disables server-only features (API routes, sitemap, robots, manifest)
+ * and wraps client dynamic routes for static export compatibility.
+ */
+
+const { execSync } = require('child_process')
+const fs = require('fs')
+const path = require('path')
+
+const APP_DIR = path.join(__dirname, '..', 'src', 'app')
+
+// Files/folders to temporarily disable during native build
+const ITEMS_TO_DISABLE = [
+ { path: 'api', type: 'dir' },
+ { path: 'sitemap.ts', type: 'file' },
+ { path: 'robots.ts', type: 'file' },
+ { path: 'manifest.ts', type: 'file' },
+ { path: 'jobs/route.ts', type: 'file' },
+]
+
+const MODIFIED_FILES = []
+const WRAPPER_FILES = []
+
+function getDisabledPath(itemPath) {
+ const dir = path.dirname(itemPath)
+ const base = path.basename(itemPath)
+ if (dir === '.') {
+ return `_${base}.disabled`
+ }
+ return path.join(dir, `_${base}.disabled`)
+}
+
+function disableItems() {
+ console.log('📦 Disabling server-only features...')
+ for (const item of ITEMS_TO_DISABLE) {
+ const src = path.join(APP_DIR, item.path)
+ const dest = path.join(APP_DIR, getDisabledPath(item.path))
+ if (fs.existsSync(src)) {
+ fs.renameSync(src, dest)
+ console.log(` ↳ Disabled: ${item.path}`)
+ }
+ }
+}
+
+function restoreItems() {
+ console.log('🔄 Restoring disabled items...')
+ for (const item of ITEMS_TO_DISABLE) {
+ const src = path.join(APP_DIR, getDisabledPath(item.path))
+ const dest = path.join(APP_DIR, item.path)
+ if (fs.existsSync(src)) {
+ fs.renameSync(src, dest)
+ console.log(` ↳ Restored: ${item.path}`)
+ }
+ }
+}
+
+function findDynamicRoutes(dir, routes = []) {
+ const items = fs.readdirSync(dir, { withFileTypes: true })
+
+ for (const item of items) {
+ if (item.name.includes('.disabled') || item.name.startsWith('_')) continue
+
+ const fullPath = path.join(dir, item.name)
+
+ if (item.isDirectory()) {
+ if (item.name.includes('[')) {
+ const pagePath = path.join(fullPath, 'page.tsx')
+ if (fs.existsSync(pagePath)) {
+ routes.push(pagePath)
+ }
+ }
+ findDynamicRoutes(fullPath, routes)
+ }
+ }
+
+ return routes
+}
+
+function wrapClientComponents() {
+ console.log('📝 Wrapping client components for static export...')
+ const dynamicRoutes = findDynamicRoutes(APP_DIR)
+
+ for (const pagePath of dynamicRoutes) {
+ const content = fs.readFileSync(pagePath, 'utf-8')
+
+ // Skip if already has generateStaticParams (server component)
+ if (content.includes('generateStaticParams')) {
+ console.log(` ↳ Skipped (already has generateStaticParams): ${path.relative(APP_DIR, pagePath)}`)
+ continue
+ }
+
+ // Check if it's a client component
+ const isClientComponent =
+ content.trimStart().startsWith("'use client'") || content.trimStart().startsWith('"use client"')
+
+ if (isClientComponent) {
+ // For client components, we need to create a wrapper
+ const dir = path.dirname(pagePath)
+
+ // Find the default export name
+ const exportMatch = content.match(/export\s+default\s+(?:function\s+)?(\w+)/)
+ if (!exportMatch) {
+ console.log(` ↳ Skipped (no default export found): ${path.relative(APP_DIR, pagePath)}`)
+ continue
+ }
+ const componentName = exportMatch[1]
+
+ // Rename original to _ClientPage.tsx
+ const clientPagePath = path.join(dir, '_ClientPage.tsx')
+ fs.renameSync(pagePath, clientPagePath)
+ MODIFIED_FILES.push({ original: pagePath, renamed: clientPagePath })
+
+ // Check if the component expects params
+ const hasParams = content.includes('params:') || content.includes('PageProps')
+
+ // Create wrapper page.tsx
+ const wrapperContent = hasParams
+ ? `// Auto-generated wrapper for static export
+import ClientPage from './_ClientPage'
+
+export function generateStaticParams() {
+ return []
+}
+
+export default function Page(props: any) {
+ return
+}
+`
+ : `// Auto-generated wrapper for static export
+import ClientPage from './_ClientPage'
+
+export function generateStaticParams() {
+ return []
+}
+
+export default function Page() {
+ return
+}
+`
+ fs.writeFileSync(pagePath, wrapperContent)
+ WRAPPER_FILES.push(pagePath)
+
+ console.log(` ↳ Wrapped: ${path.relative(APP_DIR, pagePath)}`)
+ } else {
+ // Server component - just add generateStaticParams
+ MODIFIED_FILES.push({ path: pagePath, original: content, type: 'content' })
+
+ const staticParamsExport = `// Added for static export compatibility
+export function generateStaticParams() {
+ return []
+}
+
+`
+ const newContent = staticParamsExport + content
+ fs.writeFileSync(pagePath, newContent)
+
+ console.log(` ↳ Added generateStaticParams: ${path.relative(APP_DIR, pagePath)}`)
+ }
+ }
+}
+
+function restoreModifiedFiles() {
+ if (MODIFIED_FILES.length === 0 && WRAPPER_FILES.length === 0) return
+
+ console.log('🔄 Restoring modified files...')
+
+ // Remove wrapper files
+ for (const wrapperPath of WRAPPER_FILES) {
+ if (fs.existsSync(wrapperPath)) {
+ fs.unlinkSync(wrapperPath)
+ }
+ }
+
+ // Restore renamed/modified files
+ for (const file of MODIFIED_FILES) {
+ if (file.type === 'content') {
+ // Restore content
+ fs.writeFileSync(file.path, file.original)
+ console.log(` ↳ Restored content: ${path.relative(APP_DIR, file.path)}`)
+ } else {
+ // Restore renamed file
+ if (fs.existsSync(file.renamed)) {
+ fs.renameSync(file.renamed, file.original)
+ console.log(` ↳ Restored: ${path.relative(APP_DIR, file.original)}`)
+ }
+ }
+ }
+}
+
+async function main() {
+ let buildSucceeded = false
+
+ try {
+ disableItems()
+ wrapClientComponents()
+
+ // Clean build cache to ensure fresh build
+ console.log('🧹 Cleaning build cache...')
+ if (fs.existsSync(path.join(__dirname, '..', '.next'))) {
+ fs.rmSync(path.join(__dirname, '..', '.next'), { recursive: true })
+ }
+
+ console.log('\n🏗️ Building static export...\n')
+ execSync('NATIVE_BUILD=true next build --webpack', {
+ stdio: 'inherit',
+ cwd: path.join(__dirname, '..'),
+ env: { ...process.env, NATIVE_BUILD: 'true' },
+ })
+
+ buildSucceeded = true
+ console.log('\n✅ Native build completed successfully!')
+ console.log(' Output directory: ./out')
+ } catch (error) {
+ console.error('\n❌ Build failed:', error.message)
+ process.exitCode = 1
+ } finally {
+ restoreModifiedFiles()
+ restoreItems()
+ }
+
+ if (buildSucceeded) {
+ console.log('\n📱 Next steps:')
+ console.log(' pnpm cap:sync # Sync with native projects')
+ console.log(' pnpm cap:open:android # Open in Android Studio')
+ }
+}
+
+main()
diff --git a/src/app/(mobile-ui)/layout.tsx b/src/app/(mobile-ui)/layout.tsx
index 24aea9d87..4a938f2aa 100644
--- a/src/app/(mobile-ui)/layout.tsx
+++ b/src/app/(mobile-ui)/layout.tsx
@@ -24,8 +24,10 @@ import { IS_DEV } from '@/constants/general.consts'
import { usePullToRefresh } from '@/hooks/usePullToRefresh'
import { useNetworkStatus } from '@/hooks/useNetworkStatus'
import { useAccountSetupRedirect } from '@/hooks/useAccountSetupRedirect'
+import { useNativePlugins } from '@/hooks/useNativePlugins'
const Layout = ({ children }: { children: React.ReactNode }) => {
+ useNativePlugins()
const pathName = usePathname()
// Allow access to public paths without authentication
diff --git a/src/app/(mobile-ui)/qr/[code]/page.tsx b/src/app/(mobile-ui)/qr/[code]/page.tsx
index ec6994235..c19439c4f 100644
--- a/src/app/(mobile-ui)/qr/[code]/page.tsx
+++ b/src/app/(mobile-ui)/qr/[code]/page.tsx
@@ -1,5 +1,6 @@
'use client'
+import { getAuthToken } from '@/utils/auth-token'
import { Button } from '@/components/0_Bruddle/Button'
import Card from '@/components/Global/Card'
import NavHeader from '@/components/Global/NavHeader'
@@ -12,7 +13,6 @@ import ErrorAlert from '@/components/Global/ErrorAlert'
import { Icon } from '@/components/Global/Icons/Icon'
import { saveRedirectUrl, generateInviteCodeLink, sanitizeRedirectURL } from '@/utils/general.utils'
import { getShakeClass } from '@/utils/perk.utils'
-import Cookies from 'js-cookie'
import { useRedirectQrStatus } from '@/hooks/useRedirectQrStatus'
import { useHoldToClaim } from '@/hooks/useHoldToClaim'
@@ -91,7 +91,7 @@ export default function RedirectQrClaimPage() {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- Authorization: `Bearer ${Cookies.get('jwt-token')}`,
+ Authorization: `Bearer ${getAuthToken()}`,
},
body: JSON.stringify({
targetUrl: inviteLink, // Pass the correctly formatted invite link
diff --git a/src/app/(setup)/setup/page.tsx b/src/app/(setup)/setup/page.tsx
index 2056c9f14..c7b3ecc81 100644
--- a/src/app/(setup)/setup/page.tsx
+++ b/src/app/(setup)/setup/page.tsx
@@ -10,6 +10,7 @@ import { Suspense, useEffect, useState } from 'react'
import { setupSteps as masterSetupSteps } from '../../../components/Setup/Setup.consts'
import UnsupportedBrowserModal from '@/components/Global/UnsupportedBrowserModal'
import { isLikelyWebview, isDeviceOsSupported } from '@/components/Setup/Setup.utils'
+import { isCapacitor } from '@/utils/capacitor'
import { getFromCookie } from '@/utils/general.utils'
import { DeviceType, useDeviceType } from '@/hooks/useGetDeviceType'
import { useAuth } from '@/context/authContext'
@@ -41,17 +42,40 @@ function SetupPageContent() {
setIsLoading(true)
await new Promise((resolve) => setTimeout(resolve, 100)) // ensure other initializations can complete
- // check for native passkey support
+ const localDeviceType = detectedDeviceType
+
+ // in capacitor, passkeys are handled natively — skip all browser/webview/os/pwa checks
+ // and go straight to the landing (signup) flow
+ if (isCapacitor()) {
+ setDeviceType(localDeviceType)
+ // check for invite code — if present, go to signup instead of landing
+ const inviteCodeFromCookie = getFromCookie('inviteCode')
+ const userInviteCode = inviteCode || inviteCodeFromCookie
+ const targetStep = userInviteCode ? 'signup' : 'landing'
+ const stepIndex = steps.findIndex((s: ISetupStep) => s.screenId === targetStep)
+ if (stepIndex !== -1) {
+ dispatch(setupActions.setStep(stepIndex + 1))
+ }
+ setIsLoading(false)
+ return
+ }
+
+ // check if device has a platform authenticator (biometric/pin)
+ // using UVPA instead of isConditionalMediationAvailable because:
+ // - conditional mediation is about autofill ui, not actual passkey support
+ // - isConditionalMediationAvailable returns false in android webviews even when passkeys work
+ // - UVPA correctly detects whether the device can do passkeys
let passkeySupport = true
try {
- passkeySupport = await PublicKeyCredential.isConditionalMediationAvailable()
+ if (PublicKeyCredential?.isUserVerifyingPlatformAuthenticatorAvailable) {
+ passkeySupport = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
+ }
} catch (e) {
passkeySupport = false
console.error('Error checking passkey support:', e)
}
const ua = typeof navigator !== 'undefined' ? navigator.userAgent : ''
- const localDeviceType = detectedDeviceType
const osSupportedByVersion = isDeviceOsSupported(ua)
const webviewByUASignature = isLikelyWebview() // initial webview check based on ua signatures
diff --git a/src/app/actions/bridge/get-customer.ts b/src/app/actions/bridge/get-customer.ts
index 94e5fccf5..bf5bf265d 100644
--- a/src/app/actions/bridge/get-customer.ts
+++ b/src/app/actions/bridge/get-customer.ts
@@ -1,8 +1,7 @@
-'use server'
-
-import { unstable_cache } from 'next/cache'
+import { unstable_cache } from '@/utils/no-cache'
import { countryData } from '@/components/AddMoney/consts'
-import { PEANUT_API_KEY, PEANUT_API_URL } from '@/constants/general.consts'
+import { PEANUT_API_URL } from '@/constants/general.consts'
+import { getAuthHeaders } from '@/utils/auth-token'
type BridgeCustomer = {
id: string
@@ -41,10 +40,7 @@ export const getBridgeCustomerCountry = async (
const runner = unstable_cache(
async () => {
const response = await fetch(`${PEANUT_API_URL}/bridge/customers/${bridgeCustomerId}` as string, {
- headers: {
- 'Content-Type': 'application/json',
- 'api-key': PEANUT_API_KEY,
- },
+ headers: getAuthHeaders(),
cache: 'no-store',
})
diff --git a/src/app/actions/card.ts b/src/app/actions/card.ts
index b6436ea3d..c2892e02a 100644
--- a/src/app/actions/card.ts
+++ b/src/app/actions/card.ts
@@ -1,13 +1,9 @@
-'use server'
+// card api calls — works in both web (server action) and native (client-side)
+// migrated from 'use server' to support capacitor static export
import { PEANUT_API_URL } from '@/constants/general.consts'
import { fetchWithSentry } from '@/utils/sentry.utils'
-import { getJWTCookie } from '@/utils/cookie-migration.utils'
-
-const API_KEY = process.env.PEANUT_API_KEY
-if (!API_KEY) {
- throw new Error('PEANUT_API_KEY environment variable is not set')
-}
+import { getAuthHeaders } from '@/utils/auth-token'
export interface CardInfoResponse {
hasPurchased: boolean
@@ -43,18 +39,10 @@ export interface CardErrorResponse {
* Get card pioneer info for the authenticated user
*/
export const getCardInfo = async (): Promise<{ data?: CardInfoResponse; error?: string }> => {
- const jwtToken = (await getJWTCookie())?.value
- if (!jwtToken) {
- return { error: 'Authentication required' }
- }
-
try {
const response = await fetchWithSentry(`${PEANUT_API_URL}/card`, {
method: 'GET',
- headers: {
- Authorization: `Bearer ${jwtToken}`,
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
})
if (!response.ok) {
@@ -73,19 +61,10 @@ export const getCardInfo = async (): Promise<{ data?: CardInfoResponse; error?:
* Initiate card pioneer purchase
*/
export const purchaseCard = async (): Promise<{ data?: CardPurchaseResponse; error?: string; errorCode?: string }> => {
- const jwtToken = (await getJWTCookie())?.value
- if (!jwtToken) {
- return { error: 'Authentication required', errorCode: 'NOT_AUTHENTICATED' }
- }
-
try {
const response = await fetchWithSentry(`${PEANUT_API_URL}/card/purchase`, {
method: 'POST',
- headers: {
- Authorization: `Bearer ${jwtToken}`,
- 'api-key': API_KEY,
- 'Content-Type': 'application/json',
- },
+ headers: getAuthHeaders(),
body: JSON.stringify({}),
})
diff --git a/src/app/actions/claimLinks.ts b/src/app/actions/claimLinks.ts
index e3feeeffe..2007f32b2 100644
--- a/src/app/actions/claimLinks.ts
+++ b/src/app/actions/claimLinks.ts
@@ -1,5 +1,4 @@
-'use server'
-import { unstable_cache } from 'next/cache'
+import { unstable_cache } from '@/utils/no-cache'
import peanut, { interfaces as peanutInterfaces } from '@squirrel-labs/peanut-sdk'
import type { Address, Hash } from 'viem'
import { getContract } from 'viem'
diff --git a/src/app/actions/currency.ts b/src/app/actions/currency.ts
index 39a1d84be..bcaa5883c 100644
--- a/src/app/actions/currency.ts
+++ b/src/app/actions/currency.ts
@@ -1,4 +1,3 @@
-'use server'
import { getExchangeRate } from './exchange-rate'
import { AccountType } from '@/interfaces'
import { mantecaApi } from '@/services/manteca'
diff --git a/src/app/actions/ens.ts b/src/app/actions/ens.ts
index 6e9998134..fca5a7883 100644
--- a/src/app/actions/ens.ts
+++ b/src/app/actions/ens.ts
@@ -1,16 +1,12 @@
-'use server'
-import { unstable_cache } from 'next/cache'
+import { unstable_cache } from '@/utils/no-cache'
import { fetchWithSentry } from '@/utils/sentry.utils'
import { PEANUT_API_URL } from '@/constants/general.consts'
-
-const API_KEY = process.env.PEANUT_API_KEY!
+import { getAuthHeaders } from '@/utils/auth-token'
export const resolveEns = unstable_cache(
async (ensName: string): Promise => {
const response = await fetchWithSentry(`${PEANUT_API_URL}/ens/${ensName}`, {
- headers: {
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
})
if (response.status === 404) return undefined
diff --git a/src/app/actions/exchange-rate.ts b/src/app/actions/exchange-rate.ts
index 0ed97f55b..273b5370c 100644
--- a/src/app/actions/exchange-rate.ts
+++ b/src/app/actions/exchange-rate.ts
@@ -1,9 +1,7 @@
-'use server'
-
import { fetchWithSentry } from '@/utils/sentry.utils'
import { AccountType } from '@/interfaces'
-
-const API_KEY = process.env.PEANUT_API_KEY!
+import { PEANUT_API_URL } from '@/constants/general.consts'
+import { getAuthHeaders } from '@/utils/auth-token'
export interface ExchangeRateResponse {
from: string
@@ -15,7 +13,7 @@ export interface ExchangeRateResponse {
}
/**
- * Server Action to fetch the current exchange rate for a given bank account type.
+ * Fetch the current exchange rate for a given bank account type.
*
* This calls the `/bridge/exchange-rate` API endpoint.
*
@@ -25,23 +23,13 @@ export interface ExchangeRateResponse {
export async function getExchangeRate(
accountType: AccountType
): Promise<{ data?: ExchangeRateResponse; error?: string }> {
- const apiUrl = process.env.PEANUT_API_URL
-
- if (!apiUrl || !API_KEY) {
- console.error('API URL or API Key is not configured.')
- return { error: 'Server configuration error.' }
- }
-
try {
- const url = new URL(`${apiUrl}/bridge/exchange-rate`)
+ const url = new URL(`${PEANUT_API_URL}/bridge/exchange-rate`)
url.searchParams.append('accountType', accountType)
const response = await fetchWithSentry(url.toString(), {
method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
})
const data = await response.json()
diff --git a/src/app/actions/external-accounts.ts b/src/app/actions/external-accounts.ts
index bc47f33bb..d97ad533f 100644
--- a/src/app/actions/external-accounts.ts
+++ b/src/app/actions/external-accounts.ts
@@ -1,23 +1,17 @@
-'use server'
-
import { fetchWithSentry } from '@/utils/sentry.utils'
import { type AddBankAccountPayload } from './types/users.types'
import { type IBridgeAccount } from '@/interfaces'
-
-const API_KEY = process.env.PEANUT_API_KEY!
-const API_URL = process.env.PEANUT_API_URL!
+import { PEANUT_API_URL } from '@/constants/general.consts'
+import { getAuthHeaders } from '@/utils/auth-token'
export async function createBridgeExternalAccountForGuest(
customerId: string,
accountDetails: AddBankAccountPayload
): Promise {
try {
- const response = await fetchWithSentry(`${API_URL}/bridge/customers/${customerId}/external-accounts`, {
+ const response = await fetchWithSentry(`${PEANUT_API_URL}/bridge/customers/${customerId}/external-accounts`, {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
body: JSON.stringify({ ...accountDetails, reuseOnError: true }), // note: reuseOnError is used to avoid showing errors for duplicate accounts on guest flow
})
diff --git a/src/app/actions/history.ts b/src/app/actions/history.ts
index fc6d163ef..e92441982 100644
--- a/src/app/actions/history.ts
+++ b/src/app/actions/history.ts
@@ -1,5 +1,3 @@
-'use server'
-
import { EHistoryEntryType, completeHistoryEntry } from '@/utils/history.utils'
import type { HistoryEntry } from '@/utils/history.utils'
import { PEANUT_API_URL } from '@/constants/general.consts'
diff --git a/src/app/actions/ibanToBic.ts b/src/app/actions/ibanToBic.ts
index 830182065..cfa3188bb 100644
--- a/src/app/actions/ibanToBic.ts
+++ b/src/app/actions/ibanToBic.ts
@@ -1,5 +1,3 @@
-'use server'
-
// @ts-ignore: CommonJS module without types
import { ibanToBic } from 'iban-to-bic'
diff --git a/src/app/actions/invites.ts b/src/app/actions/invites.ts
index eff8d6d0e..7dc17083a 100644
--- a/src/app/actions/invites.ts
+++ b/src/app/actions/invites.ts
@@ -1,27 +1,14 @@
-'use server'
-
import { fetchWithSentry } from '@/utils/sentry.utils'
import { PEANUT_API_URL } from '@/constants/general.consts'
-
-const API_KEY = process.env.PEANUT_API_KEY!
+import { getAuthHeaders } from '@/utils/auth-token'
export async function validateInviteCode(
inviteCode: string
): Promise<{ data?: { success: boolean; username: string }; error?: string }> {
- const apiUrl = PEANUT_API_URL
-
- if (!apiUrl || !API_KEY) {
- console.error('API URL or API Key is not configured.')
- return { error: 'Server configuration error.' }
- }
-
try {
- const response = await fetchWithSentry(`${apiUrl}/invites/validate`, {
+ const response = await fetchWithSentry(`${PEANUT_API_URL}/invites/validate`, {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
body: JSON.stringify({ inviteCode }),
})
diff --git a/src/app/actions/offramp.ts b/src/app/actions/offramp.ts
index b4d9a45dd..c9f0c91cf 100644
--- a/src/app/actions/offramp.ts
+++ b/src/app/actions/offramp.ts
@@ -1,10 +1,7 @@
-'use server'
-
import { type TCreateOfframpRequest } from '../../services/services.types'
import { fetchWithSentry } from '@/utils/sentry.utils'
-import { getJWTCookie } from '@/utils/cookie-migration.utils'
-
-const API_KEY = process.env.PEANUT_API_KEY!
+import { PEANUT_API_URL } from '@/constants/general.consts'
+import { getAuthHeaders } from '@/utils/auth-token'
export type CreateOfframpSuccessResponse = {
transferId: string
@@ -15,7 +12,7 @@ export type CreateOfframpSuccessResponse = {
}
/**
- * server Action to initiate an off-ramp transfer.
+ * Initiate an off-ramp transfer.
*
* calls the `/bridge/offramp/create` API endpoint to create the transfer
* and returns the provider's instructions for the user to deposit funds
@@ -26,27 +23,10 @@ export type CreateOfframpSuccessResponse = {
export async function createOfframp(
params: TCreateOfframpRequest
): Promise<{ data?: CreateOfframpSuccessResponse; error?: string }> {
- const apiUrl = process.env.PEANUT_API_URL
-
- if (!apiUrl || !API_KEY) {
- console.error('API URL or API Key is not configured.')
- return { error: 'Server configuration error.' }
- }
-
try {
- const jwtToken = (await getJWTCookie())?.value
-
- if (!jwtToken) {
- return { error: 'Authentication token not found.' }
- }
-
- const response = await fetchWithSentry(`${apiUrl}/bridge/offramp/create`, {
+ const response = await fetchWithSentry(`${PEANUT_API_URL}/bridge/offramp/create`, {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${jwtToken}`,
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
body: JSON.stringify({
...params,
provider: 'bridge', // note: bridge is currently the only provider
@@ -72,20 +52,10 @@ export async function createOfframp(
export async function createOfframpForGuest(
params: TCreateOfframpRequest
): Promise<{ data?: CreateOfframpSuccessResponse; error?: string }> {
- const apiUrl = process.env.PEANUT_API_URL
-
- if (!apiUrl || !API_KEY) {
- console.error('API URL or API Key is not configured.')
- return { error: 'Server configuration error.' }
- }
-
try {
- const response = await fetchWithSentry(`${apiUrl}/bridge/offramp/create-for-guest`, {
+ const response = await fetchWithSentry(`${PEANUT_API_URL}/bridge/offramp/create-for-guest`, {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
body: JSON.stringify({
...params,
provider: 'bridge',
@@ -109,7 +79,7 @@ export async function createOfframpForGuest(
}
/**
- * Server Action to confirm an off-ramp transfer after the user has sent funds.
+ * Confirm an off-ramp transfer after the user has sent funds.
*
* this calls the `/bridge/transfers/:transferId/confirm` API endpoint, providing
* the on-chain transaction hash. This makes the transfer visible in the user's history.
@@ -122,27 +92,10 @@ export async function confirmOfframp(
transferId: string,
txHash: string
): Promise<{ data?: { success: boolean }; error?: string }> {
- const apiUrl = process.env.PEANUT_API_URL
-
- if (!apiUrl || !API_KEY) {
- console.error('API URL or API Key is not configured.')
- return { error: 'Server configuration error.' }
- }
-
try {
- const jwtToken = (await getJWTCookie())?.value
-
- if (!jwtToken) {
- return { error: 'Authentication token not found.' }
- }
-
- const response = await fetchWithSentry(`${apiUrl}/bridge/transfers/${transferId}/confirm`, {
+ const response = await fetchWithSentry(`${PEANUT_API_URL}/bridge/transfers/${transferId}/confirm`, {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${jwtToken}`,
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
body: JSON.stringify({ txHash }),
})
diff --git a/src/app/actions/onramp.ts b/src/app/actions/onramp.ts
index d4b4a4434..7fbec274f 100644
--- a/src/app/actions/onramp.ts
+++ b/src/app/actions/onramp.ts
@@ -1,12 +1,9 @@
-'use server'
-
import { fetchWithSentry } from '@/utils/sentry.utils'
import { type CountryData } from '@/components/AddMoney/consts'
import { getCurrencyConfig } from '@/utils/bridge.utils'
import { getCurrencyPrice } from '@/app/actions/currency'
-import { getJWTCookie } from '@/utils/cookie-migration.utils'
-
-const API_KEY = process.env.PEANUT_API_KEY!
+import { PEANUT_API_URL } from '@/constants/general.consts'
+import { getAuthHeaders } from '@/utils/auth-token'
export interface CreateOnrampGuestParams {
amount: string
@@ -16,7 +13,7 @@ export interface CreateOnrampGuestParams {
}
/**
- * Server Action to cancel an on-ramp transfer.
+ * Cancel an on-ramp transfer.
*
* calls the `/bridge/onramp/:transferId/cancel` API endpoint to cancel the transfer
* and returns the success status or error message.
@@ -25,27 +22,10 @@ export interface CreateOnrampGuestParams {
* @returns An object containing either the successful response data or an error.
*/
export async function cancelOnramp(transferId: string): Promise<{ data?: { success: boolean }; error?: string }> {
- const apiUrl = process.env.PEANUT_API_URL
-
- if (!apiUrl || !API_KEY) {
- console.error('API URL or API Key is not configured.')
- return { error: 'Server configuration error.' }
- }
-
try {
- const jwtToken = (await getJWTCookie())?.value
-
- if (!jwtToken) {
- return { error: 'Authentication token not found.' }
- }
-
- const response = await fetchWithSentry(`${apiUrl}/bridge/onramp/${transferId}/cancel`, {
+ const response = await fetchWithSentry(`${PEANUT_API_URL}/bridge/onramp/${transferId}/cancel`, {
method: 'DELETE',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${jwtToken}`,
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
})
if (!response.ok) {
@@ -66,24 +46,14 @@ export async function cancelOnramp(transferId: string): Promise<{ data?: { succe
export async function createOnrampForGuest(
params: CreateOnrampGuestParams
): Promise<{ data?: { success: boolean }; error?: string }> {
- const apiUrl = process.env.PEANUT_API_URL
-
- if (!apiUrl || !API_KEY) {
- console.error('API URL or API Key is not configured.')
- return { error: 'Server configuration error.' }
- }
-
try {
const { currency, paymentRail } = getCurrencyConfig(params.country.id, 'onramp')
const price = await getCurrencyPrice(currency)
const amount = (Number(params.amount) * price.buy).toFixed(2)
- const response = await fetchWithSentry(`${apiUrl}/bridge/onramp/create-for-guest`, {
+ const response = await fetchWithSentry(`${PEANUT_API_URL}/bridge/onramp/create-for-guest`, {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
body: JSON.stringify({
amount,
userId: params.userId,
diff --git a/src/app/actions/squid.ts b/src/app/actions/squid.ts
index 38b7777c0..ef249f4c7 100644
--- a/src/app/actions/squid.ts
+++ b/src/app/actions/squid.ts
@@ -1,7 +1,5 @@
-'use server'
-
import { getSquidChains, getSquidTokens } from '@squirrel-labs/peanut-sdk'
-import { unstable_cache } from 'next/cache'
+import { unstable_cache } from '@/utils/no-cache'
import { interfaces } from '@squirrel-labs/peanut-sdk'
import { supportedPeanutChains } from '@/constants/general.consts'
diff --git a/src/app/actions/sumsub.ts b/src/app/actions/sumsub.ts
index 7222004c6..c8332ce08 100644
--- a/src/app/actions/sumsub.ts
+++ b/src/app/actions/sumsub.ts
@@ -1,23 +1,13 @@
-'use server'
-
import { type InitiateSumsubKycResponse, type KYCRegionIntent } from './types/sumsub.types'
import { fetchWithSentry } from '@/utils/sentry.utils'
import { PEANUT_API_URL } from '@/constants/general.consts'
-import { getJWTCookie } from '@/utils/cookie-migration.utils'
-
-const API_KEY = process.env.PEANUT_API_KEY!
+import { getAuthHeaders } from '@/utils/auth-token'
// initiate kyc flow (using sumsub) and get websdk access token
export const initiateSumsubKyc = async (params?: {
regionIntent?: KYCRegionIntent
levelName?: string
}): Promise<{ data?: InitiateSumsubKycResponse; error?: string }> => {
- const jwtToken = (await getJWTCookie())?.value
-
- if (!jwtToken) {
- return { error: 'Authentication required' }
- }
-
const body: Record = {
regionIntent: params?.regionIntent,
levelName: params?.levelName,
@@ -26,11 +16,7 @@ export const initiateSumsubKyc = async (params?: {
try {
const response = await fetchWithSentry(`${PEANUT_API_URL}/users/identity`, {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${jwtToken}`,
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
body: JSON.stringify(body),
})
diff --git a/src/app/actions/tokens.ts b/src/app/actions/tokens.ts
index d84632811..a8881155a 100644
--- a/src/app/actions/tokens.ts
+++ b/src/app/actions/tokens.ts
@@ -1,5 +1,4 @@
-'use server'
-import { unstable_cache } from 'next/cache'
+import { unstable_cache } from '@/utils/no-cache'
import {
isAddressZero,
estimateIfIsStableCoinFromPrice,
diff --git a/src/app/actions/users.ts b/src/app/actions/users.ts
index 07e1498ef..630ac5e96 100644
--- a/src/app/actions/users.ts
+++ b/src/app/actions/users.ts
@@ -1,26 +1,16 @@
-'use server'
-
import { type ApiUser } from '@/services/users'
import { fetchWithSentry } from '@/utils/sentry.utils'
import { type AddBankAccountPayload, BridgeEndorsementType, type InitiateKycResponse } from './types/users.types'
import { type User } from '@/interfaces'
import { type ContactsResponse } from '@/interfaces'
import { PEANUT_API_URL } from '@/constants/general.consts'
-import { getJWTCookie } from '@/utils/cookie-migration.utils'
-
-const API_KEY = process.env.PEANUT_API_KEY!
+import { getAuthHeaders } from '@/utils/auth-token'
export const updateUserById = async (payload: Record): Promise<{ data?: ApiUser; error?: string }> => {
- const jwtToken = (await getJWTCookie())?.value
-
try {
const response = await fetchWithSentry(`${PEANUT_API_URL}/update-user`, {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${jwtToken}`,
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
body: JSON.stringify(payload),
})
@@ -38,15 +28,10 @@ export const updateUserById = async (payload: Record): Promise<{ da
export const getKycDetails = async (params?: {
endorsements: BridgeEndorsementType[]
}): Promise<{ data?: InitiateKycResponse; error?: string }> => {
- const jwtToken = (await getJWTCookie())?.value
try {
const response = await fetchWithSentry(`${PEANUT_API_URL}/users/initiate-kyc`, {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${jwtToken}`,
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
body: JSON.stringify(params || {}),
})
@@ -66,16 +51,10 @@ export const getKycDetails = async (params?: {
}
export const addBankAccount = async (payload: AddBankAccountPayload): Promise<{ data?: any; error?: string }> => {
- const jwtToken = (await getJWTCookie())?.value
-
try {
const response = await fetchWithSentry(`${PEANUT_API_URL}/users/accounts`, {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${jwtToken}`,
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
body: JSON.stringify(payload),
})
@@ -98,10 +77,7 @@ export async function getUserById(userId: string): Promise {
try {
const response = await fetchWithSentry(`${PEANUT_API_URL}/users/${userId}`, {
method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
})
if (!response.ok) {
@@ -123,12 +99,6 @@ export async function getContacts(params: {
offset: number
search?: string
}): Promise<{ data?: ContactsResponse; error?: string }> {
- const jwtToken = (await getJWTCookie())?.value
-
- if (!jwtToken) {
- throw new Error('Not authenticated')
- }
-
try {
const queryParams = new URLSearchParams({
limit: params.limit.toString(),
@@ -142,11 +112,7 @@ export async function getContacts(params: {
const response = await fetchWithSentry(`${PEANUT_API_URL}/users/contacts?${queryParams}`, {
method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'api-key': API_KEY,
- Authorization: `Bearer ${jwtToken}`,
- },
+ headers: getAuthHeaders(),
})
if (!response.ok) {
@@ -163,15 +129,10 @@ export async function getContacts(params: {
// fetch bridge ToS acceptance link for users with pending ToS
export const getBridgeTosLink = async (): Promise<{ data?: { tosLink: string }; error?: string }> => {
- const jwtToken = (await getJWTCookie())?.value
try {
const response = await fetchWithSentry(`${PEANUT_API_URL}/users/bridge-tos-link`, {
method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${jwtToken}`,
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
})
const responseJson = await response.json()
if (!response.ok) {
@@ -185,15 +146,10 @@ export const getBridgeTosLink = async (): Promise<{ data?: { tosLink: string };
// confirm bridge ToS acceptance after user closes the ToS iframe
export const confirmBridgeTos = async (): Promise<{ data?: { accepted: boolean }; error?: string }> => {
- const jwtToken = (await getJWTCookie())?.value
try {
const response = await fetchWithSentry(`${PEANUT_API_URL}/users/bridge-tos-confirm`, {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${jwtToken}`,
- 'api-key': API_KEY,
- },
+ headers: getAuthHeaders(),
body: JSON.stringify({}),
})
const responseJson = await response.json()
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 4e88336ff..07e233c46 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -161,7 +161,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
{process.env.NODE_ENV !== 'development' && (