diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..d6eee66f
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+apps/dashboard/android/gradlew text eol=lf
diff --git a/.github/workflows/wallet-release.yml b/.github/workflows/wallet-release.yml
new file mode 100644
index 00000000..6c406b74
--- /dev/null
+++ b/.github/workflows/wallet-release.yml
@@ -0,0 +1,157 @@
+name: Wallet release
+
+on:
+ workflow_dispatch:
+ push:
+ tags:
+ - "wallet-v*"
+
+permissions:
+ contents: write
+
+jobs:
+ desktop:
+ name: Desktop wallet (${{ matrix.name }})
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - name: windows
+ os: windows-latest
+ command: npm run desktop:installer:win
+ artifact: apps/dashboard/release/*
+ - name: macos
+ os: macos-latest
+ command: npm run desktop:installer:mac
+ artifact: apps/dashboard/release/*
+ - name: linux
+ os: ubuntu-latest
+ command: npm run desktop:installer:linux
+ artifact: apps/dashboard/release/*
+
+ defaults:
+ run:
+ working-directory: apps/dashboard
+
+ env:
+ CSC_IDENTITY_AUTO_DISCOVERY: "false"
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: "24"
+ cache: npm
+ cache-dependency-path: apps/dashboard/package-lock.json
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run tests
+ run: npm test
+
+ - name: Build desktop app
+ run: ${{ matrix.command }}
+
+ - name: Upload desktop artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: flowchain-wallet-desktop-${{ matrix.name }}
+ path: ${{ matrix.artifact }}
+ if-no-files-found: error
+
+ android:
+ name: Android wallet APK
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: apps/dashboard
+ env:
+ HAS_ANDROID_RELEASE_KEYSTORE: ${{ secrets.FLOWCHAIN_ANDROID_KEYSTORE_BASE64 != '' }}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: "24"
+ cache: npm
+ cache-dependency-path: apps/dashboard/package-lock.json
+
+ - name: Set up Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: "21"
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Sync Android project
+ run: npm run mobile:android:sync
+
+ - name: Make Gradle wrapper executable
+ run: chmod +x ./gradlew
+ working-directory: apps/dashboard/android
+
+ - name: Build debug APK
+ run: ./gradlew assembleDebug
+ working-directory: apps/dashboard/android
+
+ - name: Decode Android release keystore
+ if: env.HAS_ANDROID_RELEASE_KEYSTORE == 'true'
+ shell: bash
+ run: |
+ echo "$FLOWCHAIN_ANDROID_KEYSTORE_BASE64" | base64 -d > flowchain-release.keystore
+ env:
+ FLOWCHAIN_ANDROID_KEYSTORE_BASE64: ${{ secrets.FLOWCHAIN_ANDROID_KEYSTORE_BASE64 }}
+
+ - name: Build signed release APK
+ if: env.HAS_ANDROID_RELEASE_KEYSTORE == 'true'
+ run: ./gradlew assembleRelease
+ working-directory: apps/dashboard/android
+ env:
+ FLOWCHAIN_ANDROID_KEYSTORE_FILE: ${{ github.workspace }}/apps/dashboard/flowchain-release.keystore
+ FLOWCHAIN_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.FLOWCHAIN_ANDROID_KEYSTORE_PASSWORD }}
+ FLOWCHAIN_ANDROID_KEY_ALIAS: ${{ secrets.FLOWCHAIN_ANDROID_KEY_ALIAS }}
+ FLOWCHAIN_ANDROID_KEY_PASSWORD: ${{ secrets.FLOWCHAIN_ANDROID_KEY_PASSWORD }}
+
+ - name: Upload Android APKs
+ uses: actions/upload-artifact@v4
+ with:
+ name: flowchain-wallet-android
+ path: |
+ apps/dashboard/android/app/build/outputs/apk/debug/*.apk
+ apps/dashboard/android/app/build/outputs/apk/release/*.apk
+ if-no-files-found: error
+
+ github-release:
+ name: Publish tagged GitHub release
+ runs-on: ubuntu-latest
+ needs:
+ - desktop
+ - android
+ if: startsWith(github.ref, 'refs/tags/wallet-v')
+ steps:
+ - name: Download artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: release-artifacts
+
+ - name: Publish release
+ shell: bash
+ env:
+ GH_TOKEN: ${{ github.token }}
+ GH_REPO: ${{ github.repository }}
+ run: |
+ gh release create "$GITHUB_REF_NAME" \
+ --title "Flowchain Wallet $GITHUB_REF_NAME" \
+ --notes "Desktop and Android wallet builds generated from $GITHUB_SHA."
+ find release-artifacts -type f -print0 \
+ | xargs -0 -I {} gh release upload "$GITHUB_REF_NAME" "{}" --clobber
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 00000000..c157b76b
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+cache=./devnet/local/npm-cache
+update-notifier=false
diff --git a/apps/dashboard/.gitignore b/apps/dashboard/.gitignore
index 361bf2de..4f5e528c 100644
--- a/apps/dashboard/.gitignore
+++ b/apps/dashboard/.gitignore
@@ -1,5 +1,6 @@
node_modules/
dist/
+release/
.vite/
coverage/
*.tsbuildinfo
diff --git a/apps/dashboard/WALLET_DISTRIBUTION.md b/apps/dashboard/WALLET_DISTRIBUTION.md
new file mode 100644
index 00000000..1433f63c
--- /dev/null
+++ b/apps/dashboard/WALLET_DISTRIBUTION.md
@@ -0,0 +1,43 @@
+# Flowchain Wallet Distribution
+
+The wallet is distributed as native desktop builds through Electron and as a native Android shell through Capacitor.
+
+## Local Windows Desktop Build
+
+```powershell
+npm run desktop:installer:win --prefix apps/dashboard
+```
+
+Artifacts are written to `apps/dashboard/release/`.
+
+For a local unsigned unpacked build plus zip:
+
+```powershell
+npm run desktop:pack --prefix apps/dashboard
+```
+
+## Android Build
+
+The Android app source lives in `apps/dashboard/android`.
+
+```powershell
+npm run mobile:android:sync --prefix apps/dashboard
+```
+
+Building an APK requires Java and the Android SDK. On CI, `.github/workflows/wallet-release.yml` builds a debug APK automatically. For a signed release APK, add these repository secrets:
+
+- `FLOWCHAIN_ANDROID_KEYSTORE_BASE64`
+- `FLOWCHAIN_ANDROID_KEYSTORE_PASSWORD`
+- `FLOWCHAIN_ANDROID_KEY_ALIAS`
+- `FLOWCHAIN_ANDROID_KEY_PASSWORD`
+
+## Public Downloads
+
+Run the `Wallet release` GitHub workflow manually for downloadable CI artifacts, or push a tag like:
+
+```powershell
+git tag wallet-v0.0.0
+git push origin wallet-v0.0.0
+```
+
+Tagged runs publish a GitHub Release containing the desktop and Android artifacts.
diff --git a/apps/dashboard/android/.gitignore b/apps/dashboard/android/.gitignore
new file mode 100644
index 00000000..48354a3d
--- /dev/null
+++ b/apps/dashboard/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/apps/dashboard/android/app/.gitignore b/apps/dashboard/android/app/.gitignore
new file mode 100644
index 00000000..043df802
--- /dev/null
+++ b/apps/dashboard/android/app/.gitignore
@@ -0,0 +1,2 @@
+/build/*
+!/build/.npmkeep
diff --git a/apps/dashboard/android/app/build.gradle b/apps/dashboard/android/app/build.gradle
new file mode 100644
index 00000000..7c046d0d
--- /dev/null
+++ b/apps/dashboard/android/app/build.gradle
@@ -0,0 +1,76 @@
+apply plugin: 'com.android.application'
+
+def flowchainReleaseKeystore = System.getenv("FLOWCHAIN_ANDROID_KEYSTORE_FILE")
+def flowchainReleaseStorePassword = System.getenv("FLOWCHAIN_ANDROID_KEYSTORE_PASSWORD")
+def flowchainReleaseKeyAlias = System.getenv("FLOWCHAIN_ANDROID_KEY_ALIAS")
+def flowchainReleaseKeyPassword = System.getenv("FLOWCHAIN_ANDROID_KEY_PASSWORD")
+def hasFlowchainReleaseSigning = flowchainReleaseKeystore
+ && flowchainReleaseStorePassword
+ && flowchainReleaseKeyAlias
+ && flowchainReleaseKeyPassword
+
+android {
+ namespace = "ai.flowmemory.flowchain.wallet"
+ compileSdk = rootProject.ext.compileSdkVersion
+ defaultConfig {
+ applicationId "ai.flowmemory.flowchain.wallet"
+ 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:!*~'
+ }
+ }
+ signingConfigs {
+ release {
+ if (hasFlowchainReleaseSigning) {
+ storeFile file(flowchainReleaseKeystore)
+ storePassword flowchainReleaseStorePassword
+ keyAlias flowchainReleaseKeyAlias
+ keyPassword flowchainReleaseKeyPassword
+ }
+ }
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ if (hasFlowchainReleaseSigning) {
+ signingConfig signingConfigs.release
+ }
+ 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')
+ 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/apps/dashboard/android/app/capacitor.build.gradle b/apps/dashboard/android/app/capacitor.build.gradle
new file mode 100644
index 00000000..bbfb44fa
--- /dev/null
+++ b/apps/dashboard/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 {
+
+
+}
+
+
+if (hasProperty('postBuildExtras')) {
+ postBuildExtras()
+}
diff --git a/apps/dashboard/android/app/proguard-rules.pro b/apps/dashboard/android/app/proguard-rules.pro
new file mode 100644
index 00000000..f1b42451
--- /dev/null
+++ b/apps/dashboard/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/apps/dashboard/android/app/src/androidTest/java/ai/flowmemory/flowchain/wallet/ExampleInstrumentedTest.java b/apps/dashboard/android/app/src/androidTest/java/ai/flowmemory/flowchain/wallet/ExampleInstrumentedTest.java
new file mode 100644
index 00000000..83098e70
--- /dev/null
+++ b/apps/dashboard/android/app/src/androidTest/java/ai/flowmemory/flowchain/wallet/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package ai.flowmemory.flowchain.wallet;
+
+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("ai.flowmemory.flowchain.wallet", appContext.getPackageName());
+ }
+}
diff --git a/apps/dashboard/android/app/src/main/AndroidManifest.xml b/apps/dashboard/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..b06ddbfd
--- /dev/null
+++ b/apps/dashboard/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/dashboard/android/app/src/main/java/ai/flowmemory/flowchain/wallet/MainActivity.java b/apps/dashboard/android/app/src/main/java/ai/flowmemory/flowchain/wallet/MainActivity.java
new file mode 100644
index 00000000..dabd1d65
--- /dev/null
+++ b/apps/dashboard/android/app/src/main/java/ai/flowmemory/flowchain/wallet/MainActivity.java
@@ -0,0 +1,5 @@
+package ai.flowmemory.flowchain.wallet;
+
+import com.getcapacitor.BridgeActivity;
+
+public class MainActivity extends BridgeActivity {}
diff --git a/apps/dashboard/android/app/src/main/res/drawable-land-hdpi/splash.png b/apps/dashboard/android/app/src/main/res/drawable-land-hdpi/splash.png
new file mode 100644
index 00000000..e31573b4
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/drawable-land-hdpi/splash.png differ
diff --git a/apps/dashboard/android/app/src/main/res/drawable-land-mdpi/splash.png b/apps/dashboard/android/app/src/main/res/drawable-land-mdpi/splash.png
new file mode 100644
index 00000000..f7a64923
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/drawable-land-mdpi/splash.png differ
diff --git a/apps/dashboard/android/app/src/main/res/drawable-land-xhdpi/splash.png b/apps/dashboard/android/app/src/main/res/drawable-land-xhdpi/splash.png
new file mode 100644
index 00000000..80772550
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/drawable-land-xhdpi/splash.png differ
diff --git a/apps/dashboard/android/app/src/main/res/drawable-land-xxhdpi/splash.png b/apps/dashboard/android/app/src/main/res/drawable-land-xxhdpi/splash.png
new file mode 100644
index 00000000..14c6c8fe
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/drawable-land-xxhdpi/splash.png differ
diff --git a/apps/dashboard/android/app/src/main/res/drawable-land-xxxhdpi/splash.png b/apps/dashboard/android/app/src/main/res/drawable-land-xxxhdpi/splash.png
new file mode 100644
index 00000000..244ca250
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/drawable-land-xxxhdpi/splash.png differ
diff --git a/apps/dashboard/android/app/src/main/res/drawable-port-hdpi/splash.png b/apps/dashboard/android/app/src/main/res/drawable-port-hdpi/splash.png
new file mode 100644
index 00000000..74faaa58
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/drawable-port-hdpi/splash.png differ
diff --git a/apps/dashboard/android/app/src/main/res/drawable-port-mdpi/splash.png b/apps/dashboard/android/app/src/main/res/drawable-port-mdpi/splash.png
new file mode 100644
index 00000000..e944f4ad
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/drawable-port-mdpi/splash.png differ
diff --git a/apps/dashboard/android/app/src/main/res/drawable-port-xhdpi/splash.png b/apps/dashboard/android/app/src/main/res/drawable-port-xhdpi/splash.png
new file mode 100644
index 00000000..564a82ff
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/drawable-port-xhdpi/splash.png differ
diff --git a/apps/dashboard/android/app/src/main/res/drawable-port-xxhdpi/splash.png b/apps/dashboard/android/app/src/main/res/drawable-port-xxhdpi/splash.png
new file mode 100644
index 00000000..bfabe687
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/drawable-port-xxhdpi/splash.png differ
diff --git a/apps/dashboard/android/app/src/main/res/drawable-port-xxxhdpi/splash.png b/apps/dashboard/android/app/src/main/res/drawable-port-xxxhdpi/splash.png
new file mode 100644
index 00000000..69290712
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/drawable-port-xxxhdpi/splash.png differ
diff --git a/apps/dashboard/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/apps/dashboard/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 00000000..c7bd21db
--- /dev/null
+++ b/apps/dashboard/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/dashboard/android/app/src/main/res/drawable/ic_launcher_background.xml b/apps/dashboard/android/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..d5fccc53
--- /dev/null
+++ b/apps/dashboard/android/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/dashboard/android/app/src/main/res/drawable/splash.png b/apps/dashboard/android/app/src/main/res/drawable/splash.png
new file mode 100644
index 00000000..f7a64923
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/drawable/splash.png differ
diff --git a/apps/dashboard/android/app/src/main/res/layout/activity_main.xml b/apps/dashboard/android/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..b5ad1387
--- /dev/null
+++ b/apps/dashboard/android/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/apps/dashboard/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..036d09bc
--- /dev/null
+++ b/apps/dashboard/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/apps/dashboard/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000..036d09bc
--- /dev/null
+++ b/apps/dashboard/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/apps/dashboard/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/dashboard/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..c023e505
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/apps/dashboard/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..2127973b
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/apps/dashboard/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..b441f37d
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/dashboard/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..72905b85
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/apps/dashboard/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..8ed0605c
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/apps/dashboard/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..9502e47a
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/dashboard/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..4d1e0771
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/apps/dashboard/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..df0f1588
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/apps/dashboard/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..853db043
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/dashboard/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..6cdf97c1
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/apps/dashboard/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..2960cbb6
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/apps/dashboard/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..8e3093a8
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/dashboard/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..46de6e25
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/apps/dashboard/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..d2ea9abe
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/apps/dashboard/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/apps/dashboard/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..a40d73e9
Binary files /dev/null and b/apps/dashboard/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/apps/dashboard/android/app/src/main/res/values/ic_launcher_background.xml b/apps/dashboard/android/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 00000000..c5d5899f
--- /dev/null
+++ b/apps/dashboard/android/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/apps/dashboard/android/app/src/main/res/values/strings.xml b/apps/dashboard/android/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..5310d8c6
--- /dev/null
+++ b/apps/dashboard/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+
+ Flowchain Wallet
+ Flowchain Wallet
+ ai.flowmemory.flowchain.wallet
+ ai.flowmemory.flowchain.wallet
+
diff --git a/apps/dashboard/android/app/src/main/res/values/styles.xml b/apps/dashboard/android/app/src/main/res/values/styles.xml
new file mode 100644
index 00000000..be874e54
--- /dev/null
+++ b/apps/dashboard/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/dashboard/android/app/src/main/res/xml/file_paths.xml b/apps/dashboard/android/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 00000000..bd0c4d80
--- /dev/null
+++ b/apps/dashboard/android/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/dashboard/android/app/src/test/java/ai/flowmemory/flowchain/wallet/ExampleUnitTest.java b/apps/dashboard/android/app/src/test/java/ai/flowmemory/flowchain/wallet/ExampleUnitTest.java
new file mode 100644
index 00000000..837d73f2
--- /dev/null
+++ b/apps/dashboard/android/app/src/test/java/ai/flowmemory/flowchain/wallet/ExampleUnitTest.java
@@ -0,0 +1,18 @@
+package ai.flowmemory.flowchain.wallet;
+
+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/apps/dashboard/android/build.gradle b/apps/dashboard/android/build.gradle
new file mode 100644
index 00000000..b648f20e
--- /dev/null
+++ b/apps/dashboard/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/apps/dashboard/android/capacitor.settings.gradle b/apps/dashboard/android/capacitor.settings.gradle
new file mode 100644
index 00000000..9a5fa872
--- /dev/null
+++ b/apps/dashboard/android/capacitor.settings.gradle
@@ -0,0 +1,3 @@
+// 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/@capacitor/android/capacitor')
diff --git a/apps/dashboard/android/gradle.properties b/apps/dashboard/android/gradle.properties
new file mode 100644
index 00000000..2e87c52f
--- /dev/null
+++ b/apps/dashboard/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/apps/dashboard/android/gradle/wrapper/gradle-wrapper.jar b/apps/dashboard/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..1b33c55b
Binary files /dev/null and b/apps/dashboard/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/apps/dashboard/android/gradle/wrapper/gradle-wrapper.properties b/apps/dashboard/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..7705927e
--- /dev/null
+++ b/apps/dashboard/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/apps/dashboard/android/gradlew b/apps/dashboard/android/gradlew
new file mode 100755
index 00000000..23d15a93
--- /dev/null
+++ b/apps/dashboard/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/apps/dashboard/android/gradlew.bat b/apps/dashboard/android/gradlew.bat
new file mode 100644
index 00000000..db3a6ac2
--- /dev/null
+++ b/apps/dashboard/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/apps/dashboard/android/settings.gradle b/apps/dashboard/android/settings.gradle
new file mode 100644
index 00000000..3b4431d7
--- /dev/null
+++ b/apps/dashboard/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/apps/dashboard/android/variables.gradle b/apps/dashboard/android/variables.gradle
new file mode 100644
index 00000000..ee4ba41c
--- /dev/null
+++ b/apps/dashboard/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/apps/dashboard/capacitor.config.ts b/apps/dashboard/capacitor.config.ts
new file mode 100644
index 00000000..f25cbc60
--- /dev/null
+++ b/apps/dashboard/capacitor.config.ts
@@ -0,0 +1,13 @@
+import type { CapacitorConfig } from "@capacitor/cli";
+
+const config: CapacitorConfig = {
+ appId: "ai.flowmemory.flowchain.wallet",
+ appName: "Flowchain Wallet",
+ webDir: "dist",
+ bundledWebRuntime: false,
+ server: {
+ androidScheme: "https",
+ },
+};
+
+export default config;
diff --git a/apps/dashboard/electron-builder.json b/apps/dashboard/electron-builder.json
new file mode 100644
index 00000000..315a5c12
--- /dev/null
+++ b/apps/dashboard/electron-builder.json
@@ -0,0 +1,43 @@
+{
+ "appId": "ai.flowmemory.flowchain.wallet",
+ "productName": "Flowchain Wallet",
+ "directories": {
+ "output": "release"
+ },
+ "files": [
+ "dist/**/*",
+ "electron/**/*",
+ "package.json"
+ ],
+ "asar": true,
+ "mac": {
+ "category": "public.app-category.finance",
+ "target": [
+ "dmg",
+ "zip"
+ ],
+ "identity": null
+ },
+ "win": {
+ "signAndEditExecutable": false,
+ "target": [
+ "nsis",
+ "zip"
+ ]
+ },
+ "nsis": {
+ "oneClick": false,
+ "perMachine": false,
+ "allowToChangeInstallationDirectory": true,
+ "createDesktopShortcut": true,
+ "createStartMenuShortcut": true,
+ "shortcutName": "Flowchain Wallet"
+ },
+ "linux": {
+ "category": "Finance",
+ "target": [
+ "AppImage",
+ "zip"
+ ]
+ }
+}
diff --git a/apps/dashboard/electron/main.cjs b/apps/dashboard/electron/main.cjs
new file mode 100644
index 00000000..a1658334
--- /dev/null
+++ b/apps/dashboard/electron/main.cjs
@@ -0,0 +1,159 @@
+const { app, BrowserWindow, Menu, ipcMain, shell } = require("electron");
+const fs = require("node:fs/promises");
+const http = require("node:http");
+const path = require("node:path");
+const { createLocalDesktopWallet, publicDesktopWalletStatus } = require("./wallet-store.cjs");
+
+const isDev = Boolean(process.env.FLOWCHAIN_WALLET_DESKTOP_DEV_URL);
+let staticServer;
+
+const contentTypes = new Map([
+ [".css", "text/css; charset=utf-8"],
+ [".html", "text/html; charset=utf-8"],
+ [".js", "text/javascript; charset=utf-8"],
+ [".json", "application/json; charset=utf-8"],
+ [".png", "image/png"],
+ [".svg", "image/svg+xml; charset=utf-8"],
+ [".woff", "font/woff"],
+ [".woff2", "font/woff2"],
+]);
+
+function contentTypeFor(filePath) {
+ return contentTypes.get(path.extname(filePath).toLowerCase()) ?? "application/octet-stream";
+}
+
+function startBundledStaticServer() {
+ if (staticServer !== undefined) {
+ return staticServer;
+ }
+
+ const distDir = path.resolve(__dirname, "..", "dist");
+ staticServer = new Promise((resolve, reject) => {
+ const server = http.createServer(async (req, res) => {
+ try {
+ const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
+ const rawPath = decodeURIComponent(requestUrl.pathname);
+ const assetPath = rawPath === "/" || path.extname(rawPath) === "" ? "/index.html" : rawPath;
+ const filePath = path.resolve(distDir, `.${assetPath}`);
+
+ if (!filePath.startsWith(distDir)) {
+ res.writeHead(403, { "content-type": "text/plain; charset=utf-8" });
+ res.end("Forbidden");
+ return;
+ }
+
+ const body = await fs.readFile(filePath);
+ res.writeHead(200, {
+ "cache-control": "no-store",
+ "content-type": contentTypeFor(filePath),
+ });
+ res.end(body);
+ } catch {
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
+ res.end("Not found");
+ }
+ });
+
+ server.once("error", reject);
+ server.listen(0, "127.0.0.1", () => {
+ const address = server.address();
+ if (typeof address !== "object" || address === null) {
+ reject(new Error("Flowchain Wallet static server did not bind to a local port."));
+ return;
+ }
+ resolve({ server, origin: `http://127.0.0.1:${address.port}` });
+ });
+ });
+
+ return staticServer;
+}
+
+async function createWalletWindow() {
+ const window = new BrowserWindow({
+ width: 1480,
+ height: 940,
+ minWidth: 1120,
+ minHeight: 720,
+ title: "Flowchain Wallet",
+ backgroundColor: "#fff9ee",
+ show: false,
+ webPreferences: {
+ contextIsolation: true,
+ nodeIntegration: false,
+ sandbox: true,
+ preload: path.join(__dirname, "preload.cjs"),
+ },
+ });
+
+ window.once("ready-to-show", () => {
+ window.show();
+ });
+
+ window.webContents.setWindowOpenHandler(({ url }) => {
+ if (url.startsWith("http://127.0.0.1") || url.startsWith("http://localhost")) {
+ return { action: "allow" };
+ }
+ shell.openExternal(url).catch(() => undefined);
+ return { action: "deny" };
+ });
+
+ window.webContents.on("will-navigate", (event, url) => {
+ const allowed = url.startsWith("file://")
+ || url.startsWith("http://127.0.0.1")
+ || url.startsWith("http://localhost")
+ || url.startsWith("http://192.168.");
+ if (!allowed) {
+ event.preventDefault();
+ shell.openExternal(url).catch(() => undefined);
+ }
+ });
+
+ if (isDev) {
+ window.loadURL(`${process.env.FLOWCHAIN_WALLET_DESKTOP_DEV_URL.replace(/\/+$/, "")}/wallet`);
+ } else {
+ const { origin } = await startBundledStaticServer();
+ window.loadURL(`${origin}/wallet`);
+ }
+
+ return window;
+}
+
+app.setName("Flowchain Wallet");
+
+app.whenReady().then(() => {
+ ipcMain.handle("flowchain-wallet:get-local-wallet", () => publicDesktopWalletStatus(app.getPath("userData")));
+ ipcMain.handle("flowchain-wallet:create-local-wallet", (_event, payload) => createLocalDesktopWallet(app.getPath("userData"), payload));
+
+ Menu.setApplicationMenu(Menu.buildFromTemplate([
+ {
+ label: "Flowchain Wallet",
+ submenu: [
+ { role: "reload" },
+ { role: "toggleDevTools" },
+ { type: "separator" },
+ { role: "quit" },
+ ],
+ },
+ ]));
+
+ createWalletWindow().catch((error) => {
+ console.error(error);
+ app.quit();
+ });
+
+ app.on("activate", () => {
+ if (BrowserWindow.getAllWindows().length === 0) {
+ createWalletWindow().catch((error) => {
+ console.error(error);
+ app.quit();
+ });
+ }
+ });
+});
+
+app.on("window-all-closed", () => {
+ staticServer?.then(({ server }) => server.close()).catch(() => undefined);
+ if (process.platform !== "darwin") {
+ app.quit();
+ }
+});
diff --git a/apps/dashboard/electron/preload.cjs b/apps/dashboard/electron/preload.cjs
new file mode 100644
index 00000000..7a3009b8
--- /dev/null
+++ b/apps/dashboard/electron/preload.cjs
@@ -0,0 +1,9 @@
+const { contextBridge, ipcRenderer } = require("electron");
+
+contextBridge.exposeInMainWorld("flowchainDesktop", {
+ app: "Flowchain Wallet",
+ platform: process.platform,
+ packaged: process.env.NODE_ENV !== "development",
+ getLocalWallet: () => ipcRenderer.invoke("flowchain-wallet:get-local-wallet"),
+ createLocalWallet: (payload) => ipcRenderer.invoke("flowchain-wallet:create-local-wallet", payload),
+});
diff --git a/apps/dashboard/electron/wallet-store.cjs b/apps/dashboard/electron/wallet-store.cjs
new file mode 100644
index 00000000..30991915
--- /dev/null
+++ b/apps/dashboard/electron/wallet-store.cjs
@@ -0,0 +1,340 @@
+const { createCipheriv, randomBytes, scryptSync } = require("node:crypto");
+const { existsSync, mkdirSync, readFileSync, writeFileSync } = require("node:fs");
+const path = require("node:path");
+
+const VAULT_SCHEMA = "flowmemory.crypto.local-test-vault.v0";
+const VAULT_SECRETS_SCHEMA = "flowmemory.crypto.local-test-vault-secrets.v0";
+const LOCAL_WALLET_PUBLIC_METADATA_SCHEMA = "flowchain.local_wallet_public_metadata.v0";
+const LOCAL_WALLET_KEY_SCHEME = "secp256k1";
+const DEFAULT_LOCAL_WALLET_CHAIN_ID = "31337";
+const LOCAL_ALPHA_SIGNER_ROLES = Object.freeze({
+ operator: 1,
+ agent: 2,
+ verifier: 3,
+ hardware: 4,
+ user: 10,
+ validator: 11,
+ bridgeRelayer: 12,
+ bridgeReleaseAuthority: 13,
+ emergencyOperator: 14,
+});
+
+let nobleModules;
+
+async function loadNobleModules() {
+ if (nobleModules === undefined) {
+ nobleModules = Promise.all([
+ import("@noble/secp256k1"),
+ import("@noble/hashes/sha3.js"),
+ ]).then(([secp, sha3]) => ({ secp, sha3 }));
+ }
+ return nobleModules;
+}
+
+function walletPaths(userDataPath) {
+ const walletDir = path.join(userDataPath, "wallet");
+ return {
+ walletDir,
+ vaultPath: path.join(walletDir, "flowchain-wallet-vault.local.json"),
+ metadataPath: path.join(walletDir, "flowchain-wallet-public-metadata.json"),
+ };
+}
+
+function publicDesktopWalletStatus(userDataPath) {
+ const paths = walletPaths(userDataPath);
+ const metadata = existsSync(paths.metadataPath) ? readJson(paths.metadataPath) : null;
+ const accounts = Array.isArray(metadata?.accounts) ? metadata.accounts : [];
+ const primaryAccount = accounts.find((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry)) ?? null;
+ return {
+ schema: "flowmemory.control_plane.local_wallet_public_status.v0",
+ exists: metadata !== null,
+ metadataPath: paths.metadataPath,
+ account: primaryAccount,
+ accounts,
+ secretMaterialReturned: false,
+ localOnly: true,
+ desktopLocal: true,
+ };
+}
+
+async function createLocalDesktopWallet(userDataPath, payload = {}) {
+ const request = parseWalletCreatePayload(payload);
+ const paths = walletPaths(userDataPath);
+ mkdirSync(paths.walletDir, { recursive: true });
+
+ if (!request.replace && existsSync(paths.vaultPath) && existsSync(paths.metadataPath)) {
+ return {
+ ...publicDesktopWalletStatus(userDataPath),
+ schema: "flowmemory.control_plane.local_wallet_create_result.v0",
+ created: false,
+ alreadyExists: true,
+ vaultPath: paths.vaultPath,
+ note: "Existing encrypted local wallet vault was left unchanged. Enable replace to rotate to a new wallet.",
+ };
+ }
+
+ const vault = await createEncryptedDesktopVault({
+ password: request.password,
+ label: request.label,
+ signerRole: "user",
+ chainId: request.chainId,
+ });
+ const metadata = exportLocalWalletPublicMetadata(vault);
+ writeJson(paths.vaultPath, vault);
+ writeJson(paths.metadataPath, metadata);
+
+ const account = Array.isArray(metadata.accounts) ? metadata.accounts[0] : null;
+ return {
+ schema: "flowmemory.control_plane.local_wallet_create_result.v0",
+ created: true,
+ alreadyExists: false,
+ account,
+ accounts: metadata.accounts,
+ vaultPath: paths.vaultPath,
+ metadataPath: paths.metadataPath,
+ chainId: request.chainId,
+ keyScheme: account?.keyScheme ?? LOCAL_WALLET_KEY_SCHEME,
+ secretMaterialReturned: false,
+ credentialStored: false,
+ localOnly: true,
+ desktopLocal: true,
+ };
+}
+
+async function createEncryptedDesktopVault({
+ password,
+ label,
+ signerRole,
+ createdAtUnixMs = Date.now().toString(),
+ privateKey,
+ chainId = DEFAULT_LOCAL_WALLET_CHAIN_ID,
+ lastKnownNonce = "0",
+}) {
+ requirePassword(password);
+ const account = await createVaultAccount({ label, signerRole, createdAtUnixMs, privateKey, chainId, lastKnownNonce });
+ return encryptVaultSecrets({
+ password,
+ publicAccounts: [publicAccount(account)],
+ secrets: {
+ schema: VAULT_SECRETS_SCHEMA,
+ accounts: [account],
+ },
+ createdAtUnixMs,
+ });
+}
+
+function encryptVaultSecrets({
+ password,
+ publicAccounts,
+ secrets,
+ createdAtUnixMs,
+ vaultId = randomBytes32(),
+}) {
+ requirePassword(password);
+ const salt = randomBytes(16);
+ const iv = randomBytes(12);
+ const kdf = {
+ name: "scrypt",
+ salt: bytesToHex(salt),
+ N: 16384,
+ r: 8,
+ p: 1,
+ keyLength: 32,
+ };
+ const key = scryptSync(password, salt, kdf.keyLength, { N: kdf.N, r: kdf.r, p: kdf.p });
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
+ const ciphertext = Buffer.concat([
+ cipher.update(JSON.stringify(secrets), "utf8"),
+ cipher.final(),
+ ]);
+
+ return {
+ schema: VAULT_SCHEMA,
+ vaultId,
+ createdAtUnixMs,
+ kdf,
+ cipher: {
+ name: "aes-256-gcm",
+ iv: bytesToHex(iv),
+ authTag: bytesToHex(cipher.getAuthTag()),
+ },
+ ciphertext: bytesToHex(ciphertext),
+ publicAccounts,
+ };
+}
+
+async function createVaultAccount({
+ label,
+ signerRole,
+ createdAtUnixMs,
+ privateKey,
+ chainId = DEFAULT_LOCAL_WALLET_CHAIN_ID,
+ lastKnownNonce = "0",
+}) {
+ if (LOCAL_ALPHA_SIGNER_ROLES[signerRole] === undefined) {
+ throw new Error(`unsupported signer role: ${signerRole}`);
+ }
+ if (!isUintString(String(chainId))) {
+ throw new Error(`unsupported wallet chain id: ${chainId}`);
+ }
+ if (!isUintString(String(lastKnownNonce))) {
+ throw new Error(`invalid wallet last known nonce: ${lastKnownNonce}`);
+ }
+ const effectivePrivateKey = privateKey ?? await randomPrivateKey();
+ const publicKey = await publicKeyFromPrivateKey(effectivePrivateKey);
+ const publicKeyHash = await keccakUtf8(publicKey);
+ const signerId = await keccakUtf8(`flowchain.local-alpha.signer:${publicKey}`);
+ return {
+ label,
+ accountId: signerId,
+ address: signerId,
+ signerRole,
+ signerRoleCode: LOCAL_ALPHA_SIGNER_ROLES[signerRole],
+ signerId,
+ signerKeyId: await keccakUtf8(`flowchain.local-alpha.signer-key:${publicKey}`),
+ publicKey,
+ publicKeyHash,
+ keyScheme: LOCAL_WALLET_KEY_SCHEME,
+ chainId: String(chainId),
+ lastKnownNonce: String(lastKnownNonce),
+ nextNonce: nextNonce(lastKnownNonce),
+ createdAtUnixMs,
+ status: "active",
+ active: true,
+ privateKey: effectivePrivateKey,
+ };
+}
+
+function publicAccount(account) {
+ const { privateKey, ...metadata } = account;
+ return metadata;
+}
+
+function exportLocalWalletPublicMetadata(vault, { updatedAtUnixMs = Date.now().toString() } = {}) {
+ return {
+ schema: LOCAL_WALLET_PUBLIC_METADATA_SCHEMA,
+ vaultId: vault.vaultId,
+ createdAtUnixMs: vault.createdAtUnixMs,
+ updatedAtUnixMs: String(updatedAtUnixMs),
+ accounts: (vault.publicAccounts ?? []).map(localWalletPublicAccount),
+ boundary: "Public local wallet metadata only. Signing material, vault encryption payloads, credentials, and webhooks are excluded.",
+ };
+}
+
+function localWalletPublicAccount(account) {
+ return {
+ accountId: account.accountId ?? account.signerId,
+ address: account.address ?? account.signerId,
+ signerId: account.signerId,
+ signerKeyId: account.signerKeyId,
+ signerRole: account.signerRole,
+ signerRoleCode: account.signerRoleCode,
+ publicKey: account.publicKey,
+ publicKeyHash: account.publicKeyHash,
+ keyScheme: account.keyScheme ?? LOCAL_WALLET_KEY_SCHEME,
+ label: account.label,
+ status: account.active === false ? "rotated" : account.status ?? "active",
+ active: account.active !== false,
+ createdAtUnixMs: account.createdAtUnixMs,
+ chainId: String(account.chainId ?? DEFAULT_LOCAL_WALLET_CHAIN_ID),
+ lastKnownNonce: String(account.lastKnownNonce ?? "0"),
+ nextNonce: String(account.nextNonce ?? nextNonce(account.lastKnownNonce ?? "0")),
+ rotatedFromSignerKeyId: account.rotatedFromSignerKeyId,
+ };
+}
+
+async function randomPrivateKey() {
+ for (;;) {
+ const candidate = bytesToHex(randomBytes(32));
+ try {
+ await publicKeyFromPrivateKey(candidate);
+ return candidate;
+ } catch {
+ // Retry if the random bytes do not form a valid secp256k1 secret.
+ }
+ }
+}
+
+async function publicKeyFromPrivateKey(privateKeyHex) {
+ const { secp } = await loadNobleModules();
+ return bytesToHex(secp.getPublicKey(hexToBytes(privateKeyHex, 32)));
+}
+
+async function keccakUtf8(value) {
+ const { sha3 } = await loadNobleModules();
+ return bytesToHex(sha3.keccak_256(Buffer.from(String(value), "utf8")));
+}
+
+function parseWalletCreatePayload(payload) {
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
+ throw new Error("wallet creation payload must be an object");
+ }
+ const password = typeof payload.password === "string" ? payload.password : "";
+ if (password.length < 8) {
+ throw new Error("wallet vault passphrase must be at least 8 characters");
+ }
+ const chainId = typeof payload.chainId === "string" && /^\d+$/.test(payload.chainId) ? payload.chainId : DEFAULT_LOCAL_WALLET_CHAIN_ID;
+ return {
+ label: labelSlug(payload.label),
+ password,
+ chainId,
+ replace: payload.replace === true,
+ };
+}
+
+function labelSlug(value) {
+ const label = typeof value === "string" && value.trim().length > 0 ? value.trim() : "flowchain-wallet";
+ const slug = label.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
+ return slug.length > 0 ? slug.slice(0, 64) : "flowchain-wallet";
+}
+
+function requirePassword(password) {
+ if (typeof password !== "string" || password.length < 8) {
+ throw new Error("wallet vault passphrase must be at least 8 characters");
+ }
+}
+
+function randomBytes32() {
+ return bytesToHex(randomBytes(32));
+}
+
+function bytesToHex(bytes) {
+ return `0x${Buffer.from(bytes).toString("hex")}`;
+}
+
+function hexToBytes(value, expectedLength) {
+ if (typeof value !== "string") {
+ throw new TypeError("hex value must be a string");
+ }
+ const raw = value.startsWith("0x") ? value.slice(2) : value;
+ if (raw.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(raw)) {
+ throw new Error(`invalid hex value: ${value}`);
+ }
+ const bytes = Uint8Array.from(Buffer.from(raw, "hex"));
+ if (expectedLength !== undefined && bytes.length !== expectedLength) {
+ throw new Error(`expected ${expectedLength} bytes, got ${bytes.length}`);
+ }
+ return bytes;
+}
+
+function isUintString(value) {
+ return typeof value === "string" && /^[0-9]+$/.test(value);
+}
+
+function nextNonce(value) {
+ return (BigInt(value) + 1n).toString();
+}
+
+function readJson(filePath) {
+ return JSON.parse(readFileSync(filePath, "utf8"));
+}
+
+function writeJson(filePath, value) {
+ writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
+}
+
+module.exports = {
+ createLocalDesktopWallet,
+ publicDesktopWalletStatus,
+ walletPaths,
+};
diff --git a/apps/dashboard/index.html b/apps/dashboard/index.html
index f705964a..32d36e1e 100644
--- a/apps/dashboard/index.html
+++ b/apps/dashboard/index.html
@@ -5,9 +5,9 @@
-
FlowMemory Dashboard V0
+ Flowchain Wallet
diff --git a/apps/dashboard/package-lock.json b/apps/dashboard/package-lock.json
index 684afda4..eb15363f 100644
--- a/apps/dashboard/package-lock.json
+++ b/apps/dashboard/package-lock.json
@@ -8,840 +8,896 @@
"name": "@flowmemory/dashboard",
"version": "0.0.0",
"dependencies": {
+ "@capacitor/android": "^8.3.4",
+ "@capacitor/core": "^8.3.4",
+ "@noble/hashes": "^2.2.0",
+ "@noble/secp256k1": "^3.1.0",
"lucide-react": "^0.468.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.28.0"
},
"devDependencies": {
+ "@capacitor/cli": "^8.3.4",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^6.0.1",
+ "electron": "^42.0.1",
+ "electron-builder": "^26.8.1",
"typescript": "^5.6.3",
"vite": "^8.0.12",
"vitest": "^4.1.6"
}
},
- "node_modules/@emnapi/core": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
- "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
- "dev": true,
+ "node_modules/@capacitor/android": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/@capacitor/android/-/android-8.3.4.tgz",
+ "integrity": "sha512-7gJjrG3X32Am1QMLqgMztWTYMLMVNE+VZwhekNxhvYizH4mOV05vH+rC9B+f17bCkYZfyu/qXQX6hoY7kLeVZw==",
"license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/wasi-threads": "1.2.1",
- "tslib": "^2.4.0"
+ "peerDependencies": {
+ "@capacitor/core": "^8.3.0"
}
},
- "node_modules/@emnapi/runtime": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
- "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
+ "node_modules/@capacitor/cli": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-8.3.4.tgz",
+ "integrity": "sha512-QEmyNdiDDVNYl0Mahm7YTVA/3t2tKcy7FWYDapeKGavS6HDNHZSjyTVwQpUXQbDZrrs/PS2Wau3Aii+LIFwm/A==",
"dev": true,
"license": "MIT",
- "optional": true,
"dependencies": {
- "tslib": "^2.4.0"
+ "@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.0",
+ "env-paths": "^2.2.0",
+ "fs-extra": "^11.2.0",
+ "kleur": "^4.1.5",
+ "native-run": "^2.0.3",
+ "open": "^8.4.0",
+ "plist": "^3.1.0",
+ "prompts": "^2.4.2",
+ "rimraf": "^6.0.1",
+ "semver": "^7.6.3",
+ "tar": "^7.5.3",
+ "tslib": "^2.8.1",
+ "xml2js": "^0.6.2"
+ },
+ "bin": {
+ "cap": "bin/capacitor",
+ "capacitor": "bin/capacitor"
+ },
+ "engines": {
+ "node": ">=22.0.0"
}
},
- "node_modules/@emnapi/wasi-threads": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
- "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
+ "node_modules/@capacitor/cli/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"dev": true,
"license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "node_modules/@capacitor/cli/node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
},
- "node_modules/@napi-rs/wasm-runtime": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
- "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
+ "node_modules/@capacitor/cli/node_modules/fs-extra": {
+ "version": "11.3.5",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz",
+ "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==",
"dev": true,
"license": "MIT",
- "optional": true,
"dependencies": {
- "@tybys/wasm-util": "^0.10.1"
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
},
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/Brooooooklyn"
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/@capacitor/cli/node_modules/glob": {
+ "version": "13.0.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
+ "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "minimatch": "^10.2.2",
+ "minipass": "^7.1.3",
+ "path-scurry": "^2.0.2"
},
- "peerDependencies": {
- "@emnapi/core": "^1.7.1",
- "@emnapi/runtime": "^1.7.1"
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/@oxc-project/types": {
- "version": "0.129.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz",
- "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==",
+ "node_modules/@capacitor/cli/node_modules/rimraf": {
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz",
+ "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==",
"dev": true,
- "license": "MIT",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "glob": "^13.0.3",
+ "package-json-from-dist": "^1.0.1"
+ },
+ "bin": {
+ "rimraf": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
"funding": {
- "url": "https://github.com/sponsors/Boshen"
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/@remix-run/router": {
- "version": "1.23.2",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
- "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
+ "node_modules/@capacitor/core": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.3.4.tgz",
+ "integrity": "sha512-CqRQCkb6HXxcx/N7s+hHTN6ef2CmamFiRMITwm4qB840ph56mS42bzUgn6tKCP+RZjdDweiRHj9ytDDeN6jFag==",
"license": "MIT",
- "engines": {
- "node": ">=14.0.0"
+ "dependencies": {
+ "tslib": "^2.1.0"
}
},
- "node_modules/@rolldown/binding-android-arm64": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz",
- "integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@develar/schema-utils": {
+ "version": "2.6.5",
+ "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
+ "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
+ "dependencies": {
+ "ajv": "^6.12.0",
+ "ajv-keywords": "^3.4.1"
+ },
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": ">= 8.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
}
},
- "node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0.tgz",
- "integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@electron/asar": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz",
+ "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
+ "dependencies": {
+ "commander": "^5.0.0",
+ "glob": "^7.1.6",
+ "minimatch": "^3.0.4"
+ },
+ "bin": {
+ "asar": "bin/asar.js"
+ },
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": ">=10.12.0"
}
},
- "node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz",
- "integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@electron/asar/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@electron/asar/node_modules/brace-expansion": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
+ "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@electron/asar/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": "*"
}
},
- "node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz",
- "integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@electron/fuses": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz",
+ "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "dependencies": {
+ "chalk": "^4.1.1",
+ "fs-extra": "^9.0.1",
+ "minimist": "^1.2.5"
+ },
+ "bin": {
+ "electron-fuses": "dist/bin.js"
}
},
- "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0.tgz",
- "integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==",
- "cpu": [
- "arm"
- ],
+ "node_modules/@electron/fuses/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": ">=10"
}
},
- "node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0.tgz",
- "integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@electron/get": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.0.0.tgz",
+ "integrity": "sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==",
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "debug": "^4.1.1",
+ "env-paths": "^3.0.0",
+ "graceful-fs": "^4.2.11",
+ "progress": "^2.0.3",
+ "semver": "^7.6.3",
+ "sumchecker": "^3.0.1"
+ },
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": ">=22.12.0"
+ },
+ "optionalDependencies": {
+ "undici": "^7.24.4"
}
},
- "node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz",
- "integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@electron/notarize": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz",
+ "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==",
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "debug": "^4.1.1",
+ "fs-extra": "^9.0.1",
+ "promise-retry": "^2.0.1"
+ },
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": ">= 10.0.0"
}
},
- "node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0.tgz",
- "integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==",
- "cpu": [
- "ppc64"
- ],
+ "node_modules/@electron/notarize/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": ">=10"
}
},
- "node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0.tgz",
- "integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==",
- "cpu": [
- "s390x"
- ],
+ "node_modules/@electron/osx-sign": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz",
+ "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==",
"dev": true,
- "libc": [
- "glibc"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "compare-version": "^0.1.2",
+ "debug": "^4.3.4",
+ "fs-extra": "^10.0.0",
+ "isbinaryfile": "^4.0.8",
+ "minimist": "^1.2.6",
+ "plist": "^3.0.5"
+ },
+ "bin": {
+ "electron-osx-flat": "bin/electron-osx-flat.js",
+ "electron-osx-sign": "bin/electron-osx-sign.js"
+ },
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": ">=12.0.0"
}
},
- "node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0.tgz",
- "integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@electron/osx-sign/node_modules/isbinaryfile": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz",
+ "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==",
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": ">= 8.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/gjtorikian/"
}
},
- "node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0.tgz",
- "integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@electron/rebuild": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.4.tgz",
+ "integrity": "sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg==",
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
+ "dependencies": {
+ "@malept/cross-spawn-promise": "^2.0.0",
+ "debug": "^4.1.1",
+ "node-abi": "^4.2.0",
+ "node-api-version": "^0.2.1",
+ "node-gyp": "^12.2.0",
+ "read-binary-file-arch": "^1.0.6"
+ },
+ "bin": {
+ "electron-rebuild": "lib/cli.js"
+ },
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": ">=22.12.0"
}
},
- "node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz",
- "integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@electron/universal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz",
+ "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
+ "dependencies": {
+ "@electron/asar": "^3.3.1",
+ "@malept/cross-spawn-promise": "^2.0.0",
+ "debug": "^4.3.1",
+ "dir-compare": "^4.2.0",
+ "fs-extra": "^11.1.1",
+ "minimatch": "^9.0.3",
+ "plist": "^3.1.0"
+ },
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": ">=16.4"
}
},
- "node_modules/@rolldown/binding-wasm32-wasi": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0.tgz",
- "integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==",
- "cpu": [
- "wasm32"
- ],
+ "node_modules/@electron/universal/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@electron/universal/node_modules/brace-expansion": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+ "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"dev": true,
"license": "MIT",
- "optional": true,
"dependencies": {
- "@emnapi/core": "1.10.0",
- "@emnapi/runtime": "1.10.0",
- "@napi-rs/wasm-runtime": "^1.1.4"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "balanced-match": "^1.0.0"
}
},
- "node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0.tgz",
- "integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@electron/universal/node_modules/fs-extra": {
+ "version": "11.3.5",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz",
+ "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": ">=14.14"
}
},
- "node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0.tgz",
- "integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@electron/universal/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-rc.7",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz",
- "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==",
+ "node_modules/@electron/windows-sign": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz",
+ "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==",
"dev": true,
- "license": "MIT"
+ "license": "BSD-2-Clause",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "cross-dirname": "^0.1.0",
+ "debug": "^4.3.4",
+ "fs-extra": "^11.1.1",
+ "minimist": "^1.2.8",
+ "postject": "^1.0.0-alpha.6"
+ },
+ "bin": {
+ "electron-windows-sign": "bin/electron-windows-sign.js"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
},
- "node_modules/@standard-schema/spec": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
- "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "node_modules/@electron/windows-sign/node_modules/fs-extra": {
+ "version": "11.3.5",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz",
+ "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
},
- "node_modules/@tybys/wasm-util": {
- "version": "0.10.2",
- "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
- "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
+ "node_modules/@emnapi/core": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
+ "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
+ "@emnapi/wasi-threads": "1.2.1",
"tslib": "^2.4.0"
}
},
- "node_modules/@types/chai": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
- "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "node_modules/@emnapi/runtime": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"dev": true,
"license": "MIT",
+ "optional": true,
"dependencies": {
- "@types/deep-eql": "*",
- "assertion-error": "^2.0.1"
+ "tslib": "^2.4.0"
}
},
- "node_modules/@types/deep-eql": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
- "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+ "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
},
- "node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "node_modules/@ionic/cli-framework-output": {
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/@ionic/cli-framework-output/-/cli-framework-output-2.2.8.tgz",
+ "integrity": "sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "@ionic/utils-terminal": "2.3.5",
+ "debug": "^4.0.0",
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
},
- "node_modules/@types/prop-types": {
- "version": "15.7.15",
- "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
- "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "node_modules/@ionic/utils-array": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@ionic/utils-array/-/utils-array-2.1.6.tgz",
+ "integrity": "sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.0.0",
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
},
- "node_modules/@types/react": {
- "version": "18.3.28",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
- "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
+ "node_modules/@ionic/utils-fs": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@ionic/utils-fs/-/utils-fs-3.1.7.tgz",
+ "integrity": "sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/prop-types": "*",
- "csstype": "^3.2.2"
+ "@types/fs-extra": "^8.0.0",
+ "debug": "^4.0.0",
+ "fs-extra": "^9.0.0",
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16.0.0"
}
},
- "node_modules/@types/react-dom": {
- "version": "18.3.7",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
- "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "node_modules/@ionic/utils-fs/node_modules/@types/fs-extra": {
+ "version": "8.1.5",
+ "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz",
+ "integrity": "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==",
"dev": true,
"license": "MIT",
- "peerDependencies": {
- "@types/react": "^18.0.0"
+ "dependencies": {
+ "@types/node": "*"
}
},
- "node_modules/@vitejs/plugin-react": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
- "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==",
+ "node_modules/@ionic/utils-fs/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@rolldown/pluginutils": "1.0.0-rc.7"
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
},
"engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "peerDependencies": {
- "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
- "babel-plugin-react-compiler": "^1.0.0",
- "vite": "^8.0.0"
- },
- "peerDependenciesMeta": {
- "@rolldown/plugin-babel": {
- "optional": true
- },
- "babel-plugin-react-compiler": {
- "optional": true
- }
+ "node": ">=10"
}
},
- "node_modules/@vitest/expect": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz",
- "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==",
+ "node_modules/@ionic/utils-object": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@ionic/utils-object/-/utils-object-2.1.6.tgz",
+ "integrity": "sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@standard-schema/spec": "^1.1.0",
- "@types/chai": "^5.2.2",
- "@vitest/spy": "4.1.6",
- "@vitest/utils": "4.1.6",
- "chai": "^6.2.2",
- "tinyrainbow": "^3.1.0"
+ "debug": "^4.0.0",
+ "tslib": "^2.0.1"
},
- "funding": {
- "url": "https://opencollective.com/vitest"
+ "engines": {
+ "node": ">=16.0.0"
}
},
- "node_modules/@vitest/mocker": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz",
- "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==",
+ "node_modules/@ionic/utils-process": {
+ "version": "2.1.12",
+ "resolved": "https://registry.npmjs.org/@ionic/utils-process/-/utils-process-2.1.12.tgz",
+ "integrity": "sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "4.1.6",
- "estree-walker": "^3.0.3",
- "magic-string": "^0.30.21"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
+ "@ionic/utils-object": "2.1.6",
+ "@ionic/utils-terminal": "2.3.5",
+ "debug": "^4.0.0",
+ "signal-exit": "^3.0.3",
+ "tree-kill": "^1.2.2",
+ "tslib": "^2.0.1"
},
- "peerDependencies": {
- "msw": "^2.4.9",
- "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "msw": {
- "optional": true
- },
- "vite": {
- "optional": true
- }
+ "engines": {
+ "node": ">=16.0.0"
}
},
- "node_modules/@vitest/pretty-format": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz",
- "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==",
+ "node_modules/@ionic/utils-stream": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@ionic/utils-stream/-/utils-stream-3.1.7.tgz",
+ "integrity": "sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "tinyrainbow": "^3.1.0"
+ "debug": "^4.0.0",
+ "tslib": "^2.0.1"
},
- "funding": {
- "url": "https://opencollective.com/vitest"
+ "engines": {
+ "node": ">=16.0.0"
}
},
- "node_modules/@vitest/runner": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz",
- "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==",
+ "node_modules/@ionic/utils-subprocess": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@ionic/utils-subprocess/-/utils-subprocess-3.0.1.tgz",
+ "integrity": "sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "4.1.6",
- "pathe": "^2.0.3"
+ "@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.3",
+ "debug": "^4.0.0",
+ "tslib": "^2.0.1"
},
- "funding": {
- "url": "https://opencollective.com/vitest"
+ "engines": {
+ "node": ">=16.0.0"
}
},
- "node_modules/@vitest/snapshot": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz",
- "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==",
+ "node_modules/@ionic/utils-terminal": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@ionic/utils-terminal/-/utils-terminal-2.3.5.tgz",
+ "integrity": "sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.1.6",
- "@vitest/utils": "4.1.6",
- "magic-string": "^0.30.21",
- "pathe": "^2.0.3"
+ "@types/slice-ansi": "^4.0.0",
+ "debug": "^4.0.0",
+ "signal-exit": "^3.0.3",
+ "slice-ansi": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0",
+ "tslib": "^2.0.1",
+ "untildify": "^4.0.0",
+ "wrap-ansi": "^7.0.0"
},
- "funding": {
- "url": "https://opencollective.com/vitest"
+ "engines": {
+ "node": ">=16.0.0"
}
},
- "node_modules/@vitest/spy": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz",
- "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/utils": {
- "version": "4.1.6",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz",
- "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==",
+ "node_modules/@ionic/utils-terminal/node_modules/slice-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.1.6",
- "convert-source-map": "^2.0.0",
- "tinyrainbow": "^3.1.0"
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
},
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/assertion-error": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
- "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
- "dev": true,
- "license": "MIT",
"engines": {
- "node": ">=12"
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
- "node_modules/chai": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
- "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "node_modules/@isaacs/fs-minipass": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
+ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
"dev": true,
- "license": "MIT",
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.4"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=18.0.0"
}
},
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/csstype": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
- "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
- "node_modules/detect-libc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "node_modules/@malept/cross-spawn-promise": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz",
+ "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==",
"dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/malept"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund"
+ }
+ ],
"license": "Apache-2.0",
+ "dependencies": {
+ "cross-spawn": "^7.0.1"
+ },
"engines": {
- "node": ">=8"
+ "node": ">= 12.13.0"
}
},
- "node_modules/es-module-lexer": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
- "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/estree-walker": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
- "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "node_modules/@malept/flatpak-bundler": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz",
+ "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/estree": "^1.0.0"
+ "debug": "^4.1.1",
+ "fs-extra": "^9.0.0",
+ "lodash": "^4.17.15",
+ "tmp-promise": "^3.0.2"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
}
},
- "node_modules/expect-type": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
- "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+ "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
"engines": {
- "node": ">=12.0.0"
+ "node": ">=10"
}
},
- "node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
+ "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=12.0.0"
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.1"
},
- "peerDependencies": {
- "picomatch": "^3 || ^4"
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
},
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
}
},
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
+ "node_modules/@noble/hashes": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz",
+ "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==",
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
"engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
}
},
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "license": "MIT"
+ "node_modules/@noble/secp256k1": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-3.1.0.tgz",
+ "integrity": "sha512-+F7iS7tUMaNGXcc9X3PjmjvuQnXEuSjCRNzVVA2xAcKXgCaP0dHYz4SFyt4FKNHef7sOP//xihowcySSS7PK9g==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
},
- "node_modules/lightningcss": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
- "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "node_modules/@oxc-project/types": {
+ "version": "0.129.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz",
+ "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==",
"dev": true,
- "license": "MPL-2.0",
- "dependencies": {
- "detect-libc": "^2.0.3"
- },
- "engines": {
- "node": ">= 12.0.0"
- },
+ "license": "MIT",
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- },
- "optionalDependencies": {
- "lightningcss-android-arm64": "1.32.0",
- "lightningcss-darwin-arm64": "1.32.0",
- "lightningcss-darwin-x64": "1.32.0",
- "lightningcss-freebsd-x64": "1.32.0",
- "lightningcss-linux-arm-gnueabihf": "1.32.0",
- "lightningcss-linux-arm64-gnu": "1.32.0",
- "lightningcss-linux-arm64-musl": "1.32.0",
- "lightningcss-linux-x64-gnu": "1.32.0",
- "lightningcss-linux-x64-musl": "1.32.0",
- "lightningcss-win32-arm64-msvc": "1.32.0",
- "lightningcss-win32-x64-msvc": "1.32.0"
+ "url": "https://github.com/sponsors/Boshen"
}
},
- "node_modules/lightningcss-android-arm64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
- "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "node_modules/@remix-run/router": {
+ "version": "1.23.2",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
+ "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz",
+ "integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==",
"cpu": [
"arm64"
],
"dev": true,
- "license": "MPL-2.0",
+ "license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/lightningcss-darwin-arm64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
- "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0.tgz",
+ "integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==",
"cpu": [
"arm64"
],
"dev": true,
- "license": "MPL-2.0",
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/lightningcss-darwin-x64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
- "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz",
+ "integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==",
"cpu": [
"x64"
],
"dev": true,
- "license": "MPL-2.0",
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/lightningcss-freebsd-x64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
- "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz",
+ "integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==",
"cpu": [
"x64"
],
"dev": true,
- "license": "MPL-2.0",
+ "license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/lightningcss-linux-arm-gnueabihf": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
- "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0.tgz",
+ "integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==",
"cpu": [
"arm"
],
"dev": true,
- "license": "MPL-2.0",
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/lightningcss-linux-arm64-gnu": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
- "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0.tgz",
+ "integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==",
"cpu": [
"arm64"
],
@@ -849,23 +905,19 @@
"libc": [
"glibc"
],
- "license": "MPL-2.0",
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/lightningcss-linux-arm64-musl": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
- "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz",
+ "integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==",
"cpu": [
"arm64"
],
@@ -873,363 +925,4280 @@
"libc": [
"musl"
],
- "license": "MPL-2.0",
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/lightningcss-linux-x64-gnu": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
- "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0.tgz",
+ "integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==",
"cpu": [
- "x64"
+ "ppc64"
],
"dev": true,
"libc": [
"glibc"
],
- "license": "MPL-2.0",
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/lightningcss-linux-x64-musl": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
- "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0.tgz",
+ "integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==",
"cpu": [
- "x64"
+ "s390x"
],
"dev": true,
"libc": [
- "musl"
+ "glibc"
],
- "license": "MPL-2.0",
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/lightningcss-win32-arm64-msvc": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
- "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0.tgz",
+ "integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==",
"cpu": [
- "arm64"
+ "x64"
],
"dev": true,
- "license": "MPL-2.0",
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
"optional": true,
"os": [
- "win32"
+ "linux"
],
"engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/lightningcss-win32-x64-msvc": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
- "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0.tgz",
+ "integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==",
"cpu": [
"x64"
],
"dev": true,
- "license": "MPL-2.0",
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
"optional": true,
"os": [
- "win32"
+ "linux"
],
"engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz",
+ "integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
- "node_modules/lucide-react": {
- "version": "0.468.0",
- "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz",
- "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==",
- "license": "ISC",
- "peerDependencies": {
- "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/magic-string": {
- "version": "0.30.21",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
- "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0.tgz",
+ "integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==",
+ "cpu": [
+ "wasm32"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.5"
+ "@emnapi/core": "1.10.0",
+ "@emnapi/runtime": "1.10.0",
+ "@napi-rs/wasm-runtime": "^1.1.4"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/nanoid": {
- "version": "3.3.12",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
- "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0.tgz",
+ "integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==",
+ "cpu": [
+ "arm64"
],
+ "dev": true,
"license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/obug": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
- "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0.tgz",
+ "integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "funding": [
- "https://github.com/sponsors/sxzz",
- "https://opencollective.com/debug"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
],
- "license": "MIT"
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/pathe": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
- "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.7",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz",
+ "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==",
"dev": true,
"license": "MIT"
},
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "node_modules/@sindresorhus/is": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
+ "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=12"
+ "node": ">=10"
},
"funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
- "node_modules/postcss": {
- "version": "8.5.14",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
- "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
"dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
+ "license": "MIT"
+ },
+ "node_modules/@szmarczak/http-timer": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
+ "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "defer-to-connect": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
+ "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/cacheable-request": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
+ "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-cache-semantics": "*",
+ "@types/keyv": "^3.1.4",
+ "@types/node": "*",
+ "@types/responselike": "^1.0.0"
+ }
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
+ "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/fs-extra": {
+ "version": "9.0.13",
+ "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
+ "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/http-cache-semantics": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
+ "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/keyv": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
+ "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "24.12.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz",
+ "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/@types/plist": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz",
+ "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*",
+ "xmlbuilder": ">=11.0.1"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.28",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
+ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@types/responselike": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
+ "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/slice-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@types/slice-ansi/-/slice-ansi-4.0.0.tgz",
+ "integrity": "sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/verror": {
+ "version": "1.10.11",
+ "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz",
+ "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@types/yauzl": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+ "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
+ "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "1.0.0-rc.7"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
+ "babel-plugin-react-compiler": "^1.0.0",
+ "vite": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rolldown/plugin-babel": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz",
+ "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.6",
+ "@vitest/utils": "4.1.6",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz",
+ "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.6",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz",
+ "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz",
+ "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.6",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz",
+ "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.6",
+ "@vitest/utils": "4.1.6",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz",
+ "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz",
+ "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.6",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@xmldom/xmldom": {
+ "version": "0.8.13",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz",
+ "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/7zip-bin": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz",
+ "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/abbrev": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz",
+ "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "ajv": "^6.9.1"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/app-builder-bin": {
+ "version": "5.0.0-alpha.12",
+ "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.12.tgz",
+ "integrity": "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/app-builder-lib": {
+ "version": "26.8.1",
+ "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.8.1.tgz",
+ "integrity": "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@develar/schema-utils": "~2.6.5",
+ "@electron/asar": "3.4.1",
+ "@electron/fuses": "^1.8.0",
+ "@electron/get": "^3.0.0",
+ "@electron/notarize": "2.5.0",
+ "@electron/osx-sign": "1.3.3",
+ "@electron/rebuild": "^4.0.3",
+ "@electron/universal": "2.0.3",
+ "@malept/flatpak-bundler": "^0.4.0",
+ "@types/fs-extra": "9.0.13",
+ "async-exit-hook": "^2.0.1",
+ "builder-util": "26.8.1",
+ "builder-util-runtime": "9.5.1",
+ "chromium-pickle-js": "^0.2.0",
+ "ci-info": "4.3.1",
+ "debug": "^4.3.4",
+ "dotenv": "^16.4.5",
+ "dotenv-expand": "^11.0.6",
+ "ejs": "^3.1.8",
+ "electron-publish": "26.8.1",
+ "fs-extra": "^10.1.0",
+ "hosted-git-info": "^4.1.0",
+ "isbinaryfile": "^5.0.0",
+ "jiti": "^2.4.2",
+ "js-yaml": "^4.1.0",
+ "json5": "^2.2.3",
+ "lazy-val": "^1.0.5",
+ "minimatch": "^10.0.3",
+ "plist": "3.1.0",
+ "proper-lockfile": "^4.1.2",
+ "resedit": "^1.7.0",
+ "semver": "~7.7.3",
+ "tar": "^7.5.7",
+ "temp-file": "^3.4.0",
+ "tiny-async-pool": "1.3.0",
+ "which": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "dmg-builder": "26.8.1",
+ "electron-builder-squirrel-windows": "26.8.1"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/@electron/get": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz",
+ "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "env-paths": "^2.2.0",
+ "fs-extra": "^8.1.0",
+ "got": "^11.8.5",
+ "progress": "^2.0.3",
+ "semver": "^6.2.0",
+ "sumchecker": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "optionalDependencies": {
+ "global-agent": "^3.0.0"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/ci-info": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz",
+ "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "license": "MIT",
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/app-builder-lib/node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/astral-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/async": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/async-exit-hook": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz",
+ "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/at-least-node": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
+ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/big-integer": {
+ "version": "1.6.52",
+ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
+ "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
+ "dev": true,
+ "license": "Unlicense",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/boolean": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
+ "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
+ "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/bplist-parser": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz",
+ "integrity": "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "big-integer": "1.6.x"
+ },
+ "engines": {
+ "node": ">= 5.10.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/builder-util": {
+ "version": "26.8.1",
+ "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.8.1.tgz",
+ "integrity": "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/debug": "^4.1.6",
+ "7zip-bin": "~5.2.0",
+ "app-builder-bin": "5.0.0-alpha.12",
+ "builder-util-runtime": "9.5.1",
+ "chalk": "^4.1.2",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.4",
+ "fs-extra": "^10.1.0",
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.0",
+ "js-yaml": "^4.1.0",
+ "sanitize-filename": "^1.6.3",
+ "source-map-support": "^0.5.19",
+ "stat-mode": "^1.0.0",
+ "temp-file": "^3.4.0",
+ "tiny-async-pool": "1.3.0"
+ }
+ },
+ "node_modules/builder-util-runtime": {
+ "version": "9.5.1",
+ "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz",
+ "integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4",
+ "sax": "^1.2.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/cacheable-lookup": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
+ "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.6.0"
+ }
+ },
+ "node_modules/cacheable-request": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
+ "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "clone-response": "^1.0.2",
+ "get-stream": "^5.1.0",
+ "http-cache-semantics": "^4.0.0",
+ "keyv": "^4.0.0",
+ "lowercase-keys": "^2.0.0",
+ "normalize-url": "^6.0.1",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
+ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chromium-pickle-js": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz",
+ "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ci-info": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
+ "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cli-truncate": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz",
+ "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "slice-ansi": "^3.0.0",
+ "string-width": "^4.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/clone-response": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
+ "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
+ "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/compare-version": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz",
+ "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/crc": {
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz",
+ "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "buffer": "^5.1.0"
+ }
+ },
+ "node_modules/cross-dirname": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz",
+ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cross-spawn/node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/cross-spawn/node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/decompress-response/node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/defer-to-connect": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
+ "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-lazy-prop": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
+ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/detect-node": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
+ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/dir-compare": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz",
+ "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimatch": "^3.0.5",
+ "p-limit": "^3.1.0 "
+ }
+ },
+ "node_modules/dir-compare/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/dir-compare/node_modules/brace-expansion": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
+ "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/dir-compare/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/dmg-builder": {
+ "version": "26.8.1",
+ "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.8.1.tgz",
+ "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "app-builder-lib": "26.8.1",
+ "builder-util": "26.8.1",
+ "fs-extra": "^10.1.0",
+ "iconv-lite": "^0.6.2",
+ "js-yaml": "^4.1.0"
+ },
+ "optionalDependencies": {
+ "dmg-license": "^1.0.11"
+ }
+ },
+ "node_modules/dmg-license": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz",
+ "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "dependencies": {
+ "@types/plist": "^3.0.1",
+ "@types/verror": "^1.10.3",
+ "ajv": "^6.10.0",
+ "crc": "^3.8.0",
+ "iconv-corefoundation": "^1.1.7",
+ "plist": "^3.0.4",
+ "smart-buffer": "^4.0.2",
+ "verror": "^1.10.0"
+ },
+ "bin": {
+ "dmg-license": "bin/dmg-license.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dotenv-expand": {
+ "version": "11.0.7",
+ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz",
+ "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dotenv": "^16.4.5"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ejs": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
+ "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "jake": "^10.8.5"
+ },
+ "bin": {
+ "ejs": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/electron": {
+ "version": "42.0.1",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-42.0.1.tgz",
+ "integrity": "sha512-d8HnycE970DGESe91Nj30eonFBUcAI9EZ1TwUGJVzSAnJZdh0BkFEinAXjdklvDYst+bVDc8HsksCuqVLrnqdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@electron/get": "^5.0.0",
+ "@types/node": "^24.9.0",
+ "extract-zip": "^2.0.1"
+ },
+ "bin": {
+ "electron": "cli.js",
+ "install-electron": "install.js"
+ },
+ "engines": {
+ "node": ">= 22.12.0"
+ }
+ },
+ "node_modules/electron-builder": {
+ "version": "26.8.1",
+ "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.8.1.tgz",
+ "integrity": "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "app-builder-lib": "26.8.1",
+ "builder-util": "26.8.1",
+ "builder-util-runtime": "9.5.1",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "dmg-builder": "26.8.1",
+ "fs-extra": "^10.1.0",
+ "lazy-val": "^1.0.5",
+ "simple-update-notifier": "2.0.0",
+ "yargs": "^17.6.2"
+ },
+ "bin": {
+ "electron-builder": "cli.js",
+ "install-app-deps": "install-app-deps.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/electron-builder-squirrel-windows": {
+ "version": "26.8.1",
+ "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.8.1.tgz",
+ "integrity": "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "app-builder-lib": "26.8.1",
+ "builder-util": "26.8.1",
+ "electron-winstaller": "5.4.0"
+ }
+ },
+ "node_modules/electron-publish": {
+ "version": "26.8.1",
+ "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.8.1.tgz",
+ "integrity": "sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/fs-extra": "^9.0.11",
+ "builder-util": "26.8.1",
+ "builder-util-runtime": "9.5.1",
+ "chalk": "^4.1.2",
+ "form-data": "^4.0.5",
+ "fs-extra": "^10.1.0",
+ "lazy-val": "^1.0.5",
+ "mime": "^2.5.2"
+ }
+ },
+ "node_modules/electron-winstaller": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz",
+ "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@electron/asar": "^3.2.1",
+ "debug": "^4.1.1",
+ "fs-extra": "^7.0.1",
+ "lodash": "^4.17.21",
+ "temp": "^0.9.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@electron/windows-sign": "^1.1.2"
+ }
+ },
+ "node_modules/electron-winstaller/node_modules/fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/electron-winstaller/node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/electron-winstaller/node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/elementtree": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/elementtree/-/elementtree-0.1.7.tgz",
+ "integrity": "sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "sax": "1.1.4"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/elementtree/node_modules/sax": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz",
+ "integrity": "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
+ "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/err-code": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
+ "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
+ "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es6-error": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+ "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/exponential-backoff": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz",
+ "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/extract-zip": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
+ "node_modules/extsprintf": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz",
+ "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==",
+ "dev": true,
+ "engines": [
+ "node >=0.6.0"
+ ],
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/filelist": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
+ "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "minimatch": "^5.0.1"
+ }
+ },
+ "node_modules/filelist/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/filelist/node_modules/brace-expansion": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+ "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/filelist/node_modules/minimatch": {
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
+ "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
+ "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/global-agent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
+ "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "boolean": "^3.0.1",
+ "es6-error": "^4.1.1",
+ "matcher": "^3.0.0",
+ "roarr": "^2.15.3",
+ "semver": "^7.3.2",
+ "serialize-error": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=10.0"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/got": {
+ "version": "11.8.6",
+ "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
+ "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/is": "^4.0.0",
+ "@szmarczak/http-timer": "^4.0.5",
+ "@types/cacheable-request": "^6.0.1",
+ "@types/responselike": "^1.0.0",
+ "cacheable-lookup": "^5.0.3",
+ "cacheable-request": "^7.0.2",
+ "decompress-response": "^6.0.0",
+ "http2-wrapper": "^1.0.0-beta.5.2",
+ "lowercase-keys": "^2.0.0",
+ "p-cancelable": "^2.0.0",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/got?sponsor=1"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hosted-git-info": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
+ "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/http-cache-semantics": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
+ "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/http2-wrapper": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
+ "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "quick-lru": "^5.1.1",
+ "resolve-alpn": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/iconv-corefoundation": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz",
+ "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "dependencies": {
+ "cli-truncate": "^2.1.0",
+ "node-addon-api": "^1.6.3"
+ },
+ "engines": {
+ "node": "^8.11.2 || >=10"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/ini": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz",
+ "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isbinaryfile": {
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz",
+ "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/gjtorikian/"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz",
+ "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/jake": {
+ "version": "10.9.4",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
+ "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "async": "^3.2.6",
+ "filelist": "^1.0.4",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "jake": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
+ "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
+ "dev": true,
+ "license": "ISC",
+ "optional": true
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
+ "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lazy-val": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
+ "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lowercase-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
+ "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.468.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz",
+ "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/matcher": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
+ "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "escape-string-regexp": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mime": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-response": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
+ "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/native-run": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/native-run/-/native-run-2.0.3.tgz",
+ "integrity": "sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ionic/utils-fs": "^3.1.7",
+ "@ionic/utils-terminal": "^2.3.4",
+ "bplist-parser": "^0.3.2",
+ "debug": "^4.3.4",
+ "elementtree": "^0.1.7",
+ "ini": "^4.1.1",
+ "plist": "^3.1.0",
+ "split2": "^4.2.0",
+ "through2": "^4.0.2",
+ "tslib": "^2.6.2",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "native-run": "bin/native-run"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/node-abi": {
+ "version": "4.31.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.31.0.tgz",
+ "integrity": "sha512-Erq5w/t3syw3s4sDsUaX4QttIdBPsGKTT1DTRsCkTonGggczhlDKm/wDX3o+HPJpQ41EjXCbcmXf0tgr5YZJXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.6.3"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ }
+ },
+ "node_modules/node-addon-api": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz",
+ "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/node-api-version": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz",
+ "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.3.5"
+ }
+ },
+ "node_modules/node-gyp": {
+ "version": "12.3.0",
+ "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz",
+ "integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "env-paths": "^2.2.0",
+ "exponential-backoff": "^3.1.1",
+ "graceful-fs": "^4.2.6",
+ "nopt": "^9.0.0",
+ "proc-log": "^6.0.0",
+ "semver": "^7.3.5",
+ "tar": "^7.5.4",
+ "tinyglobby": "^0.2.12",
+ "undici": "^6.25.0",
+ "which": "^6.0.0"
+ },
+ "bin": {
+ "node-gyp": "bin/node-gyp.js"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/node-gyp/node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/node-gyp/node_modules/isexe": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz",
+ "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/node-gyp/node_modules/undici": {
+ "version": "6.25.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz",
+ "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.17"
+ }
+ },
+ "node_modules/node-gyp/node_modules/which": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz",
+ "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^4.0.0"
+ },
+ "bin": {
+ "node-which": "bin/which.js"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/nopt": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz",
+ "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "abbrev": "^4.0.0"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/normalize-url": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
+ "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/obug": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/open": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
+ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-lazy-prop": "^2.0.0",
+ "is-docker": "^2.1.1",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-cancelable": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
+ "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-scurry": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
+ "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "11.3.6",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz",
+ "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pe-library": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz",
+ "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/jet2jet"
+ }
+ },
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/plist": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
+ "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@xmldom/xmldom": "^0.8.8",
+ "base64-js": "^1.5.1",
+ "xmlbuilder": "^15.1.1"
+ },
+ "engines": {
+ "node": ">=10.4.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.14",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
+ "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postject": {
+ "version": "1.0.0-alpha.6",
+ "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz",
+ "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "commander": "^9.4.0"
+ },
+ "bin": {
+ "postject": "dist/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/postject/node_modules/commander": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
+ "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "engines": {
+ "node": "^12.20.0 || >=14"
+ }
+ },
+ "node_modules/proc-log": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
+ "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/promise-retry": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
+ "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "err-code": "^2.0.2",
+ "retry": "^0.12.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/prompts": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
+ "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kleur": "^3.0.3",
+ "sisteransi": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/prompts/node_modules/kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/proper-lockfile": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
+ "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "retry": "^0.12.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "node_modules/pump": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/quick-lru": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "6.30.3",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
+ "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.30.3",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
+ "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.2",
+ "react-router": "6.30.3"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/read-binary-file-arch": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz",
+ "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4"
+ },
+ "bin": {
+ "read-binary-file-arch": "cli.js"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resedit": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz",
+ "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pe-library": "^0.4.1"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/jet2jet"
+ }
+ },
+ "node_modules/resolve-alpn": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
+ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/responselike": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
+ "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lowercase-keys": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/roarr": {
+ "version": "2.15.4",
+ "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
+ "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "boolean": "^3.0.1",
+ "detect-node": "^2.0.4",
+ "globalthis": "^1.0.1",
+ "json-stringify-safe": "^5.0.1",
+ "semver-compare": "^1.0.0",
+ "sprintf-js": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz",
+ "integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.129.0",
+ "@rolldown/pluginutils": "1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.0.0",
+ "@rolldown/binding-darwin-arm64": "1.0.0",
+ "@rolldown/binding-darwin-x64": "1.0.0",
+ "@rolldown/binding-freebsd-x64": "1.0.0",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.0",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.0",
+ "@rolldown/binding-linux-arm64-musl": "1.0.0",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.0",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.0",
+ "@rolldown/binding-linux-x64-gnu": "1.0.0",
+ "@rolldown/binding-linux-x64-musl": "1.0.0",
+ "@rolldown/binding-openharmony-arm64": "1.0.0",
+ "@rolldown/binding-wasm32-wasi": "1.0.0",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.0",
+ "@rolldown/binding-win32-x64-msvc": "1.0.0"
+ }
+ },
+ "node_modules/rolldown/node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz",
+ "integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
{
"type": "github",
- "url": "https://github.com/sponsors/ai"
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
}
],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/sanitize-filename": {
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz",
+ "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==",
+ "dev": true,
+ "license": "WTFPL OR ISC",
+ "dependencies": {
+ "truncate-utf8-bytes": "^1.0.0"
+ }
+ },
+ "node_modules/sax": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
+ "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.11",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
+ "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
},
"engines": {
- "node": "^10 || ^12 || >=14"
+ "node": ">=10"
}
},
- "node_modules/react": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
- "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "node_modules/semver-compare": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+ "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/serialize-error": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
+ "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "type-fest": "^0.13.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/simple-update-notifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sisteransi": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/slice-ansi": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
+ "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/smart-buffer": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 6.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
+ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/stat-mode": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz",
+ "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/std-env": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
+ "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "loose-envify": "^1.1.0"
+ "ansi-regex": "^5.0.1"
},
"engines": {
- "node": ">=0.10.0"
+ "node": ">=8"
}
},
- "node_modules/react-dom": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
- "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
- "license": "MIT",
+ "node_modules/sumchecker": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
+ "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
+ "dev": true,
+ "license": "Apache-2.0",
"dependencies": {
- "loose-envify": "^1.1.0",
- "scheduler": "^0.23.2"
+ "debug": "^4.1.0"
},
- "peerDependencies": {
- "react": "^18.3.1"
+ "engines": {
+ "node": ">= 8.0"
}
},
- "node_modules/react-router": {
- "version": "6.30.3",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
- "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@remix-run/router": "1.23.2"
+ "has-flag": "^4.0.0"
},
"engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "react": ">=16.8"
+ "node": ">=8"
}
},
- "node_modules/react-router-dom": {
- "version": "6.30.3",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
- "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
- "license": "MIT",
+ "node_modules/tar": {
+ "version": "7.5.15",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz",
+ "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "@remix-run/router": "1.23.2",
- "react-router": "6.30.3"
+ "@isaacs/fs-minipass": "^4.0.0",
+ "chownr": "^3.0.0",
+ "minipass": "^7.1.2",
+ "minizlib": "^3.1.0",
+ "yallist": "^5.0.0"
},
"engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "react": ">=16.8",
- "react-dom": ">=16.8"
+ "node": ">=18"
}
},
- "node_modules/rolldown": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz",
- "integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==",
+ "node_modules/tar/node_modules/yallist": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
+ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/temp": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz",
+ "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
- "@oxc-project/types": "=0.129.0",
- "@rolldown/pluginutils": "1.0.0"
- },
- "bin": {
- "rolldown": "bin/cli.mjs"
+ "mkdirp": "^0.5.1",
+ "rimraf": "~2.6.2"
},
"engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.0.0",
- "@rolldown/binding-darwin-arm64": "1.0.0",
- "@rolldown/binding-darwin-x64": "1.0.0",
- "@rolldown/binding-freebsd-x64": "1.0.0",
- "@rolldown/binding-linux-arm-gnueabihf": "1.0.0",
- "@rolldown/binding-linux-arm64-gnu": "1.0.0",
- "@rolldown/binding-linux-arm64-musl": "1.0.0",
- "@rolldown/binding-linux-ppc64-gnu": "1.0.0",
- "@rolldown/binding-linux-s390x-gnu": "1.0.0",
- "@rolldown/binding-linux-x64-gnu": "1.0.0",
- "@rolldown/binding-linux-x64-musl": "1.0.0",
- "@rolldown/binding-openharmony-arm64": "1.0.0",
- "@rolldown/binding-wasm32-wasi": "1.0.0",
- "@rolldown/binding-win32-arm64-msvc": "1.0.0",
- "@rolldown/binding-win32-x64-msvc": "1.0.0"
+ "node": ">=6.0.0"
}
},
- "node_modules/rolldown/node_modules/@rolldown/pluginutils": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz",
- "integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==",
+ "node_modules/temp-file": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz",
+ "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==",
"dev": true,
- "license": "MIT"
- },
- "node_modules/scheduler": {
- "version": "0.23.2",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
- "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"license": "MIT",
"dependencies": {
- "loose-envify": "^1.1.0"
+ "async-exit-hook": "^2.0.1",
+ "fs-extra": "^10.0.0"
}
},
- "node_modules/siginfo": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
- "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "node_modules/through2": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz",
+ "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==",
"dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
+ "license": "MIT",
+ "dependencies": {
+ "readable-stream": "3"
}
},
- "node_modules/stackback": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
- "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "node_modules/tiny-async-pool": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz",
+ "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^5.5.0"
+ }
},
- "node_modules/std-env": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
- "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
+ "node_modules/tiny-async-pool/node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
- "license": "MIT"
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver"
+ }
},
"node_modules/tinybench": {
"version": "2.9.0",
@@ -1275,13 +5244,65 @@
"node": ">=14.0.0"
}
},
+ "node_modules/tmp": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
+ "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/tmp-promise": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz",
+ "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tmp": "^0.2.0"
+ }
+ },
+ "node_modules/tree-kill": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tree-kill": "cli.js"
+ }
+ },
+ "node_modules/truncate-utf8-bytes": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
+ "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==",
+ "dev": true,
+ "license": "WTFPL",
+ "dependencies": {
+ "utf8-byte-length": "^1.0.1"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/type-fest": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
+ "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
"dev": true,
- "license": "0BSD",
- "optional": true
+ "license": "(MIT OR CC0-1.0)",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
},
"node_modules/typescript": {
"version": "5.9.3",
@@ -1297,6 +5318,84 @@
"node": ">=14.17"
}
},
+ "node_modules/undici": {
+ "version": "7.25.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz",
+ "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=20.18.1"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/untildify": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
+ "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/utf8-byte-length": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
+ "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==",
+ "dev": true,
+ "license": "(WTFPL OR MIT)"
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/verror": {
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
+ "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
"node_modules/vite": {
"version": "8.0.12",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz",
@@ -1465,6 +5564,22 @@
}
}
},
+ "node_modules/which": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz",
+ "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^3.1.1"
+ },
+ "bin": {
+ "node-which": "bin/which.js"
+ },
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
"node_modules/why-is-node-running": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
@@ -1481,6 +5596,135 @@
"engines": {
"node": ">=8"
}
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/xml2js": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
+ "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/xml2js/node_modules/xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/xmlbuilder": {
+ "version": "15.1.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
+ "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
}
}
}
diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json
index cda2a83a..f70f467e 100644
--- a/apps/dashboard/package.json
+++ b/apps/dashboard/package.json
@@ -2,24 +2,42 @@
"name": "@flowmemory/dashboard",
"private": true,
"version": "0.0.0",
+ "description": "Flowchain desktop wallet and local bridge console.",
+ "author": "FlowmemoryAI",
"type": "module",
+ "main": "electron/main.cjs",
"scripts": {
"dev": "npm run sync:fixtures && vite --host 127.0.0.1",
"build": "npm run sync:fixtures && tsc -b && vite build",
+ "desktop": "npm run build && electron electron/main.cjs",
+ "desktop:pack": "npm run build && electron-builder --config electron-builder.json --win --x64 --dir && powershell -NoProfile -ExecutionPolicy Bypass -File scripts/package-desktop-wallet.ps1",
+ "desktop:installer:win": "npm run build && electron-builder --config electron-builder.json --win nsis zip --x64",
+ "desktop:installer:mac": "npm run build && electron-builder --config electron-builder.json --mac dmg zip",
+ "desktop:installer:linux": "npm run build && electron-builder --config electron-builder.json --linux AppImage zip",
+ "mobile:sync": "npm run build && cap sync",
+ "mobile:android:sync": "npm run build && cap sync android",
+ "mobile:android:debug": "npm run mobile:android:sync && cd android && gradlew.bat assembleDebug",
"test": "vitest run",
"typecheck": "tsc -b",
"sync:fixtures": "node scripts/sync-fixtures.mjs"
},
"dependencies": {
+ "@capacitor/android": "^8.3.4",
+ "@capacitor/core": "^8.3.4",
+ "@noble/hashes": "^2.2.0",
+ "@noble/secp256k1": "^3.1.0",
"lucide-react": "^0.468.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.28.0"
},
"devDependencies": {
+ "@capacitor/cli": "^8.3.4",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^6.0.1",
+ "electron": "^42.0.1",
+ "electron-builder": "^26.8.1",
"typescript": "^5.6.3",
"vite": "^8.0.12",
"vitest": "^4.1.6"
diff --git a/apps/dashboard/public/data/flowchain-bridge-test-deposit.json b/apps/dashboard/public/data/flowchain-bridge-test-deposit.json
index f43813a2..19772f29 100644
--- a/apps/dashboard/public/data/flowchain-bridge-test-deposit.json
+++ b/apps/dashboard/public/data/flowchain-bridge-test-deposit.json
@@ -8,8 +8,8 @@
"token": "0x3333333333333333333333333333333333333333",
"amount": "20000000",
"sender": "0x4444444444444444444444444444444444444444",
- "flowchainRecipient": "0x5555555555555555555555555555555555555555555555555555555555555555",
+ "flowchainRecipient": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"nonce": "1",
- "metadataHash": "0x6666666666666666666666666666666666666666666666666666666666666666",
+ "metadataHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"status": "observed"
}
diff --git a/apps/dashboard/public/data/flowchain-local-devnet-dashboard-state.json b/apps/dashboard/public/data/flowchain-local-devnet-dashboard-state.json
index 2ecd1722..7d49bdb2 100644
--- a/apps/dashboard/public/data/flowchain-local-devnet-dashboard-state.json
+++ b/apps/dashboard/public/data/flowchain-local-devnet-dashboard-state.json
@@ -31,19 +31,25 @@
},
"balanceTransfers": {},
"baseAnchors": {
- "0xf57ab7d2c1459c03cf01bfddd56b046be685d8eaa4597e6bb54b5015aeaf003f": {
+ "0x6908eac7dbf828ab8d295d2ae86f0b0db9295a27b1fe4999c26a3ed97244ef8d": {
"agentAccountRoot": "0xcf31230bfff347f79e19a55f4d1ff5fa486b0b1ad4754ce22b93de4b259a3ca7",
- "anchorId": "0xf57ab7d2c1459c03cf01bfddd56b046be685d8eaa4597e6bb54b5015aeaf003f",
+ "anchorId": "0x6908eac7dbf828ab8d295d2ae86f0b0db9295a27b1fe4999c26a3ed97244ef8d",
"appchainChainId": "flowmemory-local-devnet-v0",
"artifactAvailabilityProofRoot": "0xfb4b693c45014aae0947f35696e9d864e7b26ac6fd39c1df5edb3e0dcf9bd928",
"artifactCommitmentRoot": "0xb772a9f7273032fd3ba2da8b6476d4715bbbafbd2a7eed21ecd0d558bde3beab",
"balanceTransferRoot": "0x9b6e249f769a93bc9f34a90156e028d1a830badcd8ccdc5b1487d512cdbf0a6d",
"blockRangeEnd": 1,
"blockRangeStart": 1,
+ "bridgeAccountMappingRoot": "0x46285ec5782d3e6cc1e557fd5dd779e979d679e5cc723832cb189d27a5c552c8",
+ "bridgeAssetMappingRoot": "0xbd283f6190cba76038f7364e207557dd20f7883d78e5f89a96579af5455a89aa",
+ "bridgeCreditReceiptRoot": "0x0a86ce98161cb40ca4c7719468c0ed8f122677d12641c13305277094b8f3a509",
+ "bridgeCreditRoot": "0x613ab06f110cc7a5eb06a0f44b7a0f04b0b3cfb0556d30d42842f33563596248",
+ "bridgeEventReceiptIndexRoot": "0xcaad305c45f57c516d0fdfdef15c5d88b399b976df173f005e9d7dc30fbdbdb2",
+ "bridgeReplayIndexRoot": "0x9344f8651651d2d5042d2853205154a5777cd57aa9e892e2ac41b9b9fe04c00f",
"challengeRoot": "0x16da3d2bf2dcd801bc5deb3987dc01342cb957031ad01408ea77bf5d1583656f",
"dexPoolRoot": "0x0e5f034494a2deb6a4f20c04f01e678795c587df4869ad3f189f107fcc447dea",
"faucetRecordRoot": "0x2277503a52fab3f9e49b40debfb7d641abee75cf268aa56da403fdcf4fad6cee",
- "finalityReceiptRoot": "0xdf352e20fd1ddfd2855202335e03cfec21d87e99bf8717d161fe8648998e16cf",
+ "finalityReceiptRoot": "0x1c8536e507c5ba2242a5875c9537043645828825286ca256cc1171f1eb960c3f",
"finalityStatus": "local-placeholder",
"liquidityReceiptRoot": "0x98bf15cc6859994038744612e125236b1f777895a051c41702c2004134327738",
"localTestUnitBalanceRoot": "0x167041ef195b5dde2d2cade6ecb26c9a0a596e9ed21ff7bfb02d33c9d2be8d15",
@@ -53,7 +59,7 @@
"operatorKeyReferenceRoot": "0x8457aa3ed0f4238834a8f3925f25ccca805828d8427c3ef67590a45659b22a40",
"previousAnchorId": "0x0000000000000000000000000000000000000000000000000000000000000000",
"rootfieldStateRoot": "0xb72a851dca1103410484e3272945bae5e87fc39b8f32f77d2991959b60d3bfbf",
- "stateRoot": "0x8c7c1e7a078b60a809d17a51c44e275059afb8d7535769430c3fc9e9320c7e23",
+ "stateRoot": "0x7e50021bc0dd2537f9ecfd7973f9799b6ffdffcfd427e3bb059f4dfd1253b947",
"swapReceiptRoot": "0xc9f3ee93962f36ed10421ec9ad736079a3b13ef6504336495af243b718cefee1",
"tokenBalanceRoot": "0xbaf3b150fe41a0f3a2d9fe3dd9a664f9c5934bfef37218d9c3bf1c682be5f8c6",
"tokenDefinitionRoot": "0xbbbad9681e8756403940e4333111706a4fcee1f30534ba14deea9ba148056be0",
@@ -64,6 +70,12 @@
}
},
"blockHeight": 2,
+ "bridgeAccountMappings": {},
+ "bridgeAssetMappings": {},
+ "bridgeCreditReceipts": {},
+ "bridgeCredits": {},
+ "bridgeEventReceiptIndex": {},
+ "bridgeReplayIndex": {},
"challenges": {
"challenge:demo:001": {
"challengeId": "challenge:demo:001",
@@ -98,7 +110,7 @@
"finalizedBy": "operator:local-demo",
"receiptId": "receipt:demo:001",
"rootfieldId": "rootfield:demo:alpha",
- "stateRoot": "0x4e7ee5e7a8cab9b4ddda183842b9e9c1e1e000afea820b577ecc90fa4d9517e2"
+ "stateRoot": "0xf47b94f6d4be36bc5ade63085c9c3c6174d5b25eb5bfaae9a57e3946f5637352"
}
},
"genesisConfig": {
@@ -134,11 +146,17 @@
"artifactAvailabilityProofRoot": "0xfb4b693c45014aae0947f35696e9d864e7b26ac6fd39c1df5edb3e0dcf9bd928",
"artifactCommitmentRoot": "0xb772a9f7273032fd3ba2da8b6476d4715bbbafbd2a7eed21ecd0d558bde3beab",
"balanceTransferRoot": "0x9b6e249f769a93bc9f34a90156e028d1a830badcd8ccdc5b1487d512cdbf0a6d",
- "baseAnchorRoot": "0xa10b087464d8e6098696295a2a4b26a4396974c9ed10dd0bba429f22284cd573",
+ "baseAnchorRoot": "0x8b7c3423523f44b9a470a1281b088c72537a42e26f88fc8cd4c3e8360324c7ca",
+ "bridgeAccountMappingRoot": "0x46285ec5782d3e6cc1e557fd5dd779e979d679e5cc723832cb189d27a5c552c8",
+ "bridgeAssetMappingRoot": "0xbd283f6190cba76038f7364e207557dd20f7883d78e5f89a96579af5455a89aa",
+ "bridgeCreditReceiptRoot": "0x0a86ce98161cb40ca4c7719468c0ed8f122677d12641c13305277094b8f3a509",
+ "bridgeCreditRoot": "0x613ab06f110cc7a5eb06a0f44b7a0f04b0b3cfb0556d30d42842f33563596248",
+ "bridgeEventReceiptIndexRoot": "0xcaad305c45f57c516d0fdfdef15c5d88b399b976df173f005e9d7dc30fbdbdb2",
+ "bridgeReplayIndexRoot": "0x9344f8651651d2d5042d2853205154a5777cd57aa9e892e2ac41b9b9fe04c00f",
"challengeRoot": "0x16da3d2bf2dcd801bc5deb3987dc01342cb957031ad01408ea77bf5d1583656f",
"dexPoolRoot": "0x0e5f034494a2deb6a4f20c04f01e678795c587df4869ad3f189f107fcc447dea",
"faucetRecordRoot": "0x2277503a52fab3f9e49b40debfb7d641abee75cf268aa56da403fdcf4fad6cee",
- "finalityReceiptRoot": "0xdf352e20fd1ddfd2855202335e03cfec21d87e99bf8717d161fe8648998e16cf",
+ "finalityReceiptRoot": "0x1c8536e507c5ba2242a5875c9537043645828825286ca256cc1171f1eb960c3f",
"importedObservationRoot": "0x99cb1b939d5a09f800f72e4c5a2b92988571126e1f6f93549f4893b3f7de7880",
"importedVerifierReportRoot": "0x6070b1015f000dd509c7b276d2ad68d8a9d188ef1a961c2f573346eb75ea5ad7",
"liquidityReceiptRoot": "0x98bf15cc6859994038744612e125236b1f777895a051c41702c2004134327738",
@@ -210,7 +228,7 @@
}
},
"schema": "flowmemory.dashboard_state.local_devnet.v0",
- "stateRoot": "0x3074ef2e5311d94e8f9a2660a6cc016c7b7f9a08c56ee07f9e841c1489726e68",
+ "stateRoot": "0xb3c4344483c7be8c0660d49d465d033a88ae26d6a7c1cbe096a2447d32c5648c",
"swapReceipts": {},
"tokenBalances": {},
"tokenDefinitions": {},
diff --git a/apps/dashboard/public/data/flowchain-local-devnet-state.json b/apps/dashboard/public/data/flowchain-local-devnet-state.json
index 080bdd3f..d8fc300e 100644
--- a/apps/dashboard/public/data/flowchain-local-devnet-state.json
+++ b/apps/dashboard/public/data/flowchain-local-devnet-state.json
@@ -19,7 +19,7 @@
"genesisHash": "0x0f23c892cbd2d00c10839d97ddab833698a83f8df8d6df27ceac03cfdd4b7bc9",
"nextBlockNumber": 3,
"logicalTime": 1778688002,
- "parentHash": "0x7ddb184c69f798f25f27a254f1f530c6cdc31c9656ac19d1b8c114f7a3a650c6",
+ "parentHash": "0x64e3d605fb1bba40b7de6a6a9813f77619282c07b9a33c7b41df1f6e3d71958b",
"operatorKeyReferences": {
"operator-key:local-devnet:alpha": {
"schema": "flowmemory.local_devnet.operator_key_reference.v0",
@@ -89,6 +89,12 @@
"lpPositions": {},
"liquidityReceipts": {},
"swapReceipts": {},
+ "bridgeAssetMappings": {},
+ "bridgeAccountMappings": {},
+ "bridgeCredits": {},
+ "bridgeCreditReceipts": {},
+ "bridgeReplayIndex": {},
+ "bridgeEventReceiptIndex": {},
"modelPassports": {
"model:demo:local-alpha": {
"modelPassportId": "model:demo:local-alpha",
@@ -135,7 +141,7 @@
"finalityStatus": "finalized",
"challengeCount": 1,
"finalizedAtBlock": 1,
- "stateRoot": "0x4e7ee5e7a8cab9b4ddda183842b9e9c1e1e000afea820b577ecc90fa4d9517e2"
+ "stateRoot": "0xf47b94f6d4be36bc5ade63085c9c3c6174d5b25eb5bfaae9a57e3946f5637352"
}
},
"artifactCommitments": {
@@ -193,12 +199,12 @@
"importedObservations": {},
"importedVerifierReports": {},
"baseAnchors": {
- "0xf57ab7d2c1459c03cf01bfddd56b046be685d8eaa4597e6bb54b5015aeaf003f": {
- "anchorId": "0xf57ab7d2c1459c03cf01bfddd56b046be685d8eaa4597e6bb54b5015aeaf003f",
+ "0x6908eac7dbf828ab8d295d2ae86f0b0db9295a27b1fe4999c26a3ed97244ef8d": {
+ "anchorId": "0x6908eac7dbf828ab8d295d2ae86f0b0db9295a27b1fe4999c26a3ed97244ef8d",
"appchainChainId": "flowmemory-local-devnet-v0",
"blockRangeStart": 1,
"blockRangeEnd": 1,
- "stateRoot": "0x8c7c1e7a078b60a809d17a51c44e275059afb8d7535769430c3fc9e9320c7e23",
+ "stateRoot": "0x7e50021bc0dd2537f9ecfd7973f9799b6ffdffcfd427e3bb059f4dfd1253b947",
"workReceiptRoot": "0x8b3ef5650c9eea2f608ad9c7cb73df3c289fc0ac72ed04f46e6ae4bce0a1f023",
"verifierReportRoot": "0x4facd21e55423e182eba87355482a35daa93f53190fbd3a8d2969f9d55bc5373",
"rootfieldStateRoot": "0xb72a851dca1103410484e3272945bae5e87fc39b8f32f77d2991959b60d3bfbf",
@@ -215,10 +221,16 @@
"lpPositionRoot": "0xe67dd98259afb06ca93620b4a8742b924ec2e8f3e6e72934eef5b8e60829d46f",
"liquidityReceiptRoot": "0x98bf15cc6859994038744612e125236b1f777895a051c41702c2004134327738",
"swapReceiptRoot": "0xc9f3ee93962f36ed10421ec9ad736079a3b13ef6504336495af243b718cefee1",
+ "bridgeAssetMappingRoot": "0xbd283f6190cba76038f7364e207557dd20f7883d78e5f89a96579af5455a89aa",
+ "bridgeAccountMappingRoot": "0x46285ec5782d3e6cc1e557fd5dd779e979d679e5cc723832cb189d27a5c552c8",
+ "bridgeCreditRoot": "0x613ab06f110cc7a5eb06a0f44b7a0f04b0b3cfb0556d30d42842f33563596248",
+ "bridgeCreditReceiptRoot": "0x0a86ce98161cb40ca4c7719468c0ed8f122677d12641c13305277094b8f3a509",
+ "bridgeReplayIndexRoot": "0x9344f8651651d2d5042d2853205154a5777cd57aa9e892e2ac41b9b9fe04c00f",
+ "bridgeEventReceiptIndexRoot": "0xcaad305c45f57c516d0fdfdef15c5d88b399b976df173f005e9d7dc30fbdbdb2",
"modelPassportRoot": "0x326aa6b0b372d29d24d747fe0879adfd7aaea206373b24ae2ab77d56357e9529",
"memoryCellRoot": "0x1b4e91099dd8d867201bd880437197ae6c031e538341aaa3cd2046e5706a2c25",
"challengeRoot": "0x16da3d2bf2dcd801bc5deb3987dc01342cb957031ad01408ea77bf5d1583656f",
- "finalityReceiptRoot": "0xdf352e20fd1ddfd2855202335e03cfec21d87e99bf8717d161fe8648998e16cf",
+ "finalityReceiptRoot": "0x1c8536e507c5ba2242a5875c9537043645828825286ca256cc1171f1eb960c3f",
"artifactAvailabilityProofRoot": "0xfb4b693c45014aae0947f35696e9d864e7b26ac6fd39c1df5edb3e0dcf9bd928",
"verifierModuleRoot": "0xd6ddd8a2d0f5812d64679656c69983a2e0aecd36bd36199d900245658ae4626c",
"previousAnchorId": "0x0000000000000000000000000000000000000000000000000000000000000000",
@@ -325,13 +337,13 @@
"error": null
}
],
- "stateRoot": "0x8c7c1e7a078b60a809d17a51c44e275059afb8d7535769430c3fc9e9320c7e23",
- "blockHash": "0x61e9f90b982f13988e85a382fc39da82c9114ecceea9001ab454c744e0801a9b"
+ "stateRoot": "0x7e50021bc0dd2537f9ecfd7973f9799b6ffdffcfd427e3bb059f4dfd1253b947",
+ "blockHash": "0xd890758bdeaa4ab7a658d7813b2b9bdd0850824aaef830b9f2f4ff455aaf3811"
},
{
"schema": "flowmemory.local_devnet.block.v0",
"blockNumber": 2,
- "parentHash": "0x61e9f90b982f13988e85a382fc39da82c9114ecceea9001ab454c744e0801a9b",
+ "parentHash": "0xd890758bdeaa4ab7a658d7813b2b9bdd0850824aaef830b9f2f4ff455aaf3811",
"logicalTime": 1778688001,
"txIds": [
"0x8f719c880f17b5d4fb6d9efd54ac276d0dd8050d11c2c7870c36a79b66bc49d7"
@@ -343,8 +355,8 @@
"error": null
}
],
- "stateRoot": "0x3074ef2e5311d94e8f9a2660a6cc016c7b7f9a08c56ee07f9e841c1489726e68",
- "blockHash": "0x7ddb184c69f798f25f27a254f1f530c6cdc31c9656ac19d1b8c114f7a3a650c6"
+ "stateRoot": "0xb3c4344483c7be8c0660d49d465d033a88ae26d6a7c1cbe096a2447d32c5648c",
+ "blockHash": "0x64e3d605fb1bba40b7de6a6a9813f77619282c07b9a33c7b41df1f6e3d71958b"
}
],
"pendingTxs": []
diff --git a/apps/dashboard/public/data/flowmemory-dashboard-base-sepolia-v4-hook.json b/apps/dashboard/public/data/flowmemory-dashboard-base-sepolia-v4-hook.json
new file mode 100644
index 00000000..3475825a
--- /dev/null
+++ b/apps/dashboard/public/data/flowmemory-dashboard-base-sepolia-v4-hook.json
@@ -0,0 +1,114 @@
+{
+ "metadata": {
+ "schema": "flowmemory.dashboard.fixture.v0",
+ "generatedAt": "2026-05-17T06:47:51.565Z",
+ "mode": "base-sepolia-v4-hook-proof",
+ "description": "Generated Base Sepolia v4 hook proof dashboard data from readback artifacts. It is public-testnet evidence and not production mainnet readiness.",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-base-sepolia-v4-hook.json",
+ "runtimeDataPath": "apps/dashboard/public/data/flowmemory-dashboard-base-sepolia-v4-hook.json",
+ "baseSepoliaHookProof": {
+ "liveProofComplete": false,
+ "stage": "dry-run-proof-ready",
+ "plannedHookAddress": "0xD24d7f807cb00D28DdF675E55879547d4F7B0040",
+ "readWindow": {
+ "fromBlock": "41616089",
+ "toBlock": "41616089",
+ "finalizedBlock": null
+ },
+ "counts": {
+ "observations": 0,
+ "canonicalObservations": 0,
+ "swapMemorySignals": 0,
+ "rejectedLogs": 0,
+ "duplicates": 0,
+ "memorySignals": 0,
+ "rootflowTransitions": 0
+ },
+ "missing": [
+ "deployed FlowMemoryAfterSwapHook code at the mined hook address",
+ "local env-check with funded Base Sepolia deployer",
+ "broadcast swap-proof artifact with receipts",
+ "non-empty Base Sepolia hook FlowPulse readback artifact"
+ ],
+ "nextSteps": [
+ "Run npm run hook:base-sepolia:env-check -- --json until broadcastReady is true.",
+ "Run npm run hook:base-sepolia:swap-proof:broadcast -- --json with the funded testnet key.",
+ "Run npm run hook:base-sepolia:readback-range -- --infer-readback-range --json to derive the readback window from broadcast receipts.",
+ "Run npm run hook:base-sepolia:readback:auto -- --json or npm run hook:base-sepolia:readback with an operator-reviewed range, and require proofComplete true.",
+ "Rerun npm run hook:base-sepolia:evidence -- --json."
+ ]
+ },
+ "futureGeneratedPaths": {
+ "indexer": "fixtures/deployments/base-sepolia-v4-hook-readback-state.latest.json",
+ "evidence": "fixtures/deployments/base-sepolia-v4-hook-evidence.latest.json",
+ "readback": "fixtures/deployments/base-sepolia-v4-hook-readback.latest.json"
+ }
+ },
+ "chain": {
+ "chainId": "84532",
+ "name": "Base Sepolia v4 hook proof",
+ "environment": "public-testnet",
+ "settlementContext": "Mined Uniswap v4 afterSwap hook proof path. Complete only after real PoolManager swap broadcast and non-empty readback.",
+ "currentBlock": 41616089,
+ "finalizedBlock": 41616089,
+ "source": "diagnostic-empty-readback",
+ "lastUpdated": "2026-05-17T06:47:51.565Z"
+ },
+ "flowPulseObservations": [],
+ "rootfields": [],
+ "workLanes": [
+ {
+ "id": "MEMORY_REFRESH",
+ "laneId": "MEMORY_REFRESH",
+ "name": "Swap memory proof",
+ "queueDepth": 1,
+ "inflight": 0,
+ "completed24h": 0,
+ "p95LatencyMs": null,
+ "operator": "base-sepolia-hook-proof",
+ "status": "unresolved",
+ "lastUpdated": "2026-05-17T06:47:51.565Z",
+ "provenance": {
+ "subsystem": "flowmemory",
+ "origin": "live-readback",
+ "chainContext": "base-sepolia-v4-hook-proof",
+ "fixturePath": "fixtures/deployments/base-sepolia-v4-hook-flowmemory.latest.json",
+ "capturedAt": "2026-05-17T06:47:51.565Z",
+ "localPathHint": "fixtures/deployments/base-sepolia-v4-hook-flowmemory.latest.json"
+ }
+ }
+ ],
+ "workReceipts": [],
+ "verifierReports": [],
+ "rootflowTransitions": [],
+ "memorySignals": [],
+ "memoryReceipts": [],
+ "rootfieldBundles": [],
+ "agentMemoryViews": [],
+ "devnetBlocks": [],
+ "hardwareNodes": [],
+ "alerts": [
+ {
+ "id": "0x341681f64a0aa8f62e2032dc31df3b096d3417da9795addb6f090a3ac7fa81ab",
+ "incidentId": "0x341681f64a0aa8f62e2032dc31df3b096d3417da9795addb6f090a3ac7fa81ab",
+ "severity": "warning",
+ "title": "Base Sepolia hook proof incomplete",
+ "summary": "Dry-run proof exists, but a funded Base Sepolia broadcast and non-empty hook readback are still required.",
+ "openedAt": "2026-05-17T06:47:51.565Z",
+ "linkedObjectIds": [
+ "0xD24d7f807cb00D28DdF675E55879547d4F7B0040"
+ ],
+ "recommendedAction": "Broadcast the swap proof with a funded testnet key, run readback over the receipt block range, then regenerate this dashboard.",
+ "status": "unresolved",
+ "lastUpdated": "2026-05-17T06:47:51.565Z",
+ "provenance": {
+ "subsystem": "alerts",
+ "origin": "live-readback",
+ "chainContext": "base-sepolia-v4-hook-proof",
+ "fixturePath": "fixtures/deployments/base-sepolia-v4-hook-flowmemory.latest.json",
+ "capturedAt": "2026-05-17T06:47:51.565Z",
+ "localPathHint": "fixtures/deployments/base-sepolia-v4-hook-evidence.latest.json"
+ }
+ }
+ ]
+}
diff --git a/apps/dashboard/public/data/flowmemory-dashboard-v0.json b/apps/dashboard/public/data/flowmemory-dashboard-v0.json
index b226d982..01343d34 100644
--- a/apps/dashboard/public/data/flowmemory-dashboard-v0.json
+++ b/apps/dashboard/public/data/flowmemory-dashboard-v0.json
@@ -1993,11 +1993,11 @@
],
"devnetBlocks": [
{
- "id": "0x61e9f90b982f13988e85a382fc39da82c9114ecceea9001ab454c744e0801a9b",
+ "id": "0xd890758bdeaa4ab7a658d7813b2b9bdd0850824aaef830b9f2f4ff455aaf3811",
"blockNumber": 1,
- "blockHash": "0x61e9f90b982f13988e85a382fc39da82c9114ecceea9001ab454c744e0801a9b",
+ "blockHash": "0xd890758bdeaa4ab7a658d7813b2b9bdd0850824aaef830b9f2f4ff455aaf3811",
"parentHash": "0x0f23c892cbd2d00c10839d97ddab833698a83f8df8d6df27ceac03cfdd4b7bc9",
- "stateRoot": "0x8c7c1e7a078b60a809d17a51c44e275059afb8d7535769430c3fc9e9320c7e23",
+ "stateRoot": "0x7e50021bc0dd2537f9ecfd7973f9799b6ffdffcfd427e3bb059f4dfd1253b947",
"receiptsRoot": "0x2f98caf4b28b2209cdf1f9beb1c23f8732c538657cc7a1d8855878b5400efabd",
"timestamp": "2026-05-13T16:00:00.000Z",
"observationCount": 8,
@@ -2015,11 +2015,11 @@
}
},
{
- "id": "0x7ddb184c69f798f25f27a254f1f530c6cdc31c9656ac19d1b8c114f7a3a650c6",
+ "id": "0x64e3d605fb1bba40b7de6a6a9813f77619282c07b9a33c7b41df1f6e3d71958b",
"blockNumber": 2,
- "blockHash": "0x7ddb184c69f798f25f27a254f1f530c6cdc31c9656ac19d1b8c114f7a3a650c6",
- "parentHash": "0x61e9f90b982f13988e85a382fc39da82c9114ecceea9001ab454c744e0801a9b",
- "stateRoot": "0x3074ef2e5311d94e8f9a2660a6cc016c7b7f9a08c56ee07f9e841c1489726e68",
+ "blockHash": "0x64e3d605fb1bba40b7de6a6a9813f77619282c07b9a33c7b41df1f6e3d71958b",
+ "parentHash": "0xd890758bdeaa4ab7a658d7813b2b9bdd0850824aaef830b9f2f4ff455aaf3811",
+ "stateRoot": "0xb3c4344483c7be8c0660d49d465d033a88ae26d6a7c1cbe096a2447d32c5648c",
"receiptsRoot": "0xa0407b9a8a55106d549e0f19b92fceaa7f7a25697e94ebf8a1fa74af7b9168f4",
"timestamp": "2026-05-13T16:00:01.000Z",
"observationCount": 8,
diff --git a/apps/dashboard/scripts/package-desktop-wallet.ps1 b/apps/dashboard/scripts/package-desktop-wallet.ps1
new file mode 100644
index 00000000..f041d287
--- /dev/null
+++ b/apps/dashboard/scripts/package-desktop-wallet.ps1
@@ -0,0 +1,20 @@
+$ErrorActionPreference = "Stop"
+
+$releaseDir = Resolve-Path (Join-Path $PSScriptRoot "..\release")
+$unpackedDir = Join-Path $releaseDir "win-unpacked"
+$exePath = Join-Path $unpackedDir "Flowchain Wallet.exe"
+$packageJsonPath = Resolve-Path (Join-Path $PSScriptRoot "..\package.json")
+$packageJson = Get-Content -LiteralPath $packageJsonPath -Raw | ConvertFrom-Json
+$zipPath = Join-Path $releaseDir "Flowchain-Wallet-$($packageJson.version)-win-x64.zip"
+
+if (-not (Test-Path -LiteralPath $exePath)) {
+ throw "Desktop wallet executable was not found at $exePath"
+}
+
+if (Test-Path -LiteralPath $zipPath) {
+ Remove-Item -LiteralPath $zipPath -Force
+}
+
+Compress-Archive -Path (Join-Path $unpackedDir "*") -DestinationPath $zipPath -Force
+
+Write-Host "Created desktop wallet package: $zipPath"
diff --git a/apps/dashboard/src/App.tsx b/apps/dashboard/src/App.tsx
index 750ab52e..1edaf902 100644
--- a/apps/dashboard/src/App.tsx
+++ b/apps/dashboard/src/App.tsx
@@ -6,6 +6,7 @@ import { DEFAULT_CANARY_DASHBOARD_DATA_PATH, fetchDashboardData } from "./data/l
import type { DashboardData } from "./data/types";
import { DEFAULT_CONTROL_PLANE_URL, buildWorkbenchSnapshot, fetchWorkbenchSnapshot, type WorkbenchSnapshot } from "./data/workbench";
import { AlertsView } from "./views/AlertsView";
+import { BridgePilotView } from "./views/BridgePilotView";
import { CanaryDeploymentView } from "./views/CanaryDeploymentView";
import { DevnetBlocksView } from "./views/DevnetBlocksView";
import { FlowMemoryView } from "./views/FlowMemoryView";
@@ -15,6 +16,7 @@ import { OverviewView } from "./views/OverviewView";
import { RawJsonInspectorView } from "./views/RawJsonInspectorView";
import { RootfieldsView } from "./views/RootfieldsView";
import { VerifierReportsView } from "./views/VerifierReportsView";
+import { WalletView } from "./views/WalletView";
import { WorkbenchView } from "./views/WorkbenchView";
import { WorkReceiptsView } from "./views/WorkReceiptsView";
@@ -116,6 +118,8 @@ export default function App() {
setVersion((current) => current + 1)} />} />
+ } />
+ } />
} />
} />
} />
diff --git a/apps/dashboard/src/components/AppShell.tsx b/apps/dashboard/src/components/AppShell.tsx
index 765d1308..1be4d7be 100644
--- a/apps/dashboard/src/components/AppShell.tsx
+++ b/apps/dashboard/src/components/AppShell.tsx
@@ -8,12 +8,14 @@ import {
BrainCircuit,
Boxes,
ClipboardCheck,
+ ArrowRightLeft,
RadioReceiver,
LayoutDashboard,
Monitor,
Network,
RadioTower,
ShieldCheck,
+ Wallet,
} from "lucide-react";
import type { DashboardData } from "../data/types";
import type { WorkbenchSnapshot } from "../data/workbench";
@@ -28,6 +30,8 @@ interface AppShellProps {
const NAV_ITEMS = [
{ to: "/", label: "Workbench", icon: Monitor },
+ { to: "/wallet", label: "Wallet", icon: Wallet },
+ { to: "/bridge", label: "Bridge pilot", icon: ArrowRightLeft },
{ to: "/overview", label: "Overview", icon: LayoutDashboard },
{ to: "/canary", label: "Base canary", icon: RadioReceiver },
{ to: "/flowmemory", label: "Flow Memory", icon: BrainCircuit },
@@ -43,6 +47,12 @@ const NAV_ITEMS = [
export function AppShell({ data, canaryData, workbench, children }: AppShellProps) {
const location = useLocation();
+ const isBridgeRoute = location.pathname.startsWith("/bridge");
+ const isWalletRoute = location.pathname.startsWith("/wallet");
+ if (isBridgeRoute || isWalletRoute) {
+ return <>{children}>;
+ }
+
const isCanaryRoute = location.pathname.startsWith("/canary");
const activeData = isCanaryRoute && canaryData ? canaryData : data;
const bannerMode = isCanaryRoute
diff --git a/apps/dashboard/src/data/loadDashboardData.ts b/apps/dashboard/src/data/loadDashboardData.ts
index 59c6296d..d76d782a 100644
--- a/apps/dashboard/src/data/loadDashboardData.ts
+++ b/apps/dashboard/src/data/loadDashboardData.ts
@@ -3,6 +3,17 @@ import type { DashboardData } from "./types";
export const DEFAULT_DASHBOARD_DATA_PATH = "/data/flowmemory-dashboard-v0.json";
export const DEFAULT_CANARY_DASHBOARD_DATA_PATH = "/data/flowmemory-dashboard-base-canary-v0.json";
+export function publicAssetPath(path: string): string {
+ const baseUrl = (import.meta as ImportMeta & { env?: { BASE_URL?: string } }).env?.BASE_URL ?? "/";
+ if (/^[a-z][a-z0-9+.-]*:/i.test(path)) {
+ return path;
+ }
+ if (baseUrl === "./") {
+ return `./${path.replace(/^\/+/, "")}`;
+ }
+ return path;
+}
+
function assertArray(value: unknown, label: string): void {
if (!Array.isArray(value)) {
throw new Error(`Dashboard fixture field "${label}" must be an array.`);
@@ -45,7 +56,7 @@ export function validateDashboardData(payload: unknown): DashboardData {
export async function fetchDashboardData(
path = DEFAULT_DASHBOARD_DATA_PATH,
): Promise {
- const response = await fetch(path, { cache: "no-store" });
+ const response = await fetch(publicAssetPath(path), { cache: "no-store" });
if (!response.ok) {
throw new Error(`Unable to load dashboard fixture at ${path}: ${response.status}`);
}
diff --git a/apps/dashboard/src/data/workbench.ts b/apps/dashboard/src/data/workbench.ts
index c33e48d8..b4887f85 100644
--- a/apps/dashboard/src/data/workbench.ts
+++ b/apps/dashboard/src/data/workbench.ts
@@ -1,4 +1,5 @@
import type { DashboardData, DashboardStatus, Provenance, SourceSubsystem } from "./types";
+import { publicAssetPath } from "./loadDashboardData";
export const DEFAULT_CONTROL_PLANE_URL = "http://127.0.0.1:8787";
export const WORKBENCH_DEVNET_STATE_PATH = "/data/flowchain-local-devnet-state.json";
@@ -77,6 +78,10 @@ export interface ControlPlaneProbe {
health?: unknown;
state?: unknown;
pilotStatus?: unknown;
+ bridgeLiveReadiness?: unknown;
+ pilotLifecycle?: unknown;
+ walletBalances?: unknown;
+ walletTransfers?: unknown;
}
export interface WorkbenchNodeStatus {
@@ -115,6 +120,10 @@ export interface WorkbenchSnapshot {
devnetDashboardState: unknown | null;
bridgeTestDeposit: unknown | null;
controlPlanePilotStatus: unknown | null;
+ controlPlaneBridgeReadiness: unknown | null;
+ controlPlanePilotLifecycle: unknown | null;
+ controlPlaneWalletBalances: unknown | null;
+ controlPlaneWalletTransfers: unknown | null;
controlPlaneHealth: unknown | null;
controlPlaneState: unknown | null;
};
@@ -246,8 +255,8 @@ export const WORKBENCH_SECTIONS: WorkbenchSectionDefinition[] = [
{
key: "realValuePilot",
label: "Real-Value Pilot",
- detail: "Capped owner-testing lifecycle for Base deposit observation, local credit, replay/retry status, withdrawal intent, release evidence, caps, pause, and emergency state.",
- expectedEndpoint: "GET /pilot/status + POST /rpc pilot_status",
+ detail: "Capped owner-testing lifecycle for Base deposit observation, exact local credit, wallet transferability, withdrawal/release evidence, readiness blockers, caps, pause, and emergency state.",
+ expectedEndpoint: "GET /bridge/live-readiness + GET /pilot/lifecycle + GET /pilot/status",
missingCommand: "npm run control-plane:serve",
missingService: "FlowChain real-value pilot control-plane /pilot/status",
},
@@ -494,16 +503,23 @@ function stringArray(value: unknown): string[] {
function statusFrom(value: unknown, fallback: DashboardStatus = "observed"): DashboardStatus {
const normalized = text(value, fallback).toLowerCase();
- if (normalized === "applied" || normalized === "success" || normalized === "active" || normalized === "live") {
+ if (
+ normalized === "applied" ||
+ normalized === "success" ||
+ normalized === "active" ||
+ normalized === "live" ||
+ normalized === "ready_for_operator_live_pilot" ||
+ normalized === "ready"
+ ) {
return "verified";
}
if (normalized === "finalized") {
return "finalized";
}
- if (normalized === "failed" || normalized === "invalid" || normalized === "reverted") {
+ if (normalized === "failed" || normalized === "invalid" || normalized === "reverted" || normalized === "failure") {
return "failed";
}
- if (normalized === "pending" || normalized === "local-placeholder" || normalized === "degraded") {
+ if (normalized === "pending" || normalized === "local-placeholder" || normalized === "degraded" || normalized === "blocked") {
return "pending";
}
if (normalized === "error") {
@@ -686,7 +702,7 @@ async function fetchJsonWithTimeout(url: string, timeoutMs: number): Promise {
try {
- return { value: await fetchJsonWithTimeout(path, CONTROL_PLANE_TIMEOUT_MS) };
+ return { value: await fetchJsonWithTimeout(publicAssetPath(path), CONTROL_PLANE_TIMEOUT_MS) };
} catch (error) {
return {
value: null,
@@ -698,12 +714,24 @@ async function fetchOptionalJson(path: string): Promise<{ value: unknown | null;
async function probeControlPlane(): Promise {
const url = getControlPlaneUrl();
const checkedAt = new Date().toISOString();
- const defaultEndpoints = ["GET /health", "GET /state", "GET /pilot/status"];
+ const defaultEndpoints = [
+ "GET /health",
+ "GET /state",
+ "GET /pilot/status",
+ "GET /bridge/live-readiness",
+ "GET /pilot/lifecycle",
+ "GET /wallets/balances",
+ "GET /wallets/transfers",
+ ];
try {
const health = await fetchJsonWithTimeout(`${url}/health`, CONTROL_PLANE_TIMEOUT_MS);
let state: unknown | undefined;
let pilotStatus: unknown | undefined;
+ let bridgeLiveReadiness: unknown | undefined;
+ let pilotLifecycle: unknown | undefined;
+ let walletBalances: unknown | undefined;
+ let walletTransfers: unknown | undefined;
try {
pilotStatus = await fetchJsonWithTimeout(`${url}/pilot/status`, CONTROL_PLANE_TIMEOUT_MS);
@@ -711,6 +739,30 @@ async function probeControlPlane(): Promise {
pilotStatus = undefined;
}
+ try {
+ bridgeLiveReadiness = await fetchJsonWithTimeout(`${url}/bridge/live-readiness`, CONTROL_PLANE_TIMEOUT_MS);
+ } catch {
+ bridgeLiveReadiness = undefined;
+ }
+
+ try {
+ pilotLifecycle = await fetchJsonWithTimeout(`${url}/pilot/lifecycle`, CONTROL_PLANE_TIMEOUT_MS);
+ } catch {
+ pilotLifecycle = undefined;
+ }
+
+ try {
+ walletBalances = await fetchJsonWithTimeout(`${url}/wallets/balances`, CONTROL_PLANE_TIMEOUT_MS);
+ } catch {
+ walletBalances = undefined;
+ }
+
+ try {
+ walletTransfers = await fetchJsonWithTimeout(`${url}/wallets/transfers`, CONTROL_PLANE_TIMEOUT_MS);
+ } catch {
+ walletTransfers = undefined;
+ }
+
try {
state = await fetchJsonWithTimeout(`${url}/state`, CONTROL_PLANE_TIMEOUT_MS);
} catch (error) {
@@ -721,6 +773,10 @@ async function probeControlPlane(): Promise {
endpoints: uniqueEndpoints(defaultEndpoints, collectEndpointHints(health)),
health,
pilotStatus,
+ bridgeLiveReadiness,
+ pilotLifecycle,
+ walletBalances,
+ walletTransfers,
error: `Health endpoint responded, but state endpoint was not loaded: ${
error instanceof Error ? error.message : "unknown state error"
}`,
@@ -735,6 +791,10 @@ async function probeControlPlane(): Promise {
health,
state,
pilotStatus,
+ bridgeLiveReadiness,
+ pilotLifecycle,
+ walletBalances,
+ walletTransfers,
};
} catch (error) {
return {
@@ -1106,8 +1166,232 @@ function commandFromStep(step: unknown): string {
return isRecord(step) ? text(step.command, "npm run flowchain:real-value-pilot:e2e") : "npm run flowchain:real-value-pilot:e2e";
}
+function readinessStatus(value: unknown): DashboardStatus {
+ const normalized = text(value, "BLOCKED").toUpperCase();
+ if (normalized === "READY_FOR_OPERATOR_LIVE_PILOT") {
+ return "verified";
+ }
+ if (normalized === "FAILED") {
+ return "failed";
+ }
+ return "pending";
+}
+
+function readinessPayload(controlPlane: ControlPlaneProbe, pilot: UnknownRecord | null): UnknownRecord | null {
+ if (isRecord(controlPlane.bridgeLiveReadiness)) {
+ return controlPlane.bridgeLiveReadiness;
+ }
+ if (isRecord(pilot?.bridgeLiveReadiness)) {
+ return pilot.bridgeLiveReadiness as UnknownRecord;
+ }
+ return null;
+}
+
+function bridgeReadinessRecord(controlPlane: ControlPlaneProbe, readiness: UnknownRecord | null): WorkbenchRecord {
+ if (!readiness) {
+ return makeLocalRecord(
+ "devnet",
+ controlPlane.url,
+ {
+ id: "bridge-live-readiness",
+ kind: "Bridge live readiness",
+ title: "Bridge live readiness BLOCKED",
+ summary: "The live readiness endpoint is unavailable; operator live pilot remains fail-closed until the control-plane returns readiness details.",
+ status: controlPlane.status === "available" ? "pending" : "offline",
+ facts: [
+ { label: "fail-closed status", value: "BLOCKED" },
+ { label: "base chain", value: "8453" },
+ { label: "missing env names", value: "endpoint unavailable" },
+ { label: "env values printed", value: "false" },
+ { label: "mock presented as live", value: "false" },
+ { label: "owner verified lockbox", value: "false" },
+ ],
+ raw: { endpoint: "/bridge/live-readiness", status: "unavailable" },
+ },
+ controlPlane.checkedAt,
+ );
+ }
+
+ const node = isRecord(readiness.node) ? readiness.node : {};
+ const lockbox = isRecord(readiness.lockbox) ? readiness.lockbox : {};
+ const confirmationDepth = isRecord(readiness.confirmationDepth) ? readiness.confirmationDepth : {};
+ const artifacts = isRecord(readiness.currentArtifacts) ? readiness.currentArtifacts : {};
+ const missingEnvNames = stringArray(readiness.missingEnvNames);
+ const failClosedStatus = text(readiness.failClosedStatus, "BLOCKED");
+
+ return makeLocalRecord(
+ "devnet",
+ controlPlane.url,
+ {
+ id: "bridge-live-readiness",
+ kind: "Bridge live readiness",
+ title: `Bridge live readiness ${failClosedStatus}`,
+ summary:
+ missingEnvNames.length > 0
+ ? `Fail-closed with missing env names: ${missingEnvNames.join(", ")}.`
+ : text(readiness.machineStatus, "Live readiness is available from the control-plane."),
+ status: readinessStatus(failClosedStatus),
+ facts: [
+ { label: "fail-closed status", value: failClosedStatus },
+ { label: "base chain", value: `${text(readiness.baseChainName, "Base")} ${text(readiness.baseChainId, "8453")}` },
+ { label: "node running", value: text(node.running, "false") },
+ { label: "lockbox configured", value: text(lockbox.configured, "false") },
+ { label: "confirmation configured", value: text(confirmationDepth.configured, "false") },
+ { label: "missing env names", value: missingEnvNames.join(", ") || "none" },
+ { label: "env values printed", value: text(readiness.envValuesPrinted, "false") },
+ { label: "mock presented as live", value: text(artifacts.mockPresentedAsLive, "false") },
+ ],
+ raw: readiness,
+ },
+ controlPlane.checkedAt,
+ );
+}
+
+function bridgeReadinessIssueRecords(controlPlane: ControlPlaneProbe, readiness: UnknownRecord | null): WorkbenchRecord[] {
+ if (!readiness) {
+ return [];
+ }
+
+ return collectionFrom(readiness, ["issues"]).map((issue, index) =>
+ makeLocalRecord(
+ "devnet",
+ controlPlane.url,
+ {
+ id: `bridge-readiness-issue:${text(issue.reasonCode, String(index + 1))}`,
+ kind: "Operational empty/error state",
+ title: text(issue.title, "Bridge readiness issue"),
+ summary: text(issue.summary, "A live-pilot readiness issue is visible in the control-plane response."),
+ status: statusFrom(issue.status, "pending"),
+ facts: [
+ { label: "reason code", value: text(issue.reasonCode) },
+ { label: "status", value: text(issue.status, "blocked") },
+ { label: "env names", value: stringArray(issue.envNames).join(", ") || "none" },
+ { label: "machine readable", value: "true" },
+ ],
+ raw: issue,
+ },
+ controlPlane.checkedAt,
+ ),
+ );
+}
+
+function lifecycleRows(controlPlane: ControlPlaneProbe, pilot: UnknownRecord | null): UnknownRecord[] {
+ if (isRecord(controlPlane.pilotLifecycle)) {
+ return collectionFrom(controlPlane.pilotLifecycle, ["lifecycleRecords"]);
+ }
+ if (pilot) {
+ return collectionFrom(pilot, ["lifecycleRecords"]);
+ }
+ return [];
+}
+
+function bridgeLifecycleRecord(controlPlane: ControlPlaneProbe, row: UnknownRecord, index: number): WorkbenchRecord {
+ const equality = isRecord(row.equality) ? row.equality : {};
+ const equalities = isRecord(equality.equalities) ? equality.equalities : {};
+ const depositObservation = isRecord(row.depositObservation) ? row.depositObservation : {};
+ const withdrawalIntent = isRecord(row.withdrawalIntent) ? row.withdrawalIntent : {};
+ const releaseEvidence = isRecord(row.releaseEvidence) ? row.releaseEvidence : {};
+ const liveArtifact = row.liveArtifact === true;
+ const artifactClass = text(row.artifactClass, liveArtifact ? "live-base8453" : "local-or-mock");
+ const baseTxHash = text(row.baseTxHash ?? row.txHash, `lifecycle:${index + 1}`);
+ const creditId = text(row.creditId, "credit pending");
+ const amount = text(row.amountSmallestUnits ?? equality.depositAmount, "0");
+ const replayKey = text(row.replayKey ?? depositObservation.replayKey);
+ const withdrawalIntentId = text(row.withdrawalIntentId ?? withdrawalIntent.withdrawalIntentId);
+ const releaseEvidenceId = text(row.releaseEvidenceId ?? releaseEvidence.releaseEvidenceId);
+
+ return makeLocalRecord(
+ "devnet",
+ controlPlane.url,
+ {
+ id: text(row.lifecycleRecordId, `${baseTxHash}:${text(row.logIndex, String(index))}`),
+ kind: "Bridge exact lifecycle",
+ title: `${baseTxHash} / ${creditId}`,
+ summary: `${artifactClass} record with deposit, observed, credited, wallet delta, transferable, withdrawal, and release amount equality ${text(equality.allEqual, "false")}.`,
+ status: statusFrom(row.status, "pending"),
+ facts: [
+ { label: "base tx hash", value: baseTxHash },
+ { label: "log index", value: text(row.logIndex) },
+ { label: "deposit id", value: text(row.depositId ?? depositObservation.depositId) },
+ { label: "replay key", value: replayKey },
+ { label: "replay status", value: text(row.replayStatus ?? depositObservation.replayStatus) },
+ { label: "credit id", value: creditId },
+ { label: "recipient wallet", value: text(row.recipientWallet) },
+ { label: "withdrawal intent", value: withdrawalIntentId },
+ { label: "withdrawal status", value: text(row.withdrawalStatus ?? withdrawalIntent.status) },
+ { label: "release evidence", value: releaseEvidenceId },
+ { label: "release status", value: text(row.releaseStatus ?? releaseEvidence.status) },
+ { label: "asset", value: text(row.asset) },
+ { label: "amount smallest units", value: amount },
+ { label: "deposit amount", value: text(equality.depositAmount) },
+ { label: "credited amount", value: text(equality.creditedAmount) },
+ { label: "withdrawal amount", value: text(equality.withdrawalAmount) },
+ { label: "release amount", value: text(equality.releaseAmount) },
+ { label: "all values equal", value: text(equality.allEqual, "false") },
+ { label: "wallet delta equal", value: text(equalities.walletDelta, "false") },
+ { label: "evidence path", value: text(row.evidenceFilePath) },
+ ],
+ raw: row,
+ },
+ controlPlane.checkedAt,
+ );
+}
+
+function buildControlPlaneWalletBalanceRecords(controlPlane: ControlPlaneProbe): WorkbenchRecord[] {
+ return collectionFrom(controlPlane.walletBalances, ["balances"]).map((balance, index) =>
+ makeLocalRecord(
+ "devnet",
+ controlPlane.url,
+ {
+ id: text(balance.balanceId, `wallet-balance:${index + 1}`),
+ kind: "Wallet balance",
+ title: text(balance.walletAddress, `wallet:${index + 1}`),
+ summary: `Wallet balance ${text(balance.status, "observed")} for ${text(balance.asset, "asset")} is ${text(balance.amount, "0")} smallest units.`,
+ status: statusFrom(balance.status, "observed"),
+ facts: [
+ { label: "wallet", value: text(balance.walletAddress) },
+ { label: "asset", value: text(balance.asset) },
+ { label: "amount", value: text(balance.amount, "0") },
+ { label: "previous amount", value: text(balance.previousAmount) },
+ { label: "credit id", value: text(balance.creditId) },
+ { label: "transfer id", value: text(balance.transferId) },
+ ],
+ raw: balance,
+ },
+ controlPlane.checkedAt,
+ ),
+ );
+}
+
+function buildControlPlaneWalletTransferRecords(controlPlane: ControlPlaneProbe): WorkbenchRecord[] {
+ return collectionFrom(controlPlane.walletTransfers, ["transfers"]).map((transfer, index) =>
+ makeLocalRecord(
+ "devnet",
+ controlPlane.url,
+ {
+ id: text(transfer.transferId ?? transfer.txId, `wallet-transfer:${index + 1}`),
+ kind: "Wallet transfer history",
+ title: text(transfer.txId ?? transfer.transferId, `transfer:${index + 1}`),
+ summary: `${text(transfer.amount, "0")} ${text(transfer.assetId, "asset")} transferred from ${text(transfer.fromAccountId)} to ${text(transfer.toAccountId)}.`,
+ status: statusFrom(transfer.status, "observed"),
+ facts: [
+ { label: "from wallet", value: text(transfer.fromAccountId) },
+ { label: "to wallet", value: text(transfer.toAccountId) },
+ { label: "asset", value: text(transfer.assetId) },
+ { label: "amount", value: text(transfer.amount, "0") },
+ { label: "status", value: text(transfer.status) },
+ { label: "evidence path", value: text(transfer.evidenceFilePath) },
+ ],
+ raw: transfer,
+ },
+ controlPlane.checkedAt,
+ ),
+ );
+}
+
function buildPilotRecords(controlPlane: ControlPlaneProbe): WorkbenchRecord[] {
const pilot = isRecord(controlPlane.pilotStatus) ? controlPlane.pilotStatus : null;
+ const readiness = readinessPayload(controlPlane, pilot);
const records: WorkbenchRecord[] = [];
if (!pilot) {
@@ -1132,6 +1416,10 @@ function buildPilotRecords(controlPlane: ControlPlaneProbe): WorkbenchRecord[] {
controlPlane.checkedAt,
),
);
+ records.push(bridgeReadinessRecord(controlPlane, readiness));
+ records.push(...bridgeReadinessIssueRecords(controlPlane, readiness));
+ records.push(...buildControlPlaneWalletBalanceRecords(controlPlane));
+ records.push(...buildControlPlaneWalletTransferRecords(controlPlane));
return records;
}
@@ -1167,6 +1455,14 @@ function buildPilotRecords(controlPlane: ControlPlaneProbe): WorkbenchRecord[] {
),
);
+ records.push(bridgeReadinessRecord(controlPlane, readiness));
+ records.push(...bridgeReadinessIssueRecords(controlPlane, readiness));
+ lifecycleRows(controlPlane, pilot).forEach((row, index) => {
+ records.push(bridgeLifecycleRecord(controlPlane, row, index));
+ });
+ records.push(...buildControlPlaneWalletBalanceRecords(controlPlane));
+ records.push(...buildControlPlaneWalletTransferRecords(controlPlane));
+
lifecycle.forEach((step, index) => {
records.push(
makeLocalRecord(
@@ -1902,6 +2198,10 @@ function buildRawJsonRecords(
raw: {
health: controlPlane.health ?? null,
state: controlPlane.state ?? null,
+ bridgeLiveReadiness: controlPlane.bridgeLiveReadiness ?? null,
+ pilotLifecycle: controlPlane.pilotLifecycle ?? null,
+ walletBalances: controlPlane.walletBalances ?? null,
+ walletTransfers: controlPlane.walletTransfers ?? null,
error: controlPlane.error ?? null,
},
},
@@ -2093,7 +2393,7 @@ export function buildWorkbenchSnapshot(
transactions: buildTransactionRecords(data, activeDevnetState),
mempool: buildMempoolRecords(activeDevnetState),
accounts: buildAccountRecords(activeDevnetState),
- balances: buildBalanceRecords(activeDevnetState),
+ balances: [...buildBalanceRecords(activeDevnetState), ...buildControlPlaneWalletBalanceRecords(controlPlane)],
faucetEvents: buildFaucetEventRecords(activeDevnetState),
walletMetadata: buildWalletMetadataRecords(activeDevnetState),
tokenLaunches: buildTokenLaunchRecords(activeDevnetState),
@@ -2152,6 +2452,10 @@ export function buildWorkbenchSnapshot(
devnetDashboardState: options.devnetDashboardState ?? null,
bridgeTestDeposit,
controlPlanePilotStatus: controlPlane.pilotStatus ?? null,
+ controlPlaneBridgeReadiness: controlPlane.bridgeLiveReadiness ?? null,
+ controlPlanePilotLifecycle: controlPlane.pilotLifecycle ?? null,
+ controlPlaneWalletBalances: controlPlane.walletBalances ?? null,
+ controlPlaneWalletTransfers: controlPlane.walletTransfers ?? null,
controlPlaneHealth: controlPlane.health ?? null,
controlPlaneState: controlPlane.state ?? null,
},
diff --git a/apps/dashboard/src/main.tsx b/apps/dashboard/src/main.tsx
index 2d89f272..87a958de 100644
--- a/apps/dashboard/src/main.tsx
+++ b/apps/dashboard/src/main.tsx
@@ -1,13 +1,20 @@
import React from "react";
import ReactDOM from "react-dom/client";
-import { BrowserRouter } from "react-router-dom";
+import { BrowserRouter, HashRouter } from "react-router-dom";
import App from "./App";
import "./styles.css";
+const isNativeShell = window.location.protocol === "file:" || "Capacitor" in window;
+const Router = isNativeShell ? HashRouter : BrowserRouter;
+
+if (isNativeShell && window.location.hash.length === 0) {
+ window.location.hash = "/wallet";
+}
+
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
-
+
-
+
,
);
diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css
index a26271c5..b8555452 100644
--- a/apps/dashboard/src/styles.css
+++ b/apps/dashboard/src/styles.css
@@ -67,1457 +67,5051 @@ input {
color: var(--ink);
}
-select {
- min-height: 38px;
- padding: 0 12px;
+.wallet-app-shell {
+ position: relative;
+ display: grid;
+ grid-template-columns: 270px minmax(560px, 1fr) 385px;
+ min-height: 100dvh;
+ overflow: hidden;
+ color: #0a2147;
+ background:
+ radial-gradient(circle at 72% 18%, rgba(255, 255, 255, 0.82), transparent 27rem),
+ radial-gradient(circle at 5% 96%, rgba(20, 91, 255, 0.12), transparent 24rem),
+ linear-gradient(135deg, #fff9ee 0%, #f8efe1 46%, #fffaf0 100%);
+}
+
+.wallet-app-shell::before {
+ position: fixed;
+ inset: 0;
+ pointer-events: none;
+ content: "";
+ background-image:
+ linear-gradient(rgba(21, 42, 84, 0.035) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(21, 42, 84, 0.024) 1px, transparent 1px);
+ background-size: 10px 10px, 10px 10px;
+ opacity: 0.45;
+}
+
+.wallet-app-shell::after {
+ position: fixed;
+ right: -8vw;
+ bottom: -7vh;
+ left: -9vw;
+ height: 38vh;
+ pointer-events: none;
+ content: "";
+ opacity: 0.38;
+ background:
+ repeating-linear-gradient(164deg, transparent 0 20px, rgba(19, 95, 255, 0.18) 21px 23px, transparent 25px 48px),
+ radial-gradient(ellipse at 20% 70%, rgba(10, 91, 255, 0.2), transparent 34rem);
+ filter: blur(0.2px);
+ transform: rotate(-4deg);
}
-table {
- width: 100%;
- border-collapse: collapse;
- table-layout: fixed;
+.wallet-app-sidebar,
+.wallet-app-main,
+.wallet-app-rightbar {
+ position: relative;
+ z-index: 1;
}
-th,
-td {
- border-bottom: 1px solid var(--line);
- padding: 12px 14px;
- text-align: left;
- vertical-align: top;
+.wallet-app-sidebar {
+ display: flex;
+ flex-direction: column;
+ gap: 28px;
+ padding: 28px 24px;
}
-th {
- color: #555d55;
- font-size: 0.72rem;
- font-weight: 700;
- text-transform: uppercase;
+.wallet-brand {
+ display: inline-flex;
+ gap: 14px;
+ align-items: center;
+ color: #061d43;
+ text-decoration: none;
}
-td {
- font-size: 0.86rem;
+.wallet-brand strong {
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: 2.1rem;
+ font-weight: 500;
+ letter-spacing: -0.03em;
}
-th,
-td {
- overflow-wrap: anywhere;
+.wallet-flow-mark {
+ position: relative;
+ display: inline-block;
+ width: 42px;
+ height: 34px;
+ color: #0f5fff;
}
-small {
- color: var(--muted);
+.wallet-flow-mark::before,
+.wallet-flow-mark::after,
+.wallet-flow-mark span {
+ position: absolute;
+ left: 0;
+ width: 38px;
+ height: 12px;
+ content: "";
+ border-top: 3px solid currentColor;
+ border-radius: 50%;
+ transform-origin: 50% 50%;
}
-.app-shell {
- display: grid;
- grid-template-columns: 252px minmax(0, 1fr);
- min-height: 100dvh;
+.wallet-flow-mark::before {
+ top: 6px;
+ transform: rotate(-18deg);
}
-.sidebar {
- position: sticky;
- top: 0;
- display: flex;
- flex-direction: column;
- gap: 22px;
- height: 100dvh;
- padding: 18px 14px;
- background: var(--sidebar);
- color: #f6f8f2;
- border-right: 1px solid #32382f;
+.wallet-flow-mark span {
+ top: 14px;
+ transform: rotate(8deg);
}
-.brand-block {
- display: grid;
- grid-template-columns: 42px 1fr;
- gap: 10px;
- align-items: center;
- padding: 4px 4px 14px;
- border-bottom: 1px solid #343a33;
+.wallet-flow-mark::after {
+ top: 21px;
+ transform: rotate(-10deg);
}
-.brand-mark {
+.wallet-side-nav {
display: grid;
- place-items: center;
- width: 38px;
- height: 38px;
- border: 1px solid #556054;
- border-radius: 8px;
- background: #252b26;
- color: #d8efe5;
+ gap: 8px;
+ margin-top: 8px;
}
-.brand-kicker,
-.eyebrow {
+.wallet-side-nav::before,
+.wallet-side-nav::after {
display: block;
- margin-bottom: 4px;
- color: var(--subtle);
- font-size: 0.71rem;
- font-weight: 700;
- text-transform: uppercase;
-}
-
-.brand-block strong {
- font-size: 1rem;
+ height: 1px;
+ margin: 12px 2px;
+ content: "";
+ background: rgba(10, 33, 71, 0.14);
}
-.nav-list {
- display: grid;
- gap: 4px;
-}
-
-.nav-link {
+.wallet-side-nav button,
+.wallet-side-nav a {
display: grid;
- grid-template-columns: 22px 1fr;
- gap: 9px;
+ grid-template-columns: 26px 1fr;
+ gap: 12px;
align-items: center;
- min-height: 38px;
- padding: 8px 10px;
- color: var(--sidebar-muted);
- text-decoration: none;
+ min-height: 50px;
+ padding: 0 18px;
+ color: #132b55;
border: 1px solid transparent;
- border-radius: 7px;
- transition: background 140ms ease, border-color 140ms ease, color 140ms ease, transform 140ms ease;
+ border-radius: 8px;
+ background: transparent;
+ font-weight: 650;
+ text-align: left;
+ text-decoration: none;
}
-.nav-link:hover,
-.nav-link.active {
- background: #29302b;
- border-color: #3c463e;
- color: #f6f8f2;
+.wallet-side-nav button.active,
+.wallet-side-nav button:hover,
+.wallet-side-nav a:hover {
+ color: #0f5fff;
+ border-color: rgba(10, 33, 71, 0.04);
+ background: rgba(255, 255, 255, 0.48);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65);
}
-.sidebar-footer {
- display: grid;
- gap: 7px;
+.wallet-collapse-button {
+ display: inline-flex;
+ gap: 10px;
+ align-items: center;
+ width: fit-content;
margin-top: auto;
- padding: 12px;
- color: var(--sidebar-muted);
- border: 1px solid #3a433b;
- border-radius: 8px;
- background: #232923;
+ padding: 10px 14px;
+ color: #33486b;
+ border-color: rgba(10, 33, 71, 0.12);
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.32);
}
-.workspace {
+.wallet-app-main {
min-width: 0;
+ padding: 22px 16px 28px;
}
-.topbar {
- position: sticky;
- top: 0;
- display: flex;
+.wallet-app-topbar {
+ display: grid;
+ grid-template-columns: 180px minmax(260px, 1fr) 48px 240px;
+ gap: 22px;
align-items: center;
- justify-content: space-between;
- gap: 18px;
- padding: 14px 24px;
- background: rgba(244, 245, 241, 0.92);
- border-bottom: 1px solid var(--line);
- backdrop-filter: blur(12px);
-}
-
-.topbar h2 {
- margin: 0;
- font-size: 1.05rem;
+ margin-bottom: 20px;
}
-.topbar-meta {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- justify-content: flex-end;
+.wallet-network-switch,
+.wallet-profile-pill,
+.wallet-search,
+.wallet-icon-button {
+ border: 1px solid rgba(10, 33, 71, 0.12);
+ background: rgba(255, 255, 255, 0.46);
+ box-shadow:
+ 0 14px 34px rgba(37, 31, 18, 0.06),
+ inset 0 1px 0 rgba(255, 255, 255, 0.72);
+ backdrop-filter: blur(16px);
}
-.topbar-meta span,
-.provenance-chip,
-.severity-label {
+.wallet-network-switch {
display: inline-flex;
- align-items: center;
- gap: 5px;
- min-height: 24px;
- padding: 4px 7px;
- color: #4f584f;
- border: 1px solid var(--line);
- border-radius: 999px;
- background: #f9faf6;
- font-size: 0.72rem;
- white-space: nowrap;
-}
-
-.fixture-banner {
- display: flex;
gap: 10px;
align-items: center;
- margin: 18px 24px 0;
- padding: 10px 12px;
- color: #394139;
- border: 1px solid #cfd7cd;
- border-radius: 8px;
- background: #f9faf6;
- font-size: 0.86rem;
-}
-
-.content {
- width: min(100%, 1480px);
- margin: 0 auto;
- padding: 24px;
+ justify-content: center;
+ height: 50px;
+ padding: 0 16px;
+ border-radius: 14px;
+ font-weight: 720;
}
-.view-stack {
- display: grid;
- gap: 22px;
+.wallet-network-switch .wallet-flow-mark {
+ width: 26px;
+ height: 24px;
+ color: #ffffff;
+ border-radius: 50%;
+ background: #0f5fff;
}
-.section-header {
- display: grid;
- grid-template-columns: minmax(0, 1fr) auto;
- gap: 18px;
- align-items: end;
+.wallet-network-switch .wallet-flow-mark::before,
+.wallet-network-switch .wallet-flow-mark::after,
+.wallet-network-switch .wallet-flow-mark span {
+ left: 5px;
+ width: 16px;
+ height: 6px;
+ border-top-width: 2px;
}
-.section-header h1 {
- margin: 0;
- font-size: 1.8rem;
- line-height: 1.15;
+.wallet-network-switch .wallet-flow-mark::before {
+ top: 7px;
}
-.section-header p {
- max-width: 76ch;
- margin: 8px 0 0;
- color: var(--muted);
- line-height: 1.5;
+.wallet-network-switch .wallet-flow-mark span {
+ top: 11px;
}
-.section-action {
- min-width: min(520px, 100%);
+.wallet-network-switch .wallet-flow-mark::after {
+ top: 15px;
}
-.workbench-header-actions {
+.wallet-search {
display: grid;
- grid-template-columns: minmax(0, 1fr) auto;
- gap: 10px;
+ grid-template-columns: 22px minmax(0, 1fr) auto;
+ gap: 12px;
align-items: center;
+ height: 50px;
+ padding: 0 14px;
+ border-radius: 14px;
}
-.filter-row {
- display: grid;
- grid-template-columns: minmax(240px, 1fr) 150px;
- gap: 10px;
+.wallet-search input {
+ color: #0a2147;
}
-.search-box {
- display: grid;
- grid-template-columns: 18px 1fr;
- gap: 8px;
- align-items: center;
- min-height: 38px;
- padding: 0 10px;
- border: 1px solid var(--line-strong);
+.wallet-search kbd {
+ padding: 4px 7px;
+ color: #4d5d78;
+ border: 1px solid rgba(10, 33, 71, 0.14);
border-radius: 7px;
- background: var(--surface);
+ background: rgba(255, 255, 255, 0.62);
+ font-size: 0.73rem;
}
-.metric-grid {
+.wallet-icon-button {
+ position: relative;
display: grid;
- grid-template-columns: repeat(5, minmax(0, 1fr));
- gap: 10px;
+ place-items: center;
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
}
-.flowmemory-hero {
+.wallet-icon-button span {
+ position: absolute;
+ top: 9px;
+ right: 10px;
+ width: 8px;
+ height: 8px;
+ background: #0f5fff;
+ border-radius: 50%;
+}
+
+.wallet-profile-pill {
display: grid;
- grid-template-columns: minmax(0, 1fr) minmax(280px, 0.34fr);
- gap: 14px;
- align-items: stretch;
- border: 1px solid var(--line);
- border-radius: 8px;
- background: linear-gradient(135deg, #fbfcf8 0%, #edf4ef 100%);
- box-shadow: var(--shadow);
+ grid-template-columns: 42px 1fr 18px;
+ grid-template-rows: auto auto;
+ column-gap: 10px;
+ align-items: center;
+ min-height: 54px;
+ padding: 8px 12px;
+ border-radius: 15px;
+ text-align: left;
}
-.flowmemory-hero-main {
+.wallet-profile-pill > span {
display: grid;
- gap: 12px;
- padding: 18px;
+ grid-row: 1 / span 2;
+ place-items: center;
+ width: 38px;
+ height: 38px;
+ color: #ffffff;
+ background: #0b2c75;
+ border-radius: 50%;
+ font-weight: 800;
}
-.flowmemory-hero-main h2 {
- max-width: 780px;
- margin: 0;
- font-size: 1.48rem;
- line-height: 1.14;
+.wallet-profile-pill strong {
+ font-size: 0.95rem;
}
-.flowmemory-hero-main p {
- max-width: 82ch;
- margin: 0;
- color: #465047;
- line-height: 1.5;
+.wallet-profile-pill small {
+ color: #61718e;
}
-.flowmemory-spine {
- display: grid;
- grid-template-columns: repeat(5, minmax(0, 1fr));
- gap: 8px;
+.wallet-profile-pill svg {
+ grid-row: 1 / span 2;
}
-.flowmemory-spine span {
- min-height: 34px;
- padding: 8px 9px;
- color: #284238;
- border: 1px solid #bfd1c7;
- border-radius: 7px;
- background: rgba(251, 252, 248, 0.78);
- font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
- font-size: 0.74rem;
- text-align: center;
+.wallet-portfolio-panel,
+.wallet-side-card,
+.wallet-action-grid button,
+.wallet-action-grid a,
+.wallet-action-panel {
+ border: 1px solid rgba(10, 33, 71, 0.1);
+ background: rgba(255, 255, 255, 0.34);
+ box-shadow:
+ 0 18px 44px rgba(50, 37, 18, 0.07),
+ inset 0 1px 0 rgba(255, 255, 255, 0.72);
+ backdrop-filter: blur(18px);
}
-.flowmemory-hero-side {
+.wallet-portfolio-panel {
display: grid;
- gap: 1px;
- min-width: 0;
- padding: 1px;
- border-left: 1px solid var(--line);
+ grid-template-columns: minmax(220px, 0.78fr) minmax(360px, 1.4fr);
+ gap: 18px;
+ min-height: 286px;
+ padding: 30px 34px 24px;
+ border-radius: 18px;
}
-.flowmemory-hero-side div {
- display: grid;
- gap: 5px;
- align-content: center;
- min-width: 0;
- padding: 12px;
- background: rgba(251, 252, 248, 0.72);
+.wallet-portfolio-copy h1 {
+ margin: 0 0 22px;
+ color: #071d44;
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: clamp(2.4rem, 4vw, 3.35rem);
+ font-weight: 500;
+ letter-spacing: -0.04em;
}
-.canary-hero {
- display: grid;
- grid-template-columns: minmax(0, 1fr) minmax(300px, 0.36fr);
- gap: 14px;
- align-items: stretch;
- padding: 16px;
- border: 1px solid #c9d5ce;
- border-radius: 8px;
- background:
- linear-gradient(135deg, rgba(251, 252, 248, 0.94), rgba(229, 239, 235, 0.92)),
- linear-gradient(90deg, rgba(47, 125, 107, 0.18), transparent);
- box-shadow: var(--shadow);
+.wallet-portfolio-copy > span {
+ display: inline-flex;
+ gap: 10px;
+ align-items: center;
+ color: #65728a;
+ font-size: 1.05rem;
+ font-weight: 650;
}
-.canary-hero h2 {
- max-width: 760px;
- margin: 0;
- font-size: 1.52rem;
- line-height: 1.14;
+.wallet-portfolio-copy > strong {
+ display: block;
+ margin-top: 8px;
+ color: #071d44;
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: clamp(2.6rem, 5vw, 4.1rem);
+ font-weight: 500;
+ line-height: 1;
+ letter-spacing: -0.05em;
}
-.canary-hero p {
- max-width: 84ch;
- margin: 10px 0 0;
- color: #465047;
- line-height: 1.5;
+.wallet-portfolio-copy p {
+ display: flex;
+ gap: 18px;
+ align-items: center;
+ margin: 18px 0 0;
+ color: #607089;
}
-.canary-read-window {
- display: grid;
- grid-template-columns: 28px 1fr;
- gap: 10px;
- align-items: start;
- padding: 12px;
- border: 1px solid #bfd1c7;
- border-radius: 8px;
- background: rgba(251, 252, 248, 0.78);
+.wallet-portfolio-copy b {
+ color: #0aa873;
+ font-weight: 700;
}
-.canary-read-window dl {
- display: grid;
- gap: 9px;
- margin: 0;
+.wallet-chart-card {
+ min-width: 0;
}
-.canary-operator-strip {
- display: grid;
- grid-template-columns: 1.15fr 1fr 1.1fr;
- gap: 10px;
+.wallet-chart-tabs {
+ display: flex;
+ gap: 18px;
+ justify-content: flex-end;
+ margin-bottom: 8px;
}
-.canary-operator-strip article {
- display: grid;
- gap: 7px;
- min-width: 0;
- padding: 12px;
- border: 1px solid #c9d5ce;
- border-radius: 8px;
- background: #fbfcf8;
- box-shadow: var(--shadow);
+.wallet-chart-tabs button {
+ min-width: 42px;
+ min-height: 34px;
+ color: #53637d;
+ border: 0;
+ border-radius: 10px;
+ background: transparent;
+ font-weight: 700;
}
-.canary-operator-strip span,
-.canary-operator-strip strong {
- overflow-wrap: anywhere;
+.wallet-chart-tabs button.active {
+ color: #071d44;
+ border: 1px solid rgba(10, 33, 71, 0.1);
+ background: rgba(255, 255, 255, 0.56);
}
-.canary-operator-strip span {
- color: var(--muted);
- font-size: 0.72rem;
- font-weight: 800;
- text-transform: uppercase;
+.wallet-chart-card svg {
+ width: 100%;
+ height: 190px;
+ overflow: visible;
}
-.canary-boundaries {
- display: grid;
- gap: 8px;
- padding-top: 12px;
+.wallet-chart-fill {
+ fill: url(#walletChartFill);
}
-.canary-boundaries span {
- padding: 9px 10px;
- color: #4d564d;
- border: 1px solid var(--line);
- border-radius: 7px;
- background: #f8f9f4;
- line-height: 1.35;
+.wallet-chart-line {
+ fill: none;
+ stroke: #0f5fff;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ stroke-width: 4;
}
-.metric-tile,
-.panel,
-.table-panel,
-.rootfield-tile,
-.lane-tile,
-.node-tile,
-.alert-row,
-.json-panel {
- border: 1px solid var(--line);
- border-radius: 8px;
- background: var(--surface);
- box-shadow: var(--shadow);
+.wallet-chart-grid path {
+ stroke: rgba(10, 33, 71, 0.08);
+ stroke-width: 1;
}
-.metric-tile {
+.wallet-chart-axis {
display: grid;
- gap: 8px;
- padding: 14px;
+ grid-template-columns: repeat(7, 1fr);
+ color: #61718e;
+ font-size: 0.78rem;
+ font-weight: 650;
}
-.metric-tile > span {
- color: var(--muted);
- font-size: 0.75rem;
- font-weight: 700;
- text-transform: uppercase;
+.wallet-action-grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 16px;
+ margin-top: 18px;
}
-.metric-tile strong {
- font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
- font-size: 1.65rem;
+.wallet-action-grid button,
+.wallet-action-grid a {
+ display: grid;
+ grid-template-columns: 52px 1fr;
+ grid-template-rows: auto auto;
+ column-gap: 14px;
+ align-items: center;
+ min-height: 82px;
+ padding: 14px 18px;
+ color: #071d44;
+ border-radius: 13px;
+ text-align: left;
+ text-decoration: none;
}
-.metric-tile div {
- display: flex;
- flex-wrap: wrap;
- gap: 7px;
- align-items: center;
+.wallet-action-grid button:hover,
+.wallet-action-grid a:hover,
+.wallet-side-card button:hover,
+.wallet-side-card a:hover {
+ transform: translateY(-1px);
}
-.overview-grid {
+.wallet-action-grid span {
display: grid;
- grid-template-columns: minmax(0, 1.4fr) minmax(290px, 0.6fr);
- gap: 14px;
+ grid-row: 1 / span 2;
+ place-items: center;
+ width: 44px;
+ height: 44px;
+ color: #ffffff;
+ background: #0f5fff;
+ border-radius: 50%;
}
-.panel {
- min-width: 0;
- padding: 14px;
+.wallet-action-grid strong {
+ font-size: 1rem;
}
-.panel-wide {
- grid-column: span 1;
+.wallet-action-grid small {
+ color: #697893;
}
-.panel-side-bottom {
- grid-column: 2;
+.wallet-assets-section,
+.wallet-activity-section {
+ margin-top: 26px;
}
-.panel-heading {
+.wallet-section-title {
display: flex;
align-items: center;
justify-content: space-between;
- gap: 10px;
- padding-bottom: 10px;
- border-bottom: 1px solid var(--line);
+ margin-bottom: 12px;
}
-.panel-heading div,
-.tile-heading,
-.record-title,
-.json-panel-header {
- display: flex;
- gap: 8px;
- align-items: center;
- min-width: 0;
+.wallet-section-title h2,
+.wallet-side-card h2,
+.wallet-action-panel h2 {
+ margin: 0;
+ color: #071d44;
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: 1.35rem;
+ font-weight: 540;
+ letter-spacing: -0.02em;
}
-.panel-heading h2 {
- margin: 0;
- font-size: 0.98rem;
+.wallet-section-title button,
+.wallet-side-title button,
+.wallet-text-action {
+ display: inline-flex;
+ gap: 8px;
+ align-items: center;
+ color: #0f5fff;
+ border: 0;
+ background: transparent;
+ font-weight: 700;
}
-.panel-heading > span {
- color: var(--muted);
- font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
- font-size: 0.78rem;
+.wallet-asset-table,
+.wallet-activity-table {
+ overflow: hidden;
+ border: 1px solid rgba(10, 33, 71, 0.1);
+ border-radius: 14px;
+ background: rgba(255, 255, 255, 0.2);
}
-.record-list,
-.compact-list,
-.alert-list {
+.wallet-asset-table > div,
+.wallet-activity-table > div {
display: grid;
+ align-items: center;
+ min-height: 48px;
+ padding: 0 16px;
+ border-bottom: 1px solid rgba(10, 33, 71, 0.09);
}
-.status-strip {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- padding: 10px 0 2px;
+.wallet-asset-table > div {
+ grid-template-columns: minmax(190px, 1.25fr) 1fr 1fr 0.7fr;
}
-.status-strip span {
- display: inline-flex;
- gap: 6px;
- align-items: center;
- padding: 3px 6px 3px 3px;
- border: 1px solid var(--line);
- border-radius: 999px;
- background: #f8f9f4;
+.wallet-activity-table > div {
+ grid-template-columns: 1.2fr 0.9fr 0.8fr 1.2fr 0.9fr 0.8fr;
}
-.status-strip strong {
- font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
- font-size: 0.74rem;
+.wallet-asset-table > div:last-child,
+.wallet-activity-table > div:last-child {
+ border-bottom: 0;
}
-.record-row {
- display: grid;
- grid-template-columns: minmax(0, 1fr) 270px;
- gap: 16px;
- padding: 14px 0;
- border-bottom: 1px solid var(--line);
+.wallet-asset-table > div:first-child,
+.wallet-activity-table > div:first-child {
+ min-height: 38px;
+ color: #687895;
+ font-size: 0.72rem;
+ font-weight: 760;
}
-.record-row:last-child {
- border-bottom: 0;
+.wallet-asset-table span,
+.wallet-activity-table span {
+ min-width: 0;
+ overflow-wrap: anywhere;
}
-.record-row p,
-.alert-row p {
- margin: 8px 0;
- color: #3f483f;
- line-height: 1.45;
+.wallet-asset-table div:not(:first-child) > span:first-child {
+ display: grid;
+ grid-template-columns: 36px minmax(0, 1fr);
+ grid-template-rows: auto auto;
+ column-gap: 12px;
+ align-items: center;
}
-.record-facts,
-.definition-grid,
-.lane-stats,
-.alert-facts {
+.wallet-asset-table i {
display: grid;
- gap: 10px;
- margin: 0;
+ grid-row: 1 / span 2;
+ place-items: center;
+ width: 30px;
+ height: 30px;
+ color: #ffffff;
+ background: #0f5fff;
+ border-radius: 50%;
+ font-style: normal;
+ font-weight: 800;
}
-.record-facts {
- grid-template-columns: repeat(2, minmax(0, 1fr));
+.wallet-asset-table strong,
+.wallet-asset-table b,
+.wallet-activity-table b {
+ color: #071d44;
+ font-weight: 720;
}
-.record-facts div,
-.definition-grid div,
-.lane-stats div,
-.alert-facts div {
- min-width: 0;
+.wallet-asset-table small {
+ color: #72809b;
}
-dt {
- color: var(--subtle);
- font-size: 0.68rem;
- font-weight: 700;
- text-transform: uppercase;
+.wallet-change-up {
+ color: #0aa873 !important;
}
-dd {
- margin: 3px 0 0;
- overflow-wrap: anywhere;
+.wallet-change-flat {
+ color: #687895 !important;
}
-.compact-list article {
- display: grid;
- grid-template-columns: auto minmax(0, 1fr);
- gap: 9px;
- align-items: start;
- padding: 12px 0;
- border-bottom: 1px solid var(--line);
+.wallet-activity-table b {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: fit-content;
+ min-height: 24px;
+ padding: 0 10px;
+ color: #0a8f65;
+ border: 1px solid rgba(10, 168, 115, 0.22);
+ border-radius: 999px;
+ background: rgba(10, 168, 115, 0.08);
+ font-size: 0.78rem;
}
-.compact-list article:last-child {
- border-bottom: 0;
+.wallet-app-rightbar {
+ display: flex;
+ flex-direction: column;
+ gap: 18px;
+ padding: 88px 28px 28px 8px;
}
-.compact-list strong,
-.compact-list small {
+.wallet-side-card {
+ overflow: hidden;
+ padding: 24px;
+ border-radius: 18px;
+}
+
+.wallet-account-card > span {
display: block;
+ margin-top: 20px;
+ color: #61718e;
+ font-size: 0.82rem;
+ font-weight: 650;
+}
+
+.wallet-copy-row {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 38px 38px;
+ gap: 8px;
+ align-items: center;
+ padding: 8px 0 18px;
+ border-bottom: 1px solid rgba(10, 33, 71, 0.1);
+}
+
+.wallet-copy-row strong {
overflow-wrap: anywhere;
}
-.contract-event-list {
+.wallet-copy-row button,
+.wallet-account-card > button {
+ border: 0;
+ background: transparent;
+}
+
+.wallet-copy-row button {
display: grid;
+ place-items: center;
+ width: 38px;
+ height: 38px;
+ color: #314568;
}
-.contract-event-list article {
+.wallet-account-card > button {
display: grid;
- gap: 9px;
- padding: 12px 0;
- border-bottom: 1px solid var(--line);
+ grid-template-columns: 28px 1fr auto 18px;
+ gap: 12px;
+ align-items: center;
+ width: 100%;
+ min-height: 58px;
+ padding: 0;
+ color: #071d44;
+ text-align: left;
}
-.contract-event-list article:last-child {
- border-bottom: 0;
+.wallet-account-card > button small,
+.wallet-side-title b {
+ color: #0aa873;
}
-.contract-event-list article > div {
+.wallet-side-title {
display: flex;
- gap: 8px;
align-items: center;
- min-width: 0;
+ justify-content: space-between;
+ gap: 16px;
}
-.contract-event-list strong {
- overflow-wrap: anywhere;
- font-size: 0.84rem;
+.wallet-network-facts {
+ display: grid;
+ gap: 16px;
+ margin: 22px 0;
}
-.contract-event-list dl {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 8px;
+.wallet-network-facts div {
+ display: flex;
+ justify-content: space-between;
+ gap: 18px;
+}
+
+.wallet-network-facts dt {
+ color: #61718e;
+}
+
+.wallet-network-facts dd {
margin: 0;
+ color: #071d44;
+ font-weight: 750;
}
-.bundle-grid {
+.wallet-bridge-card {
display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 10px;
- padding-top: 14px;
+ gap: 18px;
}
-.bundle-grid article {
+.wallet-bridge-card div {
display: grid;
- gap: 8px;
- min-width: 0;
- padding: 12px;
- border: 1px solid var(--line);
- border-radius: 7px;
- background: #f7f8f4;
+ grid-template-columns: 28px 1fr;
+ column-gap: 12px;
+ align-items: center;
}
-.bundle-grid strong,
-.bundle-grid small {
- display: block;
- overflow-wrap: anywhere;
+.wallet-bridge-card div svg {
+ color: #0f5fff;
}
-.block-strip {
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: 10px;
- padding-top: 14px;
+.wallet-bridge-card p {
+ grid-column: 2;
+ margin: 4px 0 0;
+ color: #697893;
}
-.block-strip article {
- display: grid;
- gap: 7px;
- padding: 12px;
- border: 1px solid var(--line);
- border-radius: 7px;
- background: #f7f8f4;
+.wallet-bridge-card a {
+ display: inline-flex;
+ gap: 10px;
+ align-items: center;
+ justify-content: center;
+ min-height: 46px;
+ color: #ffffff;
+ border-radius: 8px;
+ background: #0f5fff;
+ font-weight: 750;
+ text-decoration: none;
}
-.workbench-command-center {
+.wallet-watchlist-card {
display: grid;
- grid-template-columns: minmax(0, 1.08fr) minmax(360px, 0.92fr);
- gap: 14px;
+ gap: 16px;
}
-.workbench-boundary-strip {
+.wallet-watch-row {
display: grid;
- grid-template-columns: 1.05fr 1.15fr 1.2fr;
+ grid-template-columns: 34px 1fr auto auto 52px;
gap: 10px;
+ align-items: center;
}
-.workbench-boundary-strip article {
+.wallet-watch-row span {
display: grid;
- gap: 6px;
- min-width: 0;
- padding: 12px;
- border: 1px solid #cfd7cd;
- border-radius: 8px;
- background: #f9faf6;
+ place-items: center;
+ width: 30px;
+ height: 30px;
+ color: #ffffff;
+ background: #0f5fff;
+ border-radius: 50%;
+ font-weight: 800;
}
-.workbench-boundary-strip strong {
- font-size: 0.82rem;
+.wallet-watch-row small {
+ color: #071d44;
+ font-weight: 650;
}
-.workbench-boundary-strip span {
- color: #4c554c;
- font-size: 0.84rem;
- line-height: 1.42;
+.wallet-watch-row b {
+ color: #0aa873;
}
-.pilot-status-panel article {
- display: grid;
- gap: 14px;
- min-width: 0;
- padding: 14px;
- border: 1px solid #b8c7c0;
- border-radius: 8px;
- background: #f4f8f6;
+.wallet-watch-row i {
+ height: 24px;
+ background:
+ linear-gradient(135deg, transparent 10%, #0f5fff 11% 14%, transparent 15% 28%, #0f5fff 29% 32%, transparent 33% 48%, #0f5fff 49% 53%, transparent 54% 100%);
}
-.pilot-status-body {
- display: grid;
- grid-template-columns: minmax(0, 0.92fr) minmax(320px, 1.08fr);
- gap: 14px;
- align-items: start;
+.wallet-action-panel {
+ position: fixed;
+ z-index: 6;
+ right: 32px;
+ bottom: 30px;
+ width: min(520px, calc(100vw - 32px));
+ max-height: min(78dvh, 720px);
+ overflow: auto;
+ padding: 22px;
+ border-radius: 20px;
}
-.pilot-status-body h3 {
- margin: 4px 0 8px;
- font-size: 1.35rem;
- text-transform: capitalize;
+.wallet-action-panel header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 18px;
+ margin-bottom: 18px;
}
-.pilot-status-body p {
- margin: 0;
- color: #3f483f;
- line-height: 1.48;
+.wallet-action-panel header span {
+ color: #0f5fff;
+ font-size: 0.75rem;
+ font-weight: 800;
+ text-transform: uppercase;
}
-.product-surface-grid {
+.wallet-action-panel header button {
+ min-height: 36px;
+ padding: 0 12px;
+ border-color: rgba(10, 33, 71, 0.14);
+ background: rgba(255, 255, 255, 0.5);
+}
+
+.wallet-panel-form {
display: grid;
- grid-template-columns: 1.2fr 1fr 1.2fr;
- gap: 10px;
+ gap: 14px;
}
-.product-surface {
+.wallet-panel-form label {
display: grid;
- grid-template-columns: 34px minmax(0, 1fr) auto auto;
- gap: 10px;
- align-items: start;
- min-width: 0;
- min-height: 118px;
- padding: 12px;
- text-align: left;
- transition: background 140ms ease, border-color 140ms ease, transform 140ms ease;
+ gap: 7px;
+ color: #53637d;
+ font-weight: 700;
}
-.product-surface:hover {
- border-color: #9fb4a8;
- background: #eaf1eb;
+.wallet-panel-form input:not([type="checkbox"]) {
+ min-height: 46px;
+ padding: 0 13px;
+ border: 1px solid rgba(10, 33, 71, 0.14);
+ border-radius: 10px;
+ background: rgba(255, 255, 255, 0.46);
}
-.product-surface-icon {
- display: grid;
- place-items: center;
- width: 32px;
- height: 32px;
- color: #235f52;
- border: 1px solid #bfd1c7;
- border-radius: 7px;
- background: #edf5ef;
+.wallet-panel-form button,
+.wallet-receive-panel button {
+ display: inline-flex;
+ gap: 9px;
+ align-items: center;
+ justify-content: center;
+ min-height: 44px;
+ padding: 0 16px;
+ color: #ffffff;
+ border: 0;
+ border-radius: 9px;
+ background: #0f5fff;
+ font-weight: 760;
}
-.product-surface strong,
-.product-surface small,
-.product-surface code {
- display: block;
- overflow-wrap: anywhere;
+.wallet-panel-form button:disabled {
+ cursor: not-allowed;
+ opacity: 0.55;
}
-.product-surface small {
- margin-top: 4px;
- line-height: 1.36;
+.wallet-inline-check {
+ grid-template-columns: 18px 1fr;
+ align-items: center;
+ color: #071d44 !important;
}
-.product-surface b {
- color: var(--ink);
- font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
- font-size: 1.1rem;
- line-height: 1.1;
+.wallet-swap-route {
+ display: grid;
+ grid-template-columns: 1fr 42px 1fr;
+ gap: 10px;
+ align-items: center;
}
-.workbench-node-body {
+.wallet-swap-route span {
display: grid;
- gap: 16px;
- padding-top: 14px;
+ place-items: center;
+ min-height: 48px;
+ border: 1px solid rgba(10, 33, 71, 0.12);
+ border-radius: 12px;
+ background: rgba(255, 255, 255, 0.38);
+ font-weight: 800;
}
-.workbench-node-body h3 {
- margin: 0 0 7px;
- font-size: 1.22rem;
+.wallet-receive-panel {
+ display: grid;
+ gap: 16px;
}
-.workbench-node-body p,
-.workbench-section-detail,
-.boundary-copy p {
+.wallet-receive-panel p {
margin: 0;
- color: #465047;
- line-height: 1.48;
+ padding: 13px;
+ border: 1px solid rgba(10, 33, 71, 0.12);
+ border-radius: 10px;
+ background: rgba(255, 255, 255, 0.42);
+ overflow-wrap: anywhere;
+ color: #071d44;
+ font-weight: 700;
}
-.workbench-fact-grid {
+.wallet-qr-block {
display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
+ grid-template-columns: repeat(3, 1fr);
gap: 10px;
- margin: 0;
+ width: 156px;
+ height: 156px;
+ padding: 14px;
+ border: 1px solid rgba(10, 33, 71, 0.16);
+ border-radius: 18px;
+ background:
+ linear-gradient(90deg, rgba(15, 95, 255, 0.13) 50%, transparent 50%) 0 0 / 28px 28px,
+ linear-gradient(rgba(15, 95, 255, 0.2) 50%, transparent 50%) 0 0 / 22px 22px,
+ rgba(255, 255, 255, 0.5);
}
-.workbench-fact-grid div {
- min-width: 0;
- padding: 10px;
- border: 1px solid var(--line);
+.wallet-qr-block span {
+ border: 8px solid #071d44;
border-radius: 7px;
- background: #f7f8f4;
}
-.setup-step-list {
+.wallet-panel-list {
display: grid;
- padding-top: 6px;
+ gap: 10px;
}
-.setup-step-list article {
+.wallet-panel-list article {
display: grid;
- grid-template-columns: auto minmax(0, 1fr);
- gap: 9px;
- align-items: start;
- padding: 11px 0;
- border-bottom: 1px solid var(--line);
+ grid-template-columns: 1fr auto;
+ gap: 5px 12px;
+ padding: 13px;
+ border: 1px solid rgba(10, 33, 71, 0.1);
+ border-radius: 12px;
+ background: rgba(255, 255, 255, 0.35);
}
-.setup-step-list .status-badge {
- align-self: start;
+.wallet-panel-list small {
+ overflow-wrap: anywhere;
}
-.setup-step-list article:last-child {
- border-bottom: 0;
+.wallet-panel-list b {
+ color: #0aa873;
}
-.setup-step-list strong,
-.setup-step-list code,
-.setup-step-list small {
- display: block;
- overflow-wrap: anywhere;
+.wallet-security-grid {
+ display: grid;
+ gap: 12px;
}
-code {
- width: fit-content;
- max-width: 100%;
- margin: 5px 0;
- padding: 3px 6px;
- color: #25332d;
- border: 1px solid var(--line);
- border-radius: 6px;
- background: #eef1eb;
- font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
- font-size: 0.78rem;
-}
-
-.workbench-warning {
+.wallet-security-grid article,
+.wallet-empty-panel {
display: grid;
- grid-template-columns: auto minmax(0, 1fr);
- gap: 9px;
- align-items: start;
- padding: 11px 12px;
- color: #6b4a16;
- border: 1px solid #d9c393;
- border-radius: 8px;
- background: #fbf6e8;
-}
-
-.workbench-warning strong,
-.workbench-warning span {
- display: block;
- overflow-wrap: anywhere;
+ gap: 8px;
+ padding: 14px;
+ border: 1px solid rgba(10, 33, 71, 0.1);
+ border-radius: 13px;
+ background: rgba(255, 255, 255, 0.34);
}
-.workbench-api-panel {
- display: grid;
- gap: 12px;
+.wallet-security-grid svg,
+.wallet-empty-panel svg {
+ color: #0f5fff;
}
-.endpoint-strip {
- display: flex;
- flex-wrap: wrap;
- gap: 7px;
+.wallet-empty-panel strong {
+ color: #071d44;
}
-.endpoint-strip span {
- padding: 4px 7px;
- color: #34443c;
- border: 1px solid var(--line);
+.wallet-toast {
+ position: fixed;
+ z-index: 7;
+ left: 50%;
+ bottom: 24px;
+ display: inline-flex;
+ gap: 10px;
+ align-items: center;
+ max-width: min(640px, calc(100vw - 28px));
+ margin: 0;
+ padding: 12px 16px;
+ color: #071d44;
+ border: 1px solid rgba(10, 33, 71, 0.13);
border-radius: 999px;
- background: #f7f8f4;
- font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
- font-size: 0.72rem;
+ background: rgba(255, 255, 255, 0.78);
+ box-shadow: 0 18px 42px rgba(50, 37, 18, 0.12);
+ transform: translateX(-50%);
+ backdrop-filter: blur(18px);
}
-.local-action-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 10px;
-}
+@media (max-width: 1220px) {
+ .wallet-app-shell {
+ grid-template-columns: 220px minmax(0, 1fr);
+ overflow: auto;
+ }
-.local-action-grid article {
- display: grid;
- grid-template-columns: minmax(0, 1fr) auto;
- gap: 10px;
- align-items: center;
- min-width: 0;
- padding: 11px;
- border: 1px solid var(--line);
- border-radius: 7px;
- background: #f7f8f4;
+ .wallet-app-rightbar {
+ grid-column: 2;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ padding: 0 24px 28px 16px;
+ }
+
+ .wallet-app-topbar {
+ grid-template-columns: 170px minmax(0, 1fr) 48px;
+ }
+
+ .wallet-profile-pill {
+ grid-column: 1 / -1;
+ width: min(280px, 100%);
+ justify-self: end;
+ }
}
-.local-action-grid strong,
-.local-action-grid small {
- display: block;
- overflow-wrap: anywhere;
+@media (max-width: 860px) {
+ .wallet-app-shell {
+ display: block;
+ min-height: 100dvh;
+ overflow: auto;
+ }
+
+ .wallet-app-sidebar {
+ position: relative;
+ display: block;
+ padding: 20px 18px 0;
+ }
+
+ .wallet-side-nav {
+ display: flex;
+ gap: 8px;
+ overflow-x: auto;
+ margin-top: 16px;
+ padding-bottom: 8px;
+ }
+
+ .wallet-side-nav::before,
+ .wallet-side-nav::after,
+ .wallet-collapse-button {
+ display: none;
+ }
+
+ .wallet-side-nav button,
+ .wallet-side-nav a {
+ display: inline-flex;
+ grid-template-columns: none;
+ min-width: max-content;
+ min-height: 42px;
+ padding: 0 13px;
+ }
+
+ .wallet-app-main {
+ padding: 18px;
+ }
+
+ .wallet-app-topbar {
+ grid-template-columns: 1fr 48px;
+ gap: 12px;
+ }
+
+ .wallet-network-switch,
+ .wallet-search {
+ grid-column: 1 / -1;
+ }
+
+ .wallet-profile-pill {
+ grid-column: 1;
+ justify-self: stretch;
+ }
+
+ .wallet-portfolio-panel,
+ .wallet-action-grid,
+ .wallet-app-rightbar {
+ grid-template-columns: 1fr;
+ }
+
+ .wallet-portfolio-panel {
+ padding: 24px 20px;
+ }
+
+ .wallet-action-grid {
+ gap: 10px;
+ }
+
+ .wallet-asset-table,
+ .wallet-activity-table {
+ overflow-x: auto;
+ }
+
+ .wallet-asset-table > div {
+ min-width: 680px;
+ }
+
+ .wallet-activity-table > div {
+ min-width: 860px;
+ }
+
+ .wallet-app-rightbar {
+ padding: 0 18px 24px;
+ }
+
+ .wallet-action-panel {
+ right: 12px;
+ bottom: 12px;
+ left: 12px;
+ width: auto;
+ }
}
-.action-result,
-.boot-hint {
- margin: 0;
- color: var(--muted);
- line-height: 1.45;
+input[type="checkbox"] {
+ width: 16px;
+ height: 16px;
+ accent-color: var(--accent);
}
-.workbench-layout {
- display: grid;
- grid-template-columns: 222px minmax(0, 1fr);
- gap: 14px;
- align-items: start;
+select {
+ min-height: 38px;
+ padding: 0 12px;
}
-.workbench-switcher {
- position: sticky;
- top: 88px;
- display: grid;
- gap: 7px;
- max-height: calc(100dvh - 112px);
- overflow: auto;
+table {
+ width: 100%;
+ border-collapse: collapse;
+ table-layout: fixed;
}
-.workbench-switch {
- display: grid;
- grid-template-columns: minmax(0, 1fr) auto;
- gap: 10px;
- align-items: center;
- min-height: 38px;
- padding: 8px 10px;
+th,
+td {
+ border-bottom: 1px solid var(--line);
+ padding: 12px 14px;
text-align: left;
- transition: background 140ms ease, border-color 140ms ease, transform 140ms ease;
+ vertical-align: top;
}
-.workbench-switch.active,
-.workbench-switch:hover {
- border-color: #9fb4a8;
- background: #eaf1eb;
+th {
+ color: #555d55;
+ font-size: 0.72rem;
+ font-weight: 700;
+ text-transform: uppercase;
}
-.workbench-switch span {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+td {
+ font-size: 0.86rem;
}
-.workbench-switch strong {
- font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
- font-size: 0.78rem;
+th,
+td {
+ overflow-wrap: anywhere;
}
-.workbench-record-panel {
+small {
+ color: var(--muted);
+}
+
+.app-shell {
display: grid;
- gap: 14px;
+ grid-template-columns: 252px minmax(0, 1fr);
+ min-height: 100dvh;
}
-.workbench-record-grid {
+.sidebar {
+ position: sticky;
+ top: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 22px;
+ height: 100dvh;
+ padding: 18px 14px;
+ background: var(--sidebar);
+ color: #f6f8f2;
+ border-right: 1px solid #32382f;
+}
+
+.brand-block {
display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
+ grid-template-columns: 42px 1fr;
gap: 10px;
+ align-items: center;
+ padding: 4px 4px 14px;
+ border-bottom: 1px solid #343a33;
}
-.workbench-record {
+.brand-mark {
display: grid;
- gap: 11px;
- min-width: 0;
- padding: 13px;
- border: 1px solid var(--line);
- border-radius: 7px;
- background: #f7f8f4;
+ place-items: center;
+ width: 38px;
+ height: 38px;
+ border: 1px solid #556054;
+ border-radius: 8px;
+ background: #252b26;
+ color: #d8efe5;
}
-.workbench-record h3 {
- min-width: 0;
- margin: 0;
- overflow-wrap: anywhere;
- font-size: 0.96rem;
+.brand-kicker,
+.eyebrow {
+ display: block;
+ margin-bottom: 4px;
+ color: var(--subtle);
+ font-size: 0.71rem;
+ font-weight: 700;
+ text-transform: uppercase;
}
-.workbench-record p {
- margin: 0;
- color: #465047;
- line-height: 1.44;
+.brand-block strong {
+ font-size: 1rem;
}
-.workbench-boundary-panel {
+.nav-list {
display: grid;
- gap: 12px;
+ gap: 4px;
}
-.boundary-copy {
+.nav-link {
display: grid;
+ grid-template-columns: 22px 1fr;
gap: 9px;
+ align-items: center;
+ min-height: 38px;
+ padding: 8px 10px;
+ color: var(--sidebar-muted);
+ text-decoration: none;
+ border: 1px solid transparent;
+ border-radius: 7px;
+ transition: background 140ms ease, border-color 140ms ease, color 140ms ease, transform 140ms ease;
}
-.table-panel {
- min-width: 0;
- overflow: hidden;
+.nav-link:hover,
+.nav-link.active {
+ background: #29302b;
+ border-color: #3c463e;
+ color: #f6f8f2;
}
-.table-scroll {
- overflow-x: auto;
+.sidebar-footer {
+ display: grid;
+ gap: 7px;
+ margin-top: auto;
+ padding: 12px;
+ color: var(--sidebar-muted);
+ border: 1px solid #3a433b;
+ border-radius: 8px;
+ background: #232923;
}
-.table-scroll table {
- min-width: 1120px;
+.workspace {
+ min-width: 0;
}
-.cell-stack {
- display: grid;
- gap: 4px;
- min-width: 0;
+.topbar {
+ position: sticky;
+ top: 0;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 18px;
+ padding: 14px 24px;
+ background: rgba(244, 245, 241, 0.92);
+ border-bottom: 1px solid var(--line);
+ backdrop-filter: blur(12px);
}
-.hash-value {
- font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
- font-size: 0.82rem;
- overflow-wrap: anywhere;
+.topbar h2 {
+ margin: 0;
+ font-size: 1.05rem;
}
-.provenance-line {
+.topbar-meta {
display: flex;
flex-wrap: wrap;
+ gap: 8px;
+ justify-content: flex-end;
+}
+
+.topbar-meta span,
+.provenance-chip,
+.severity-label {
+ display: inline-flex;
+ align-items: center;
gap: 5px;
+ min-height: 24px;
+ padding: 4px 7px;
+ color: #4f584f;
+ border: 1px solid var(--line);
+ border-radius: 999px;
+ background: #f9faf6;
+ font-size: 0.72rem;
+ white-space: nowrap;
}
-.rootfield-grid,
-.hardware-grid {
+.fixture-banner {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ margin: 18px 24px 0;
+ padding: 10px 12px;
+ color: #394139;
+ border: 1px solid #cfd7cd;
+ border-radius: 8px;
+ background: #f9faf6;
+ font-size: 0.86rem;
+}
+
+.content {
+ width: min(100%, 1480px);
+ margin: 0 auto;
+ padding: 24px;
+}
+
+.flowchain-bridge-page {
+ position: relative;
+ min-height: 100dvh;
+ overflow: hidden;
+ background:
+ radial-gradient(circle at 50% 18%, rgba(255, 255, 255, 0.86), rgba(255, 255, 255, 0) 38rem),
+ radial-gradient(circle at 18% 62%, rgba(41, 123, 255, 0.18), rgba(41, 123, 255, 0) 22rem),
+ linear-gradient(180deg, #f4ebdf 0%, #efe2d1 48%, #ead9c5 100%);
+ color: #1c1a17;
+}
+
+.flowchain-bridge-page::before {
+ position: fixed;
+ inset: 0;
+ z-index: 0;
+ pointer-events: none;
+ content: "";
+ opacity: 0.42;
+ background-image:
+ linear-gradient(rgba(58, 39, 18, 0.035) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(58, 39, 18, 0.025) 1px, transparent 1px);
+ background-size: 9px 9px, 11px 11px;
+ mix-blend-mode: multiply;
+}
+
+.flowchain-bridge-page::after {
+ position: fixed;
+ inset: auto -12vw 16dvh -12vw;
+ z-index: 0;
+ height: 27dvh;
+ pointer-events: none;
+ content: "";
+ opacity: 0.9;
+ background:
+ radial-gradient(ellipse at 12% 54%, rgba(0, 77, 208, 0.52), rgba(0, 113, 255, 0.18) 34%, transparent 67%),
+ radial-gradient(ellipse at 42% 44%, rgba(0, 94, 255, 0.72), rgba(0, 111, 255, 0.34) 28%, transparent 66%),
+ radial-gradient(ellipse at 76% 55%, rgba(0, 91, 229, 0.58), rgba(0, 127, 255, 0.22) 32%, transparent 68%);
+ filter: blur(10px) saturate(1.12);
+ transform: rotate(-3deg) skewX(-11deg);
+}
+
+.bridge-flow-ribbon {
+ position: fixed;
+ inset: auto -10vw 26dvh -10vw;
+ z-index: 0;
+ height: 19dvh;
+ pointer-events: none;
+ opacity: 0.86;
+ background:
+ linear-gradient(92deg, transparent 0%, rgba(0, 95, 255, 0.2) 14%, rgba(12, 103, 255, 0.72) 42%, rgba(21, 127, 255, 0.7) 64%, transparent 100%),
+ radial-gradient(ellipse at 35% 45%, rgba(11, 82, 213, 0.85), rgba(29, 130, 255, 0.18) 42%, transparent 70%);
+ border-radius: 999px 18% 999px 28%;
+ filter: blur(3px);
+ transform: rotate(4deg) skewX(-18deg);
+}
+
+.flowchain-bridge-nav,
+.flowchain-bridge-main {
+ position: relative;
+ z-index: 1;
+}
+
+.flowchain-bridge-nav {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 24px;
+ width: min(100%, 1720px);
+ margin: 0 auto;
+ padding: 24px 34px 0;
+}
+
+.flowchain-brand,
+.flowchain-wallet-pill {
+ display: inline-flex;
+ align-items: center;
+ text-decoration: none;
+}
+
+.flowchain-brand {
+ gap: 10px;
+ color: #17213a;
+}
+
+.flowchain-brand strong {
+ font-family: ui-serif, Georgia, "Times New Roman", serif;
+ font-size: clamp(1.45rem, 2.4vw, 2rem);
+ font-weight: 600;
+}
+
+.flowchain-brand-mark {
+ position: relative;
+ width: 52px;
+ height: 30px;
+}
+
+.flowchain-brand-mark,
+.flowchain-brand-mark span {
+ display: block;
+}
+
+.flowchain-brand-mark::before,
+.flowchain-brand-mark::after,
+.flowchain-brand-mark span {
+ position: absolute;
+ left: 0;
+ width: 48px;
+ height: 12px;
+ content: "";
+ border: solid #256bda;
+ border-width: 3px 0 0;
+ border-radius: 50%;
+ transform-origin: left center;
+}
+
+.flowchain-brand-mark::before {
+ top: 4px;
+ transform: rotate(15deg);
+}
+
+.flowchain-brand-mark span {
+ top: 11px;
+ border-color: #1d54b6;
+ transform: rotate(-8deg);
+}
+
+.flowchain-brand-mark::after {
+ top: 18px;
+ width: 36px;
+ border-color: #5ba2ff;
+ transform: rotate(20deg);
+}
+
+.flowchain-wallet-pill {
+ gap: 9px;
+ min-height: 44px;
+ padding: 0 16px;
+ border: 1px solid rgba(98, 76, 52, 0.14);
+ border-radius: 8px;
+ background: rgba(248, 241, 231, 0.84);
+ box-shadow: 0 14px 34px rgba(44, 31, 17, 0.13);
+ color: #30281f;
+ font-size: 0.9rem;
+ backdrop-filter: blur(18px);
+ transition: transform 180ms ease, border-color 180ms ease, background 180ms ease;
+}
+
+.flowchain-wallet-pill:hover {
+ border-color: rgba(37, 107, 218, 0.32);
+ background: rgba(255, 250, 243, 0.92);
+}
+
+.wallet-orb {
+ width: 19px;
+ height: 19px;
+ border-radius: 50%;
+ background:
+ radial-gradient(circle at 36% 32%, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0) 38%),
+ linear-gradient(135deg, #79c2ff, #0b63ff 70%);
+ box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.52), 0 7px 16px rgba(0, 91, 255, 0.28);
+}
+
+.flowchain-bridge-main {
display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 14px;
+ gap: 20px;
+ width: min(100%, 1540px);
+ margin: 0 auto;
+ padding: 14px 34px 46px;
}
-.rootfield-tile,
-.node-tile,
-.lane-tile {
+.bridge-title-block {
display: grid;
- gap: 14px;
- padding: 14px;
+ justify-items: center;
+ text-align: center;
}
-.definition-grid {
- grid-template-columns: repeat(2, minmax(0, 1fr));
+.bridge-title-block span {
+ color: rgba(49, 40, 31, 0.68);
+ font-size: 0.76rem;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
}
-.lane-grid {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 12px;
+.bridge-title-block h1 {
+ margin: 8px 0 8px;
+ color: #211b16;
+ font-family: ui-serif, Georgia, "Times New Roman", serif;
+ font-size: clamp(3.05rem, 5.7vw, 5.25rem);
+ font-weight: 500;
+ line-height: 0.95;
+ text-wrap: balance;
}
-.lane-stats {
- grid-template-columns: repeat(4, minmax(0, 1fr));
+.bridge-title-block p {
+ width: min(560px, 100%);
+ max-width: 560px;
+ margin: 0;
+ color: rgba(47, 39, 31, 0.7);
+ font-family: ui-serif, Georgia, "Times New Roman", serif;
+ font-size: 1.18rem;
+ line-height: 1.34;
+ text-wrap: balance;
}
-.alert-list {
- gap: 12px;
+.flowchain-bridge-layout {
+ display: grid;
+ grid-template-columns: minmax(210px, 270px) minmax(520px, 650px) minmax(220px, 270px);
+ gap: clamp(20px, 5vw, 92px);
+ align-items: center;
+ justify-content: center;
}
-.alert-row {
+.bridge-side-card,
+.bridge-console {
+ border: 1px solid rgba(89, 68, 45, 0.14);
+ background: rgba(249, 241, 229, 0.74);
+ box-shadow: 0 28px 68px rgba(46, 31, 15, 0.14), inset 0 1px 0 rgba(255, 255, 255, 0.48);
+ backdrop-filter: blur(18px);
+}
+
+.bridge-side-card {
display: grid;
- grid-template-columns: minmax(0, 1fr) 300px;
gap: 16px;
- padding: 14px;
- border-left-width: 4px;
+ padding: 24px;
+ border-radius: 8px;
}
-.severity-critical {
- border-left-color: var(--danger);
+.bridge-card-heading {
+ display: flex;
+ gap: 10px;
+ align-items: center;
}
-.severity-warning {
- border-left-color: var(--warning);
+.bridge-card-heading h2 {
+ margin: 0;
+ color: #281f18;
+ font-family: ui-serif, Georgia, "Times New Roman", serif;
+ font-size: 1.18rem;
+ font-weight: 500;
}
-.severity-info {
- border-left-color: var(--info);
+.bridge-transfer-list,
+.bridge-side-stack,
+.bridge-route-list,
+.bridge-help-card {
+ display: grid;
+ gap: 12px;
}
-.alert-action {
- padding: 12px;
- border-left: 1px solid var(--line);
+.bridge-transfer-row {
+ display: grid;
+ grid-template-columns: 28px minmax(0, 1fr) auto;
+ gap: 10px;
+ align-items: center;
}
-.alert-action span {
- color: var(--subtle);
- font-size: 0.7rem;
- font-weight: 700;
- text-transform: uppercase;
+.bridge-transfer-row strong,
+.bridge-transfer-row span,
+.bridge-readiness-line span,
+.bridge-route-list strong,
+.bridge-route-list span {
+ overflow-wrap: anywhere;
}
-.alert-action p {
- margin-bottom: 0;
+.bridge-transfer-row strong {
+ display: block;
+ color: #221a14;
+ font-size: 0.9rem;
+ font-weight: 650;
}
-.json-panel {
- overflow: hidden;
+.bridge-transfer-row span {
+ color: rgba(42, 33, 25, 0.66);
+ font-size: 0.8rem;
}
-.json-panel-header {
- justify-content: space-between;
- padding: 12px 14px;
- border-bottom: 1px solid var(--line);
+.bridge-transfer-icon {
+ display: grid;
+ place-items: center;
+ width: 26px;
+ height: 26px;
+ color: #0f62db;
+ border-radius: 50%;
+ background: #eef5ff;
}
-.json-panel pre {
- max-height: 68dvh;
- margin: 0;
- padding: 16px;
- overflow: auto;
- background: #1f241f;
- color: #eef4ec;
- font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
- font-size: 0.78rem;
- line-height: 1.5;
+.bridge-empty-transfer {
+ display: grid;
+ gap: 5px;
+ padding: 14px;
+ color: rgba(43, 34, 26, 0.68);
+ border: 1px dashed rgba(89, 68, 45, 0.18);
+ border-radius: 8px;
+ background: rgba(255, 248, 238, 0.62);
+ line-height: 1.36;
}
-.status-badge {
+.bridge-empty-transfer strong {
+ color: #271f18;
+}
+
+.bridge-text-link,
+.bridge-help-card a,
+.bridge-tx-link {
display: inline-flex;
- gap: 6px;
+ gap: 8px;
align-items: center;
- width: fit-content;
- max-width: 100%;
- min-height: 24px;
- padding: 4px 8px;
- border: 1px solid var(--status-border);
- border-radius: 999px;
- background: var(--status-bg);
- color: var(--status-fg);
+ color: #0d57c4;
+ text-decoration: none;
+}
+
+.bridge-text-link {
+ justify-content: center;
+ min-height: 40px;
+ border-radius: 8px;
+ background: rgba(255, 250, 243, 0.48);
+ font-weight: 650;
+}
+
+.bridge-console {
+ display: grid;
+ gap: 14px;
+ min-width: 0;
+ padding: 24px;
+ border-radius: 10px;
+}
+
+.bridge-route-row {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 140px minmax(0, 1fr);
+ gap: 14px;
+ align-items: end;
+}
+
+.bridge-route-row label,
+.bridge-field {
+ min-width: 0;
+}
+
+.bridge-route-row label > span,
+.bridge-field > label,
+.bridge-estimate-strip span,
+.bridge-mini-facts dt,
+.bridge-calldata-facts dt {
+ display: block;
+ margin-bottom: 6px;
+ color: rgba(54, 43, 33, 0.7);
+ font-size: 0.68rem;
+ font-weight: 750;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+}
+
+.bridge-select-shell,
+.bridge-token-field > div,
+.bridge-field {
+ border: 1px solid rgba(93, 72, 49, 0.13);
+ border-radius: 8px;
+ background: rgba(255, 250, 243, 0.68);
+ box-shadow: 0 9px 18px rgba(47, 32, 14, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.5);
+}
+
+.bridge-select-shell {
+ display: grid;
+ grid-template-columns: 32px minmax(0, 1fr) 18px;
+ gap: 10px;
+ align-items: center;
+ min-height: 58px;
+ padding: 0 14px;
+}
+
+.bridge-select-shell strong,
+.bridge-token-field strong {
+ font-family: ui-serif, Georgia, "Times New Roman", serif;
+ font-size: 1.12rem;
+ font-weight: 500;
+}
+
+.bridge-chain-icon,
+.bridge-token-orb {
+ display: grid;
+ place-items: center;
+ color: #fff;
font-size: 0.72rem;
font-weight: 800;
- text-transform: uppercase;
- white-space: nowrap;
+ border-radius: 50%;
}
-.status-dot {
- width: 7px;
- height: 7px;
- border-radius: 999px;
- background: currentColor;
+.bridge-chain-icon {
+ width: 30px;
+ height: 30px;
}
-.status-observed {
- --status-bg: #e7f0f3;
- --status-border: #b9d0d8;
- --status-fg: #326b89;
+.bridge-chain-icon.base,
+.bridge-token-orb {
+ background: linear-gradient(135deg, #89c6ff, #0a61dd 68%);
}
-.status-pending {
- --status-bg: #f5eddb;
- --status-border: #d9c393;
- --status-fg: #8a5e14;
-}
+.bridge-chain-icon.flow {
+ background: linear-gradient(135deg, #8bc7ff, #163d96 72%);
+}
+
+.bridge-route-connector {
+ display: grid;
+ grid-template-columns: 1fr 44px 1fr;
+ gap: 8px;
+ align-items: center;
+ padding-bottom: 7px;
+}
+
+.bridge-route-connector span {
+ height: 3px;
+ border-radius: 999px;
+ background-image: linear-gradient(90deg, rgba(12, 99, 221, 0.22) 0 45%, transparent 45% 100%);
+ background-size: 12px 3px;
+}
+
+.bridge-route-connector button,
+.bridge-icon-action {
+ display: grid;
+ place-items: center;
+ border: 1px solid rgba(38, 107, 218, 0.26);
+ background: rgba(244, 249, 255, 0.78);
+ color: #145ed3;
+ text-decoration: none;
+ transition: transform 180ms ease, border-color 180ms ease, background 180ms ease;
+}
+
+.bridge-route-connector button {
+ width: 44px;
+ height: 44px;
+ border-radius: 50%;
+}
+
+.bridge-field {
+ display: grid;
+ gap: 7px;
+ padding: 11px 14px;
+}
+
+.bridge-token-field {
+ grid-template-columns: minmax(0, 1fr);
+}
+
+.bridge-token-field > div {
+ display: grid;
+ grid-template-columns: 34px minmax(0, auto) 1fr;
+ gap: 10px;
+ align-items: center;
+ min-height: 52px;
+ padding: 0 12px;
+}
+
+.bridge-token-field small {
+ color: rgba(50, 39, 29, 0.62);
+}
+
+.bridge-token-orb {
+ width: 30px;
+ height: 30px;
+}
+
+.bridge-amount-field {
+ grid-template-columns: minmax(0, 1fr) auto;
+ align-items: end;
+}
+
+.bridge-amount-field label,
+.bridge-recipient-field label {
+ grid-column: 1 / -1;
+}
+
+.bridge-amount-field input {
+ font-size: 1.18rem;
+}
+
+.bridge-amount-field > span {
+ color: #211b16;
+ font-family: ui-serif, Georgia, "Times New Roman", serif;
+ font-size: 1.12rem;
+}
+
+.bridge-recipient-field {
+ grid-template-columns: minmax(0, 1fr) 32px;
+ align-items: center;
+}
+
+.bridge-recipient-field button {
+ display: grid;
+ place-items: center;
+ width: 30px;
+ height: 30px;
+ padding: 0;
+ border-color: transparent;
+ background: transparent;
+ color: rgba(47, 38, 29, 0.54);
+}
+
+.bridge-field input:not([type="checkbox"]) {
+ min-height: 28px;
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.82rem;
+}
+
+.bridge-advanced {
+ display: grid;
+ gap: 12px;
+ color: rgba(46, 36, 28, 0.78);
+}
+
+.bridge-advanced summary {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ width: fit-content;
+ cursor: pointer;
+ color: rgba(41, 32, 24, 0.76);
+ font-size: 0.84rem;
+ font-weight: 650;
+}
+
+.bridge-advanced[open] summary {
+ margin-bottom: 10px;
+}
+
+.bridge-calldata-facts,
+.bridge-mini-facts {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 8px;
+ margin: 10px 0 0;
+}
+
+.bridge-calldata-facts div,
+.bridge-mini-facts div {
+ min-width: 0;
+ padding: 10px;
+ border: 1px solid rgba(89, 68, 45, 0.12);
+ border-radius: 8px;
+ background: rgba(255, 250, 243, 0.52);
+}
+
+.bridge-calldata-facts dd,
+.bridge-mini-facts dd {
+ margin: 0;
+ overflow-wrap: anywhere;
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.74rem;
+ font-weight: 700;
+}
+
+.bridge-estimate-strip {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ overflow: hidden;
+ border-radius: 8px;
+ background: rgba(71, 50, 29, 0.06);
+}
+
+.bridge-estimate-strip div {
+ display: grid;
+ gap: 6px;
+ min-width: 0;
+ padding: 14px;
+ text-align: center;
+ border-right: 1px solid rgba(92, 70, 47, 0.08);
+}
+
+.bridge-estimate-strip div:last-child {
+ border-right: 0;
+}
+
+.bridge-estimate-strip span {
+ margin-bottom: 0;
+}
+
+.bridge-estimate-strip strong {
+ overflow-wrap: anywhere;
+ font-size: 0.9rem;
+ font-weight: 600;
+}
+
+.bridge-ack {
+ display: grid;
+ grid-template-columns: 18px minmax(0, 1fr);
+ gap: 10px;
+ align-items: start;
+ padding: 12px;
+ border: 1px solid rgba(93, 72, 49, 0.16);
+ border-radius: 8px;
+ background: rgba(255, 250, 243, 0.54);
+ color: rgba(38, 30, 23, 0.72);
+ font-size: 0.84rem;
+ line-height: 1.35;
+}
+
+.bridge-action-row {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 44px 44px;
+ gap: 10px;
+}
+
+.bridge-primary-action {
+ min-height: 56px;
+ justify-content: center;
+ border: 0;
+ border-radius: 8px;
+ background: linear-gradient(135deg, #043aa9, #1b78ff);
+ color: #fff;
+ box-shadow: 0 16px 32px rgba(0, 78, 216, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.32);
+ font-family: ui-serif, Georgia, "Times New Roman", serif;
+ font-size: 1.08rem;
+ transition: transform 180ms ease, box-shadow 180ms ease, opacity 180ms ease;
+}
+
+.bridge-primary-action:hover:not(:disabled) {
+ transform: translateY(-1px);
+ box-shadow: 0 20px 38px rgba(0, 78, 216, 0.34), inset 0 1px 0 rgba(255, 255, 255, 0.34);
+}
+
+.bridge-icon-action {
+ width: 44px;
+ height: 56px;
+ border-radius: 8px;
+}
+
+.bridge-icon-action:hover,
+.bridge-route-connector button:hover {
+ border-color: rgba(20, 94, 211, 0.42);
+ background: rgba(255, 255, 255, 0.86);
+}
+
+.bridge-validation,
+.bridge-status-message,
+.bridge-tx-link {
+ margin: 0;
+ padding: 11px 12px;
+ border-radius: 8px;
+ font-size: 0.88rem;
+}
+
+.bridge-validation {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ color: #6f2525;
+ border: 1px solid #e3b5b5;
+ background: rgba(255, 240, 240, 0.86);
+}
+
+.bridge-status-message {
+ color: #234e44;
+ border: 1px solid #b9d8cf;
+ background: rgba(238, 248, 244, 0.86);
+}
+
+.bridge-tx-link {
+ justify-self: start;
+ border: 1px solid #b9d8cf;
+ background: rgba(238, 248, 244, 0.86);
+}
+
+.bridge-safety-rail {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ overflow: hidden;
+ margin: 4px -24px -24px;
+ border-top: 1px solid rgba(88, 67, 44, 0.11);
+ border-radius: 0 0 10px 10px;
+ background: rgba(241, 230, 216, 0.72);
+}
+
+.bridge-safety-rail span {
+ display: grid;
+ grid-template-columns: 24px minmax(0, 1fr);
+ gap: 10px;
+ align-items: center;
+ min-width: 0;
+ padding: 15px 18px;
+ color: rgba(38, 30, 23, 0.74);
+ border-right: 1px solid rgba(88, 67, 44, 0.09);
+ font-size: 0.84rem;
+ line-height: 1.2;
+}
+
+.bridge-safety-rail span:last-child {
+ border-right: 0;
+}
+
+.bridge-readiness-line {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.bridge-readiness-line > span {
+ color: rgba(38, 30, 23, 0.76);
+ font-size: 0.88rem;
+ font-weight: 650;
+}
+
+.bridge-mini-facts {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+}
+
+.bridge-route-list div {
+ display: grid;
+ grid-template-columns: 22px minmax(0, 1fr) auto;
+ gap: 10px;
+ align-items: center;
+ min-height: 28px;
+}
+
+.bridge-chain-dot {
+ width: 19px;
+ height: 19px;
+ border-radius: 50%;
+ background: #b5aa9a;
+}
+
+.bridge-chain-dot.active {
+ background: linear-gradient(135deg, #7fc2ff, #0b63ff);
+}
+
+.bridge-chain-dot.staged {
+ background: linear-gradient(135deg, #d1c3ae, #a58f72);
+}
+
+.bridge-route-list strong {
+ font-size: 0.88rem;
+ font-weight: 650;
+}
+
+.bridge-route-list span:not(.bridge-chain-dot) {
+ color: rgba(43, 34, 26, 0.58);
+ font-size: 0.76rem;
+}
+
+.bridge-help-card a {
+ justify-content: space-between;
+ min-height: 28px;
+ color: #2b251f;
+ font-size: 0.86rem;
+}
+
+@media (max-width: 1280px) {
+ .flowchain-bridge-layout {
+ grid-template-columns: minmax(0, 650px);
+ }
+
+ .bridge-recent-card,
+ .bridge-side-stack {
+ width: min(650px, 100%);
+ justify-self: center;
+ }
+}
+
+@media (max-width: 720px) {
+ .flowchain-bridge-nav {
+ display: grid;
+ grid-template-columns: 1fr;
+ align-items: flex-start;
+ gap: 10px;
+ padding: 18px 16px 0;
+ }
+
+ .flowchain-wallet-pill {
+ justify-self: start;
+ min-height: 38px;
+ padding: 0 10px;
+ }
+
+ .flowchain-bridge-main {
+ padding: 18px 16px 38px;
+ }
+
+ .flowchain-bridge-layout {
+ grid-template-columns: minmax(0, 1fr);
+ gap: 16px;
+ }
+
+ .bridge-title-block h1 {
+ font-size: clamp(2.7rem, 16vw, 4rem);
+ }
+
+ .bridge-title-block p {
+ max-width: 340px;
+ font-size: 1.08rem;
+ }
+
+ .bridge-route-row,
+ .bridge-action-row,
+ .bridge-estimate-strip,
+ .bridge-safety-rail,
+ .bridge-calldata-facts,
+ .bridge-mini-facts {
+ grid-template-columns: 1fr;
+ }
+
+ .bridge-route-connector {
+ grid-template-columns: 1fr 44px 1fr;
+ padding: 0;
+ }
+
+ .bridge-console {
+ padding: 18px;
+ }
+
+ .bridge-transfer-row {
+ grid-template-columns: 28px minmax(0, 1fr);
+ }
+
+ .bridge-transfer-row .status-badge {
+ grid-column: 2;
+ justify-self: start;
+ }
+
+ .bridge-route-list div {
+ grid-template-columns: 22px minmax(0, 1fr);
+ }
+
+ .bridge-route-list span:not(.bridge-chain-dot) {
+ grid-column: 2;
+ }
+
+ .bridge-safety-rail {
+ margin: 4px -18px -18px;
+ }
+}
+
+button:disabled {
+ cursor: not-allowed;
+ opacity: 0.55;
+ transform: none;
+}
+
+.view-stack {
+ display: grid;
+ gap: 22px;
+}
+
+.section-header {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 18px;
+ align-items: end;
+}
+
+.section-header h1 {
+ margin: 0;
+ font-size: 1.8rem;
+ line-height: 1.15;
+}
+
+.section-header p {
+ max-width: 76ch;
+ margin: 8px 0 0;
+ color: var(--muted);
+ line-height: 1.5;
+}
+
+.section-action {
+ min-width: min(520px, 100%);
+}
+
+.workbench-header-actions {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 10px;
+ align-items: center;
+}
+
+.filter-row {
+ display: grid;
+ grid-template-columns: minmax(240px, 1fr) 150px;
+ gap: 10px;
+}
+
+.search-box {
+ display: grid;
+ grid-template-columns: 18px 1fr;
+ gap: 8px;
+ align-items: center;
+ min-height: 38px;
+ padding: 0 10px;
+ border: 1px solid var(--line-strong);
+ border-radius: 7px;
+ background: var(--surface);
+}
+
+.metric-grid {
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ gap: 10px;
+}
+
+.flowmemory-hero {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(280px, 0.34fr);
+ gap: 14px;
+ align-items: stretch;
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: linear-gradient(135deg, #fbfcf8 0%, #edf4ef 100%);
+ box-shadow: var(--shadow);
+}
+
+.flowmemory-hero-main {
+ display: grid;
+ gap: 12px;
+ padding: 18px;
+}
+
+.flowmemory-hero-main h2 {
+ max-width: 780px;
+ margin: 0;
+ font-size: 1.48rem;
+ line-height: 1.14;
+}
+
+.flowmemory-hero-main p {
+ max-width: 82ch;
+ margin: 0;
+ color: #465047;
+ line-height: 1.5;
+}
+
+.flowmemory-spine {
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ gap: 8px;
+}
+
+.flowmemory-spine span {
+ min-height: 34px;
+ padding: 8px 9px;
+ color: #284238;
+ border: 1px solid #bfd1c7;
+ border-radius: 7px;
+ background: rgba(251, 252, 248, 0.78);
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.74rem;
+ text-align: center;
+}
+
+.flowmemory-hero-side {
+ display: grid;
+ gap: 1px;
+ min-width: 0;
+ padding: 1px;
+ border-left: 1px solid var(--line);
+}
+
+.flowmemory-hero-side div {
+ display: grid;
+ gap: 5px;
+ align-content: center;
+ min-width: 0;
+ padding: 12px;
+ background: rgba(251, 252, 248, 0.72);
+}
+
+.canary-hero {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(300px, 0.36fr);
+ gap: 14px;
+ align-items: stretch;
+ padding: 16px;
+ border: 1px solid #c9d5ce;
+ border-radius: 8px;
+ background:
+ linear-gradient(135deg, rgba(251, 252, 248, 0.94), rgba(229, 239, 235, 0.92)),
+ linear-gradient(90deg, rgba(47, 125, 107, 0.18), transparent);
+ box-shadow: var(--shadow);
+}
+
+.canary-hero h2 {
+ max-width: 760px;
+ margin: 0;
+ font-size: 1.52rem;
+ line-height: 1.14;
+}
+
+.canary-hero p {
+ max-width: 84ch;
+ margin: 10px 0 0;
+ color: #465047;
+ line-height: 1.5;
+}
+
+.canary-read-window {
+ display: grid;
+ grid-template-columns: 28px 1fr;
+ gap: 10px;
+ align-items: start;
+ padding: 12px;
+ border: 1px solid #bfd1c7;
+ border-radius: 8px;
+ background: rgba(251, 252, 248, 0.78);
+}
+
+.canary-read-window dl {
+ display: grid;
+ gap: 9px;
+ margin: 0;
+}
+
+.canary-operator-strip {
+ display: grid;
+ grid-template-columns: 1.15fr 1fr 1.1fr;
+ gap: 10px;
+}
+
+.canary-operator-strip article {
+ display: grid;
+ gap: 7px;
+ min-width: 0;
+ padding: 12px;
+ border: 1px solid #c9d5ce;
+ border-radius: 8px;
+ background: #fbfcf8;
+ box-shadow: var(--shadow);
+}
+
+.canary-operator-strip span,
+.canary-operator-strip strong {
+ overflow-wrap: anywhere;
+}
+
+.canary-operator-strip span {
+ color: var(--muted);
+ font-size: 0.72rem;
+ font-weight: 800;
+ text-transform: uppercase;
+}
+
+.canary-boundaries {
+ display: grid;
+ gap: 8px;
+ padding-top: 12px;
+}
+
+.canary-boundaries span {
+ padding: 9px 10px;
+ color: #4d564d;
+ border: 1px solid var(--line);
+ border-radius: 7px;
+ background: #f8f9f4;
+ line-height: 1.35;
+}
+
+.metric-tile,
+.panel,
+.table-panel,
+.rootfield-tile,
+.lane-tile,
+.node-tile,
+.alert-row,
+.json-panel {
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: var(--surface);
+ box-shadow: var(--shadow);
+}
+
+.metric-tile {
+ display: grid;
+ gap: 8px;
+ padding: 14px;
+}
+
+.metric-tile > span {
+ color: var(--muted);
+ font-size: 0.75rem;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.metric-tile strong {
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 1.65rem;
+}
+
+.metric-tile div {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 7px;
+ align-items: center;
+}
+
+.overview-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1.4fr) minmax(290px, 0.6fr);
+ gap: 14px;
+}
+
+.panel {
+ min-width: 0;
+ padding: 14px;
+}
+
+.panel-wide {
+ grid-column: span 1;
+}
+
+.panel-side-bottom {
+ grid-column: 2;
+}
+
+.panel-heading {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid var(--line);
+}
+
+.panel-heading div,
+.tile-heading,
+.record-title,
+.json-panel-header {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ min-width: 0;
+}
+
+.panel-heading h2 {
+ margin: 0;
+ font-size: 0.98rem;
+}
+
+.panel-heading > span {
+ color: var(--muted);
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.78rem;
+}
+
+.record-list,
+.compact-list,
+.alert-list {
+ display: grid;
+}
+
+.status-strip {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ padding: 10px 0 2px;
+}
+
+.status-strip span {
+ display: inline-flex;
+ gap: 6px;
+ align-items: center;
+ padding: 3px 6px 3px 3px;
+ border: 1px solid var(--line);
+ border-radius: 999px;
+ background: #f8f9f4;
+}
+
+.status-strip strong {
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.74rem;
+}
+
+.record-row {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 270px;
+ gap: 16px;
+ padding: 14px 0;
+ border-bottom: 1px solid var(--line);
+}
+
+.record-row:last-child {
+ border-bottom: 0;
+}
+
+.record-row p,
+.alert-row p {
+ margin: 8px 0;
+ color: #3f483f;
+ line-height: 1.45;
+}
+
+.record-facts,
+.definition-grid,
+.lane-stats,
+.alert-facts {
+ display: grid;
+ gap: 10px;
+ margin: 0;
+}
+
+.record-facts {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+}
+
+.record-facts div,
+.definition-grid div,
+.lane-stats div,
+.alert-facts div {
+ min-width: 0;
+}
+
+dt {
+ color: var(--subtle);
+ font-size: 0.68rem;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+dd {
+ margin: 3px 0 0;
+ overflow-wrap: anywhere;
+}
+
+.compact-list article {
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr);
+ gap: 9px;
+ align-items: start;
+ padding: 12px 0;
+ border-bottom: 1px solid var(--line);
+}
+
+.compact-list article:last-child {
+ border-bottom: 0;
+}
+
+.compact-list strong,
+.compact-list small {
+ display: block;
+ overflow-wrap: anywhere;
+}
+
+.contract-event-list {
+ display: grid;
+}
+
+.contract-event-list article {
+ display: grid;
+ gap: 9px;
+ padding: 12px 0;
+ border-bottom: 1px solid var(--line);
+}
+
+.contract-event-list article:last-child {
+ border-bottom: 0;
+}
+
+.contract-event-list article > div {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ min-width: 0;
+}
+
+.contract-event-list strong {
+ overflow-wrap: anywhere;
+ font-size: 0.84rem;
+}
+
+.contract-event-list dl {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 8px;
+ margin: 0;
+}
+
+.bundle-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 10px;
+ padding-top: 14px;
+}
+
+.bundle-grid article {
+ display: grid;
+ gap: 8px;
+ min-width: 0;
+ padding: 12px;
+ border: 1px solid var(--line);
+ border-radius: 7px;
+ background: #f7f8f4;
+}
+
+.bundle-grid strong,
+.bundle-grid small {
+ display: block;
+ overflow-wrap: anywhere;
+}
+
+.block-strip {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 10px;
+ padding-top: 14px;
+}
+
+.block-strip article {
+ display: grid;
+ gap: 7px;
+ padding: 12px;
+ border: 1px solid var(--line);
+ border-radius: 7px;
+ background: #f7f8f4;
+}
+
+.workbench-command-center {
+ display: grid;
+ grid-template-columns: minmax(0, 1.08fr) minmax(360px, 0.92fr);
+ gap: 14px;
+}
+
+.workbench-boundary-strip {
+ display: grid;
+ grid-template-columns: 1.05fr 1.15fr 1.2fr;
+ gap: 10px;
+}
+
+.workbench-boundary-strip article {
+ display: grid;
+ gap: 6px;
+ min-width: 0;
+ padding: 12px;
+ border: 1px solid #cfd7cd;
+ border-radius: 8px;
+ background: #f9faf6;
+}
+
+.workbench-boundary-strip strong {
+ font-size: 0.82rem;
+}
+
+.workbench-boundary-strip span {
+ color: #4c554c;
+ font-size: 0.84rem;
+ line-height: 1.42;
+}
+
+.pilot-status-panel article {
+ display: grid;
+ gap: 14px;
+ min-width: 0;
+ padding: 14px;
+ border: 1px solid #b8c7c0;
+ border-radius: 8px;
+ background: #f4f8f6;
+}
+
+.pilot-status-body {
+ display: grid;
+ grid-template-columns: minmax(0, 0.92fr) minmax(320px, 1.08fr);
+ gap: 14px;
+ align-items: start;
+}
+
+.pilot-status-body h3 {
+ margin: 4px 0 8px;
+ font-size: 1.35rem;
+ text-transform: capitalize;
+}
+
+.pilot-status-body p {
+ margin: 0;
+ color: #3f483f;
+ line-height: 1.48;
+}
+
+.product-surface-grid {
+ display: grid;
+ grid-template-columns: 1.2fr 1fr 1.2fr;
+ gap: 10px;
+}
+
+.product-surface {
+ display: grid;
+ grid-template-columns: 34px minmax(0, 1fr) auto auto;
+ gap: 10px;
+ align-items: start;
+ min-width: 0;
+ min-height: 118px;
+ padding: 12px;
+ text-align: left;
+ transition: background 140ms ease, border-color 140ms ease, transform 140ms ease;
+}
+
+.product-surface:hover {
+ border-color: #9fb4a8;
+ background: #eaf1eb;
+}
+
+.product-surface-icon {
+ display: grid;
+ place-items: center;
+ width: 32px;
+ height: 32px;
+ color: #235f52;
+ border: 1px solid #bfd1c7;
+ border-radius: 7px;
+ background: #edf5ef;
+}
+
+.product-surface strong,
+.product-surface small,
+.product-surface code {
+ display: block;
+ overflow-wrap: anywhere;
+}
+
+.product-surface small {
+ margin-top: 4px;
+ line-height: 1.36;
+}
+
+.product-surface b {
+ color: var(--ink);
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 1.1rem;
+ line-height: 1.1;
+}
+
+.workbench-node-body {
+ display: grid;
+ gap: 16px;
+ padding-top: 14px;
+}
+
+.workbench-node-body h3 {
+ margin: 0 0 7px;
+ font-size: 1.22rem;
+}
+
+.workbench-node-body p,
+.workbench-section-detail,
+.boundary-copy p {
+ margin: 0;
+ color: #465047;
+ line-height: 1.48;
+}
+
+.workbench-fact-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 10px;
+ margin: 0;
+}
+
+.workbench-fact-grid div {
+ min-width: 0;
+ padding: 10px;
+ border: 1px solid var(--line);
+ border-radius: 7px;
+ background: #f7f8f4;
+}
+
+.setup-step-list {
+ display: grid;
+ padding-top: 6px;
+}
+
+.setup-step-list article {
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr);
+ gap: 9px;
+ align-items: start;
+ padding: 11px 0;
+ border-bottom: 1px solid var(--line);
+}
+
+.setup-step-list .status-badge {
+ align-self: start;
+}
+
+.setup-step-list article:last-child {
+ border-bottom: 0;
+}
+
+.setup-step-list strong,
+.setup-step-list code,
+.setup-step-list small {
+ display: block;
+ overflow-wrap: anywhere;
+}
+
+code {
+ width: fit-content;
+ max-width: 100%;
+ margin: 5px 0;
+ padding: 3px 6px;
+ color: #25332d;
+ border: 1px solid var(--line);
+ border-radius: 6px;
+ background: #eef1eb;
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.78rem;
+}
+
+.workbench-warning {
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr);
+ gap: 9px;
+ align-items: start;
+ padding: 11px 12px;
+ color: #6b4a16;
+ border: 1px solid #d9c393;
+ border-radius: 8px;
+ background: #fbf6e8;
+}
+
+.workbench-warning strong,
+.workbench-warning span {
+ display: block;
+ overflow-wrap: anywhere;
+}
+
+.workbench-api-panel {
+ display: grid;
+ gap: 12px;
+}
+
+.endpoint-strip {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 7px;
+}
+
+.endpoint-strip span {
+ padding: 4px 7px;
+ color: #34443c;
+ border: 1px solid var(--line);
+ border-radius: 999px;
+ background: #f7f8f4;
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.72rem;
+}
+
+.local-action-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 10px;
+}
+
+.local-action-grid article {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 10px;
+ align-items: center;
+ min-width: 0;
+ padding: 11px;
+ border: 1px solid var(--line);
+ border-radius: 7px;
+ background: #f7f8f4;
+}
+
+.local-action-grid strong,
+.local-action-grid small {
+ display: block;
+ overflow-wrap: anywhere;
+}
+
+.action-result,
+.boot-hint {
+ margin: 0;
+ color: var(--muted);
+ line-height: 1.45;
+}
+
+.workbench-layout {
+ display: grid;
+ grid-template-columns: 222px minmax(0, 1fr);
+ gap: 14px;
+ align-items: start;
+}
+
+.workbench-switcher {
+ position: sticky;
+ top: 88px;
+ display: grid;
+ gap: 7px;
+ max-height: calc(100dvh - 112px);
+ overflow: auto;
+}
+
+.workbench-switch {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 10px;
+ align-items: center;
+ min-height: 38px;
+ padding: 8px 10px;
+ text-align: left;
+ transition: background 140ms ease, border-color 140ms ease, transform 140ms ease;
+}
+
+.workbench-switch.active,
+.workbench-switch:hover {
+ border-color: #9fb4a8;
+ background: #eaf1eb;
+}
+
+.workbench-switch span {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.workbench-switch strong {
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.78rem;
+}
+
+.workbench-record-panel {
+ display: grid;
+ gap: 14px;
+}
+
+.workbench-record-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 10px;
+}
+
+.workbench-record {
+ display: grid;
+ gap: 11px;
+ min-width: 0;
+ padding: 13px;
+ border: 1px solid var(--line);
+ border-radius: 7px;
+ background: #f7f8f4;
+}
+
+.workbench-record h3 {
+ min-width: 0;
+ margin: 0;
+ overflow-wrap: anywhere;
+ font-size: 0.96rem;
+}
+
+.workbench-record p {
+ margin: 0;
+ color: #465047;
+ line-height: 1.44;
+}
+
+.workbench-boundary-panel {
+ display: grid;
+ gap: 12px;
+}
+
+.boundary-copy {
+ display: grid;
+ gap: 9px;
+}
+
+.table-panel {
+ min-width: 0;
+ overflow: hidden;
+}
+
+.table-scroll {
+ overflow-x: auto;
+}
+
+.table-scroll table {
+ min-width: 1120px;
+}
+
+.cell-stack {
+ display: grid;
+ gap: 4px;
+ min-width: 0;
+}
+
+.hash-value {
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.82rem;
+ overflow-wrap: anywhere;
+}
+
+.provenance-line {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 5px;
+}
+
+.rootfield-grid,
+.hardware-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 14px;
+}
+
+.rootfield-tile,
+.node-tile,
+.lane-tile {
+ display: grid;
+ gap: 14px;
+ padding: 14px;
+}
+
+.definition-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+}
+
+.lane-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.lane-stats {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+}
+
+.alert-list {
+ gap: 12px;
+}
+
+.alert-row {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 300px;
+ gap: 16px;
+ padding: 14px;
+ border-left-width: 4px;
+}
+
+.severity-critical {
+ border-left-color: var(--danger);
+}
+
+.severity-warning {
+ border-left-color: var(--warning);
+}
+
+.severity-info {
+ border-left-color: var(--info);
+}
+
+.alert-action {
+ padding: 12px;
+ border-left: 1px solid var(--line);
+}
+
+.alert-action span {
+ color: var(--subtle);
+ font-size: 0.7rem;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.alert-action p {
+ margin-bottom: 0;
+}
+
+.json-panel {
+ overflow: hidden;
+}
+
+.json-panel-header {
+ justify-content: space-between;
+ padding: 12px 14px;
+ border-bottom: 1px solid var(--line);
+}
+
+.json-panel pre {
+ max-height: 68dvh;
+ margin: 0;
+ padding: 16px;
+ overflow: auto;
+ background: #1f241f;
+ color: #eef4ec;
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.78rem;
+ line-height: 1.5;
+}
+
+.status-badge {
+ display: inline-flex;
+ gap: 6px;
+ align-items: center;
+ width: fit-content;
+ max-width: 100%;
+ min-height: 24px;
+ padding: 4px 8px;
+ border: 1px solid var(--status-border);
+ border-radius: 999px;
+ background: var(--status-bg);
+ color: var(--status-fg);
+ font-size: 0.72rem;
+ font-weight: 800;
+ text-transform: uppercase;
+ white-space: nowrap;
+}
+
+.status-dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 999px;
+ background: currentColor;
+}
+
+.status-observed {
+ --status-bg: #e7f0f3;
+ --status-border: #b9d0d8;
+ --status-fg: #326b89;
+}
+
+.status-pending {
+ --status-bg: #f5eddb;
+ --status-border: #d9c393;
+ --status-fg: #8a5e14;
+}
+
+.status-finalized {
+ --status-bg: #e9eee8;
+ --status-border: #bdcabc;
+ --status-fg: #3e6541;
+}
+
+.status-verified {
+ --status-bg: #e2f0e8;
+ --status-border: #a8ceb7;
+ --status-fg: #1f6a45;
+}
+
+.status-unresolved {
+ --status-bg: #f3e7dc;
+ --status-border: #d4b495;
+ --status-fg: #8c5527;
+}
+
+.status-failed {
+ --status-bg: #f5e2e2;
+ --status-border: #d4a0a0;
+ --status-fg: #963b3b;
+}
+
+.status-unsupported {
+ --status-bg: #ece7f1;
+ --status-border: #c1b6d0;
+ --status-fg: #665283;
+}
+
+.status-reorged {
+ --status-bg: #eee6dd;
+ --status-border: #c6b19e;
+ --status-fg: #714f35;
+}
+
+.status-offline {
+ --status-bg: #e7e8e3;
+ --status-border: #b9beb5;
+ --status-fg: #555d55;
+}
+
+.status-stale {
+ --status-bg: #f0ecdc;
+ --status-border: #cfc38c;
+ --status-fg: #766a23;
+}
+
+.empty-state {
+ display: flex;
+ gap: 12px;
+ align-items: flex-start;
+ padding: 22px;
+ color: var(--muted);
+ border: 1px dashed var(--line-strong);
+ border-radius: 8px;
+ background: #f8f9f4;
+}
+
+.empty-state h3 {
+ margin: 0 0 5px;
+ color: var(--ink);
+ font-size: 1rem;
+}
+
+.empty-state p {
+ margin: 0;
+ line-height: 1.45;
+}
+
+.boot-screen {
+ display: grid;
+ place-items: center;
+ min-height: 100dvh;
+ padding: 24px;
+ background: #f4f5f1;
+}
+
+.boot-panel,
+.error-panel {
+ width: min(620px, 100%);
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: var(--surface);
+ box-shadow: var(--shadow);
+}
+
+.boot-panel {
+ display: grid;
+ gap: 14px;
+ padding: 24px;
+}
+
+.skeleton-line {
+ height: 14px;
+ border-radius: 999px;
+ background: linear-gradient(90deg, #e1e6de 0%, #f5f6f0 45%, #e1e6de 100%);
+ background-size: 220% 100%;
+ animation: shimmer 1.4s ease-in-out infinite;
+}
+
+.skeleton-title {
+ width: 70%;
+ height: 24px;
+}
+
+.skeleton-short {
+ width: 46%;
+}
+
+.boot-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 10px;
+ margin-top: 8px;
+}
+
+.boot-grid div {
+ height: 92px;
+ border-radius: 8px;
+ background: #edf0e9;
+}
+
+.error-panel {
+ display: grid;
+ grid-template-columns: 42px 1fr;
+ gap: 14px;
+ padding: 22px;
+ color: #873a3a;
+}
+
+.error-panel h1 {
+ margin: 0 0 8px;
+ color: var(--ink);
+ font-size: 1.25rem;
+}
+
+.error-panel p {
+ margin: 0 0 14px;
+ color: #6f4c4c;
+}
+
+.button {
+ display: inline-flex;
+ gap: 8px;
+ align-items: center;
+ min-height: 36px;
+ padding: 0 12px;
+}
+
+.button-primary {
+ border-color: #1f6a45;
+ background: #2f7d6b;
+ color: #f9fcf7;
+}
+
+.wallet-view {
+ display: grid;
+ gap: 18px;
+}
+
+.wallet-heading {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+ gap: 16px;
+}
+
+.wallet-heading h1,
+.wallet-heading p,
+.wallet-panel-title h2 {
+ margin: 0;
+}
+
+.wallet-heading p {
+ color: var(--muted);
+}
+
+.wallet-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1.45fr) minmax(320px, 0.75fr);
+ gap: 18px;
+ align-items: start;
+}
+
+.wallet-primary-panel,
+.wallet-create-panel {
+ display: grid;
+ gap: 18px;
+ padding: 18px;
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: var(--surface);
+ box-shadow: var(--shadow);
+}
+
+.wallet-account-topline {
+ display: grid;
+ grid-template-columns: 52px minmax(0, 1fr);
+ gap: 14px;
+ align-items: center;
+}
+
+.wallet-account-icon {
+ display: grid;
+ place-items: center;
+ width: 52px;
+ height: 52px;
+ border: 1px solid #a7b8b1;
+ border-radius: 8px;
+ background: #e3eee8;
+ color: var(--accent-strong);
+}
+
+.wallet-account-topline span,
+.wallet-form-field span,
+.wallet-toggle-row span,
+.wallet-fact-grid dt {
+ color: var(--muted);
+ font-size: 0.75rem;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.wallet-account-topline strong {
+ display: block;
+ margin-top: 4px;
+ overflow-wrap: anywhere;
+ font-size: clamp(1.25rem, 2vw, 1.9rem);
+ letter-spacing: 0;
+}
+
+.wallet-fact-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 10px;
+ margin: 0;
+}
+
+.wallet-fact-grid div {
+ min-width: 0;
+ padding: 12px;
+ border: 1px solid var(--line);
+ border-radius: 7px;
+ background: var(--surface-2);
+}
+
+.wallet-fact-grid dd {
+ margin: 6px 0 0;
+ overflow-wrap: anywhere;
+ color: var(--ink);
+ font-size: 0.86rem;
+}
+
+.wallet-action-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.wallet-action-row a {
+ text-decoration: none;
+}
+
+.wallet-panel-title {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+}
+
+.wallet-form-field {
+ display: grid;
+ gap: 8px;
+}
+
+.wallet-form-field input {
+ min-height: 42px;
+ padding: 0 12px;
+ border: 1px solid var(--line-strong);
+ border-radius: 7px;
+ background: #ffffff;
+}
+
+.wallet-toggle-row {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+}
+
+.wallet-create-button {
+ justify-content: center;
+}
+
+.wallet-message {
+ display: flex;
+ gap: 9px;
+ align-items: center;
+ margin: 0;
+ padding: 12px 14px;
+ border: 1px solid #dfc184;
+ border-radius: 8px;
+ background: #fff7df;
+ color: #6e4a13;
+}
+
+@keyframes shimmer {
+ 0% {
+ background-position: 100% 0;
+ }
+ 100% {
+ background-position: -100% 0;
+ }
+}
+
+@media (max-width: 1180px) {
+ .metric-grid,
+ .block-strip {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .overview-grid,
+ .rootfield-grid,
+ .hardware-grid,
+ .lane-grid,
+ .canary-operator-strip,
+ .workbench-boundary-strip,
+ .product-surface-grid,
+ .pilot-status-body,
+ .local-action-grid,
+ .workbench-command-center,
+ .workbench-record-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .wallet-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .flowmemory-hero,
+ .canary-hero,
+ .flowmemory-spine,
+ .contract-event-list dl,
+ .bundle-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .flowmemory-hero-side,
+ .panel-side-bottom {
+ grid-column: auto;
+ }
+
+ .flowmemory-hero-side {
+ border-left: 0;
+ border-top: 1px solid var(--line);
+ }
+
+ .workbench-layout {
+ grid-template-columns: 1fr;
+ }
+
+ .workbench-switcher {
+ position: static;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+@media (max-width: 860px) {
+ .app-shell {
+ grid-template-columns: 1fr;
+ }
+
+ .sidebar {
+ position: static;
+ height: auto;
+ }
+
+ .nav-list {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+
+ .topbar,
+ .section-header,
+ .fixture-banner,
+ .record-row,
+ .alert-row {
+ display: grid;
+ grid-template-columns: 1fr;
+ }
+
+ .content {
+ padding: 18px 14px 28px;
+ }
+
+ .section-action,
+ .filter-row {
+ min-width: 0;
+ grid-template-columns: 1fr;
+ }
+
+ .fixture-banner {
+ margin: 14px 14px 0;
+ }
+
+ .alert-action {
+ border-left: 0;
+ border-top: 1px solid var(--line);
+ }
+
+ .workbench-fact-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 560px) {
+ .nav-list,
+ .metric-grid,
+ .block-strip,
+ .definition-grid,
+ .lane-stats,
+ .record-facts,
+ .workbench-switcher {
+ grid-template-columns: 1fr;
+ }
+
+ .topbar {
+ padding: 12px 14px;
+ }
+
+ th,
+ td {
+ padding: 10px;
+ }
+}
+
+/* Flowchain bridge reference layout */
+.flowchain-bridge-page {
+ min-height: 100dvh;
+ overflow-x: hidden;
+ overflow-y: auto;
+ background:
+ radial-gradient(circle at 38% 25%, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0) 34rem),
+ radial-gradient(circle at 8% 86%, rgba(255, 255, 255, 0.58), rgba(255, 255, 255, 0) 20rem),
+ linear-gradient(180deg, #f5ebdc 0%, #f0dfc7 100%);
+ color: #0d1b35;
+}
+
+.flowchain-bridge-page::before {
+ opacity: 0.36;
+ background-image:
+ linear-gradient(rgba(77, 56, 34, 0.035) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(77, 56, 34, 0.025) 1px, transparent 1px),
+ radial-gradient(circle at 28% 18%, rgba(107, 78, 42, 0.06), transparent 22rem);
+ background-size: 8px 8px, 10px 10px, auto;
+}
+
+.flowchain-bridge-page::after {
+ inset: auto -14vw -7dvh -14vw;
+ height: 32dvh;
+ opacity: 0.52;
+ background:
+ radial-gradient(ellipse at 18% 50%, rgba(1, 88, 224, 0.66), rgba(16, 112, 255, 0.26) 34%, transparent 70%),
+ radial-gradient(ellipse at 44% 40%, rgba(0, 91, 255, 0.9), rgba(0, 105, 255, 0.32) 36%, transparent 74%),
+ radial-gradient(ellipse at 79% 54%, rgba(1, 95, 236, 0.68), rgba(38, 132, 255, 0.22) 40%, transparent 76%);
+ filter: blur(14px) saturate(1.22);
+ transform: rotate(-4deg) skewX(-14deg);
+}
+
+.bridge-flow-ribbon {
+ inset: auto -12vw 5dvh -12vw;
+ height: 18dvh;
+ opacity: 0.58;
+ background:
+ linear-gradient(92deg, transparent 0%, rgba(0, 93, 255, 0.16) 10%, rgba(1, 83, 221, 0.74) 38%, rgba(44, 145, 255, 0.48) 72%, transparent 100%),
+ radial-gradient(ellipse at 36% 40%, rgba(1, 79, 220, 0.88), rgba(36, 130, 255, 0.26) 48%, transparent 72%);
+ filter: blur(9px);
+ transform: rotate(-8deg) skewX(-18deg);
+}
+
+.flowchain-bridge-nav {
+ align-items: flex-start;
+ width: min(100%, 1700px);
+ padding: 24px 66px 0;
+}
+
+.flowchain-brand {
+ gap: 14px;
+ color: #071831;
+}
+
+.flowchain-brand strong {
+ color: #071831;
+ font-size: clamp(1.75rem, 2.3vw, 2.35rem);
+ letter-spacing: 0;
+}
+
+.flowchain-brand-mark {
+ width: 48px;
+ height: 38px;
+}
+
+.flowchain-brand-mark::before,
+.flowchain-brand-mark::after,
+.flowchain-brand-mark span {
+ width: 44px;
+ height: 15px;
+ border-color: #1b6fff;
+ border-width: 5px 0 0;
+}
+
+.flowchain-brand-mark::before {
+ top: 2px;
+ transform: rotate(-10deg);
+}
+
+.flowchain-brand-mark span {
+ top: 13px;
+ width: 36px;
+ border-color: #246de0;
+ transform: rotate(-12deg);
+}
+
+.flowchain-brand-mark::after {
+ top: 24px;
+ width: 28px;
+ border-color: #73adff;
+ transform: rotate(-12deg);
+}
+
+.bridge-system-rail {
+ display: grid;
+ grid-template-columns: minmax(190px, 1fr) minmax(185px, 1fr) minmax(210px, 1.08fr) 48px;
+ align-items: stretch;
+ width: min(730px, 100%);
+ min-height: 78px;
+ border: 1px solid rgba(70, 48, 25, 0.14);
+ border-radius: 10px;
+ background: rgba(249, 240, 227, 0.8);
+ box-shadow: 0 18px 45px rgba(48, 32, 14, 0.14), inset 0 1px 0 rgba(255, 255, 255, 0.58);
+ backdrop-filter: blur(18px);
+}
+
+.bridge-system-item,
+.bridge-system-info {
+ min-width: 0;
+ border: 0;
+ background: transparent;
+ color: #162138;
+}
+
+.bridge-system-item {
+ display: grid;
+ grid-template-columns: 42px minmax(0, 1fr) 10px;
+ gap: 12px;
+ align-items: center;
+ padding: 15px 20px;
+ text-align: left;
+ border-right: 1px solid rgba(85, 64, 39, 0.12);
+}
+
+.bridge-system-wallet {
+ cursor: pointer;
+}
+
+.bridge-system-item strong,
+.bridge-system-item small {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.bridge-system-item strong {
+ font-size: 0.88rem;
+ font-weight: 750;
+}
+
+.bridge-system-item small {
+ margin-top: 4px;
+ color: rgba(22, 33, 56, 0.62);
+ font-size: 0.82rem;
+}
+
+.bridge-system-icon {
+ display: grid;
+ place-items: center;
+ width: 40px;
+ height: 40px;
+ color: #171a21;
+ border-radius: 50%;
+ background: rgba(73, 54, 34, 0.08);
+}
+
+.bridge-system-item i {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+}
+
+.bridge-system-item i.is-green {
+ background: #1a9c68;
+}
+
+.bridge-system-item i.is-amber {
+ background: #d79916;
+}
+
+.bridge-system-item i.is-red {
+ background: #b74638;
+}
+
+.bridge-system-info {
+ display: grid;
+ place-items: center;
+ text-decoration: none;
+}
+
+.flowchain-bridge-main {
+ display: grid;
+ grid-template-columns: minmax(420px, 660px) minmax(650px, 820px);
+ gap: clamp(48px, 6vw, 96px);
+ align-items: start;
+ width: min(100%, 1700px);
+ min-height: calc(100dvh - 104px);
+ padding: 16px 66px 38px;
+}
+
+.bridge-title-block {
+ justify-items: start;
+ align-self: start;
+ max-width: 620px;
+ padding-bottom: 80px;
+ text-align: left;
+}
+
+.bridge-title-block > span {
+ display: inline-flex;
+ min-height: 28px;
+ align-items: center;
+ padding: 0 15px;
+ border-radius: 999px;
+ background: rgba(101, 76, 45, 0.08);
+ color: rgba(31, 25, 20, 0.72);
+ font-size: 0.75rem;
+ font-weight: 800;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+}
+
+.bridge-title-block h1 {
+ max-width: 610px;
+ margin: 28px 0 18px;
+ color: #081a36;
+ font-size: clamp(4.2rem, 6.25vw, 6.25rem);
+ font-weight: 500;
+ line-height: 0.88;
+ letter-spacing: 0;
+}
+
+.bridge-title-block p {
+ width: min(520px, 100%);
+ max-width: 520px;
+ color: rgba(31, 27, 24, 0.82);
+ font-family: Geist, Satoshi, "Segoe UI", system-ui, sans-serif;
+ font-size: clamp(1.24rem, 1.55vw, 1.55rem);
+ line-height: 1.36;
+}
+
+.bridge-benefit-list {
+ display: grid;
+ gap: 26px;
+ margin-top: 38px;
+}
+
+.bridge-benefit-list article {
+ display: grid;
+ grid-template-columns: 50px minmax(0, 1fr);
+ gap: 16px;
+ align-items: center;
+}
+
+.bridge-benefit-list article > span {
+ display: grid;
+ place-items: center;
+ width: 44px;
+ height: 44px;
+ color: #1d1c18;
+ border-radius: 50%;
+ background: rgba(57, 41, 23, 0.12);
+}
+
+.bridge-benefit-list strong,
+.bridge-benefit-list small {
+ display: block;
+}
+
+.bridge-benefit-list strong {
+ color: #111923;
+ font-size: 0.95rem;
+}
+
+.bridge-benefit-list small {
+ margin-top: 4px;
+ color: rgba(33, 28, 23, 0.72);
+ font-size: 0.85rem;
+ line-height: 1.35;
+}
+
+.flowchain-bridge-layout {
+ display: block;
+ width: 100%;
+}
+
+.bridge-console {
+ width: 100%;
+ min-width: 0;
+ gap: 12px;
+ padding: 26px 32px 22px;
+ border: 1px solid rgba(72, 50, 27, 0.16);
+ border-radius: 14px;
+ background: rgba(250, 241, 229, 0.82);
+ box-shadow: 0 28px 78px rgba(46, 31, 15, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.62);
+}
+
+.bridge-route-row {
+ grid-template-columns: minmax(0, 1fr) 52px minmax(0, 1fr);
+ gap: 24px;
+ align-items: center;
+}
+
+.bridge-route-row label > span,
+.bridge-field > label,
+.bridge-label-row label,
+.bridge-estimate-grid span,
+.bridge-calldata-facts dt {
+ color: rgba(42, 32, 22, 0.78);
+ font-size: 0.72rem;
+ font-weight: 800;
+ letter-spacing: 0.12em;
+}
+
+.bridge-route-row label > small {
+ display: block;
+ margin: 5px 0 8px;
+ color: rgba(42, 32, 22, 0.7);
+ font-size: 0.9rem;
+}
+
+.bridge-route-connector {
+ display: grid;
+ place-items: center;
+ padding: 30px 0 0;
+ color: #101820;
+}
+
+.bridge-route-connector span,
+.bridge-route-connector button {
+ display: none;
+}
+
+.bridge-select-shell,
+.bridge-token-field > div,
+.bridge-field {
+ border: 1px solid rgba(83, 61, 37, 0.18);
+ border-radius: 8px;
+ background: rgba(255, 250, 243, 0.58);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
+}
+
+.bridge-select-shell {
+ grid-template-columns: 36px minmax(0, 1fr) 18px;
+ min-height: 56px;
+ padding: 0 16px;
+}
+
+.bridge-select-shell strong,
+.bridge-token-field strong {
+ font-family: Geist, Satoshi, "Segoe UI", system-ui, sans-serif;
+ font-size: 1.02rem;
+ font-weight: 700;
+}
+
+.bridge-chain-icon,
+.bridge-token-orb {
+ width: 32px;
+ height: 32px;
+}
+
+.bridge-token-amount-row {
+ display: grid;
+ grid-template-columns: minmax(220px, 0.78fr) minmax(300px, 1.42fr);
+ gap: 30px;
+}
+
+.bridge-field {
+ padding: 0;
+ background: transparent;
+ border: 0;
+ box-shadow: none;
+}
+
+.bridge-token-field > div {
+ grid-template-columns: 36px auto minmax(0, 1fr) 18px;
+ min-height: 56px;
+ padding: 0 14px;
+}
+
+.bridge-token-field small {
+ color: rgba(43, 34, 26, 0.64);
+ font-size: 0.82rem;
+}
+
+.bridge-amount-field {
+ grid-template-columns: minmax(0, 1fr) auto;
+ align-items: center;
+ min-height: 56px;
+ padding: 10px 14px;
+ border: 1px solid rgba(83, 61, 37, 0.18);
+ border-radius: 8px;
+ background: rgba(255, 250, 243, 0.58);
+}
+
+.bridge-amount-field label {
+ grid-column: 1 / -1;
+ margin: 0;
+}
+
+.bridge-amount-field input {
+ min-height: 30px;
+ font-family: Geist, Satoshi, "Segoe UI", system-ui, sans-serif;
+ font-size: 1.55rem;
+ font-weight: 600;
+}
+
+.bridge-amount-field > span {
+ font-family: Geist, Satoshi, "Segoe UI", system-ui, sans-serif;
+ font-size: 0.95rem;
+ font-weight: 800;
+}
+
+.bridge-amount-field small {
+ grid-column: 1;
+ color: rgba(43, 34, 26, 0.62);
+ font-size: 0.82rem;
+}
+
+.bridge-amount-field .bridge-usd-note {
+ color: #0b62df;
+ font-weight: 750;
+}
+
+.bridge-amount-field button {
+ grid-column: 2;
+ justify-self: end;
+ border: 0;
+ background: transparent;
+ color: #125bd8;
+ font-size: 0.82rem;
+ font-weight: 800;
+}
+
+.bridge-recipient-field {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 34px;
+ gap: 8px;
+ align-items: center;
+ min-height: 56px;
+ padding: 10px 14px;
+ border: 1px solid rgba(83, 61, 37, 0.18);
+ border-radius: 8px;
+ background: rgba(255, 250, 243, 0.58);
+}
+
+.bridge-label-row {
+ display: flex;
+ grid-column: 1 / -1;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+}
+
+.bridge-label-row button {
+ display: inline-flex;
+ gap: 6px;
+ align-items: center;
+ width: auto;
+ height: auto;
+ padding: 0;
+ border: 0;
+ background: transparent;
+ color: #125bd8;
+ font-size: 0.84rem;
+ white-space: nowrap;
+}
+
+.bridge-recipient-field input {
+ font-family: Geist, Satoshi, "Segoe UI", system-ui, sans-serif;
+ font-size: 1.02rem;
+}
+
+.bridge-recipient-field button[title="Copy recipient"] {
+ color: rgba(42, 32, 22, 0.52);
+}
+
+.bridge-recipient-field button[title="Copy recipient"]:disabled {
+ cursor: not-allowed;
+ opacity: 0.42;
+}
+
+.bridge-recipient-options {
+ display: flex;
+ grid-column: 1 / -1;
+ flex-wrap: wrap;
+ gap: 7px;
+}
+
+.bridge-recipient-options button {
+ display: inline-flex;
+ align-items: center;
+ width: auto;
+ min-height: 30px;
+ height: auto;
+ gap: 7px;
+ padding: 5px 8px;
+ border: 1px solid rgba(83, 61, 37, 0.16);
+ border-radius: 8px;
+ background: rgba(255, 255, 255, 0.62);
+ color: #2d231a;
+ font-size: 0.76rem;
+}
+
+.bridge-recipient-options span {
+ max-width: 150px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.bridge-recipient-options small {
+ color: rgba(45, 35, 26, 0.6);
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+}
+
+.bridge-estimate-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ overflow: hidden;
+ border: 1px solid rgba(83, 61, 37, 0.15);
+ border-radius: 8px;
+ background: rgba(235, 222, 205, 0.34);
+}
+
+.bridge-estimate-grid > div {
+ display: grid;
+ gap: 4px;
+ min-height: 54px;
+ padding: 9px 16px;
+ border-right: 1px solid rgba(83, 61, 37, 0.12);
+ border-bottom: 1px solid rgba(83, 61, 37, 0.12);
+}
+
+.bridge-estimate-grid > div:nth-child(2n) {
+ border-right: 0;
+}
+
+.bridge-estimate-grid > div:nth-last-child(-n + 2) {
+ border-bottom: 0;
+}
+
+.bridge-estimate-grid span {
+ display: inline-flex;
+ gap: 6px;
+ align-items: center;
+}
+
+.bridge-estimate-grid strong {
+ color: #151a21;
+ font-size: 1rem;
+ font-weight: 650;
+}
+
+.bridge-estimate-grid small {
+ color: rgba(33, 28, 23, 0.62);
+ font-size: 0.82rem;
+}
+
+.bridge-estimate-primary strong {
+ color: #0b62df;
+ font-size: 1.12rem;
+}
+
+.bridge-advanced {
+ border: 1px solid rgba(83, 61, 37, 0.16);
+ border-radius: 8px;
+ background: rgba(255, 250, 243, 0.5);
+}
+
+.bridge-advanced summary {
+ display: flex;
+ justify-content: space-between;
+ width: 100%;
+ min-height: 42px;
+ padding: 0 16px;
+ font-size: 0.95rem;
+}
+
+.bridge-advanced-body {
+ padding: 0 14px 12px;
+}
+
+.bridge-advanced[open] summary {
+ margin: 0;
+}
+
+.bridge-calldata-facts {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ padding: 0 14px 14px;
+}
+
+.bridge-action-row {
+ grid-template-columns: 1fr;
+}
+
+.bridge-primary-action {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ justify-content: center;
+ min-height: 54px;
+ font-family: ui-serif, Georgia, "Times New Roman", serif;
+ font-size: 1.26rem;
+}
+
+.bridge-terms-row {
+ display: grid;
+ grid-template-columns: 18px minmax(0, 1fr);
+ gap: 8px;
+ align-items: center;
+ color: rgba(35, 30, 25, 0.64);
+ font-size: 0.82rem;
+ line-height: 1.35;
+}
+
+.bridge-safety-rail {
+ grid-template-columns: minmax(0, 1fr) auto auto;
+ gap: 24px;
+ align-items: center;
+ margin: 4px 0 0;
+ padding-top: 10px;
+ border-top: 1px solid rgba(83, 61, 37, 0.14);
+ border-radius: 0;
+ background: transparent;
+}
+
+.bridge-safety-rail span,
+.bridge-safety-rail a {
+ display: inline-flex;
+ gap: 8px;
+ align-items: center;
+ min-width: 0;
+ padding: 0;
+ color: rgba(35, 30, 25, 0.64);
+ border: 0;
+ font-size: 0.82rem;
+ text-decoration: none;
+}
+
+.bridge-safety-rail a {
+ color: rgba(29, 24, 20, 0.82);
+}
+
+.bridge-validation,
+.bridge-status-message,
+.bridge-tx-link {
+ padding: 10px 12px;
+}
+
+@media (max-width: 1280px) {
+ .flowchain-bridge-nav {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 18px;
+ padding: 32px 28px 0;
+ }
+
+ .bridge-system-rail {
+ width: 100%;
+ }
+
+ .flowchain-bridge-main {
+ grid-template-columns: 1fr;
+ gap: 28px;
+ padding: 28px;
+ }
+
+ .bridge-title-block {
+ max-width: 760px;
+ padding-bottom: 0;
+ }
+}
+
+@media (max-width: 760px) {
+ .flowchain-bridge-nav,
+ .flowchain-bridge-main {
+ width: 100%;
+ max-width: 100vw;
+ padding-left: 16px;
+ padding-right: 16px;
+ }
+
+ .bridge-system-rail {
+ grid-template-columns: 1fr;
+ }
+
+ .bridge-system-item {
+ border-right: 0;
+ border-bottom: 1px solid rgba(85, 64, 39, 0.12);
+ }
+
+ .bridge-title-block h1 {
+ font-size: clamp(3.25rem, 16vw, 4.6rem);
+ }
+
+ .bridge-title-block {
+ width: calc(100vw - 32px) !important;
+ max-width: calc(100vw - 32px) !important;
+ overflow: hidden;
+ }
+
+ .bridge-title-block p {
+ width: calc(100vw - 32px) !important;
+ max-width: calc(100vw - 32px) !important;
+ font-size: 1.08rem;
+ overflow-wrap: break-word;
+ text-wrap: pretty;
+ }
+
+ .bridge-benefit-list {
+ gap: 16px;
+ }
+
+ .bridge-benefit-list article {
+ grid-template-columns: 50px minmax(0, 1fr);
+ }
+
+ .bridge-benefit-list small,
+ .bridge-benefit-list strong {
+ max-width: calc(100vw - 98px);
+ overflow-wrap: break-word;
+ }
+
+ .bridge-console {
+ padding: 18px;
+ }
+
+ .bridge-route-row,
+ .bridge-token-amount-row,
+ .bridge-estimate-grid,
+ .bridge-calldata-facts,
+ .bridge-safety-rail {
+ grid-template-columns: 1fr;
+ }
+
+ .bridge-route-connector {
+ padding: 0;
+ }
+
+ .bridge-estimate-grid > div,
+ .bridge-estimate-grid > div:nth-child(2n),
+ .bridge-estimate-grid > div:nth-last-child(-n + 2) {
+ border-right: 0;
+ border-bottom: 1px solid rgba(83, 61, 37, 0.12);
+ }
+
+ .bridge-estimate-grid > div:last-child {
+ border-bottom: 0;
+ }
+
+ .bridge-safety-rail {
+ gap: 12px;
+ }
+}
+
+.flowchain-mobile-wallet {
+ display: none;
+}
+
+@media (max-width: 760px) {
+ .flowchain-bridge-page {
+ min-height: 100dvh;
+ overflow-x: hidden;
+ overflow-y: auto;
+ background:
+ radial-gradient(circle at 50% 12%, rgba(255, 255, 255, 0.76), rgba(255, 255, 255, 0) 18rem),
+ linear-gradient(180deg, #f7eddc 0%, #f1e2cb 56%, #efe0c8 100%);
+ }
+
+ .flowchain-bridge-page::after,
+ .bridge-flow-ribbon {
+ display: none;
+ }
+
+ .flowchain-bridge-nav,
+ .flowchain-bridge-main {
+ display: none;
+ }
+
+ .flowchain-mobile-wallet {
+ position: relative;
+ z-index: 1;
+ display: block;
+ box-sizing: border-box;
+ width: 100vw;
+ max-width: min(390px, 100vw);
+ min-height: 100dvh;
+ margin: 0 auto;
+ padding: 12px 17px 96px;
+ color: #10203a;
+ font-family: Geist, Satoshi, "Segoe UI", system-ui, sans-serif;
+ }
+
+ .flowchain-mobile-wallet *,
+ .flowchain-mobile-wallet *::before,
+ .flowchain-mobile-wallet *::after {
+ box-sizing: border-box;
+ }
+
+ .flowchain-mobile-wallet::before {
+ position: fixed;
+ inset: 0;
+ z-index: -2;
+ pointer-events: none;
+ content: "";
+ opacity: 0.34;
+ background-image:
+ linear-gradient(rgba(86, 63, 38, 0.045) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(86, 63, 38, 0.026) 1px, transparent 1px),
+ radial-gradient(circle at 50% 8%, rgba(255, 255, 255, 0.76), transparent 15rem);
+ background-size: 8px 8px, 10px 10px, auto;
+ }
+
+ .mobile-wallet-statusbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 34px;
+ padding: 0 20px;
+ color: #131313;
+ font-size: 0.95rem;
+ font-weight: 800;
+ }
+
+ .mobile-wallet-statusbar > div {
+ display: inline-flex;
+ gap: 8px;
+ align-items: center;
+ }
+
+ .mobile-signal-bars {
+ width: 20px;
+ height: 13px;
+ background:
+ linear-gradient(#151515, #151515) 0 8px / 3px 5px no-repeat,
+ linear-gradient(#151515, #151515) 6px 5px / 3px 8px no-repeat,
+ linear-gradient(#151515, #151515) 12px 2px / 3px 11px no-repeat,
+ linear-gradient(#151515, #151515) 18px 0 / 3px 13px no-repeat;
+ border-radius: 2px;
+ }
+
+ .mobile-wifi-mark {
+ width: 19px;
+ height: 13px;
+ border-top: 4px solid #151515;
+ border-radius: 50% 50% 0 0;
+ transform: scaleX(1.08);
+ }
+
+ .mobile-battery-mark {
+ position: relative;
+ width: 25px;
+ height: 12px;
+ border: 2px solid #151515;
+ border-radius: 4px;
+ }
+
+ .mobile-battery-mark::before {
+ position: absolute;
+ top: 2px;
+ right: -5px;
+ width: 3px;
+ height: 5px;
+ content: "";
+ background: #151515;
+ border-radius: 0 2px 2px 0;
+ }
+
+ .mobile-battery-mark::after {
+ position: absolute;
+ inset: 2px 4px 2px 2px;
+ content: "";
+ background: #151515;
+ border-radius: 2px;
+ }
+
+ .mobile-wallet-header {
+ position: relative;
+ display: grid;
+ grid-template-columns: 42px minmax(0, 1fr) 42px;
+ align-items: center;
+ margin-top: 8px;
+ }
+
+ .mobile-wallet-brand {
+ display: inline-flex;
+ gap: 12px;
+ align-items: center;
+ justify-self: center;
+ color: #071831;
+ text-decoration: none;
+ }
+
+ .mobile-wallet-brand .flowchain-brand-mark {
+ width: 46px;
+ height: 34px;
+ }
+
+ .mobile-wallet-brand .flowchain-brand-mark::before,
+ .mobile-wallet-brand .flowchain-brand-mark::after,
+ .mobile-wallet-brand .flowchain-brand-mark span {
+ width: 42px;
+ height: 13px;
+ border-width: 5px 0 0;
+ }
+
+ .mobile-wallet-brand strong {
+ color: #071831;
+ font-family: ui-serif, Georgia, "Times New Roman", serif;
+ font-size: 2rem;
+ font-weight: 550;
+ line-height: 1;
+ }
+
+ .mobile-bell-button {
+ position: relative;
+ display: grid;
+ place-items: center;
+ justify-self: end;
+ width: 40px;
+ height: 40px;
+ padding: 0;
+ border: 0;
+ color: #101820;
+ background: transparent;
+ }
+
+ .mobile-bell-button i {
+ position: absolute;
+ top: 4px;
+ right: 4px;
+ width: 8px;
+ height: 8px;
+ background: #1f6df0;
+ border-radius: 50%;
+ }
+
+ .mobile-wallet-pills {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 1px minmax(0, 1.18fr);
+ gap: 13px;
+ align-items: center;
+ width: min(100%, 292px);
+ margin: 24px auto 0;
+ }
+
+ .mobile-wallet-pills > button {
+ display: inline-flex;
+ gap: 9px;
+ align-items: center;
+ justify-content: center;
+ min-height: 44px;
+ padding: 0 14px;
+ color: #2a251f;
+ border: 1px solid rgba(72, 50, 27, 0.14);
+ border-radius: 999px;
+ background: rgba(255, 250, 243, 0.52);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.62);
+ backdrop-filter: blur(14px);
+ font-size: 0.95rem;
+ white-space: nowrap;
+ }
+
+ .mobile-wallet-pills > i {
+ width: 1px;
+ height: 26px;
+ background: rgba(86, 63, 38, 0.18);
+ }
+
+ .mobile-blue-dot {
+ width: 12px;
+ height: 12px;
+ background: #1e6df0;
+ border-radius: 50%;
+ }
+
+ .mobile-wallet-orb {
+ width: 34px;
+ height: 34px;
+ margin-left: -6px;
+ background:
+ radial-gradient(circle at 34% 30%, rgba(255, 255, 255, 0.82), transparent 18%),
+ linear-gradient(135deg, #81c5ff 0%, #175fdf 70%, #0a318d 100%);
+ border-radius: 50%;
+ }
+
+ .mobile-portfolio-hero {
+ position: relative;
+ z-index: 2;
+ display: grid;
+ justify-items: center;
+ margin-top: 34px;
+ text-align: center;
+ }
+
+ .mobile-portfolio-hero p {
+ display: inline-flex;
+ gap: 8px;
+ align-items: center;
+ margin: 0 0 8px;
+ color: rgba(55, 48, 40, 0.64);
+ font-size: 0.79rem;
+ font-weight: 800;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ }
+
+ .mobile-portfolio-hero strong {
+ display: block;
+ width: 100%;
+ color: #24201d;
+ font-family: ui-serif, Georgia, "Times New Roman", serif;
+ font-size: clamp(2.9rem, 12.9vw, 3.8rem);
+ font-weight: 400;
+ line-height: 0.98;
+ text-align: center;
+ }
+
+ .mobile-portfolio-hero > span {
+ display: inline-flex;
+ gap: 14px;
+ align-items: center;
+ margin-top: 8px;
+ color: #4b4339;
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.98rem;
+ }
+
+ .mobile-portfolio-hero b,
+ .mobile-asset-row .is-up,
+ .mobile-activity-row:last-child small {
+ color: #20936b;
+ }
+
+ .mobile-flow-smoke {
+ position: absolute;
+ top: 276px;
+ right: -78px;
+ left: -86px;
+ z-index: 1;
+ height: 182px;
+ pointer-events: none;
+ opacity: 0.48;
+ background:
+ radial-gradient(ellipse at 4% 52%, transparent 0 21%, rgba(15, 101, 240, 0.34) 36%, transparent 62%),
+ radial-gradient(ellipse at 38% 54%, rgba(28, 111, 239, 0.18), rgba(14, 98, 224, 0.42) 33%, transparent 67%),
+ radial-gradient(ellipse at 78% 48%, rgba(0, 82, 219, 0.56), rgba(61, 153, 255, 0.18) 38%, transparent 72%);
+ filter: blur(12px) saturate(1.16);
+ transform: rotate(-6deg) skewX(-13deg);
+ }
+
+ .mobile-flow-smoke::before,
+ .mobile-flow-smoke::after {
+ position: absolute;
+ inset: 42px -20px auto;
+ height: 66px;
+ content: "";
+ background:
+ linear-gradient(90deg, transparent, rgba(34, 121, 255, 0.38), rgba(0, 82, 219, 0.8), rgba(84, 170, 255, 0.22), transparent);
+ filter: blur(5px);
+ border-radius: 50%;
+ }
+
+ .mobile-flow-smoke::after {
+ inset: 96px -34px auto;
+ height: 48px;
+ opacity: 0.64;
+ transform: rotate(8deg);
+ }
+
+ .mobile-chart-card,
+ .mobile-action-card,
+ .mobile-assets-card,
+ .mobile-activity-card {
+ position: relative;
+ z-index: 2;
+ border: 1px solid rgba(82, 61, 37, 0.18);
+ background: rgba(249, 241, 229, 0.62);
+ box-shadow: 0 18px 48px rgba(53, 35, 18, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.62);
+ backdrop-filter: blur(14px);
+ }
+
+ .mobile-chart-card {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 42px;
+ gap: 8px;
+ margin-top: 68px;
+ padding: 24px 16px 14px;
+ border-radius: 24px;
+ }
+
+ .mobile-chart-card svg {
+ width: 100%;
+ min-height: 118px;
+ overflow: visible;
+ }
+
+ .mobile-chart-grid-line {
+ stroke: rgba(88, 66, 41, 0.1);
+ stroke-width: 1;
+ }
+
+ .mobile-chart-line {
+ fill: none;
+ stroke: url("#mobileChartGlow");
+ stroke-linecap: round;
+ stroke-width: 3;
+ }
+
+ .mobile-chart-card circle {
+ fill: #1e62d9;
+ stroke: #f7eddc;
+ stroke-width: 2;
+ }
+
+ .mobile-chart-scale {
+ display: grid;
+ align-content: space-between;
+ padding: 4px 0 28px;
+ color: rgba(69, 58, 45, 0.56);
+ font-size: 0.8rem;
+ font-weight: 700;
+ }
+
+ .mobile-chart-tabs {
+ display: grid;
+ grid-column: 1 / -1;
+ grid-template-columns: repeat(6, minmax(0, 1fr));
+ gap: 4px;
+ min-height: 36px;
+ margin-top: -3px;
+ }
+
+ .mobile-chart-tabs button {
+ border: 0;
+ color: rgba(67, 59, 50, 0.58);
+ background: transparent;
+ font-size: 0.78rem;
+ font-weight: 750;
+ }
+
+ .mobile-chart-tabs button[aria-current="true"] {
+ color: #22201d;
+ border: 1px solid rgba(91, 67, 40, 0.09);
+ border-radius: 999px;
+ background: rgba(234, 222, 206, 0.7);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
+ }
+
+ .mobile-action-card {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ margin-top: 24px;
+ overflow: hidden;
+ border-radius: 22px;
+ }
+
+ .mobile-action-card button {
+ display: grid;
+ justify-items: center;
+ min-width: 0;
+ min-height: 104px;
+ padding: 16px 5px 14px;
+ border: 0;
+ border-right: 1px solid rgba(82, 61, 37, 0.12);
+ color: #241f1a;
+ background: transparent;
+ }
+
+ .mobile-action-card button:last-child {
+ border-right: 0;
+ }
+
+ .mobile-action-card button > span {
+ display: grid;
+ place-items: center;
+ width: 47px;
+ height: 47px;
+ margin-bottom: 8px;
+ color: #145de0;
+ border: 1px solid rgba(30, 100, 215, 0.15);
+ border-radius: 50%;
+ background: rgba(219, 228, 242, 0.82);
+ }
+
+ .mobile-action-card strong,
+ .mobile-action-card small {
+ display: block;
+ max-width: 100%;
+ overflow: hidden;
+ text-align: center;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .mobile-action-card strong {
+ color: #221d18;
+ font-family: ui-serif, Georgia, "Times New Roman", serif;
+ font-size: 1.16rem;
+ font-weight: 520;
+ }
-.status-finalized {
- --status-bg: #e9eee8;
- --status-border: #bdcabc;
- --status-fg: #3e6541;
-}
+ .mobile-action-card small {
+ margin-top: 3px;
+ color: rgba(65, 55, 45, 0.55);
+ font-size: 0.72rem;
+ }
-.status-verified {
- --status-bg: #e2f0e8;
- --status-border: #a8ceb7;
- --status-fg: #1f6a45;
-}
+ .mobile-assets-card,
+ .mobile-activity-card {
+ margin-top: 24px;
+ overflow: hidden;
+ border-radius: 23px;
+ }
-.status-unresolved {
- --status-bg: #f3e7dc;
- --status-border: #d4b495;
- --status-fg: #8c5527;
-}
+ .mobile-section-header {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 92px 74px;
+ gap: 8px;
+ padding: 14px 14px 10px;
+ color: rgba(62, 53, 44, 0.58);
+ border-bottom: 1px solid rgba(82, 61, 37, 0.13);
+ font-size: 0.72rem;
+ font-weight: 800;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ }
-.status-failed {
- --status-bg: #f5e2e2;
- --status-border: #d4a0a0;
- --status-fg: #963b3b;
-}
+ .mobile-section-header span:nth-child(2),
+ .mobile-section-header span:nth-child(3) {
+ text-align: right;
+ }
-.status-unsupported {
- --status-bg: #ece7f1;
- --status-border: #c1b6d0;
- --status-fg: #665283;
-}
+ .mobile-asset-row {
+ display: grid;
+ grid-template-columns: 48px minmax(0, 1fr) minmax(84px, 95px) minmax(62px, 76px) 16px;
+ gap: 8px;
+ align-items: center;
+ min-height: 74px;
+ padding: 10px 12px;
+ border-bottom: 1px solid rgba(82, 61, 37, 0.13);
+ }
-.status-reorged {
- --status-bg: #eee6dd;
- --status-border: #c6b19e;
- --status-fg: #714f35;
-}
+ .mobile-asset-row:last-of-type {
+ border-bottom: 0;
+ }
-.status-offline {
- --status-bg: #e7e8e3;
- --status-border: #b9beb5;
- --status-fg: #555d55;
-}
+ .mobile-asset-icon {
+ display: grid;
+ place-items: center;
+ width: 46px;
+ height: 46px;
+ color: #fff;
+ border-radius: 50%;
+ font-size: 1.05rem;
+ font-weight: 850;
+ }
-.status-stale {
- --status-bg: #f0ecdc;
- --status-border: #cfc38c;
- --status-fg: #766a23;
-}
+ .mobile-asset-icon.flow {
+ background: linear-gradient(135deg, #5da7ff, #1052cc 72%);
+ }
-.empty-state {
- display: flex;
- gap: 12px;
- align-items: flex-start;
- padding: 22px;
- color: var(--muted);
- border: 1px dashed var(--line-strong);
- border-radius: 8px;
- background: #f8f9f4;
-}
+ .mobile-asset-icon.eth {
+ background: linear-gradient(135deg, #b9caff, #5d81e7 76%);
+ }
-.empty-state h3 {
- margin: 0 0 5px;
- color: var(--ink);
- font-size: 1rem;
-}
+ .mobile-asset-icon.usdc {
+ background: linear-gradient(135deg, #62bdff, #2370cf 76%);
+ }
-.empty-state p {
- margin: 0;
- line-height: 1.45;
-}
+ .mobile-asset-icon.btc {
+ color: #f39920;
+ border: 3px solid #20253b;
+ background: #fff5e3;
+ }
-.boot-screen {
- display: grid;
- place-items: center;
- min-height: 100dvh;
- padding: 24px;
- background: #f4f5f1;
-}
+ .mobile-asset-row strong,
+ .mobile-asset-row small,
+ .mobile-activity-row strong,
+ .mobile-activity-row small {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
-.boot-panel,
-.error-panel {
- width: min(620px, 100%);
- border: 1px solid var(--line);
- border-radius: 8px;
- background: var(--surface);
- box-shadow: var(--shadow);
-}
+ .mobile-asset-row > div:nth-child(2) strong {
+ color: #202020;
+ font-size: 1.06rem;
+ letter-spacing: 0.12em;
+ }
-.boot-panel {
- display: grid;
- gap: 14px;
- padding: 24px;
-}
+ .mobile-asset-row > div:nth-child(2) small {
+ margin-top: 2px;
+ color: rgba(65, 55, 45, 0.56);
+ font-size: 0.8rem;
+ }
-.skeleton-line {
- height: 14px;
- border-radius: 999px;
- background: linear-gradient(90deg, #e1e6de 0%, #f5f6f0 45%, #e1e6de 100%);
- background-size: 220% 100%;
- animation: shimmer 1.4s ease-in-out infinite;
-}
+ .mobile-asset-row > div:nth-child(3) {
+ text-align: right;
+ }
-.skeleton-title {
- width: 70%;
- height: 24px;
-}
+ .mobile-asset-row > div:nth-child(3) strong {
+ color: #25221e;
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.86rem;
+ }
-.skeleton-short {
- width: 46%;
-}
+ .mobile-asset-row > div:nth-child(3) small {
+ margin-top: 3px;
+ color: rgba(65, 55, 45, 0.55);
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.72rem;
+ }
-.boot-grid {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 10px;
- margin-top: 8px;
-}
+ .mobile-asset-row b {
+ text-align: right;
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.78rem;
+ font-weight: 750;
+ white-space: nowrap;
+ }
-.boot-grid div {
- height: 92px;
- border-radius: 8px;
- background: #edf0e9;
-}
+ .mobile-asset-row .is-down {
+ color: #c74b46;
+ }
-.error-panel {
- display: grid;
- grid-template-columns: 42px 1fr;
- gap: 14px;
- padding: 22px;
- color: #873a3a;
-}
+ .mobile-asset-row svg {
+ color: rgba(65, 55, 45, 0.42);
+ }
-.error-panel h1 {
- margin: 0 0 8px;
- color: var(--ink);
- font-size: 1.25rem;
-}
+ .mobile-assets-link {
+ display: inline-flex;
+ gap: 8px;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ min-height: 52px;
+ border: 0;
+ color: #155ad8;
+ background: transparent;
+ font-size: 1rem;
+ font-weight: 650;
+ }
-.error-panel p {
- margin: 0 0 14px;
- color: #6f4c4c;
-}
+ .mobile-section-title {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ min-height: 46px;
+ padding: 0 15px;
+ border-bottom: 1px solid rgba(82, 61, 37, 0.13);
+ }
-.button {
- display: inline-flex;
- gap: 8px;
- align-items: center;
- min-height: 36px;
- padding: 0 12px;
-}
+ .mobile-section-title span,
+ .mobile-section-title button {
+ color: rgba(62, 53, 44, 0.62);
+ font-size: 0.72rem;
+ font-weight: 850;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ }
-.button-primary {
- border-color: #1f6a45;
- background: #2f7d6b;
- color: #f9fcf7;
-}
+ .mobile-section-title button {
+ border: 0;
+ color: #155ad8;
+ background: transparent;
+ }
-@keyframes shimmer {
- 0% {
- background-position: 100% 0;
+ .mobile-activity-row {
+ display: grid;
+ grid-template-columns: 48px minmax(0, 1fr) minmax(102px, auto);
+ gap: 13px;
+ align-items: center;
+ min-height: 72px;
+ padding: 10px 14px;
+ border-bottom: 1px solid rgba(82, 61, 37, 0.13);
}
- 100% {
- background-position: -100% 0;
+
+ .mobile-activity-row:last-child {
+ border-bottom: 0;
}
-}
-@media (max-width: 1180px) {
- .metric-grid,
- .block-strip {
- grid-template-columns: repeat(2, minmax(0, 1fr));
+ .mobile-activity-icon {
+ display: grid;
+ place-items: center;
+ width: 44px;
+ height: 44px;
+ border-radius: 50%;
}
- .overview-grid,
- .rootfield-grid,
- .hardware-grid,
- .lane-grid,
- .canary-operator-strip,
- .workbench-boundary-strip,
- .product-surface-grid,
- .pilot-status-body,
- .local-action-grid,
- .workbench-command-center,
- .workbench-record-grid {
- grid-template-columns: 1fr;
+ .mobile-activity-icon.receive {
+ color: #198e68;
+ background: rgba(49, 149, 112, 0.13);
}
- .flowmemory-hero,
- .canary-hero,
- .flowmemory-spine,
- .contract-event-list dl,
- .bundle-grid {
- grid-template-columns: 1fr;
+ .mobile-activity-icon.swap {
+ color: #135bdc;
+ background: rgba(31, 104, 224, 0.12);
}
- .flowmemory-hero-side,
- .panel-side-bottom {
- grid-column: auto;
+ .mobile-activity-row > div:nth-child(2) strong {
+ color: #221f1b;
+ font-size: 0.97rem;
}
- .flowmemory-hero-side {
- border-left: 0;
- border-top: 1px solid var(--line);
+ .mobile-activity-row > div:nth-child(2) small {
+ margin-top: 3px;
+ color: rgba(65, 55, 45, 0.55);
+ font-size: 0.78rem;
}
- .workbench-layout {
- grid-template-columns: 1fr;
+ .mobile-activity-row > div:nth-child(3) {
+ text-align: right;
}
- .workbench-switcher {
- position: static;
- grid-template-columns: repeat(2, minmax(0, 1fr));
+ .mobile-activity-row > div:nth-child(3) strong,
+ .mobile-activity-row > div:nth-child(3) small {
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.8rem;
}
-}
-@media (max-width: 860px) {
- .app-shell {
- grid-template-columns: 1fr;
+ .mobile-activity-row:first-of-type > div:nth-child(3) strong {
+ color: #20936b;
}
- .sidebar {
- position: static;
- height: auto;
+ .mobile-activity-row > div:nth-child(3) small {
+ margin-top: 3px;
+ color: rgba(65, 55, 45, 0.58);
}
- .nav-list {
- grid-template-columns: repeat(3, minmax(0, 1fr));
+ .mobile-bottom-nav {
+ position: fixed;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 8;
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ width: 100vw;
+ max-width: min(390px, 100vw);
+ min-height: 75px;
+ margin: 0 auto;
+ padding: 9px 10px calc(12px + env(safe-area-inset-bottom));
+ border-top: 1px solid rgba(83, 61, 37, 0.13);
+ background: rgba(247, 238, 225, 0.88);
+ box-shadow: 0 -16px 36px rgba(53, 35, 18, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.62);
+ backdrop-filter: blur(18px);
}
- .topbar,
- .section-header,
- .fixture-banner,
- .record-row,
- .alert-row {
+ .mobile-bottom-nav a {
display: grid;
- grid-template-columns: 1fr;
+ gap: 3px;
+ justify-items: center;
+ align-content: center;
+ color: rgba(54, 48, 42, 0.62);
+ font-size: 0.73rem;
+ font-weight: 650;
+ text-decoration: none;
}
- .content {
- padding: 18px 14px 28px;
+ .mobile-bottom-nav a[aria-current="page"] {
+ color: #165dda;
}
+}
- .section-action,
- .filter-row {
- min-width: 0;
- grid-template-columns: 1fr;
+@media (max-width: 370px) {
+ .flowchain-mobile-wallet {
+ padding-right: 12px;
+ padding-left: 12px;
}
- .fixture-banner {
- margin: 14px 14px 0;
+ .mobile-wallet-brand strong {
+ font-size: 1.72rem;
}
- .alert-action {
- border-left: 0;
- border-top: 1px solid var(--line);
+ .mobile-wallet-pills {
+ gap: 8px;
}
- .workbench-fact-grid {
- grid-template-columns: 1fr;
+ .mobile-wallet-pills > button {
+ padding: 0 10px;
+ font-size: 0.84rem;
}
-}
-@media (max-width: 560px) {
- .nav-list,
- .metric-grid,
- .block-strip,
- .definition-grid,
- .lane-stats,
- .record-facts,
- .workbench-switcher {
- grid-template-columns: 1fr;
+ .mobile-section-header {
+ grid-template-columns: minmax(0, 1fr) 84px 66px;
+ font-size: 0.64rem;
}
- .topbar {
- padding: 12px 14px;
+ .mobile-asset-row {
+ grid-template-columns: 42px minmax(0, 1fr) 82px 58px 12px;
+ gap: 6px;
+ padding-right: 8px;
+ padding-left: 8px;
}
- th,
- td {
- padding: 10px;
+ .mobile-asset-icon {
+ width: 40px;
+ height: 40px;
+ }
+
+ .mobile-asset-row > div:nth-child(2) strong {
+ font-size: 0.93rem;
+ }
+
+ .mobile-asset-row b,
+ .mobile-asset-row > div:nth-child(3) strong,
+ .mobile-asset-row > div:nth-child(3) small {
+ font-size: 0.68rem;
}
}
diff --git a/apps/dashboard/src/test/dashboardData.test.ts b/apps/dashboard/src/test/dashboardData.test.ts
index f4e16394..4defe200 100644
--- a/apps/dashboard/src/test/dashboardData.test.ts
+++ b/apps/dashboard/src/test/dashboardData.test.ts
@@ -250,6 +250,96 @@ describe("dashboard fixture", () => {
lifecycle: [],
});
}
+ if (url.endsWith("/bridge/live-readiness")) {
+ return Response.json({
+ schema: "flowmemory.control_plane.bridge_live_readiness.v0",
+ baseChainId: 8453,
+ baseChainName: "Base",
+ failClosedStatus: "BLOCKED",
+ readyForOperatorLivePilot: false,
+ lockbox: { configured: false, envName: "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", ownerVerified: false },
+ node: { running: true, chainId: "flowmemory-local-devnet-v0" },
+ confirmationDepth: { configured: false, envName: "FLOWCHAIN_BASE8453_CONFIRMATION_DEPTH" },
+ missingEnvNames: ["FLOWCHAIN_BASE8453_RPC_URL", "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS"],
+ currentArtifacts: { base8453DepositCount: 0, localOrMockDepositCount: 1, mockPresentedAsLive: false },
+ issues: [{
+ reasonCode: "missing_env",
+ status: "blocked",
+ title: "Missing live pilot env",
+ summary: "Live readiness is blocked until all required env names are present.",
+ envNames: ["FLOWCHAIN_BASE8453_RPC_URL", "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS"],
+ }],
+ envValuesPrinted: false,
+ localOnly: true,
+ productionReady: false,
+ });
+ }
+ if (url.endsWith("/pilot/lifecycle")) {
+ return Response.json({
+ schema: "flowmemory.control_plane.bridge_lifecycle_record_list.v0",
+ count: 1,
+ lifecycleRecords: [{
+ lifecycleRecordId: "lifecycle:1",
+ baseTxHash: `0x${"1".repeat(64)}`,
+ logIndex: 0,
+ depositId: "deposit:1",
+ replayKey: "replay:1",
+ replayStatus: "accepted",
+ creditId: "credit:1",
+ recipientWallet: "wallet:credited",
+ withdrawalIntentId: "withdrawal:1",
+ withdrawalStatus: "requested",
+ releaseEvidenceId: "release:1",
+ releaseStatus: "recorded",
+ asset: "local-test-unit",
+ amountSmallestUnits: "100",
+ status: "credited",
+ artifactClass: "local-or-mock",
+ liveArtifact: false,
+ evidenceFilePath: "fixtures/bridge/local-runtime-bridge-handoff.json",
+ equality: {
+ depositAmount: "100",
+ observedAmount: "100",
+ creditedAmount: "100",
+ walletDelta: "100",
+ transferableAmount: "100",
+ withdrawalAmount: "100",
+ releaseAmount: "100",
+ allEqual: true,
+ equalities: { walletDelta: true },
+ },
+ }],
+ });
+ }
+ if (url.endsWith("/wallets/balances")) {
+ return Response.json({
+ schema: "flowmemory.control_plane.wallet_balance_list.v0",
+ count: 1,
+ balances: [{
+ balanceId: "balance:credited",
+ walletAddress: "wallet:credited",
+ asset: "local-test-unit",
+ amount: "100",
+ status: "credited",
+ creditId: "credit:1",
+ }],
+ });
+ }
+ if (url.endsWith("/wallets/transfers")) {
+ return Response.json({
+ schema: "flowmemory.control_plane.wallet_transfer_history.v0",
+ count: 1,
+ transfers: [{
+ transferId: "transfer:1",
+ txId: "tx:transfer:1",
+ fromAccountId: "wallet:credited",
+ toAccountId: "wallet:recipient",
+ assetId: "local-test-unit",
+ amount: "100",
+ status: "applied",
+ }],
+ });
+ }
if (url === WORKBENCH_DEVNET_STATE_PATH) {
return Response.json(devnetState);
}
@@ -270,15 +360,87 @@ describe("dashboard fixture", () => {
expect(workbench.raw.controlPlaneHealth).toEqual({ status: "ok" });
expect(workbench.raw.controlPlaneState).toEqual({ state: devnetState });
expect(workbench.raw.controlPlanePilotStatus).toMatchObject({ state: "degraded" });
+ expect(workbench.raw.controlPlaneBridgeReadiness).toMatchObject({ failClosedStatus: "BLOCKED" });
+ expect(workbench.raw.controlPlanePilotLifecycle).toMatchObject({ count: 1 });
+ expect(workbench.sections.realValuePilot.some((record) => record.kind === "Bridge live readiness")).toBe(true);
+ expect(workbench.sections.realValuePilot.some((record) => record.kind === "Bridge exact lifecycle")).toBe(true);
+ expect(workbench.sections.realValuePilot.some((record) => record.kind === "Wallet transfer history")).toBe(true);
+ const lifecycleRecord = workbench.sections.realValuePilot.find((record) => record.kind === "Bridge exact lifecycle");
+ expect(lifecycleRecord?.facts.find((fact) => fact.label === "replay key")?.value).toBe("replay:1");
+ expect(lifecycleRecord?.facts.find((fact) => fact.label === "withdrawal intent")?.value).toBe("withdrawal:1");
+ expect(lifecycleRecord?.facts.find((fact) => fact.label === "release evidence")?.value).toBe("release:1");
+ expect(lifecycleRecord?.facts.find((fact) => fact.label === "withdrawal amount")?.value).toBe("100");
+ expect(lifecycleRecord?.facts.find((fact) => fact.label === "release amount")?.value).toBe("100");
expect(workbench.raw.devnetState).toEqual(devnetState);
expect(workbench.raw.bridgeTestDeposit).toEqual(bridgeTestDeposit);
expect(workbench.loadIssues).toEqual([]);
expect(fetchMock).toHaveBeenCalledWith("http://127.0.0.1:8787/health", expect.any(Object));
expect(fetchMock).toHaveBeenCalledWith("http://127.0.0.1:8787/pilot/status", expect.any(Object));
+ expect(fetchMock).toHaveBeenCalledWith("http://127.0.0.1:8787/bridge/live-readiness", expect.any(Object));
+ expect(fetchMock).toHaveBeenCalledWith("http://127.0.0.1:8787/pilot/lifecycle", expect.any(Object));
expect(fetchMock).toHaveBeenCalledWith(WORKBENCH_DEVNET_STATE_PATH, expect.any(Object));
expect(fetchMock).toHaveBeenCalledWith(WORKBENCH_BRIDGE_TEST_DEPOSIT_PATH, expect.any(Object));
});
+ it("renders bridge readiness live-blocked without env values", () => {
+ const configuredButHidden = "https://example.invalid/rpc-redacted";
+ const workbench = buildWorkbenchSnapshot(data, {
+ controlPlane: {
+ url: "http://127.0.0.1:8787",
+ status: "available",
+ checkedAt: "2026-05-14T15:00:00.000Z",
+ endpoints: ["GET /health", "GET /state", "GET /bridge/live-readiness", "GET /pilot/lifecycle"],
+ health: { status: "ok" },
+ state: devnetState,
+ pilotStatus: {
+ schema: "flowmemory.control_plane.real_value_pilot_status.v0",
+ state: "degraded",
+ stateReason: "Waiting for Base 8453 deposit.",
+ baseChainId: 8453,
+ cappedOwnerTesting: true,
+ broadPublicReadiness: false,
+ productionReady: false,
+ browserStoresSecrets: false,
+ nextOperatorStep: { command: "npm run control-plane:serve" },
+ lifecycle: [],
+ },
+ bridgeLiveReadiness: {
+ schema: "flowmemory.control_plane.bridge_live_readiness.v0",
+ baseChainId: 8453,
+ baseChainName: "Base",
+ failClosedStatus: "BLOCKED",
+ readyForOperatorLivePilot: false,
+ lockbox: { configured: false, envName: "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", ownerVerified: false },
+ node: { running: true, chainId: "flowmemory-local-devnet-v0" },
+ confirmationDepth: { configured: false, envName: "FLOWCHAIN_BASE8453_CONFIRMATION_DEPTH" },
+ missingEnvNames: ["FLOWCHAIN_BASE8453_RPC_URL", "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS"],
+ currentArtifacts: { base8453DepositCount: 0, localOrMockDepositCount: 1, mockPresentedAsLive: false },
+ issues: [{
+ reasonCode: "missing_env",
+ status: "blocked",
+ title: "Missing live pilot env",
+ summary: "Live readiness is blocked until all required env names are present.",
+ envNames: ["FLOWCHAIN_BASE8453_RPC_URL", "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS"],
+ }],
+ envValuesPrinted: false,
+ localOnly: true,
+ productionReady: false,
+ },
+ },
+ devnetState,
+ devnetDashboardState,
+ });
+ const html = renderToStaticMarkup(createElement(WorkbenchView, { data, workbench }));
+
+ expect(html).toContain("Bridge live readiness");
+ expect(html).toContain("BLOCKED");
+ expect(html).toContain("FLOWCHAIN_BASE8453_RPC_URL");
+ expect(html).toContain("FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS");
+ expect(html).toContain("env values printed");
+ expect(html).toContain("false");
+ expect(html).not.toContain(configuredButHidden);
+ });
+
it("renders the critical workbench view labels from fixture fallback", () => {
const workbench = buildWorkbenchSnapshot(data, {
devnetState,
@@ -291,6 +453,7 @@ describe("dashboard fixture", () => {
expect(html).toContain("Node and API status");
expect(html).toContain("Control-plane offline");
expect(html).toContain("Real-value pilot");
+ expect(html).toContain("Bridge live readiness");
expect(html).toContain("capped owner testing");
expect(html).toContain("public readiness");
expect(html).toContain("Wallet Metadata");
diff --git a/apps/dashboard/src/views/BridgePilotView.tsx b/apps/dashboard/src/views/BridgePilotView.tsx
new file mode 100644
index 00000000..9a734b92
--- /dev/null
+++ b/apps/dashboard/src/views/BridgePilotView.tsx
@@ -0,0 +1,987 @@
+import { useEffect, useMemo, useState } from "react";
+import {
+ AlertTriangle,
+ ArrowRightLeft,
+ Bell,
+ ChevronDown,
+ ChevronRight,
+ CircleDollarSign,
+ Copy,
+ Download,
+ ExternalLink,
+ Home,
+ Info,
+ Lock,
+ Send,
+ ShieldCheck,
+ UserRound,
+ Wallet,
+ Zap,
+} from "lucide-react";
+import { StatusBadge } from "../components/StatusBadge";
+import type { DashboardStatus } from "../data/types";
+import type { WorkbenchSnapshot } from "../data/workbench";
+
+type EthereumProvider = {
+ request(args: { method: string; params?: unknown[] | Record }): Promise;
+};
+
+declare global {
+ interface Window {
+ ethereum?: EthereumProvider;
+ }
+}
+
+interface BridgePilotViewProps {
+ workbench: WorkbenchSnapshot;
+}
+
+type BridgeReadiness = {
+ failClosedStatus?: string;
+ readyForOperatorLivePilot?: boolean;
+ missingEnvNames?: string[];
+ issues?: Array<{ reasonCode?: string; status?: string; title?: string; summary?: string }>;
+ lockbox?: { configured?: boolean; ownerVerified?: boolean };
+ currentArtifacts?: { base8453DepositCount?: number };
+ envValuesPrinted?: boolean;
+ productionReady?: boolean;
+};
+
+const BASE_CHAIN_ID_DECIMAL = 8453;
+const BASE_CHAIN_ID_HEX = "0x2105";
+const BASE_RPC_URL = "https://mainnet.base.org";
+const BASE_EXPLORER_URL = "https://basescan.org";
+const LOCKBOX_ADDRESS = "0xe731Bc6b117d92deDCA40a7ccAec11d16205026a";
+const LOCK_NATIVE_SELECTOR = "0x1326d1ec";
+const MAX_DEPOSIT_WEI = 100000000000000n;
+const ETH_USD_RATE_URL = "https://api.coinbase.com/v2/exchange-rates?currency=ETH";
+const ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
+const BLOCKED_RECIPIENT_PLACEHOLDER = "0x5555555555555555555555555555555555555555555555555555555555555555";
+const BLOCKED_METADATA_PLACEHOLDER = "0x6666666666666666666666666666666666666666666666666666666666666666";
+const RECIPIENT_CANDIDATE_FIELDS = new Set(["accountId", "signerId", "address", "flowchainRecipient", "recipient"]);
+
+type BridgeRecipientOption = {
+ value: string;
+ label: string;
+ source: string;
+};
+
+function asReadiness(value: unknown): BridgeReadiness | null {
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
+ return null;
+ }
+
+ return value as BridgeReadiness;
+}
+
+function statusFromReadiness(readiness: BridgeReadiness | null): DashboardStatus {
+ if (!readiness) {
+ return "pending";
+ }
+
+ if (readiness.readyForOperatorLivePilot || readiness.failClosedStatus === "READY_FOR_OPERATOR_LIVE_PILOT") {
+ return "verified";
+ }
+
+ if (readiness.failClosedStatus === "FAILED") {
+ return "failed";
+ }
+
+ return "pending";
+}
+
+function isBytes32(value: string): boolean {
+ return /^0x[0-9a-fA-F]{64}$/.test(value);
+}
+
+function isZeroBytes32(value: string): boolean {
+ return /^0x0{64}$/i.test(value);
+}
+
+function normalizeInput(value: string): string {
+ return value.trim();
+}
+
+function isRecord(value: unknown): value is Record {
+ return value !== null && typeof value === "object" && !Array.isArray(value);
+}
+
+function blockedPlaceholderLabel(value: string): string | null {
+ const normalized = value.toLowerCase();
+ if (normalized === BLOCKED_RECIPIENT_PLACEHOLDER) {
+ return "old 0x555... placeholder";
+ }
+ if (normalized === BLOCKED_METADATA_PLACEHOLDER) {
+ return "old 0x666... placeholder";
+ }
+ return null;
+}
+
+function addRecipientOption(
+ options: BridgeRecipientOption[],
+ seen: Set,
+ value: unknown,
+ label: string,
+ source: string,
+): void {
+ if (typeof value !== "string") {
+ return;
+ }
+ const normalized = normalizeInput(value);
+ if (!isBytes32(normalized) || isZeroBytes32(normalized) || blockedPlaceholderLabel(normalized)) {
+ return;
+ }
+ const key = normalized.toLowerCase();
+ if (seen.has(key)) {
+ return;
+ }
+ seen.add(key);
+ options.push({ value: normalized, label, source });
+}
+
+function collectRecipientOptionsFromRaw(
+ value: unknown,
+ options: BridgeRecipientOption[],
+ seen: Set,
+ source: string,
+ label: string,
+ depth = 0,
+): void {
+ if (depth > 3) {
+ return;
+ }
+ if (Array.isArray(value)) {
+ value.forEach((item, index) => {
+ collectRecipientOptionsFromRaw(item, options, seen, source, `${label} ${index + 1}`, depth + 1);
+ });
+ return;
+ }
+ if (!isRecord(value)) {
+ return;
+ }
+
+ for (const [key, nestedValue] of Object.entries(value)) {
+ if (RECIPIENT_CANDIDATE_FIELDS.has(key)) {
+ addRecipientOption(options, seen, nestedValue, label, source);
+ }
+ if (Array.isArray(nestedValue) || isRecord(nestedValue)) {
+ collectRecipientOptionsFromRaw(nestedValue, options, seen, source, label, depth + 1);
+ }
+ }
+}
+
+function collectRecipientOptions(workbench: WorkbenchSnapshot): BridgeRecipientOption[] {
+ const options: BridgeRecipientOption[] = [];
+ const seen = new Set();
+ const records = [
+ ...workbench.sections.walletMetadata,
+ ...workbench.sections.accounts,
+ ...workbench.sections.bridgeCredits,
+ ...workbench.sections.bridgeDeposits,
+ ];
+
+ for (const record of records) {
+ collectRecipientOptionsFromRaw(record.raw, options, seen, record.kind, record.title);
+ }
+
+ return options.slice(0, 6);
+}
+
+function validateRecipient(value: string): string | null {
+ if (value.length === 0) {
+ return "Enter a real Flowchain account id before bridging.";
+ }
+ if (!isBytes32(value)) {
+ return "Recipient must be a 32-byte Flowchain account id.";
+ }
+ if (isZeroBytes32(value)) {
+ return "Recipient cannot be the zero account id.";
+ }
+ const blockedLabel = blockedPlaceholderLabel(value);
+ if (blockedLabel) {
+ return `Recipient cannot be the ${blockedLabel}.`;
+ }
+ return null;
+}
+
+function validateMetadataHash(value: string): string | null {
+ if (value.length === 0) {
+ return null;
+ }
+ if (!isBytes32(value)) {
+ return "Metadata hash must be blank or a bytes32 value.";
+ }
+ const blockedLabel = blockedPlaceholderLabel(value);
+ if (blockedLabel) {
+ return `Metadata hash cannot be the ${blockedLabel}.`;
+ }
+ return null;
+}
+
+function shorten(value: string): string {
+ return `${value.slice(0, 6)}...${value.slice(-4)}`;
+}
+
+function parseEthToWei(value: string): { wei: bigint | null; error: string | null } {
+ const trimmed = value.trim();
+ if (!/^\d+(\.\d{0,18})?$/.test(trimmed)) {
+ return { wei: null, error: "Use a decimal ETH amount with up to 18 decimals." };
+ }
+
+ const [wholePart, fractionPart = ""] = trimmed.split(".");
+ const whole = BigInt(wholePart.length > 0 ? wholePart : "0");
+ const fraction = BigInt(fractionPart.padEnd(18, "0"));
+ const wei = whole * 10n ** 18n + fraction;
+
+ if (wei <= 0n) {
+ return { wei: null, error: "Amount must be greater than zero." };
+ }
+
+ if (wei > MAX_DEPOSIT_WEI) {
+ return { wei: null, error: "Amount exceeds the 0.0001 ETH pilot cap." };
+ }
+
+ return { wei, error: null };
+}
+
+function parseEthDecimal(value: string): number | null {
+ const trimmed = value.trim();
+ if (!/^\d+(\.\d*)?$/.test(trimmed)) {
+ return null;
+ }
+
+ const parsed = Number(trimmed);
+ return Number.isFinite(parsed) ? parsed : null;
+}
+
+function formatUsd(value: number): string {
+ return new Intl.NumberFormat("en-US", {
+ style: "currency",
+ currency: "USD",
+ maximumFractionDigits: value < 1 ? 4 : 2,
+ }).format(value);
+}
+
+function usdEstimateFromEth(value: string, rate: number | null): string | null {
+ const parsed = parseEthDecimal(value);
+ if (parsed === null || rate === null) {
+ return null;
+ }
+
+ return `~ ${formatUsd(parsed * rate)} USD`;
+}
+
+function toQuantityHex(value: bigint): string {
+ return `0x${value.toString(16)}`;
+}
+
+function buildLockNativeData(flowchainRecipient: string, metadataHash: string): string {
+ return `${LOCK_NATIVE_SELECTOR}${flowchainRecipient.slice(2)}${metadataHash.slice(2)}`;
+}
+
+async function switchToBase(provider: EthereumProvider): Promise {
+ try {
+ await provider.request({
+ method: "wallet_switchEthereumChain",
+ params: [{ chainId: BASE_CHAIN_ID_HEX }],
+ });
+ } catch (error) {
+ const code = typeof error === "object" && error !== null && "code" in error ? Number((error as { code?: unknown }).code) : 0;
+ if (code !== 4902) {
+ throw error;
+ }
+
+ await provider.request({
+ method: "wallet_addEthereumChain",
+ params: [
+ {
+ chainId: BASE_CHAIN_ID_HEX,
+ chainName: "Base",
+ nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
+ rpcUrls: [BASE_RPC_URL],
+ blockExplorerUrls: [BASE_EXPLORER_URL],
+ },
+ ],
+ });
+ }
+}
+
+function errorMessage(error: unknown): string {
+ if (error instanceof Error && error.message.trim().length > 0) {
+ return error.message;
+ }
+
+ return "Wallet request failed or was rejected.";
+}
+
+export function BridgePilotView({ workbench }: BridgePilotViewProps) {
+ const readiness = asReadiness(workbench.controlPlane.bridgeLiveReadiness);
+ const [walletAddress, setWalletAddress] = useState(null);
+ const [chainId, setChainId] = useState(null);
+ const [amountEth, setAmountEth] = useState("0.00001");
+ const [recipient, setRecipient] = useState("");
+ const [metadataHash, setMetadataHash] = useState("");
+ const [ethUsdRate, setEthUsdRate] = useState(null);
+ const [priceStatus, setPriceStatus] = useState<"loading" | "ready" | "unavailable">("loading");
+ const [statusMessage, setStatusMessage] = useState(null);
+ const [txHash, setTxHash] = useState(null);
+
+ const normalizedRecipient = normalizeInput(recipient);
+ const normalizedMetadataHash = normalizeInput(metadataHash);
+ const effectiveMetadataHash = normalizedMetadataHash.length === 0 ? ZERO_BYTES32 : normalizedMetadataHash;
+ const amount = useMemo(() => parseEthToWei(amountEth), [amountEth]);
+ const usdEstimate = useMemo(() => usdEstimateFromEth(amountEth, ethUsdRate), [amountEth, ethUsdRate]);
+ const pilotCapUsdEstimate = useMemo(() => usdEstimateFromEth("0.0001", ethUsdRate), [ethUsdRate]);
+ const recipientOptions = useMemo(() => collectRecipientOptions(workbench), [workbench]);
+ const hardIssues = useMemo(
+ () =>
+ (readiness?.issues ?? []).filter(
+ (issue) => issue.status === "blocked" && issue.reasonCode !== "no_deposits_observed",
+ ),
+ [readiness],
+ );
+ const missingEnvNames = readiness?.missingEnvNames ?? [];
+ const validationIssue =
+ amount.error ??
+ validateRecipient(normalizedRecipient) ??
+ validateMetadataHash(normalizedMetadataHash) ??
+ (missingEnvNames.length > 0 ? `Control-plane is missing ${missingEnvNames.join(", ")}.` : null) ??
+ (hardIssues.length > 0 ? hardIssues.map((issue) => issue.reasonCode ?? issue.title ?? "blocked").join(", ") : null);
+ const sendDisabled = !walletAddress || validationIssue !== null;
+ const readinessStatus = statusFromReadiness(readiness);
+ const chainLabel = chainId === BASE_CHAIN_ID_HEX ? `Base ${BASE_CHAIN_ID_DECIMAL}` : chainId ?? "Connect wallet";
+ const routeLabel = readinessStatus === "verified" ? "Verified" : "Pilot route";
+ const receivedAmount = amount.wei === null ? "0 ETH" : `${amountEth || "0"} ETH`;
+ const usdEstimateLabel =
+ usdEstimate ??
+ (priceStatus === "loading" ? "Loading ETH/USD quote" : "USD quote unavailable");
+ const mobileAccountLabel = walletAddress ? shorten(walletAddress) : "0x4F...A7F3";
+ const mobileNetworkLabel = chainId && chainId !== BASE_CHAIN_ID_HEX ? chainLabel : "Mainnet";
+
+ useEffect(() => {
+ const controller = new AbortController();
+
+ async function loadEthUsdRate() {
+ try {
+ const response = await fetch(ETH_USD_RATE_URL, { signal: controller.signal });
+ if (!response.ok) {
+ throw new Error(`ETH/USD quote failed with ${response.status}`);
+ }
+
+ const payload = (await response.json()) as { data?: { rates?: { USD?: string } } };
+ const parsed = Number(payload.data?.rates?.USD);
+ if (!Number.isFinite(parsed) || parsed <= 0) {
+ throw new Error("ETH/USD quote payload did not include a positive USD rate.");
+ }
+
+ setEthUsdRate(parsed);
+ setPriceStatus("ready");
+ } catch (error) {
+ if (error instanceof DOMException && error.name === "AbortError") {
+ return;
+ }
+ setPriceStatus("unavailable");
+ }
+ }
+
+ void loadEthUsdRate();
+
+ return () => controller.abort();
+ }, []);
+
+ const connectWallet = async () => {
+ const provider = window.ethereum;
+ if (!provider) {
+ setStatusMessage("No browser wallet detected.");
+ return;
+ }
+
+ try {
+ const accounts = await provider.request({ method: "eth_requestAccounts" });
+ const nextChainId = await provider.request({ method: "eth_chainId" });
+ const accountList = Array.isArray(accounts) ? accounts : [];
+ setWalletAddress(typeof accountList[0] === "string" ? accountList[0] : null);
+ setChainId(typeof nextChainId === "string" ? nextChainId : null);
+ setStatusMessage(null);
+ } catch (error) {
+ setStatusMessage(errorMessage(error));
+ }
+ };
+
+ const sendDeposit = async () => {
+ const provider = window.ethereum;
+ if (!provider || !walletAddress || amount.wei === null || validationIssue !== null) {
+ return;
+ }
+
+ try {
+ setStatusMessage("Waiting for wallet confirmation.");
+ setTxHash(null);
+ await switchToBase(provider);
+ const transactionHash = await provider.request({
+ method: "eth_sendTransaction",
+ params: [
+ {
+ from: walletAddress,
+ to: LOCKBOX_ADDRESS,
+ value: toQuantityHex(amount.wei),
+ data: buildLockNativeData(normalizedRecipient, effectiveMetadataHash),
+ },
+ ],
+ });
+ if (typeof transactionHash === "string") {
+ setTxHash(transactionHash);
+ setStatusMessage("Transaction submitted on Base.");
+ } else {
+ setStatusMessage("Wallet returned without a transaction hash.");
+ }
+ const nextChainId = await provider.request({ method: "eth_chainId" });
+ setChainId(typeof nextChainId === "string" ? nextChainId : null);
+ } catch (error) {
+ setStatusMessage(errorMessage(error));
+ }
+ };
+
+ const handlePrimaryAction = walletAddress ? sendDeposit : connectWallet;
+ const primaryActionLabel = walletAddress ? "Bridge to Flowchain" : "Connect wallet";
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {mobileNetworkLabel}
+
+
+
+
+
+ {mobileAccountLabel}
+
+
+
+
+
+
+ Total portfolio value
+
+
+ $24,785.62
+
+ ▲ 8.42%
+ +$1,927.31 (24H)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $26K
+ $24K
+ $22K
+
+
+ 1D
+ 1W
+ 1M
+ 3M
+ 1Y
+ ALL
+
+
+
+
+
+
+ Send
+ Transfer tokens
+
+
+
+ Receive
+ Deposit assets
+
+
+
+ Swap
+ Trade tokens
+
+
+
+ Bridge
+ Move assets
+
+
+
+
+
+ Assets
+ Value
+ 24h change
+
+
+ F
+
+ FLOW
+ Flow Token
+
+
+ $7,452.81
+ 2,450.12 FLOW
+
+ ▲ 4.13%
+
+
+
+ E
+
+ ETH
+ Ethereum
+
+
+ $10,917.02
+ 4.34 ETH
+
+ ▲ 2.31%
+
+
+
+
+
+ USDC
+ USD Coin
+
+
+ $2,850.00
+ 2,850.00 USDC
+
+ ▼ 0.02%
+
+
+
+ B
+
+ wBTC
+ Wrapped Bitcoin
+
+
+ $3,565.79
+ 0.0421 wBTC
+
+ ▲ 1.08%
+
+
+
+ View all assets
+
+
+
+
+
+
+ Recent activity
+ View all
+
+
+
+
+ Received FLOW
+ From 0xB8F1...1E2A
+
+
+ +250.00 FLOW
+ $405.90
+
+
+
+
+
+ Swapped ETH to FLOW
+ Via FlowSwap
+
+
+ -2.5000 ETH
+ +623.58 FLOW
+
+
+
+
+
+
+
+ Home
+
+
+
+ Assets
+
+
+
+ Swap
+
+
+
+ Bridge
+
+
+
+ Profile
+
+
+
+
+
+
+
+
+ Cross-chain bridge
+ Bridge funds into Flowchain
+ Move assets into Flowchain. Receipt tracking and capped pilot routing.
+
+
+
+
+
+
+ Fast finality
+ Quick receipt updates.
+
+
+
+
+
+
+
+ Clear fee estimates
+ Gas shown before the wallet request.
+
+
+
+
+
+
+
+ Wallet-safe routing
+ Capped pilot with wallet confirmation.
+
+
+
+
+
+
+
+
+
+ From
+ Origin chain
+
+ B
+ Base
+
+
+
+
+
+ To
+ Destination
+
+ F
+ Flowchain
+
+
+
+
+
+
+
+
Token
+
+ E
+ ETH
+ Base native Ether
+
+
+
+
+
+ Amount
+ setAmountEth(event.target.value)} inputMode="decimal" />
+ ETH
+ Pilot cap: 0.0001 ETH
+ {usdEstimateLabel}
+ setAmountEth("0.0001")}>MAX
+
+
+
+
+
+ Flowchain account id
+ setStatusMessage("Use the destination Flowchain account id. The old 0x555... placeholder is blocked.")}>
+ What is this?
+
+
+
+ {recipientOptions.length > 0 ? (
+
+ {recipientOptions.map((option) => (
+
+ {option.label}
+
+ ))}
+
+ ) : null}
+
0 ? "bridge-recipient-options" : undefined}
+ value={recipient}
+ onChange={(event) => setRecipient(event.target.value.trim())}
+ placeholder="0x... 64 hex characters"
+ spellCheck={false}
+ />
+
{
+ void navigator.clipboard?.writeText(normalizedRecipient);
+ setStatusMessage("Recipient copied.");
+ }}
+ >
+
+
+ {recipientOptions.length > 0 ? (
+
+ {recipientOptions.map((option) => (
+ {
+ setRecipient(option.value);
+ setStatusMessage(`${option.source} selected.`);
+ }}
+ >
+ {option.label}
+ {shorten(option.value)}
+
+ ))}
+
+ ) : null}
+
+
+
+
+
+ You will send
+
+
+ {receivedAmount}
+ {usdEstimateLabel}
+
+
+
+ Route
+
+
+ Base to FlowChain
+
+
+
+
+ Network fee est.
+
+
+ Wallet gas
+ {chainLabel}
+
+
+
+ Bridge fee
+
+
+ No protocol fee
+ Capped pilot route
+
+
+
+ ETA
+
+
+ After confirmations
+
+
+
+ Pilot cap
+
+
+ 0.0001 ETH
+ {pilotCapUsdEstimate ? {pilotCapUsdEstimate} : null}
+
+
+
+
+
+ Advanced details (metadata / bytes32 recipient)
+
+
+
+
+ Metadata hash
+ setMetadataHash(event.target.value.trim())}
+ placeholder="Blank sends 0x000...000"
+ spellCheck={false}
+ />
+
+
+
+
+
call
+ lockNative(bytes32,bytes32)
+
+
+
lockbox
+ {shorten(LOCKBOX_ADDRESS)}
+
+
+
chain
+ {chainLabel}
+
+
+
+
+
+
+ {primaryActionLabel}
+
+
+
+
+
+
+ By proceeding, you acknowledge this is a capped owner pilot and agree to the pilot terms.
+
+
+ {validationIssue ? (
+
+
+ {validationIssue}
+
+ ) : null}
+ {statusMessage ? {statusMessage}
: null}
+ {txHash ? (
+
+ {shorten(txHash)}
+
+
+ ) : null}
+
+
+
+
+
+
+ );
+}
diff --git a/apps/dashboard/src/views/WalletView.tsx b/apps/dashboard/src/views/WalletView.tsx
new file mode 100644
index 00000000..e188f9f8
--- /dev/null
+++ b/apps/dashboard/src/views/WalletView.tsx
@@ -0,0 +1,1017 @@
+import { useEffect, useMemo, useState } from "react";
+import { Link } from "react-router-dom";
+import {
+ Activity,
+ AlertTriangle,
+ ArrowRight,
+ ArrowRightLeft,
+ BadgeCheck,
+ Bell,
+ ChevronDown,
+ ChevronRight,
+ CircleDollarSign,
+ Cloud,
+ Copy,
+ Download,
+ ExternalLink,
+ Eye,
+ Home,
+ KeyRound,
+ Lock,
+ Network,
+ RefreshCw,
+ Search,
+ Send,
+ Settings,
+ ShieldCheck,
+ SlidersHorizontal,
+ Wallet,
+} from "lucide-react";
+import type { WorkbenchSnapshot } from "../data/workbench";
+
+type WalletAccount = {
+ accountId?: string;
+ address?: string;
+ signerId?: string;
+ signerKeyId?: string;
+ keyScheme?: string;
+ label?: string;
+ status?: string;
+ chainId?: string;
+ nextNonce?: string;
+};
+
+type WalletStatus = {
+ schema?: string;
+ exists?: boolean;
+ account?: WalletAccount | null;
+ accounts?: WalletAccount[];
+ metadataPath?: string;
+ secretMaterialReturned?: boolean;
+};
+
+type WalletCreateResult = WalletStatus & {
+ created?: boolean;
+ alreadyExists?: boolean;
+ vaultPath?: string;
+ note?: string;
+ message?: string;
+ desktopLocal?: boolean;
+};
+
+type WalletBalance = {
+ balanceId?: string;
+ walletAddress?: string;
+ asset?: string;
+ amount?: string;
+ creditedAmount?: string;
+ creditId?: string;
+ depositId?: string;
+ status?: string;
+ source?: string;
+};
+
+type WalletSendResult = {
+ schema?: string;
+ accepted?: boolean;
+ applied?: boolean;
+ transferId?: string;
+ txIds?: string[];
+ amountUnits?: string;
+ status?: string;
+ from?: {
+ requestedAccountId?: string;
+ runtimeAccountId?: string;
+ resolution?: string;
+ };
+ to?: {
+ requestedAccountId?: string;
+ runtimeAccountId?: string;
+ resolution?: string;
+ };
+ message?: string;
+};
+
+type WalletTransfer = {
+ transferId?: string;
+ txId?: string;
+ fromAccountId?: string;
+ toAccountId?: string;
+ assetId?: string;
+ amount?: string;
+ status?: string;
+ source?: string;
+};
+
+type PilotCredit = {
+ creditId?: string;
+ depositId?: string;
+ accountId?: string;
+ amount?: string;
+ token?: string;
+ status?: string;
+ txHash?: string;
+ sourceChainId?: number;
+};
+
+type BridgeStatus = {
+ deposits?: number;
+ credits?: number;
+ applied?: number;
+ pending?: number;
+ productionReadyCredits?: number;
+ publicProductionL1Ready?: boolean;
+ liveRuntimeHandoffLoaded?: boolean;
+};
+
+type WalletApiResult = {
+ payload: T;
+ url: string;
+};
+
+type ActionPanel = "home" | "wallet" | "send" | "receive" | "swap" | "activity" | "security" | "settings" | "staking";
+
+type LocalActivity = {
+ id: string;
+ type: string;
+ asset: string;
+ route: string;
+ amount: string;
+ status: string;
+};
+
+interface WalletViewProps {
+ workbench: WorkbenchSnapshot;
+}
+
+type FlowchainDesktopBridge = {
+ app?: string;
+ platform?: string;
+ packaged?: boolean;
+ getLocalWallet?: () => Promise;
+ createLocalWallet?: (payload: { label: string; password: string; chainId: string; replace: boolean }) => Promise;
+};
+
+declare global {
+ interface Window {
+ flowchainDesktop?: FlowchainDesktopBridge;
+ }
+}
+
+class WalletApiError extends Error {
+ status?: number;
+ url?: string;
+}
+
+const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
+const ETH_USD_RATE_URL = "https://api.coinbase.com/v2/exchange-rates?currency=ETH";
+
+function shortId(value: string, head = 6, tail = 4): string {
+ return value.length > head + tail + 5 ? `${value.slice(0, head)}...${value.slice(-tail)}` : value;
+}
+
+function safeText(value: unknown, fallback = "not available"): string {
+ return typeof value === "string" && value.length > 0 ? value : fallback;
+}
+
+function accountId(account: WalletAccount | null | undefined): string {
+ return safeText(account?.accountId ?? account?.address ?? account?.signerId, "");
+}
+
+function isLoopbackHost(value: string): boolean {
+ return value === "127.0.0.1" || value === "localhost" || value === "::1";
+}
+
+function walletApiCandidates(primaryUrl: string): string[] {
+ const candidates: string[] = [];
+ const browserHost = typeof window !== "undefined" ? window.location.hostname : "";
+
+ if (browserHost && !isLoopbackHost(browserHost)) {
+ candidates.push(`http://${browserHost}:8795`, `http://${browserHost}:8794`, `http://${browserHost}:8787`);
+ }
+
+ candidates.push(primaryUrl);
+
+ if (!browserHost || isLoopbackHost(browserHost)) {
+ candidates.push("http://127.0.0.1:8794", "http://127.0.0.1:8795", "http://127.0.0.1:8787");
+ }
+
+ return [...new Set(candidates.map((candidate) => candidate.replace(/\/+$/, "")))];
+}
+
+async function readJsonPayload(response: Response): Promise {
+ const body = await response.text();
+ if (body.trim().length === 0) {
+ return {};
+ }
+ try {
+ return JSON.parse(body) as unknown;
+ } catch {
+ return { message: body };
+ }
+}
+
+async function fetchWalletApi(urls: string[], path: string, init?: RequestInit): Promise> {
+ let lastError: Error | null = null;
+
+ for (const url of urls) {
+ try {
+ const response = await fetch(`${url}${path}`, init);
+ const payload = await readJsonPayload(response);
+ if (!response.ok) {
+ const error = new WalletApiError(safeText((payload as { message?: string }).message, `${path} failed with ${response.status}`));
+ error.status = response.status;
+ error.url = url;
+ lastError = error;
+ if (response.status === 404) {
+ continue;
+ }
+ throw error;
+ }
+ return { payload: payload as T, url };
+ } catch (error) {
+ lastError = error instanceof Error ? error : new Error("wallet API unavailable");
+ if (error instanceof WalletApiError && error.status !== 404) {
+ throw error;
+ }
+ }
+ }
+
+ throw lastError ?? new Error("wallet API unavailable");
+}
+
+function weiToEth(value: string | undefined): number {
+ if (!value || !/^\d+$/.test(value)) {
+ return 0;
+ }
+ return Number(value) / 1e18;
+}
+
+function formatEth(value: number): string {
+ if (value === 0) {
+ return "0 ETH";
+ }
+ if (value < 0.0001) {
+ return `${value.toFixed(8).replace(/0+$/, "").replace(/\.$/, "")} ETH`;
+ }
+ return `${value.toLocaleString(undefined, { maximumFractionDigits: 6 })} ETH`;
+}
+
+function formatUsd(value: number): string {
+ return value.toLocaleString(undefined, {
+ style: "currency",
+ currency: "USD",
+ minimumFractionDigits: value >= 1 ? 2 : 4,
+ maximumFractionDigits: value >= 1 ? 2 : 4,
+ });
+}
+
+function formatAssetAddress(asset: string | undefined): string {
+ if (!asset || asset.toLowerCase() === ZERO_ADDRESS) {
+ return "ETH";
+ }
+ return shortId(asset, 5, 4);
+}
+
+function statusLabel(value: string | undefined): string {
+ return value ? value.replace(/_/g, " ") : "pending";
+}
+
+function timestampLabel(): string {
+ return new Date().toLocaleString(undefined, {
+ month: "short",
+ day: "numeric",
+ hour: "numeric",
+ minute: "2-digit",
+ });
+}
+
+function FlowMark() {
+ return (
+
+
+
+ );
+}
+
+export function WalletView({ workbench }: WalletViewProps) {
+ const controlPlaneUrl = workbench.controlPlane.url;
+ const apiCandidates = useMemo(() => walletApiCandidates(controlPlaneUrl), [controlPlaneUrl]);
+ const [walletApiUrl, setWalletApiUrl] = useState(controlPlaneUrl);
+ const [status, setStatus] = useState(null);
+ const [result, setResult] = useState(null);
+ const [balances, setBalances] = useState([]);
+ const [transfers, setTransfers] = useState([]);
+ const [credits, setCredits] = useState([]);
+ const [bridgeStatus, setBridgeStatus] = useState(null);
+ const [ethUsdRate, setEthUsdRate] = useState(null);
+ const [label, setLabel] = useState("flow-wallet");
+ const [passphrase, setPassphrase] = useState("");
+ const [replace, setReplace] = useState(false);
+ const [sendTo, setSendTo] = useState("");
+ const [sendAmount, setSendAmount] = useState("");
+ const [swapAmount, setSwapAmount] = useState("");
+ const [search, setSearch] = useState("");
+ const [activePanel, setActivePanel] = useState("home");
+ const [loading, setLoading] = useState(false);
+ const [message, setMessage] = useState(null);
+ const [localActivity, setLocalActivity] = useState([]);
+ const desktopWalletAvailable = typeof window !== "undefined" && Boolean(window.flowchainDesktop?.createLocalWallet);
+
+ const activeAccount = result?.account ?? status?.account ?? null;
+ const activeAccountId = accountId(activeAccount);
+ const primaryBalance = balances.find((balance) => balance.status === "credited") ?? balances[0] ?? null;
+ const primaryWalletAddress = safeText(primaryBalance?.walletAddress, activeAccountId || "");
+ const totalEth = balances.reduce((sum, balance) => sum + weiToEth(balance.amount ?? balance.creditedAmount), 0);
+ const totalUsd = totalEth * (ethUsdRate ?? 0);
+ const hasWallet = Boolean(activeAccountId || primaryWalletAddress);
+ const networkBadge = bridgeStatus?.publicProductionL1Ready ? "Live" : bridgeStatus?.liveRuntimeHandoffLoaded ? "Pilot" : "Local";
+ const canCreate = passphrase.length >= 8 && !loading;
+ const visibleCredits = credits.filter((credit) =>
+ credit.sourceChainId === 8453 || credit.accountId === primaryWalletAddress || credit.status === "applied",
+ );
+ const filteredCredits = visibleCredits.filter((credit) => {
+ const query = search.trim().toLowerCase();
+ if (query.length === 0) {
+ return true;
+ }
+ return [credit.creditId, credit.txHash, credit.accountId, credit.status].some((value) => String(value ?? "").toLowerCase().includes(query));
+ });
+
+ async function copyValue(value: string, nextMessage: string) {
+ if (!value) {
+ return;
+ }
+ await navigator.clipboard?.writeText(value);
+ setMessage(nextMessage);
+ }
+
+ async function loadDesktopWalletFallback(apiMessage: string): Promise {
+ const desktop = typeof window !== "undefined" ? window.flowchainDesktop : undefined;
+ if (!desktop?.getLocalWallet) {
+ return false;
+ }
+ try {
+ const desktopStatus = await desktop.getLocalWallet();
+ setStatus(desktopStatus);
+ setBalances([]);
+ setTransfers([]);
+ setCredits([]);
+ setBridgeStatus(null);
+ setMessage(
+ desktopStatus.exists
+ ? "Wallet loaded locally. Flowchain API is unavailable, so balances and activity cannot sync yet."
+ : `${apiMessage}. Create encrypted wallet will still work locally in this desktop app.`,
+ );
+ return true;
+ } catch {
+ return false;
+ }
+ }
+
+ async function loadStatus() {
+ try {
+ const [walletResult, balanceResult, transferResult, creditResult, bridgeResult] = await Promise.all([
+ fetchWalletApi(apiCandidates, "/wallets/operator"),
+ fetchWalletApi<{ balances?: WalletBalance[] }>(apiCandidates, "/wallets/balances"),
+ fetchWalletApi<{ transfers?: WalletTransfer[] }>(apiCandidates, "/wallets/transfers"),
+ fetchWalletApi<{ credits?: PilotCredit[] }>(apiCandidates, "/pilot/credits?limit=10"),
+ fetchWalletApi(apiCandidates, "/bridge/status"),
+ ]);
+ setWalletApiUrl(walletResult.url);
+ setStatus(walletResult.payload);
+ setBalances(balanceResult.payload.balances ?? []);
+ setTransfers(transferResult.payload.transfers ?? []);
+ setCredits(creditResult.payload.credits ?? []);
+ setBridgeStatus(bridgeResult.payload);
+ setMessage(null);
+ } catch (error) {
+ const apiMessage = error instanceof Error ? error.message : "wallet data unavailable";
+ const loadedLocalWallet = await loadDesktopWalletFallback(apiMessage);
+ if (!loadedLocalWallet) {
+ setMessage(apiMessage);
+ }
+ }
+ }
+
+ useEffect(() => {
+ void loadStatus();
+ }, [apiCandidates]);
+
+ useEffect(() => {
+ let cancelled = false;
+ fetch(ETH_USD_RATE_URL, { cache: "no-store" })
+ .then((response) => response.json() as Promise<{ data?: { rates?: { USD?: string } } }>)
+ .then((payload) => {
+ if (!cancelled) {
+ const rate = Number(payload.data?.rates?.USD);
+ setEthUsdRate(Number.isFinite(rate) ? rate : null);
+ }
+ })
+ .catch(() => {
+ if (!cancelled) {
+ setEthUsdRate(null);
+ }
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ async function createWallet() {
+ if (!canCreate) {
+ return;
+ }
+ setLoading(true);
+ setMessage(null);
+ try {
+ const walletRequest = {
+ label,
+ password: passphrase,
+ chainId: "31337",
+ replace,
+ };
+ const desktop = typeof window !== "undefined" ? window.flowchainDesktop : undefined;
+ if (desktop?.createLocalWallet) {
+ const payload = await desktop.createLocalWallet(walletRequest);
+ setResult(payload);
+ setStatus(payload);
+ setBalances([]);
+ setTransfers([]);
+ setCredits([]);
+ setBridgeStatus(null);
+ setPassphrase("");
+ setActivePanel("receive");
+ setMessage(payload.alreadyExists ? safeText(payload.note, "Existing wallet loaded.") : "Wallet created locally on this device.");
+ return;
+ }
+
+ const { payload, url } = await fetchWalletApi(apiCandidates, "/wallets/create", {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify(walletRequest),
+ });
+ setWalletApiUrl(url);
+ setResult(payload);
+ setStatus(payload);
+ setPassphrase("");
+ setActivePanel("receive");
+ setMessage(payload.alreadyExists ? safeText(payload.note, "Existing wallet loaded.") : "Wallet created.");
+ await loadStatus();
+ } catch (error) {
+ setMessage(error instanceof Error ? error.message : "wallet creation failed");
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ async function submitSend() {
+ if (!sendTo.trim() || !sendAmount.trim()) {
+ setMessage("Enter a recipient and amount first.");
+ return;
+ }
+ setLoading(true);
+ setMessage(null);
+ try {
+ const { payload, url } = await fetchWalletApi(apiCandidates, "/wallets/send", {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({
+ fromAccountId: primaryWalletAddress,
+ toAccountId: sendTo.trim(),
+ amountEth: sendAmount.trim(),
+ memo: "flowchain-wallet-ui-send",
+ }),
+ });
+ setWalletApiUrl(url);
+ setLocalActivity((current) => [
+ {
+ id: payload.transferId ?? `send:${Date.now()}`,
+ type: payload.applied ? "Send applied" : "Send queued",
+ asset: "ETH",
+ route: `${shortId(payload.from?.runtimeAccountId ?? primaryWalletAddress)} to ${shortId(payload.to?.runtimeAccountId ?? sendTo)}`,
+ amount: `-${sendAmount} ETH`,
+ status: statusLabel(payload.status),
+ },
+ ...current,
+ ]);
+ setSendTo("");
+ setSendAmount("");
+ setActivePanel("activity");
+ setMessage(payload.applied ? "Send applied on Flowchain runtime." : "Send queued for Flowchain runtime.");
+ await loadStatus();
+ } catch (error) {
+ setMessage(error instanceof Error ? error.message : "wallet send failed");
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ function submitSwapDraft() {
+ if (!swapAmount.trim()) {
+ setMessage("Enter a swap amount first.");
+ return;
+ }
+ setLocalActivity((current) => [
+ {
+ id: `swap:${Date.now()}`,
+ type: "Swap quoted",
+ asset: "ETH to FLOW",
+ route: "Flowchain local quote",
+ amount: `${swapAmount} ETH`,
+ status: "Quote ready",
+ },
+ ...current,
+ ]);
+ setSwapAmount("");
+ setActivePanel("activity");
+ setMessage("Swap quote prepared. A live DEX execution endpoint is not connected to this wallet screen yet.");
+ }
+
+ const assetRows = balances.length > 0
+ ? balances.map((balance) => {
+ const eth = weiToEth(balance.amount ?? balance.creditedAmount);
+ return {
+ key: balance.balanceId ?? `${balance.walletAddress}:${balance.asset}`,
+ name: formatAssetAddress(balance.asset),
+ description: balance.source === "bridge-credit" ? "Base bridged Ether" : "Flowchain asset",
+ balance: formatEth(eth),
+ value: ethUsdRate ? formatUsd(eth * ethUsdRate) : "USD quote unavailable",
+ change: balance.status === "credited" ? "+ credited" : statusLabel(balance.status),
+ tone: "up",
+ };
+ })
+ : [
+ {
+ key: "empty-eth",
+ name: "ETH",
+ description: "No credited balance",
+ balance: "0 ETH",
+ value: "$0.00",
+ change: "0.00%",
+ tone: "flat",
+ },
+ ];
+
+ const activityRows = [
+ ...localActivity,
+ ...filteredCredits.map((credit) => ({
+ id: credit.creditId ?? `${credit.txHash}:${credit.amount}`,
+ type: credit.status === "applied" ? "Bridge credit" : "Bridge pending",
+ asset: formatAssetAddress(credit.token),
+ route: credit.txHash ? shortId(credit.txHash, 8, 6) : "Flowchain",
+ amount: `${formatEth(weiToEth(credit.amount))}`,
+ status: statusLabel(credit.status),
+ })),
+ ...transfers.map((transfer) => ({
+ id: transfer.transferId ?? transfer.txId ?? `${transfer.fromAccountId}:${transfer.toAccountId}`,
+ type: "Transfer",
+ asset: safeText(transfer.assetId, "asset"),
+ route: `${shortId(safeText(transfer.fromAccountId, "from"))} to ${shortId(safeText(transfer.toAccountId, "to"))}`,
+ amount: safeText(transfer.amount, "0"),
+ status: statusLabel(transfer.status),
+ })),
+ ];
+
+ return (
+
+
+
+
+ Flowchain
+
+
+
+ setActivePanel("home")}>
+
+ Home
+
+ setActivePanel("wallet")}>
+
+ Wallet
+
+ setActivePanel("activity")}>
+
+ Activity
+
+
+
+ Bridge
+
+ setActivePanel("swap")}>
+
+ Swap
+
+ setActivePanel("staking")}>
+
+ Staking
+
+ setActivePanel("security")}>
+
+ Security
+
+ setActivePanel("settings")}>
+
+ Settings
+
+
+
+ setMessage("Sidebar collapse is cosmetic in this local wallet build.")}>
+
+ Collapse
+
+
+
+
+
+
+
+
+
Portfolio
+
+ Total balance
+
+
+
{ethUsdRate ? formatUsd(totalUsd) : formatEth(totalEth)}
+
+ {formatEth(totalEth)}
+ {ethUsdRate ? `${formatUsd(totalUsd)} estimated` : "USD quote unavailable"}
+
+
+
+
+
+ {["1D", "7D", "30D", "90D", "1Y", "ALL"].map((range) => (
+
+ {range}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 00:00
+ 04:00
+ 08:00
+ 12:00
+ 16:00
+ 20:00
+ 24:00
+
+
+
+
+
+ setActivePanel("send")}>
+
+ Send
+ Send tokens
+
+ setActivePanel("receive")}>
+
+ Receive
+ Receive tokens
+
+ setActivePanel("swap")}>
+
+ Swap
+ Swap assets
+
+
+
+ Bridge
+ Bridge to Flowchain
+
+
+
+
+
+
Assets
+ setActivePanel("wallet")}>
+ View all assets
+
+
+
+
+
+ Asset
+ Balance
+ Value (USD)
+ State
+
+ {assetRows.map((asset) => (
+
+
+ {asset.name.slice(0, 1)}
+ {asset.description}
+ {asset.name}
+
+ {asset.balance}
+ {asset.value}
+ {asset.change}
+
+ ))}
+
+
+
+
+
+
Recent activity
+ setActivePanel("activity")}>
+ View all activity
+
+
+
+
+
+ Time
+ Type
+ Asset
+ To / From
+ Amount
+ Status
+
+ {activityRows.slice(0, 4).map((row) => (
+
+ {timestampLabel()}
+ {row.type}
+ {row.asset}
+ {row.route}
+ {row.amount}
+ {row.status}
+
+ ))}
+
+
+
+
+
+
+ Account
+ Address
+
+ {primaryWalletAddress ? shortId(primaryWalletAddress, 7, 5) : "No wallet"}
+ void copyValue(primaryWalletAddress, "Address copied.")}>
+
+
+ setActivePanel("receive")}>
+
+
+
+ setActivePanel("security")}>
+
+ Security
+ Protected
+
+
+ setActivePanel("settings")}>
+
+ Backup
+ {status?.exists ? "Vault saved locally" : "Create wallet"}
+
+
+
+
+
+
+
Flowchain network
+ {networkBadge}
+
+
+
+
Credits
+ {bridgeStatus?.credits ?? credits.length}
+
+
+
Applied
+ {bridgeStatus?.applied ?? credits.filter((credit) => credit.status === "applied").length}
+
+
+
Wallet API
+ {shortId(walletApiUrl, 15, 4)}
+
+
+ void loadStatus()}>
+ View network status
+
+
+
+
+
+
+
+
Bridge to Flowchain
+
Move assets between chains
+
+
+ Open bridge
+
+
+
+
+
+
+
Watchlist
+ setActivePanel("wallet")}>View all
+
+
+ F
+ FLOW
+ $1.098
+ +8.45%
+
+
+
+ E
+ ETH
+ {ethUsdRate ? formatUsd(ethUsdRate) : "$--"}
+ +5.23%
+
+
+
+
+
+ {activePanel !== "home" ? (
+
+
+
+ {activePanel}
+
+ {activePanel === "send" ? "Send tokens" : null}
+ {activePanel === "receive" ? "Receive tokens" : null}
+ {activePanel === "swap" ? "Swap assets" : null}
+ {activePanel === "activity" ? "Activity" : null}
+ {activePanel === "security" ? "Security" : null}
+ {activePanel === "settings" || activePanel === "wallet" ? "Wallet settings" : null}
+ {activePanel === "staking" ? "Staking" : null}
+
+
+ setActivePanel("home")}>Close
+
+
+ {activePanel === "send" ? (
+
+
+ Recipient Flowchain account
+ setSendTo(event.target.value)} placeholder="0x..." />
+
+
+ Amount
+ setSendAmount(event.target.value)} inputMode="decimal" placeholder="0.000001" />
+
+ void submitSend()}>
+
+ {loading ? "Sending" : "Send"}
+
+
+ ) : null}
+
+ {activePanel === "receive" ? (
+
+
+
+
+
+
+
{primaryWalletAddress || "Create a wallet first."}
+
void copyValue(primaryWalletAddress, "Receive address copied.")}>
+
+ Copy address
+
+
+ ) : null}
+
+ {activePanel === "swap" ? (
+
+
+ From
+ setSwapAmount(event.target.value)} inputMode="decimal" placeholder="ETH amount" />
+
+
+
+
+ Get quote
+
+
+ ) : null}
+
+ {activePanel === "activity" ? (
+
+ {activityRows.length === 0 ?
No wallet activity yet.
: null}
+ {activityRows.map((row) => (
+
+ {row.type}
+ {row.amount}
+ {row.route}
+ {row.status}
+
+ ))}
+
+ ) : null}
+
+ {activePanel === "security" ? (
+
+
+
+ Protected
+ Private material is not returned to the browser.
+
+
+
+ Encrypted vault
+ {result?.vaultPath ?? "Stored locally on this computer after creation."}
+
+
+
+ No-secret boundary
+ Passphrases and private keys are never shown in the UI.
+
+
+ ) : null}
+
+ {activePanel === "settings" || activePanel === "wallet" ? (
+
+
+ Wallet label
+ setLabel(event.target.value)} />
+
+
+ Vault passphrase
+ setPassphrase(event.target.value)} type="password" autoComplete="new-password" placeholder="8 characters minimum" />
+
+
+ setReplace(event.target.checked)} type="checkbox" />
+ Replace existing local wallet with a new address
+
+ void createWallet()}>
+
+ {loading ? "Creating" : desktopWalletAvailable ? "Create encrypted desktop wallet" : "Create encrypted wallet"}
+
+ void loadStatus()}>
+
+ Refresh wallet data
+
+
+ ) : null}
+
+ {activePanel === "staking" ? (
+
+
+ Staking is parked for now.
+ The wallet layout includes the entry point, but staking controls are not enabled yet.
+
+ ) : null}
+
+ ) : null}
+
+ {message ? (
+
+
+ {message}
+
+ ) : null}
+
+ );
+}
diff --git a/apps/dashboard/src/views/WorkbenchView.tsx b/apps/dashboard/src/views/WorkbenchView.tsx
index 4815701b..e3c8304b 100644
--- a/apps/dashboard/src/views/WorkbenchView.tsx
+++ b/apps/dashboard/src/views/WorkbenchView.tsx
@@ -59,9 +59,12 @@ export function WorkbenchView({ data, workbench, onRefresh }: WorkbenchViewProps
const bridgeRecordCount =
workbench.sections.bridgeDeposits.length + workbench.sections.bridgeCredits.length + workbench.sections.bridgeWithdrawals.length;
const pilotRecords = workbench.sections.realValuePilot;
- const pilotOverview = pilotRecords[0];
+ const pilotOverview = pilotRecords.find((record) => record.kind === "Pilot status") ?? pilotRecords[0];
+ const bridgeReadiness = pilotRecords.find((record) => record.kind === "Bridge live readiness");
const pilotState = pilotOverview?.facts.find((fact) => fact.label === "state")?.value ?? "degraded";
const pilotNextCommand = pilotOverview?.facts.find((fact) => fact.label === "next command")?.value ?? "npm run control-plane:serve";
+ const bridgeReadinessStatus = bridgeReadiness?.facts.find((fact) => fact.label === "fail-closed status")?.value ?? "BLOCKED";
+ const bridgeReadinessMissingEnv = bridgeReadiness?.facts.find((fact) => fact.label === "missing env names")?.value ?? "endpoint unavailable";
const productSurfaces: Array<{
key: WorkbenchSectionKey;
label: string;
@@ -266,6 +269,43 @@ export function WorkbenchView({ data, workbench, onRefresh }: WorkbenchViewProps
+
+
+
+
+
Bridge live readiness
+
+
+
+
+
+
base 8453 fail-closed check
+
{bridgeReadinessStatus}
+
+ {bridgeReadiness?.summary ??
+ "Live readiness is blocked until the local control-plane exposes bridge readiness details."}
+
+
+
+
+
base chain
+ {displayValue(bridgeReadiness?.facts.find((fact) => fact.label === "base chain")?.value ?? "8453")}
+
+
+
lockbox configured
+ {displayValue(bridgeReadiness?.facts.find((fact) => fact.label === "lockbox configured")?.value ?? "false")}
+
+
+
missing env names
+ {displayValue(bridgeReadinessMissingEnv)}
+
+
+
env values printed
+ {displayValue(bridgeReadiness?.facts.find((fact) => fact.label === "env values printed")?.value ?? "false")}
+
+
+
+
diff --git a/apps/dashboard/vite.config.ts b/apps/dashboard/vite.config.ts
index bd068e16..d851bccd 100644
--- a/apps/dashboard/vite.config.ts
+++ b/apps/dashboard/vite.config.ts
@@ -2,6 +2,7 @@ import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
export default defineConfig({
+ base: "./",
plugins: [react()],
test: {
environment: "node",
diff --git a/contracts/ACCESS_CONTROL_REVIEW.md b/contracts/ACCESS_CONTROL_REVIEW.md
index 66217bb9..0dbd8360 100644
--- a/contracts/ACCESS_CONTROL_REVIEW.md
+++ b/contracts/ACCESS_CONTROL_REVIEW.md
@@ -49,8 +49,9 @@ Launch risk to watch:
or finality proof.
- nonstandard ERC-20 behavior such as transfer fees, rebasing, or callbacks is
outside the pilot safety claim.
-- native releases use Solidity `transfer`; gas-heavy smart-contract recipients
- can fail and should not be used for pilot recovery without separate review.
+- native releases use an explicit low-level value transfer after checks/effects
+ and under `nonReentrant`; smart-contract recipients should still be reviewed
+ before pilot recovery.
- a compromised owner or release authority can misuse the POC; emergency
response is limited to pause, cap changes, allowlist disablement, authority
rotation, and explicit release/recovery calls.
diff --git a/contracts/DEPLOYMENT_BOUNDARY.md b/contracts/DEPLOYMENT_BOUNDARY.md
index a6e73f42..1c1ae6b7 100644
--- a/contracts/DEPLOYMENT_BOUNDARY.md
+++ b/contracts/DEPLOYMENT_BOUNDARY.md
@@ -13,6 +13,24 @@ For the private/local FlowChain testnet package, these Solidity contracts are op
- Base Sepolia deployment dry runs and explicit broadcasts for the current V0 contracts.
- Base Sepolia planning for a Uniswap v4 `afterSwap`-only hook address,
including CREATE2 salt mining for the exact hook flag target.
+- Base Sepolia dry-run and explicit broadcast commands for the v4 hook proof,
+ including a PoolManager swap through Uniswap's public testnet helper
+ contracts, provided the operator records the live readback evidence.
+- Non-secret Base Sepolia hook env checks that confirm local RPC, chain id,
+ deployer key format, deployer address, and test ETH balance before broadcast.
+- Non-secret Base Sepolia hook evidence checks that classify proof state and
+ fail a strict live-proof gate until broadcast receipts and non-empty readback
+ exist.
+- Non-secret Base Sepolia hook readback range planning from explicit operator
+ block numbers or successful broadcast receipt block numbers. Dry-run proof
+ artifacts must not be accepted as live readback range evidence.
+- Non-secret Base Sepolia hook acceptance package generation that fails until
+ env readiness, live code, broadcast receipts, selected readback range,
+ planned hook runtime-bytecode identity, successful receipts for the
+ PoolManager initialize/liquidity/swap actions, range/readback artifact
+ agreement, readback, Flow Memory outputs, FlowPulse observation integrity,
+ successful `SWAP_MEMORY_SIGNAL` evidence, Flow Memory signal linkage to
+ successful broadcast receipt transactions, and count agreement all pass.
- Base Sepolia reads from explicit RPC URLs.
- Guarded Base mainnet canary reads and source-verification dry runs for the documented V0 canary addresses only.
- Capped Base chain id `8453` bridge-pilot dry runs and explicit broadcasts for
@@ -60,7 +78,7 @@ Uniswap v4 hook path:
extra custom-accounting or dynamic-fee-adjacent flags such as
`AFTER_SWAP_RETURNS_DELTA_FLAG`.
- Base Sepolia planning targets chain id `84532`, Uniswap v4 PoolManager
- `0x9a13F98Cb987694C9F086b1F5eB990EeA8264Ec3`, and the standard CREATE2
+ `0x05E73354cFDd6745C338b50BcFDfA3Aa6fA03408`, and the standard CREATE2
deployer `0x4e59b44847b379578588920cA78FbF26c0B4956C`.
Before any Base Sepolia hook broadcast, the PR or issue must record the mined
@@ -69,6 +87,22 @@ chain, PoolManager address, source verification plan, and post-deploy reader
range. A mined/testnet hook address is still not a production hook deployment
or a Base mainnet approval.
+The current Base Sepolia hook proof runbook is
+`docs/DEPLOYMENTS/BASE_SEPOLIA_V4_HOOK_PROOF.md`. It records the current mined
+hook plan:
+
+- hook address: `0xD24d7f807cb00D28DdF675E55879547d4F7B0040`;
+- salt:
+ `0x0000000000000000000000000000000000000000000000004915000000000000`;
+- init code hash:
+ `0x2734b4f6b4f6932249d4d98240147f02cf2ba548fe1ade4a7d63d9dd0a8b9fef`;
+- active permission bits: afterSwap only, `0x0040`.
+
+The proof runner derives `FLOWMEMORY_HOOK_PROOF_OPERATOR` from the same
+testnet deployer key unless explicitly set to the same address. This prevents
+the throwaway proof tokens from being minted to one address while approvals and
+swaps are sent by another address.
+
## Private/Local FlowChain Mirror Map
The private/local FlowChain runtime owns object execution and final state. The Solidity spine may mirror or anchor only compact object references:
@@ -131,6 +165,17 @@ npm run deploy:base-sepolia:broadcast
npm run read:base-sepolia -- --rpc-url --address --from-block --to-block
npm run read:base-sepolia -- --rpc-url --address --resume-from-checkpoint --to-block
npm run verify:base-canary:sources -- --json
+npm run hook:base-sepolia:plan -- --json
+npm run hook:base-sepolia:env-check -- --json
+npm run hook:base-sepolia:evidence -- --json
+npm run hook:base-sepolia:require-live-proof -- --json
+npm run hook:base-sepolia:acceptance -- --json
+npm run hook:base-sepolia:check -- --json
+npm run hook:base-sepolia:swap-proof:dry-run -- --json
+npm run hook:base-sepolia:readback-range -- --infer-readback-range --json
+npm run hook:base-sepolia:readback:auto -- --json
+npm run hook:base-sepolia:readback -- --rpc-url --from-block --to-block --finalized-block --json
+npm run hook:base-sepolia:flowmemory
```
`deploy:base-sepolia:plan` requires no private key and writes a non-secret
@@ -144,6 +189,42 @@ outside Git.
The detailed public testnet rehearsal runbook is
`docs/DEPLOYMENTS/BASE_SEPOLIA_REHEARSAL.md`.
+The detailed v4 PoolManager hook proof runbook is
+`docs/DEPLOYMENTS/BASE_SEPOLIA_V4_HOOK_PROOF.md`.
+The env-check command writes
+`fixtures/deployments/base-sepolia-v4-hook-env-check.latest.json` and must not
+include private keys, RPC URLs, explorer API keys, raw signed transactions, seed
+phrases, or webhook URLs.
+The evidence command writes
+`fixtures/deployments/base-sepolia-v4-hook-evidence.latest.json`. The strict
+`require-live-proof` command is expected to fail until the mined hook address
+has deployed code, the broadcast artifact has receipts, and readback has at
+least one hook FlowPulse observation.
+The readback command writes Base Sepolia hook proof state/checkpoint artifacts
+under `fixtures/deployments/` and fails by default when the selected range has
+zero hook FlowPulse observations.
+The readback range command writes
+`fixtures/deployments/base-sepolia-v4-hook-readback-range.latest.json`. With
+`--infer-readback-range`, it derives `fromBlock`, `toBlock`, and
+`finalizedBlock` from successful receipts in the broadcast swap-proof artifact
+and refuses non-broadcast/dry-run artifacts.
+The live-code check also records the expected compiled hook runtime hash and
+the on-chain code hash for the mined hook address. Final acceptance requires
+those hashes to match, so a random contract at the mined address is not enough.
+The Flow Memory command writes
+`fixtures/deployments/base-sepolia-v4-hook-flowmemory.latest.json`,
+`fixtures/dashboard/flowmemory-dashboard-base-sepolia-v4-hook.json`, and
+`apps/dashboard/public/data/flowmemory-dashboard-base-sepolia-v4-hook.json`.
+It fails by default until the evidence artifact has `liveProofComplete: true`.
+`npm run hook:base-sepolia:flowmemory -- --allow-incomplete` is allowed only
+for diagnostics and must not be treated as acceptance evidence.
+The final acceptance command writes
+`fixtures/deployments/base-sepolia-v4-hook-acceptance.latest.json` and exits
+nonzero until every source artifact agrees, including the selected readback
+range artifact and the actual readback artifact's source and block window.
+`npm run hook:base-sepolia:acceptance -- --allow-incomplete --json` is
+diagnostic only.
+
`script/DeployBridgeSpine.s.sol` is a separate dry-run-by-default bridge-spine
script for local Anvil `31337`, Base Sepolia `84532`, and the capped Base
`8453` pilot. The `8453` path requires `FLOWCHAIN_BASE8453_PILOT_ACK=true` and
diff --git a/contracts/FlowChainSettlementSpine.sol b/contracts/FlowChainSettlementSpine.sol
index 2f285290..9c507932 100644
--- a/contracts/FlowChainSettlementSpine.sol
+++ b/contracts/FlowChainSettlementSpine.sol
@@ -19,8 +19,7 @@ contract FlowChainSettlementSpine {
bytes32 public constant BRIDGE_DEPOSIT_OBJECT = keccak256("flowchain.object.bridge-deposit.v0");
bytes32 public constant BRIDGE_CREDIT_OBJECT = keccak256("flowchain.object.bridge-credit.v0");
- bytes32 public constant BRIDGE_WITHDRAWAL_INTENT_OBJECT =
- keccak256("flowchain.object.bridge-withdrawal-intent.v0");
+ bytes32 public constant BRIDGE_WITHDRAWAL_INTENT_OBJECT = keccak256("flowchain.object.bridge-withdrawal-intent.v0");
bytes32 public constant MEMORY_OBJECT = keccak256("flowchain.object.memory.v0");
bytes32 public constant FINALITY_OBJECT = keccak256("flowchain.object.finality.v0");
diff --git a/contracts/FlowMemoryHookAdapter.sol b/contracts/FlowMemoryHookAdapter.sol
index 6eec0f6b..847d4f08 100644
--- a/contracts/FlowMemoryHookAdapter.sol
+++ b/contracts/FlowMemoryHookAdapter.sol
@@ -32,7 +32,16 @@ contract FlowMemoryHookAdapter is IFlowMemoryHookAdapter, IUniswapV4SwapHookLike
if (rootfieldId == bytes32(0)) revert ZeroRootfieldId();
if (commitment == bytes32(0)) revert ZeroCommitment();
- _emitSwapMemorySignal(sender, poolId, rootfieldId, commitment, bytes32(0), hookData, msg.sender, "flowmemory://uniswap-v4/after-swap");
+ _emitSwapMemorySignal(
+ sender,
+ poolId,
+ rootfieldId,
+ commitment,
+ bytes32(0),
+ hookData,
+ msg.sender,
+ "flowmemory://uniswap-v4/after-swap"
+ );
return AFTER_SWAP_SELECTOR;
}
@@ -48,7 +57,8 @@ contract FlowMemoryHookAdapter is IFlowMemoryHookAdapter, IUniswapV4SwapHookLike
FlowMemorySwapHookData memory decoded = abi.decode(hookData, (FlowMemorySwapHookData));
bytes32 poolId = _poolIdFor(key);
- bytes memory pulseContext = abi.encode(params.zeroForOne, params.amountSpecified, params.sqrtPriceLimitX96, swapDelta, hookData);
+ bytes memory pulseContext =
+ abi.encode(params.zeroForOne, params.amountSpecified, params.sqrtPriceLimitX96, swapDelta, hookData);
_emitSwapMemorySignal(
sender,
poolId,
@@ -68,12 +78,11 @@ contract FlowMemoryHookAdapter is IFlowMemoryHookAdapter, IUniswapV4SwapHookLike
pure
returns (bytes memory hookData)
{
- return abi.encode(FlowMemorySwapHookData({
- rootfieldId: rootfieldId,
- commitment: commitment,
- parentPulseId: parentPulseId,
- uri: uri
- }));
+ return abi.encode(
+ FlowMemorySwapHookData({
+ rootfieldId: rootfieldId, commitment: commitment, parentPulseId: parentPulseId, uri: uri
+ })
+ );
}
function _emitSwapMemorySignal(
diff --git a/contracts/FlowMemoryHookPlanner.sol b/contracts/FlowMemoryHookPlanner.sol
index 0784fef0..9e3c9ade 100644
--- a/contracts/FlowMemoryHookPlanner.sol
+++ b/contracts/FlowMemoryHookPlanner.sol
@@ -40,7 +40,7 @@ library FlowMemoryHookFlags {
/// operator runbook that records the exact inputs.
contract FlowMemoryHookPlanner {
uint256 public constant BASE_SEPOLIA_CHAIN_ID = 84532;
- address public constant BASE_SEPOLIA_POOL_MANAGER = 0x9a13F98Cb987694C9F086b1F5eB990EeA8264Ec3;
+ address public constant BASE_SEPOLIA_POOL_MANAGER = 0x05E73354cFDd6745C338b50BcFDfA3Aa6fA03408;
address public constant CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C;
uint160 public constant ALL_HOOK_MASK = FlowMemoryHookFlags.ALL_HOOK_MASK;
diff --git a/contracts/bridge/BaseBridgeLockbox.sol b/contracts/bridge/BaseBridgeLockbox.sol
index a9c6e561..b8015267 100644
--- a/contracts/bridge/BaseBridgeLockbox.sol
+++ b/contracts/bridge/BaseBridgeLockbox.sol
@@ -16,6 +16,7 @@ contract BaseBridgeLockbox {
uint256 perDepositCap;
uint256 totalCap;
uint256 totalLocked;
+ uint256 totalDeposited;
}
struct DepositRecord {
@@ -32,10 +33,13 @@ contract BaseBridgeLockbox {
address public constant NATIVE_TOKEN = address(0);
bytes32 public constant BRIDGE_DEPOSIT_SCHEMA_ID = keccak256("flowmemory.bridge.deposit.v0");
bytes32 public constant BRIDGE_RELEASE_SCHEMA_ID = keccak256("flowmemory.bridge.release.v0");
+ bytes32 public constant PILOT_MODE_TAG = keccak256("flowchain.base8453.owner-pilot.v0");
+ bytes32 private constant PLACEHOLDER_RECIPIENT = 0x5555555555555555555555555555555555555555555555555555555555555555;
address public owner;
address public releaseAuthority;
bool public paused;
+ bool public emergencyStopped;
uint256 public nextNonce = 1;
mapping(address token => TokenConfig config) public tokenConfigs;
@@ -48,10 +52,12 @@ contract BaseBridgeLockbox {
error NotOwner(address caller);
error NotReleaseAuthority(address caller);
error Paused();
+ error EmergencyStopped();
error ReentrantCall();
error ZeroOwner();
error ZeroReleaseAuthority();
error ZeroRecipient();
+ error PlaceholderRecipient();
error ZeroToken();
error ZeroAmount();
error ZeroEvidenceHash();
@@ -68,16 +74,19 @@ contract BaseBridgeLockbox {
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
event ReleaseAuthoritySet(address indexed previousAuthority, address indexed newAuthority);
event PausedSet(bool paused);
+ event EmergencyStopSet(bool stopped);
event TokenConfigured(address indexed token, bool allowed, uint256 perDepositCap, uint256 totalCap);
event BridgeDeposit(
bytes32 indexed depositId,
uint256 indexed sourceChainId,
address indexed sender,
+ address lockbox,
address token,
uint256 amount,
bytes32 flowchainRecipient,
uint256 nonce,
- bytes32 metadataHash
+ bytes32 metadataHash,
+ bytes32 pilotModeTag
);
event BridgeRelease(
bytes32 indexed releaseId,
@@ -109,6 +118,13 @@ contract BaseBridgeLockbox {
_;
}
+ modifier whenNotEmergencyStopped() {
+ if (emergencyStopped) {
+ revert EmergencyStopped();
+ }
+ _;
+ }
+
modifier nonReentrant() {
if (_entered) {
revert ReentrantCall();
@@ -158,13 +174,18 @@ contract BaseBridgeLockbox {
emit PausedSet(value);
}
+ function setEmergencyStopped(bool value) external onlyOwner {
+ emergencyStopped = value;
+ emit EmergencyStopSet(value);
+ }
+
function configureToken(address token, bool allowed, uint256 perDepositCap, uint256 totalCap) external onlyOwner {
TokenConfig storage config = tokenConfigs[token];
if (allowed && perDepositCap == 0) {
revert ZeroAmount();
}
- if (allowed && totalCap != 0 && totalCap < config.totalLocked) {
- revert TotalCapExceeded(token, config.totalLocked, totalCap);
+ if (allowed && totalCap != 0 && totalCap < config.totalDeposited) {
+ revert TotalCapExceeded(token, config.totalDeposited, totalCap);
}
config.allowed = allowed;
@@ -177,6 +198,7 @@ contract BaseBridgeLockbox {
external
payable
whenNotPaused
+ whenNotEmergencyStopped
nonReentrant
returns (bytes32 depositId)
{
@@ -186,6 +208,7 @@ contract BaseBridgeLockbox {
function lockERC20(address token, uint256 amount, bytes32 flowchainRecipient, bytes32 metadataHash)
external
whenNotPaused
+ whenNotEmergencyStopped
nonReentrant
returns (bytes32 depositId)
{
@@ -201,6 +224,7 @@ contract BaseBridgeLockbox {
function releaseNative(bytes32 depositId, address payable recipient, uint256 amount, bytes32 evidenceHash)
external
onlyReleaseAuthority
+ whenNotEmergencyStopped
nonReentrant
returns (bytes32 releaseId)
{
@@ -208,12 +232,19 @@ contract BaseBridgeLockbox {
revert ZeroRecipient();
}
releaseId = _recordRelease(depositId, recipient, NATIVE_TOKEN, amount, evidenceHash);
- recipient.transfer(amount);
+ // Use call instead of transfer so legitimate smart-contract recipients are not limited by the 2300 gas stipend.
+ // State is updated before this call and the function is nonReentrant.
+ // slither-disable-next-line low-level-calls
+ (bool ok,) = recipient.call{value: amount}("");
+ if (!ok) {
+ revert TransferFailed();
+ }
}
function releaseERC20(bytes32 depositId, address recipient, address token, uint256 amount, bytes32 evidenceHash)
external
onlyReleaseAuthority
+ whenNotEmergencyStopped
nonReentrant
returns (bytes32 releaseId)
{
@@ -248,6 +279,9 @@ contract BaseBridgeLockbox {
if (flowchainRecipient == bytes32(0)) {
revert ZeroRecipient();
}
+ if (flowchainRecipient == PLACEHOLDER_RECIPIENT) {
+ revert PlaceholderRecipient();
+ }
TokenConfig storage config = tokenConfigs[token];
if (!config.allowed) {
@@ -257,9 +291,10 @@ contract BaseBridgeLockbox {
revert PerDepositCapExceeded(token, amount, config.perDepositCap);
}
- uint256 nextTotal = config.totalLocked + amount;
- if (config.totalCap != 0 && nextTotal > config.totalCap) {
- revert TotalCapExceeded(token, nextTotal, config.totalCap);
+ uint256 nextTotalLocked = config.totalLocked + amount;
+ uint256 nextTotalDeposited = config.totalDeposited + amount;
+ if (config.totalCap != 0 && nextTotalDeposited > config.totalCap) {
+ revert TotalCapExceeded(token, nextTotalDeposited, config.totalCap);
}
uint256 nonce = nextNonce++;
@@ -273,7 +308,8 @@ contract BaseBridgeLockbox {
amount,
flowchainRecipient,
nonce,
- metadataHash
+ metadataHash,
+ PILOT_MODE_TAG
)
);
if (deposits[depositId]) {
@@ -291,17 +327,20 @@ contract BaseBridgeLockbox {
metadataHash: metadataHash,
exists: true
});
- config.totalLocked = nextTotal;
+ config.totalLocked = nextTotalLocked;
+ config.totalDeposited = nextTotalDeposited;
emit BridgeDeposit({
depositId: depositId,
sourceChainId: block.chainid,
sender: sender,
+ lockbox: address(this),
token: token,
amount: amount,
flowchainRecipient: flowchainRecipient,
nonce: nonce,
- metadataHash: metadataHash
+ metadataHash: metadataHash,
+ pilotModeTag: PILOT_MODE_TAG
});
}
diff --git a/crates/flowmemory-devnet/src/cli.rs b/crates/flowmemory-devnet/src/cli.rs
index 3fa867bb..1c03910c 100644
--- a/crates/flowmemory-devnet/src/cli.rs
+++ b/crates/flowmemory-devnet/src/cli.rs
@@ -1,13 +1,16 @@
use crate::hash::{hash_json, normalize_value};
use crate::model::{
- BRIDGE_PILOT_ACCOUNT_OWNER, BridgeCredit, BridgeCreditReceipt, FLOWPULSE_TOPIC0,
- ImportedFlowPulseObservation, ImportedVerifierReport, LOCAL_TEST_UNIT_ASSET_ID,
- LocalAuthorization, Transaction, bridge_event_reference_key, build_block, demo_transactions,
- deterministic_bridge_account_id, deterministic_bridge_account_mapping_id,
- deterministic_bridge_asset_mapping_id, envelope_tx, genesis_state, product_demo_transactions,
- queue_authorized_transaction, queue_transaction, state_map_roots, state_root,
+ BRIDGE_PILOT_ACCOUNT_OWNER, BridgeConfirmationProof, BridgeCredit, BridgeCreditReceipt,
+ BridgePilotCapProof, FLOWPULSE_TOPIC0, ImportedFlowPulseObservation, ImportedVerifierReport,
+ LOCAL_TEST_UNIT_ASSET_ID, LocalAuthorization, Transaction, bridge_event_reference_key,
+ build_block, demo_transactions, deterministic_bridge_account_mapping_id,
+ deterministic_bridge_asset_mapping_id, envelope_tx,
+ genesis_state, product_demo_transactions, queue_authorized_transaction, queue_transaction,
+ state_map_roots, state_root,
+};
+use crate::storage::{
+ default_state_path, load_or_genesis, load_state, reset_state, save_state, write_json_pretty,
};
-use crate::storage::{default_state_path, load_or_genesis, load_state, reset_state, save_state};
use anyhow::{Context, Result, anyhow};
use serde::{Deserialize, Serialize};
use serde_json::Value;
@@ -1134,6 +1137,8 @@ fn bridge_handoff_transactions(value: &Value) -> Result> {
continue;
}
+ let local_only = bool_field_default(credit, "localOnly", true);
+ let production_ready = bool_field_default(credit, "productionReady", false);
let source = credit
.get("source")
.and_then(Value::as_object)
@@ -1160,7 +1165,14 @@ fn bridge_handoff_transactions(value: &Value) -> Result> {
.ok_or_else(|| anyhow!("bridge credit missing amount"))?,
"amount",
)?;
- let account_id = deterministic_bridge_account_id(&flowchain_recipient);
+ let credit_id = string_value(credit, "creditId")?;
+ let deposit_id = string_value(credit, "depositId")?;
+ let observation_id = string_value(credit, "observationId")?;
+ let replay_key = string_value(credit, "replayKey")?;
+ let confirmation_proof =
+ confirmation_proof_for_credit(value, &observation_id, &deposit_id)?;
+ let pilot_cap_proof = pilot_cap_proof_for_credit(value, &observation_id, &credit_id)?;
+ let account_id = flowchain_recipient.clone();
let asset_id = LOCAL_TEST_UNIT_ASSET_ID.to_string();
let asset_mapping_id =
deterministic_bridge_asset_mapping_id(&source_chain_id, &source_token, &asset_id);
@@ -1173,6 +1185,8 @@ fn bridge_handoff_transactions(value: &Value) -> Result> {
source_chain_id: source_chain_id.clone(),
source_token: source_token.clone(),
local_asset_id: asset_id.clone(),
+ local_only,
+ production_ready,
});
}
if account_mappings.insert(account_mapping_id.clone()) {
@@ -1181,6 +1195,8 @@ fn bridge_handoff_transactions(value: &Value) -> Result> {
flowchain_recipient: flowchain_recipient.clone(),
account_id: account_id.clone(),
owner: BRIDGE_PILOT_ACCOUNT_OWNER.to_string(),
+ local_only,
+ production_ready,
});
}
if local_accounts.insert(account_id.clone()) {
@@ -1190,7 +1206,6 @@ fn bridge_handoff_transactions(value: &Value) -> Result> {
});
}
- let credit_id = string_value(credit, "creditId")?;
txs.push(Transaction::CreditBridgeFromBaseEvent {
bridge_credit_id: credit_id.clone(),
receipt_id: credit_id,
@@ -1203,13 +1218,18 @@ fn bridge_handoff_transactions(value: &Value) -> Result> {
source_contract,
tx_hash,
log_index,
- deposit_id: string_value(credit, "depositId")?,
- observation_id: string_value(credit, "observationId")?,
- replay_key: string_value(credit, "replayKey")?,
- memo: "real-value-pilot bridge handoff credit; local/testnet accounting only"
- .to_string(),
- local_only: bool_field_default(credit, "localOnly", true),
- production_ready: bool_field_default(credit, "productionReady", false),
+ deposit_id,
+ observation_id,
+ replay_key,
+ memo: if production_ready && !local_only {
+ "live Base bridge credit".to_string()
+ } else {
+ "explicit mock bridge handoff credit".to_string()
+ },
+ local_only,
+ production_ready,
+ confirmation_proof,
+ pilot_cap_proof,
});
}
@@ -1255,6 +1275,184 @@ fn bool_field_default(value: &Value, key: &str, default: bool) -> bool {
value.get(key).and_then(Value::as_bool).unwrap_or(default)
}
+fn confirmation_proof_for_credit(
+ handoff: &Value,
+ observation_id: &str,
+ deposit_id: &str,
+) -> Result> {
+ let confirmation = matching_pilot_evidence(handoff, observation_id, None)
+ .and_then(|evidence| evidence.get("guardrails"))
+ .and_then(|guardrails| guardrails.get("confirmation"))
+ .or_else(|| {
+ matching_observation(handoff, observation_id, deposit_id)
+ .and_then(|observation| observation.get("guardrails"))
+ .and_then(|guardrails| guardrails.get("confirmation"))
+ });
+ confirmation.map(confirmation_proof_from_value).transpose()
+}
+
+fn pilot_cap_proof_for_credit(
+ handoff: &Value,
+ observation_id: &str,
+ credit_id: &str,
+) -> Result > {
+ let pilot_guardrails = matching_pilot_evidence(handoff, observation_id, Some(credit_id))
+ .and_then(|evidence| evidence.get("guardrails"));
+ let observation_guardrails = matching_observation_by_observation_id(handoff, observation_id)
+ .and_then(|observation| observation.get("guardrails"));
+ let guardrails = pilot_guardrails.or_else(|| {
+ observation_guardrails.filter(|guardrails| {
+ guardrails.get("maxDepositAmount").is_some()
+ || guardrails.get("totalCapAmount").is_some()
+ || guardrails.get("supportedTokens").is_some()
+ })
+ });
+
+ let Some(guardrails) = guardrails else {
+ return Ok(None);
+ };
+
+ let max_deposit_amount_units = optional_u64_field(guardrails, "maxDepositAmount")?.unwrap_or(0);
+ let total_cap_amount_units = optional_u64_field(guardrails, "totalCapAmount")?.unwrap_or(0);
+ let supported_tokens = guardrails
+ .get("supportedTokens")
+ .and_then(Value::as_array)
+ .map(|entries| {
+ entries
+ .iter()
+ .map(|entry| {
+ entry
+ .as_str()
+ .map(|token| token.to_ascii_lowercase())
+ .ok_or_else(|| anyhow!("supportedTokens entries must be strings"))
+ })
+ .collect::>>()
+ })
+ .transpose()?
+ .unwrap_or_default();
+
+ Ok(Some(BridgePilotCapProof {
+ approved_lockbox: bool_field_default(guardrails, "approvedContract", false),
+ operator_acknowledged: bool_field_default(guardrails, "operatorAcknowledged", false),
+ no_secrets: bool_field_default(guardrails, "noSecrets", false),
+ max_usd: guardrails
+ .get("maxUsd")
+ .map(|value| value_to_string(value, "maxUsd"))
+ .transpose()?,
+ max_deposit_amount_units,
+ total_cap_amount_units,
+ pilot_mode_tag: guardrails
+ .get("pilotModeTag")
+ .and_then(Value::as_str)
+ .map(ToOwned::to_owned),
+ supported_tokens,
+ }))
+}
+
+fn confirmation_proof_from_value(value: &Value) -> Result {
+ let object = value
+ .as_object()
+ .ok_or_else(|| anyhow!("confirmation proof must be an object"))?;
+ let depth = value_to_u64(
+ object
+ .get("depth")
+ .ok_or_else(|| anyhow!("confirmation proof missing depth"))?,
+ "confirmation.depth",
+ )?;
+ let satisfied = object
+ .get("satisfied")
+ .and_then(Value::as_bool)
+ .ok_or_else(|| anyhow!("confirmation proof missing satisfied"))?;
+ Ok(BridgeConfirmationProof {
+ depth,
+ satisfied,
+ latest_block_number: optional_string_field(object, "latestBlockNumber")?,
+ required_confirmed_block_number: optional_string_field(
+ object,
+ "requiredConfirmedBlockNumber",
+ )?,
+ requested_to_block: optional_string_field(object, "requestedToBlock")?,
+ })
+}
+
+fn optional_u64_field(value: &Value, key: &str) -> Result> {
+ value
+ .get(key)
+ .map(|entry| value_to_u64(entry, key))
+ .transpose()
+}
+
+fn optional_string_field(
+ object: &serde_json::Map,
+ key: &str,
+) -> Result> {
+ object
+ .get(key)
+ .map(|entry| value_to_string(entry, key))
+ .transpose()
+}
+
+fn matching_pilot_evidence<'a>(
+ handoff: &'a Value,
+ observation_id: &str,
+ credit_id: Option<&str>,
+) -> Option<&'a Value> {
+ handoff
+ .get("pilotEvidence")
+ .and_then(Value::as_array)?
+ .iter()
+ .find(|evidence| {
+ evidence
+ .get("observationId")
+ .and_then(Value::as_str)
+ .is_some_and(|candidate| candidate == observation_id)
+ && credit_id.is_none_or(|credit_id| {
+ evidence
+ .get("creditId")
+ .and_then(Value::as_str)
+ .is_some_and(|candidate| candidate == credit_id)
+ })
+ })
+}
+
+fn matching_observation<'a>(
+ handoff: &'a Value,
+ observation_id: &str,
+ deposit_id: &str,
+) -> Option<&'a Value> {
+ handoff
+ .get("observations")
+ .and_then(Value::as_array)?
+ .iter()
+ .find(|observation| {
+ observation
+ .get("observationId")
+ .and_then(Value::as_str)
+ .is_some_and(|candidate| candidate == observation_id)
+ && observation
+ .get("deposit")
+ .and_then(|deposit| deposit.get("depositId"))
+ .and_then(Value::as_str)
+ .is_some_and(|candidate| candidate == deposit_id)
+ })
+}
+
+fn matching_observation_by_observation_id<'a>(
+ handoff: &'a Value,
+ observation_id: &str,
+) -> Option<&'a Value> {
+ handoff
+ .get("observations")
+ .and_then(Value::as_array)?
+ .iter()
+ .find(|observation| {
+ observation
+ .get("observationId")
+ .and_then(Value::as_str)
+ .is_some_and(|candidate| candidate == observation_id)
+ })
+}
+
fn observation_from_flowpulse_fixture(value: &Value) -> Result {
let raw = value
.get("rawLog")
@@ -1491,9 +1689,7 @@ fn write_runtime_boundary_files(state_path: &Path, state: &crate::model::ChainSt
}
fn write_json(path: PathBuf, value: &T) -> Result<()> {
- let body = serde_json::to_string_pretty(value)?;
- fs::write(&path, format!("{body}\n"))
- .with_context(|| format!("failed to write {}", path.display()))
+ write_json_pretty(&path, value).with_context(|| format!("failed to write {}", path.display()))
}
fn print_json(value: &T) -> Result<()> {
diff --git a/crates/flowmemory-devnet/src/lib.rs b/crates/flowmemory-devnet/src/lib.rs
index 31d97758..d7207ce1 100644
--- a/crates/flowmemory-devnet/src/lib.rs
+++ b/crates/flowmemory-devnet/src/lib.rs
@@ -6,18 +6,21 @@ pub mod storage;
pub use cli::run_cli;
pub use hash::{canonical_json, keccak_hex};
pub use model::{
- AgentAccount, ArtifactAvailabilityProof, BalanceTransfer, BaseAnchorPlaceholder, Block,
- BlockReceipt, BridgeAccountMapping, BridgeAssetMapping, BridgeCredit, BridgeCreditReceipt,
- BridgeEventReference, BridgeReplayRecord, ChainState, Challenge, DevnetConfig, DevnetError,
- DexPool, FaucetRecord, FinalityReceipt, ImportedFlowPulseObservation, ImportedVerifierReport,
- LOCAL_TEST_UNIT_ASSET_ID, LiquidityReceipt, LocalAuthorization, LocalTestToken,
- LocalTestTokenBalance, LocalTestTokenMintReceipt, LocalTestUnitBalance, LpPosition, MemoryCell,
- ModelPassport, OperatorKeyReference, StateMapRoots, SwapReceipt, Transaction, TxEnvelope,
- VerifierModule, apply_transaction, bridge_event_reference_key, build_block, default_config,
+ AgentAccount, ArtifactAvailabilityProof, BASE_MAINNET_CHAIN_ID, BRIDGE_RUNTIME_AMOUNT_STORAGE,
+ BalanceTransfer, BaseAnchorPlaceholder, Block, BlockReceipt, BridgeAccountMapping,
+ BridgeAssetMapping, BridgeConfirmationProof, BridgeCredit, BridgeCreditReceipt,
+ BridgeEventReference, BridgePilotCapProof, BridgeReplayRecord, ChainState, Challenge,
+ DevnetConfig, DevnetError, DexPool, FaucetRecord, FinalityReceipt,
+ ImportedFlowPulseObservation, ImportedVerifierReport, LOCAL_TEST_UNIT_ASSET_ID,
+ LiquidityReceipt, LocalAuthorization, LocalTestToken, LocalTestTokenBalance,
+ LocalTestTokenMintReceipt, LocalTestUnitBalance, LpPosition, MemoryCell, ModelPassport,
+ OperatorKeyReference, StateMapRoots, SwapReceipt, Transaction, TxEnvelope, VerifierModule,
+ apply_transaction, bridge_event_reference_key, build_block, default_config,
default_operator_key_references, deterministic_bridge_account_id,
deterministic_bridge_account_mapping_id, deterministic_bridge_asset_mapping_id,
- deterministic_liquidity_id, deterministic_lp_position_id, deterministic_pool_id,
- deterministic_swap_id, deterministic_token_balance_id, deterministic_token_id,
- deterministic_token_mint_id, genesis_state, product_demo_transactions,
- queue_authorized_transaction, queue_transaction, state_map_roots, state_root,
+ deterministic_bridge_credit_id, deterministic_bridge_replay_key, deterministic_liquidity_id,
+ deterministic_lp_position_id, deterministic_pool_id, deterministic_swap_id,
+ deterministic_token_balance_id, deterministic_token_id, deterministic_token_mint_id,
+ genesis_state, product_demo_transactions, queue_authorized_transaction, queue_transaction,
+ state_map_roots, state_root,
};
diff --git a/crates/flowmemory-devnet/src/model.rs b/crates/flowmemory-devnet/src/model.rs
index 179851b3..183ccf68 100644
--- a/crates/flowmemory-devnet/src/model.rs
+++ b/crates/flowmemory-devnet/src/model.rs
@@ -1,5 +1,6 @@
-use crate::hash::{hash_json, keccak_hex};
+use crate::hash::{canonical_json, hash_json, keccak_hex};
use serde::{Deserialize, Serialize};
+use serde_json::Value;
use std::collections::BTreeMap;
use thiserror::Error;
@@ -14,6 +15,8 @@ pub const FLOWPULSE_TOPIC0: &str =
"0x5d07190b9ae441b4d7b16259a48424acd451492b12f5f99a29f5bfd992c13e43";
pub const LOCAL_TEST_UNIT_ASSET_ID: &str = "asset:flowchain-local-test-unit";
pub const BRIDGE_PILOT_ACCOUNT_OWNER: &str = "operator:bridge:pilot";
+pub const BASE_MAINNET_CHAIN_ID: &str = "8453";
+pub const BRIDGE_RUNTIME_AMOUNT_STORAGE: &str = "u64";
#[derive(Debug, Error, PartialEq, Eq)]
pub enum DevnetError {
@@ -97,8 +100,20 @@ pub enum DevnetError {
BridgeCreditEventReferenceAlreadyConsumed(String),
#[error("bridge credit receipt does not exist: {0}")]
BridgeCreditReceiptMissing(String),
- #[error("bridge credit production-ready flag is not supported locally: {0}")]
- BridgeCreditProductionReadyUnsupported(String),
+ #[error("bridge credit runtime mode is invalid: {0}")]
+ BridgeCreditInvalidRuntimeMode(String),
+ #[error("live bridge credit must be from Base chain 8453: {0}")]
+ BridgeCreditWrongSourceChain(String),
+ #[error("live bridge credit is missing confirmation proof metadata: {0}")]
+ BridgeCreditMissingConfirmationProof(String),
+ #[error("live bridge credit confirmation proof is not satisfied: {0}")]
+ BridgeCreditConfirmationUnsatisfied(String),
+ #[error("live bridge credit is missing pilot cap proof metadata: {0}")]
+ BridgeCreditMissingPilotCapProof(String),
+ #[error("live bridge credit source asset is unsupported: {0}")]
+ BridgeCreditUnsupportedAsset(String),
+ #[error("live bridge credit amount exceeds pilot cap: {0}")]
+ BridgeCreditAmountExceedsPilotCap(String),
#[error("model passport already exists: {0}")]
ModelPassportAlreadyExists(String),
#[error("model passport does not exist: {0}")]
@@ -433,7 +448,9 @@ pub struct BridgeAssetMapping {
pub source_token: String,
pub local_asset_id: String,
pub created_at_block: u64,
+ #[serde(default = "crate::model::default_true")]
pub local_only: bool,
+ #[serde(default)]
pub production_ready: bool,
}
@@ -445,7 +462,9 @@ pub struct BridgeAccountMapping {
pub account_id: String,
pub owner: String,
pub created_at_block: u64,
+ #[serde(default = "crate::model::default_true")]
pub local_only: bool,
+ #[serde(default)]
pub production_ready: bool,
}
@@ -459,6 +478,35 @@ pub struct BridgeEventReference {
pub deposit_id: String,
}
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct BridgeConfirmationProof {
+ pub depth: u64,
+ pub satisfied: bool,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub latest_block_number: Option,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub required_confirmed_block_number: Option,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub requested_to_block: Option,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct BridgePilotCapProof {
+ pub approved_lockbox: bool,
+ pub operator_acknowledged: bool,
+ pub no_secrets: bool,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub max_usd: Option,
+ pub max_deposit_amount_units: u64,
+ pub total_cap_amount_units: u64,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub pilot_mode_tag: Option,
+ #[serde(default)]
+ pub supported_tokens: Vec,
+}
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BridgeCredit {
@@ -476,9 +524,16 @@ pub struct BridgeCredit {
pub memo: String,
pub credited_at_block: u64,
pub status: String,
+ #[serde(default = "crate::model::default_true")]
pub local_only: bool,
+ #[serde(default)]
pub production_ready: bool,
+ #[serde(default = "crate::model::default_true")]
pub no_value: bool,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub confirmation_proof: Option,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub pilot_cap_proof: Option,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
@@ -494,8 +549,14 @@ pub struct BridgeCreditReceipt {
pub status: String,
pub included_at_block: u64,
pub evidence: String,
+ #[serde(default = "crate::model::default_true")]
pub local_only: bool,
+ #[serde(default)]
pub production_ready: bool,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub confirmation_proof: Option,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub pilot_cap_proof: Option,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
@@ -796,12 +857,20 @@ pub enum Transaction {
source_chain_id: String,
source_token: String,
local_asset_id: String,
+ #[serde(default = "crate::model::default_true")]
+ local_only: bool,
+ #[serde(default)]
+ production_ready: bool,
},
MapBridgeAccount {
mapping_id: String,
flowchain_recipient: String,
account_id: String,
owner: String,
+ #[serde(default = "crate::model::default_true")]
+ local_only: bool,
+ #[serde(default)]
+ production_ready: bool,
},
CreditBridgeFromBaseEvent {
bridge_credit_id: String,
@@ -819,8 +888,14 @@ pub enum Transaction {
observation_id: String,
replay_key: String,
memo: String,
+ #[serde(default = "crate::model::default_true")]
local_only: bool,
+ #[serde(default)]
production_ready: bool,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ confirmation_proof: Option,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pilot_cap_proof: Option,
},
RegisterModelPassport {
model_passport_id: String,
@@ -1049,6 +1124,10 @@ pub fn default_config() -> DevnetConfig {
}
}
+pub fn default_true() -> bool {
+ true
+}
+
pub fn default_operator_key_references() -> BTreeMap {
let reference = OperatorKeyReference {
schema: OPERATOR_KEY_REFERENCE_SCHEMA.to_string(),
@@ -1300,6 +1379,67 @@ pub fn bridge_event_reference_key(
)
}
+pub fn deterministic_bridge_replay_key(
+ source_chain_id: &str,
+ source_contract: &str,
+ tx_hash: &str,
+ log_index: u64,
+ deposit_id: &str,
+) -> String {
+ external_stable_id(
+ "flowmemory.bridge_replay_key.v0",
+ &serde_json::json!({
+ "sourceChainId": chain_id_json_value(source_chain_id),
+ "sourceContract": normalize_hex_for_identity(source_contract),
+ "txHash": normalize_hex_for_identity(tx_hash),
+ "logIndex": log_index,
+ "depositId": normalize_hex_for_identity(deposit_id)
+ }),
+ )
+}
+
+pub fn deterministic_bridge_credit_id(
+ observation_id: &str,
+ deposit_id: &str,
+ replay_key: &str,
+ source_chain_id: &str,
+ source_contract: &str,
+ tx_hash: &str,
+ log_index: u64,
+) -> String {
+ external_stable_id(
+ "flowmemory.bridge_credit.v0",
+ &serde_json::json!({
+ "observationId": normalize_hex_for_identity(observation_id),
+ "depositId": normalize_hex_for_identity(deposit_id),
+ "replayKey": normalize_hex_for_identity(replay_key),
+ "sourceChainId": chain_id_json_value(source_chain_id),
+ "sourceContract": normalize_hex_for_identity(source_contract),
+ "txHash": normalize_hex_for_identity(tx_hash),
+ "logIndex": log_index
+ }),
+ )
+}
+
+fn external_stable_id(schema: &str, value: &T) -> String {
+ let envelope = serde_json::json!({
+ "schema": schema,
+ "value": value
+ });
+ keccak_hex(canonical_json(&envelope).as_bytes())
+}
+
+fn chain_id_json_value(source_chain_id: &str) -> Value {
+ source_chain_id
+ .parse::()
+ .map(Value::from)
+ .unwrap_or_else(|_| Value::String(source_chain_id.to_string()))
+}
+
+fn normalize_hex_for_identity(value: &str) -> String {
+ value.to_ascii_lowercase()
+}
+
pub fn state_root(state: &ChainState) -> String {
let view = StateCommitmentView {
schema: STATE_SCHEMA,
@@ -1654,6 +1794,7 @@ pub fn apply_transaction(state: &mut ChainState, tx: &Transaction) -> Result<(),
from_account_id.clone(),
));
}
+ let transfer_no_value = from_balance.no_value;
{
let from_balance = state
@@ -1672,6 +1813,7 @@ pub fn apply_transaction(state: &mut ChainState, tx: &Transaction) -> Result<(),
.units
.checked_add(*amount_units)
.ok_or_else(|| DevnetError::LocalTestUnitBalanceOverflow(to_account_id.clone()))?;
+ to_balance.no_value = to_balance.no_value && transfer_no_value;
to_balance.updated_at_block = state.next_block_number;
state.balance_transfers.insert(
@@ -1683,7 +1825,7 @@ pub fn apply_transaction(state: &mut ChainState, tx: &Transaction) -> Result<(),
amount_units: *amount_units,
memo: memo.clone(),
transferred_at_block: state.next_block_number,
- no_value: true,
+ no_value: transfer_no_value,
},
);
}
@@ -2203,7 +2345,10 @@ pub fn apply_transaction(state: &mut ChainState, tx: &Transaction) -> Result<(),
source_chain_id,
source_token,
local_asset_id,
+ local_only,
+ production_ready,
} => {
+ ensure_valid_bridge_runtime_mode(*local_only, *production_ready, mapping_id)?;
ensure_expected_id(
"bridge asset mapping",
mapping_id,
@@ -2229,8 +2374,8 @@ pub fn apply_transaction(state: &mut ChainState, tx: &Transaction) -> Result<(),
source_token: source_token.clone(),
local_asset_id: local_asset_id.clone(),
created_at_block: state.next_block_number,
- local_only: true,
- production_ready: false,
+ local_only: *local_only,
+ production_ready: *production_ready,
},
);
}
@@ -2239,7 +2384,10 @@ pub fn apply_transaction(state: &mut ChainState, tx: &Transaction) -> Result<(),
flowchain_recipient,
account_id,
owner,
+ local_only,
+ production_ready,
} => {
+ ensure_valid_bridge_runtime_mode(*local_only, *production_ready, mapping_id)?;
ensure_expected_id(
"bridge account mapping",
mapping_id,
@@ -2258,8 +2406,8 @@ pub fn apply_transaction(state: &mut ChainState, tx: &Transaction) -> Result<(),
account_id: account_id.clone(),
owner: owner.clone(),
created_at_block: state.next_block_number,
- local_only: true,
- production_ready: false,
+ local_only: *local_only,
+ production_ready: *production_ready,
},
);
}
@@ -2281,22 +2429,51 @@ pub fn apply_transaction(state: &mut ChainState, tx: &Transaction) -> Result<(),
memo,
local_only,
production_ready,
+ confirmation_proof,
+ pilot_cap_proof,
} => {
- if *production_ready {
- return Err(DevnetError::BridgeCreditProductionReadyUnsupported(
- bridge_credit_id.clone(),
- ));
- }
- if !*local_only {
- return Err(DevnetError::BridgeCreditProductionReadyUnsupported(
- bridge_credit_id.clone(),
- ));
- }
+ ensure_valid_bridge_runtime_mode(*local_only, *production_ready, bridge_credit_id)?;
+ let live_credit = is_live_bridge_credit(*local_only, *production_ready);
+ ensure_expected_id(
+ "bridge replay key",
+ replay_key,
+ &deterministic_bridge_replay_key(
+ source_chain_id,
+ source_contract,
+ tx_hash,
+ *log_index,
+ deposit_id,
+ ),
+ )?;
+ ensure_expected_id(
+ "bridge credit",
+ bridge_credit_id,
+ &deterministic_bridge_credit_id(
+ observation_id,
+ deposit_id,
+ replay_key,
+ source_chain_id,
+ source_contract,
+ tx_hash,
+ *log_index,
+ ),
+ )?;
+ ensure_expected_id("bridge credit receipt", receipt_id, bridge_credit_id)?;
if *amount_units == 0 {
return Err(DevnetError::BridgeCreditAmountMustBePositive(
bridge_credit_id.clone(),
));
}
+ if live_credit {
+ validate_live_bridge_credit(
+ bridge_credit_id,
+ source_chain_id,
+ source_token,
+ *amount_units,
+ confirmation_proof.as_ref(),
+ pilot_cap_proof.as_ref(),
+ )?;
+ }
if state.bridge_replay_index.contains_key(replay_key) {
return Err(DevnetError::BridgeCreditReplayAlreadyConsumed(
replay_key.clone(),
@@ -2329,6 +2506,13 @@ pub fn apply_transaction(state: &mut ChainState, tx: &Transaction) -> Result<(),
if asset_mapping.local_asset_id != asset_id.as_str() {
return Err(DevnetError::BridgeAssetMappingMissing(asset_mapping_id));
}
+ if asset_mapping.local_only != *local_only
+ || asset_mapping.production_ready != *production_ready
+ {
+ return Err(DevnetError::BridgeCreditUnsupportedAsset(
+ bridge_credit_id.clone(),
+ ));
+ }
let account_mapping_id =
deterministic_bridge_account_mapping_id(flowchain_recipient, account_id);
@@ -2341,12 +2525,20 @@ pub fn apply_transaction(state: &mut ChainState, tx: &Transaction) -> Result<(),
if account_mapping.account_id != account_id.as_str() {
return Err(DevnetError::BridgeAccountMappingMissing(account_mapping_id));
}
+ if account_mapping.local_only != *local_only
+ || account_mapping.production_ready != *production_ready
+ {
+ return Err(DevnetError::BridgeAccountMappingMissing(account_mapping_id));
+ }
let mapped_owner = account_mapping.owner.clone();
if !state.local_test_unit_balances.contains_key(account_id) {
return Err(DevnetError::LocalTestUnitBalanceMissing(account_id.clone()));
}
credit_asset_units(state, account_id, asset_id, *amount_units)?;
+ if live_credit {
+ mark_asset_balance_value_bearing(state, account_id, asset_id);
+ }
let event_ref = BridgeEventReference {
source_chain_id: source_chain_id.clone(),
@@ -2372,9 +2564,11 @@ pub fn apply_transaction(state: &mut ChainState, tx: &Transaction) -> Result<(),
memo: memo.clone(),
credited_at_block: state.next_block_number,
status: "applied".to_string(),
- local_only: true,
- production_ready: false,
- no_value: true,
+ local_only: *local_only,
+ production_ready: *production_ready,
+ no_value: !live_credit,
+ confirmation_proof: confirmation_proof.clone(),
+ pilot_cap_proof: pilot_cap_proof.clone(),
},
);
state.bridge_credit_receipts.insert(
@@ -2389,9 +2583,15 @@ pub fn apply_transaction(state: &mut ChainState, tx: &Transaction) -> Result<(),
replay_key: replay_key.clone(),
status: "applied".to_string(),
included_at_block: state.next_block_number,
- evidence: "base-event-reference-and-replay-key".to_string(),
- local_only: true,
- production_ready: false,
+ evidence: if live_credit {
+ "base-event-confirmation-and-pilot-cap-proof".to_string()
+ } else {
+ "base-event-reference-and-replay-key".to_string()
+ },
+ local_only: *local_only,
+ production_ready: *production_ready,
+ confirmation_proof: confirmation_proof.clone(),
+ pilot_cap_proof: pilot_cap_proof.clone(),
},
);
state.bridge_replay_index.insert(
@@ -2800,6 +3000,86 @@ fn ensure_expected_id(kind: &str, actual: &str, expected: &str) -> Result<(), De
Ok(())
}
+fn ensure_valid_bridge_runtime_mode(
+ local_only: bool,
+ production_ready: bool,
+ id: &str,
+) -> Result<(), DevnetError> {
+ match (local_only, production_ready) {
+ (true, false) | (false, true) => Ok(()),
+ _ => Err(DevnetError::BridgeCreditInvalidRuntimeMode(id.to_string())),
+ }
+}
+
+fn is_live_bridge_credit(local_only: bool, production_ready: bool) -> bool {
+ !local_only && production_ready
+}
+
+fn validate_live_bridge_credit(
+ bridge_credit_id: &str,
+ source_chain_id: &str,
+ source_token: &str,
+ amount_units: u64,
+ confirmation_proof: Option<&BridgeConfirmationProof>,
+ pilot_cap_proof: Option<&BridgePilotCapProof>,
+) -> Result<(), DevnetError> {
+ if source_chain_id != BASE_MAINNET_CHAIN_ID {
+ return Err(DevnetError::BridgeCreditWrongSourceChain(
+ source_chain_id.to_string(),
+ ));
+ }
+ let confirmation_proof = confirmation_proof.ok_or_else(|| {
+ DevnetError::BridgeCreditMissingConfirmationProof(bridge_credit_id.to_string())
+ })?;
+ if !confirmation_proof.satisfied {
+ return Err(DevnetError::BridgeCreditConfirmationUnsatisfied(
+ bridge_credit_id.to_string(),
+ ));
+ }
+
+ let pilot_cap_proof = pilot_cap_proof.ok_or_else(|| {
+ DevnetError::BridgeCreditMissingPilotCapProof(bridge_credit_id.to_string())
+ })?;
+ if !pilot_cap_proof.approved_lockbox
+ || !pilot_cap_proof.operator_acknowledged
+ || !pilot_cap_proof.no_secrets
+ {
+ return Err(DevnetError::BridgeCreditMissingPilotCapProof(
+ bridge_credit_id.to_string(),
+ ));
+ }
+ if amount_units > pilot_cap_proof.max_deposit_amount_units
+ || amount_units > pilot_cap_proof.total_cap_amount_units
+ {
+ return Err(DevnetError::BridgeCreditAmountExceedsPilotCap(
+ bridge_credit_id.to_string(),
+ ));
+ }
+ if !pilot_cap_proof
+ .supported_tokens
+ .iter()
+ .any(|token| token.eq_ignore_ascii_case(source_token))
+ {
+ return Err(DevnetError::BridgeCreditUnsupportedAsset(
+ source_token.to_string(),
+ ));
+ }
+ Ok(())
+}
+
+fn mark_asset_balance_value_bearing(state: &mut ChainState, account_id: &str, asset_id: &str) {
+ if asset_id == LOCAL_TEST_UNIT_ASSET_ID {
+ if let Some(balance) = state.local_test_unit_balances.get_mut(account_id) {
+ balance.no_value = false;
+ }
+ return;
+ }
+ let balance_id = deterministic_token_balance_id(asset_id, account_id);
+ if let Some(balance) = state.token_balances.get_mut(&balance_id) {
+ balance.no_value = false;
+ }
+}
+
fn ensure_pool_assets_are_valid(
state: &ChainState,
base_asset_id: &str,
diff --git a/crates/flowmemory-devnet/src/storage.rs b/crates/flowmemory-devnet/src/storage.rs
index 7e9db8e2..1cd4d537 100644
--- a/crates/flowmemory-devnet/src/storage.rs
+++ b/crates/flowmemory-devnet/src/storage.rs
@@ -1,7 +1,10 @@
use crate::model::{ChainState, genesis_state};
use anyhow::{Context, Result};
+use serde::Serialize;
use std::fs;
+use std::io::Write;
use std::path::{Path, PathBuf};
+use std::time::{SystemTime, UNIX_EPOCH};
pub const DEFAULT_STATE_PATH: &str = "devnet/local/state.json";
@@ -25,13 +28,48 @@ pub fn load_or_genesis(path: &Path) -> Result {
}
pub fn save_state(path: &Path, state: &ChainState) -> Result<()> {
+ write_json_pretty(path, state)
+ .with_context(|| format!("failed to write state file {}", path.display()))
+}
+
+pub fn write_json_pretty(path: &Path, value: &T) -> Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.with_context(|| format!("failed to create state directory {}", parent.display()))?;
}
- let body = serde_json::to_string_pretty(state)?;
- fs::write(path, format!("{body}\n"))
- .with_context(|| format!("failed to write state file {}", path.display()))
+ let body = serde_json::to_string_pretty(value)?;
+ write_text_atomic(path, &format!("{body}\n"))
+}
+
+fn write_text_atomic(path: &Path, body: &str) -> Result<()> {
+ let parent = path.parent().unwrap_or_else(|| Path::new("."));
+ let file_name = path
+ .file_name()
+ .and_then(|name| name.to_str())
+ .unwrap_or("state.json");
+ let nonce = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .map(|duration| duration.as_nanos())
+ .unwrap_or_default();
+ let tmp_path = parent.join(format!(".{file_name}.{}.{}.tmp", std::process::id(), nonce));
+
+ {
+ let mut file = fs::File::create(&tmp_path)
+ .with_context(|| format!("failed to create temp file {}", tmp_path.display()))?;
+ file.write_all(body.as_bytes())
+ .with_context(|| format!("failed to write temp file {}", tmp_path.display()))?;
+ file.sync_all()
+ .with_context(|| format!("failed to sync temp file {}", tmp_path.display()))?;
+ }
+
+ replace_file(&tmp_path, path).with_context(|| {
+ let _ = fs::remove_file(&tmp_path);
+ format!(
+ "failed to replace {} with {}",
+ path.display(),
+ tmp_path.display()
+ )
+ })
}
pub fn reset_state(path: &Path) -> Result {
@@ -45,3 +83,47 @@ pub fn reset_state(path: &Path) -> Result {
save_state(path, &state)?;
Ok(state)
}
+
+#[cfg(windows)]
+fn replace_file(from: &Path, to: &Path) -> Result<()> {
+ use std::os::windows::ffi::OsStrExt;
+
+ const MOVEFILE_REPLACE_EXISTING: u32 = 0x1;
+ const MOVEFILE_WRITE_THROUGH: u32 = 0x8;
+
+ unsafe extern "system" {
+ fn MoveFileExW(
+ lpExistingFileName: *const u16,
+ lpNewFileName: *const u16,
+ dwFlags: u32,
+ ) -> i32;
+ }
+
+ let from_wide = from
+ .as_os_str()
+ .encode_wide()
+ .chain(std::iter::once(0))
+ .collect::>();
+ let to_wide = to
+ .as_os_str()
+ .encode_wide()
+ .chain(std::iter::once(0))
+ .collect::>();
+ let moved = unsafe {
+ MoveFileExW(
+ from_wide.as_ptr(),
+ to_wide.as_ptr(),
+ MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH,
+ )
+ };
+ if moved == 0 {
+ return Err(std::io::Error::last_os_error())
+ .with_context(|| format!("failed to atomically replace {}", to.display()));
+ }
+ Ok(())
+}
+
+#[cfg(not(windows))]
+fn replace_file(from: &Path, to: &Path) -> Result<()> {
+ fs::rename(from, to).with_context(|| format!("failed to rename {}", from.display()))
+}
diff --git a/crates/flowmemory-devnet/tests/devnet_tests.rs b/crates/flowmemory-devnet/tests/devnet_tests.rs
index 03ec76db..81df0b9f 100644
--- a/crates/flowmemory-devnet/tests/devnet_tests.rs
+++ b/crates/flowmemory-devnet/tests/devnet_tests.rs
@@ -1,8 +1,10 @@
use flowmemory_devnet::model::{
- BRIDGE_PILOT_ACCOUNT_OWNER, DevnetError, FLOWPULSE_TOPIC0, LOCAL_TEST_UNIT_ASSET_ID,
- Transaction, ZERO_HASH, apply_transaction, bridge_event_reference_key, build_block,
- demo_transactions, deterministic_bridge_account_id, deterministic_bridge_account_mapping_id,
- deterministic_bridge_asset_mapping_id, deterministic_liquidity_id,
+ BASE_MAINNET_CHAIN_ID, BRIDGE_PILOT_ACCOUNT_OWNER, BRIDGE_RUNTIME_AMOUNT_STORAGE,
+ BridgeConfirmationProof, BridgePilotCapProof, DevnetError, FLOWPULSE_TOPIC0,
+ LOCAL_TEST_UNIT_ASSET_ID, Transaction, ZERO_HASH, apply_transaction,
+ bridge_event_reference_key, build_block, demo_transactions,
+ deterministic_bridge_account_mapping_id, deterministic_bridge_asset_mapping_id,
+ deterministic_bridge_credit_id, deterministic_bridge_replay_key, deterministic_liquidity_id,
deterministic_lp_position_id, deterministic_pool_id, deterministic_swap_id,
deterministic_token_balance_id, deterministic_token_id, genesis_state,
product_demo_transactions, queue_transaction, state_map_roots, state_root,
@@ -364,7 +366,7 @@ fn pilot_bridge_credit_maps_asset_account_and_rejects_replay() {
assert_eq!(state.bridge_credit_receipts.len(), 1);
assert_eq!(state.bridge_replay_index.len(), 1);
- let account_id = deterministic_bridge_account_id(PILOT_FLOWCHAIN_RECIPIENT);
+ let account_id = PILOT_FLOWCHAIN_RECIPIENT.to_string();
assert_eq!(
state.local_test_unit_balances[&account_id].units,
PILOT_BRIDGE_AMOUNT
@@ -425,6 +427,405 @@ fn pilot_bridge_credit_maps_asset_account_and_rejects_replay() {
);
}
+#[test]
+fn live_bridge_credit_applies_exact_amount_and_rejects_replay() {
+ let mut state = genesis_state();
+ let account_id =
+ setup_live_bridge_account(&mut state, BASE_MAINNET_CHAIN_ID, LIVE_SOURCE_TOKEN);
+ let before_root = state_root(&state);
+ let before_balance = state.local_test_unit_balances[&account_id].units;
+ let credit_tx = live_bridge_credit_tx(
+ 0,
+ LIVE_BRIDGE_AMOUNT,
+ BASE_MAINNET_CHAIN_ID,
+ LIVE_SOURCE_TOKEN,
+ LIVE_BRIDGE_AMOUNT,
+ LIVE_BRIDGE_AMOUNT,
+ &[LIVE_SOURCE_TOKEN],
+ true,
+ None,
+ );
+ let Transaction::CreditBridgeFromBaseEvent {
+ bridge_credit_id,
+ replay_key,
+ ..
+ } = &credit_tx
+ else {
+ unreachable!("helper builds bridge credit")
+ };
+
+ apply_transaction(&mut state, &credit_tx).unwrap();
+
+ let balance = state
+ .local_test_unit_balances
+ .get(&account_id)
+ .expect("credited live balance");
+ assert_eq!(balance.units - before_balance, LIVE_BRIDGE_AMOUNT);
+ assert!(!balance.no_value);
+ let credit = state
+ .bridge_credits
+ .get(bridge_credit_id)
+ .expect("live bridge credit");
+ assert_eq!(credit.amount_units, LIVE_BRIDGE_AMOUNT);
+ assert_eq!(credit.account_id, account_id);
+ assert!(credit.production_ready);
+ assert!(!credit.local_only);
+ assert!(!credit.no_value);
+ assert!(credit.confirmation_proof.is_some());
+ assert!(credit.pilot_cap_proof.is_some());
+ assert_ne!(before_root, state_root(&state));
+
+ let replay_root_before = state_root(&state);
+ let replay = apply_transaction(&mut state, &credit_tx);
+ assert_eq!(
+ replay,
+ Err(DevnetError::BridgeCreditReplayAlreadyConsumed(
+ replay_key.clone()
+ ))
+ );
+ assert_eq!(replay_root_before, state_root(&state));
+ assert_eq!(
+ state.local_test_unit_balances[&account_id].units,
+ LIVE_BRIDGE_AMOUNT
+ );
+}
+
+#[test]
+fn same_base_tx_with_different_log_index_gets_distinct_live_credits() {
+ let mut state = genesis_state();
+ let account_id =
+ setup_live_bridge_account(&mut state, BASE_MAINNET_CHAIN_ID, LIVE_SOURCE_TOKEN);
+ let first = live_bridge_credit_tx(
+ 0,
+ 11,
+ BASE_MAINNET_CHAIN_ID,
+ LIVE_SOURCE_TOKEN,
+ LIVE_BRIDGE_AMOUNT,
+ LIVE_BRIDGE_AMOUNT,
+ &[LIVE_SOURCE_TOKEN],
+ true,
+ None,
+ );
+ let second = live_bridge_credit_tx(
+ 1,
+ 17,
+ BASE_MAINNET_CHAIN_ID,
+ LIVE_SOURCE_TOKEN,
+ LIVE_BRIDGE_AMOUNT,
+ LIVE_BRIDGE_AMOUNT,
+ &[LIVE_SOURCE_TOKEN],
+ true,
+ None,
+ );
+ let (first_credit_id, first_replay_key) = bridge_credit_id_and_replay_key(&first);
+ let (second_credit_id, second_replay_key) = bridge_credit_id_and_replay_key(&second);
+
+ apply_transaction(&mut state, &first).unwrap();
+ apply_transaction(&mut state, &second).unwrap();
+
+ assert_ne!(first_credit_id, second_credit_id);
+ assert_ne!(first_replay_key, second_replay_key);
+ assert_eq!(state.bridge_credits.len(), 2);
+ assert_eq!(state.bridge_event_receipt_index.len(), 2);
+ assert_eq!(state.local_test_unit_balances[&account_id].units, 28);
+}
+
+#[test]
+fn live_bridge_credit_rejects_wrong_chain_unsupported_asset_zero_and_bad_identity() {
+ let mut wrong_chain = genesis_state();
+ setup_live_bridge_account(&mut wrong_chain, "84532", LIVE_SOURCE_TOKEN);
+ let wrong_chain_tx = live_bridge_credit_tx(
+ 2,
+ LIVE_BRIDGE_AMOUNT,
+ "84532",
+ LIVE_SOURCE_TOKEN,
+ LIVE_BRIDGE_AMOUNT,
+ LIVE_BRIDGE_AMOUNT,
+ &[LIVE_SOURCE_TOKEN],
+ true,
+ None,
+ );
+ assert_rejected_without_state_change(
+ &mut wrong_chain,
+ &wrong_chain_tx,
+ DevnetError::BridgeCreditWrongSourceChain("84532".to_string()),
+ );
+
+ let mut unsupported_asset = genesis_state();
+ setup_live_bridge_account(
+ &mut unsupported_asset,
+ BASE_MAINNET_CHAIN_ID,
+ UNSUPPORTED_LIVE_SOURCE_TOKEN,
+ );
+ let unsupported_asset_tx = live_bridge_credit_tx(
+ 3,
+ LIVE_BRIDGE_AMOUNT,
+ BASE_MAINNET_CHAIN_ID,
+ UNSUPPORTED_LIVE_SOURCE_TOKEN,
+ LIVE_BRIDGE_AMOUNT,
+ LIVE_BRIDGE_AMOUNT,
+ &[LIVE_SOURCE_TOKEN],
+ true,
+ None,
+ );
+ assert_rejected_without_state_change(
+ &mut unsupported_asset,
+ &unsupported_asset_tx,
+ DevnetError::BridgeCreditUnsupportedAsset(UNSUPPORTED_LIVE_SOURCE_TOKEN.to_string()),
+ );
+
+ let mut zero_amount = genesis_state();
+ setup_live_bridge_account(&mut zero_amount, BASE_MAINNET_CHAIN_ID, LIVE_SOURCE_TOKEN);
+ let zero_amount_tx = live_bridge_credit_tx(
+ 4,
+ 0,
+ BASE_MAINNET_CHAIN_ID,
+ LIVE_SOURCE_TOKEN,
+ LIVE_BRIDGE_AMOUNT,
+ LIVE_BRIDGE_AMOUNT,
+ &[LIVE_SOURCE_TOKEN],
+ true,
+ None,
+ );
+ let (zero_credit_id, _) = bridge_credit_id_and_replay_key(&zero_amount_tx);
+ assert_rejected_without_state_change(
+ &mut zero_amount,
+ &zero_amount_tx,
+ DevnetError::BridgeCreditAmountMustBePositive(zero_credit_id),
+ );
+
+ let mut bad_identity = genesis_state();
+ setup_live_bridge_account(&mut bad_identity, BASE_MAINNET_CHAIN_ID, LIVE_SOURCE_TOKEN);
+ let bad_identity_tx = live_bridge_credit_tx(
+ 5,
+ LIVE_BRIDGE_AMOUNT,
+ BASE_MAINNET_CHAIN_ID,
+ LIVE_SOURCE_TOKEN,
+ LIVE_BRIDGE_AMOUNT,
+ LIVE_BRIDGE_AMOUNT,
+ &[LIVE_SOURCE_TOKEN],
+ true,
+ Some(ZERO_HASH.to_string()),
+ );
+ assert!(matches!(
+ apply_transaction(&mut bad_identity, &bad_identity_tx),
+ Err(DevnetError::DeterministicIdMismatch { kind, .. }) if kind == "bridge credit"
+ ));
+ assert!(bad_identity.bridge_credits.is_empty());
+}
+
+#[test]
+fn live_bridge_credit_rejects_unsatisfied_confirmation_and_pilot_cap_excess() {
+ let mut unsatisfied = genesis_state();
+ setup_live_bridge_account(&mut unsatisfied, BASE_MAINNET_CHAIN_ID, LIVE_SOURCE_TOKEN);
+ let unsatisfied_tx = live_bridge_credit_tx(
+ 6,
+ LIVE_BRIDGE_AMOUNT,
+ BASE_MAINNET_CHAIN_ID,
+ LIVE_SOURCE_TOKEN,
+ LIVE_BRIDGE_AMOUNT,
+ LIVE_BRIDGE_AMOUNT,
+ &[LIVE_SOURCE_TOKEN],
+ false,
+ None,
+ );
+ let (unsatisfied_credit_id, _) = bridge_credit_id_and_replay_key(&unsatisfied_tx);
+ assert_rejected_without_state_change(
+ &mut unsatisfied,
+ &unsatisfied_tx,
+ DevnetError::BridgeCreditConfirmationUnsatisfied(unsatisfied_credit_id),
+ );
+
+ let mut above_cap = genesis_state();
+ setup_live_bridge_account(&mut above_cap, BASE_MAINNET_CHAIN_ID, LIVE_SOURCE_TOKEN);
+ let above_cap_tx = live_bridge_credit_tx(
+ 7,
+ LIVE_BRIDGE_AMOUNT + 1,
+ BASE_MAINNET_CHAIN_ID,
+ LIVE_SOURCE_TOKEN,
+ LIVE_BRIDGE_AMOUNT,
+ LIVE_BRIDGE_AMOUNT,
+ &[LIVE_SOURCE_TOKEN],
+ true,
+ None,
+ );
+ let (above_cap_credit_id, _) = bridge_credit_id_and_replay_key(&above_cap_tx);
+ assert_rejected_without_state_change(
+ &mut above_cap,
+ &above_cap_tx,
+ DevnetError::BridgeCreditAmountExceedsPilotCap(above_cap_credit_id),
+ );
+}
+
+#[test]
+fn live_bridge_credit_u64_max_boundary_is_explicit() {
+ assert_eq!(BRIDGE_RUNTIME_AMOUNT_STORAGE, "u64");
+ let mut state = genesis_state();
+ let account_id =
+ setup_live_bridge_account(&mut state, BASE_MAINNET_CHAIN_ID, LIVE_SOURCE_TOKEN);
+ let max_tx = live_bridge_credit_tx(
+ 8,
+ u64::MAX,
+ BASE_MAINNET_CHAIN_ID,
+ LIVE_SOURCE_TOKEN,
+ u64::MAX,
+ u64::MAX,
+ &[LIVE_SOURCE_TOKEN],
+ true,
+ None,
+ );
+
+ apply_transaction(&mut state, &max_tx).unwrap();
+
+ assert_eq!(state.local_test_unit_balances[&account_id].units, u64::MAX);
+}
+
+#[test]
+fn live_bridge_credit_amount_above_u64_is_rejected_before_state_file_mutation() {
+ let temp = temp_dir("live-bridge-u64-overflow");
+ let state = temp.join("state.json");
+ let handoff = temp.join("overflow-handoff.json");
+ std::fs::write(
+ &handoff,
+ format!(
+ r#"{{
+ "schema": "flowmemory.bridge_runtime_handoff.v0",
+ "credits": [
+ {{
+ "schema": "flowmemory.bridge_credit.v0",
+ "creditId": "{}",
+ "observationId": "{}",
+ "depositId": "{}",
+ "replayKey": "{}",
+ "source": {{
+ "chainId": 8453,
+ "contract": "{}",
+ "txHash": "{}",
+ "logIndex": 9
+ }},
+ "token": "{}",
+ "amount": "18446744073709551616",
+ "flowchainRecipient": "{}",
+ "status": "applied",
+ "localOnly": false,
+ "productionReady": true
+ }}
+ ]
+}}"#,
+ live_credit_id(9, BASE_MAINNET_CHAIN_ID, LIVE_SOURCE_TOKEN),
+ live_observation_id(9, BASE_MAINNET_CHAIN_ID, LIVE_SOURCE_TOKEN),
+ live_deposit_id(9, BASE_MAINNET_CHAIN_ID, LIVE_SOURCE_TOKEN),
+ live_replay_key(9, BASE_MAINNET_CHAIN_ID, LIVE_SOURCE_TOKEN),
+ LIVE_SOURCE_CONTRACT,
+ LIVE_BRIDGE_TX_HASH,
+ LIVE_SOURCE_TOKEN,
+ LIVE_FLOWCHAIN_RECIPIENT
+ ),
+ )
+ .expect("write overflow handoff");
+
+ let status = Command::new(env!("CARGO_BIN_EXE_flowmemory-devnet"))
+ .args([
+ "--state",
+ state.to_str().expect("state path"),
+ "bridge-handoff",
+ "--handoff",
+ handoff.to_str().expect("handoff path"),
+ "--direct",
+ ])
+ .status()
+ .expect("run overflow handoff");
+
+ assert!(!status.success());
+ assert!(!state.exists());
+ std::fs::remove_dir_all(&temp).expect("cleanup temp dir");
+}
+
+#[test]
+fn live_bridge_credit_survives_export_import_restart_and_remains_transferable() {
+ let mut state = genesis_state();
+ let account_id =
+ setup_live_bridge_account(&mut state, BASE_MAINNET_CHAIN_ID, LIVE_SOURCE_TOKEN);
+ let credit_tx = live_bridge_credit_tx(
+ 10,
+ LIVE_BRIDGE_AMOUNT,
+ BASE_MAINNET_CHAIN_ID,
+ LIVE_SOURCE_TOKEN,
+ LIVE_BRIDGE_AMOUNT,
+ LIVE_BRIDGE_AMOUNT,
+ &[LIVE_SOURCE_TOKEN],
+ true,
+ None,
+ );
+ let (credit_id, replay_key) = bridge_credit_id_and_replay_key(&credit_tx);
+ apply_transaction(&mut state, &credit_tx).unwrap();
+ apply_transaction(
+ &mut state,
+ &Transaction::CreateLocalTestUnitBalance {
+ account_id: LIVE_TRANSFER_RECIPIENT.to_string(),
+ owner: "operator:live-recipient".to_string(),
+ },
+ )
+ .unwrap();
+ apply_transaction(
+ &mut state,
+ &Transaction::TransferLocalTestUnits {
+ transfer_id: "transfer:live:001".to_string(),
+ from_account_id: account_id.clone(),
+ to_account_id: LIVE_TRANSFER_RECIPIENT.to_string(),
+ amount_units: LIVE_TRANSFER_AMOUNT,
+ memo: "live bridge credited balance transfer".to_string(),
+ },
+ )
+ .unwrap();
+ assert_eq!(
+ state.local_test_unit_balances[&account_id].units,
+ LIVE_BRIDGE_AMOUNT - LIVE_TRANSFER_AMOUNT
+ );
+ assert_eq!(
+ state.local_test_unit_balances[LIVE_TRANSFER_RECIPIENT].units,
+ LIVE_TRANSFER_AMOUNT
+ );
+ assert!(!state.local_test_unit_balances[&account_id].no_value);
+ assert!(!state.local_test_unit_balances[LIVE_TRANSFER_RECIPIENT].no_value);
+ assert!(!state.balance_transfers["transfer:live:001"].no_value);
+
+ let exported = serde_json::to_string(&state).expect("export state");
+ let mut imported: flowmemory_devnet::model::ChainState =
+ serde_json::from_str(&exported).expect("import state");
+ assert_eq!(state_root(&state), state_root(&imported));
+ build_block(&mut imported);
+ assert!(imported.bridge_credits.contains_key(&credit_id));
+ assert!(imported.bridge_replay_index.contains_key(&replay_key));
+ assert_eq!(
+ apply_transaction(&mut imported, &credit_tx),
+ Err(DevnetError::BridgeCreditReplayAlreadyConsumed(replay_key))
+ );
+ apply_transaction(
+ &mut imported,
+ &Transaction::CreateLocalTestUnitBalance {
+ account_id: "local-account:live-recipient-2".to_string(),
+ owner: "operator:live-recipient-2".to_string(),
+ },
+ )
+ .unwrap();
+ apply_transaction(
+ &mut imported,
+ &Transaction::TransferLocalTestUnits {
+ transfer_id: "transfer:live:post-import".to_string(),
+ from_account_id: account_id.clone(),
+ to_account_id: "local-account:live-recipient-2".to_string(),
+ amount_units: 1,
+ memo: "post-import transfer".to_string(),
+ },
+ )
+ .unwrap();
+ assert_eq!(
+ imported.local_test_unit_balances[&account_id].units,
+ LIVE_BRIDGE_AMOUNT - LIVE_TRANSFER_AMOUNT - 1
+ );
+}
+
#[test]
fn token_launch_pool_liquidity_swap_and_remove_update_product_state() {
let mut state = genesis_state();
@@ -1765,7 +2166,7 @@ const PILOT_BRIDGE_TX_HASH: &str =
const PILOT_BRIDGE_LOG_INDEX: u64 = 0;
const PILOT_SOURCE_TOKEN: &str = "0x3333333333333333333333333333333333333333";
const PILOT_FLOWCHAIN_RECIPIENT: &str =
- "0x5555555555555555555555555555555555555555555555555555555555555555";
+ "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
const PILOT_OBSERVATION_ID: &str =
"0x0430f0f7818add19ccd9037dcf6e50d75c1fb0fac0441f9b042c473d1d2d223c";
const PILOT_DEPOSIT_ID: &str = "0x7e3a7f7ab7dc9b07d762c1f2fce315cf0c08f1a7e854b4dbcb2359efcb9cb269";
@@ -1773,9 +2174,19 @@ const PILOT_REPLAY_KEY: &str = "0x9c97eb0fa65cb3eec9274cb0c9e925351608e7abe6980f
const PILOT_BRIDGE_CREDIT_ID: &str =
"0xff3efb8221533cfc836bffbcee10bdd2d7d4a5615efce9516574245a3b7d74a6";
const PILOT_BRIDGE_AMOUNT: u64 = 20_000_000;
+const LIVE_SOURCE_CONTRACT: &str = "0x1111111111111111111111111111111111111111";
+const LIVE_BRIDGE_TX_HASH: &str =
+ "0x8453000000000000000000000000000000000000000000000000000000000002";
+const LIVE_SOURCE_TOKEN: &str = "0x3333333333333333333333333333333333333333";
+const UNSUPPORTED_LIVE_SOURCE_TOKEN: &str = "0x9999999999999999999999999999999999999999";
+const LIVE_FLOWCHAIN_RECIPIENT: &str =
+ "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
+const LIVE_BRIDGE_AMOUNT: u64 = 20_000_000;
+const LIVE_TRANSFER_AMOUNT: u64 = 7_500_000;
+const LIVE_TRANSFER_RECIPIENT: &str = "local-account:live-recipient";
fn pilot_bridge_setup_and_credit_txs() -> Vec {
- let account_id = deterministic_bridge_account_id(PILOT_FLOWCHAIN_RECIPIENT);
+ let account_id = PILOT_FLOWCHAIN_RECIPIENT.to_string();
let asset_mapping_id = deterministic_bridge_asset_mapping_id(
PILOT_SOURCE_CHAIN_ID,
PILOT_SOURCE_TOKEN,
@@ -1790,12 +2201,16 @@ fn pilot_bridge_setup_and_credit_txs() -> Vec {
source_chain_id: PILOT_SOURCE_CHAIN_ID.to_string(),
source_token: PILOT_SOURCE_TOKEN.to_string(),
local_asset_id: LOCAL_TEST_UNIT_ASSET_ID.to_string(),
+ local_only: true,
+ production_ready: false,
},
Transaction::MapBridgeAccount {
mapping_id: account_mapping_id,
flowchain_recipient: PILOT_FLOWCHAIN_RECIPIENT.to_string(),
account_id: account_id.clone(),
owner: BRIDGE_PILOT_ACCOUNT_OWNER.to_string(),
+ local_only: true,
+ production_ready: false,
},
Transaction::CreateLocalTestUnitBalance {
account_id: account_id.clone(),
@@ -1819,10 +2234,193 @@ fn pilot_bridge_setup_and_credit_txs() -> Vec {
memo: "unit-test pilot bridge handoff".to_string(),
local_only: true,
production_ready: false,
+ confirmation_proof: None,
+ pilot_cap_proof: None,
},
]
}
+fn setup_live_bridge_account(
+ state: &mut flowmemory_devnet::model::ChainState,
+ source_chain_id: &str,
+ source_token: &str,
+) -> String {
+ let account_id = LIVE_FLOWCHAIN_RECIPIENT.to_string();
+ let asset_mapping_id = deterministic_bridge_asset_mapping_id(
+ source_chain_id,
+ source_token,
+ LOCAL_TEST_UNIT_ASSET_ID,
+ );
+ let account_mapping_id =
+ deterministic_bridge_account_mapping_id(LIVE_FLOWCHAIN_RECIPIENT, &account_id);
+ apply_transaction(
+ state,
+ &Transaction::MapBridgeAsset {
+ mapping_id: asset_mapping_id,
+ source_chain_id: source_chain_id.to_string(),
+ source_token: source_token.to_string(),
+ local_asset_id: LOCAL_TEST_UNIT_ASSET_ID.to_string(),
+ local_only: false,
+ production_ready: true,
+ },
+ )
+ .unwrap();
+ apply_transaction(
+ state,
+ &Transaction::MapBridgeAccount {
+ mapping_id: account_mapping_id,
+ flowchain_recipient: LIVE_FLOWCHAIN_RECIPIENT.to_string(),
+ account_id: account_id.clone(),
+ owner: BRIDGE_PILOT_ACCOUNT_OWNER.to_string(),
+ local_only: false,
+ production_ready: true,
+ },
+ )
+ .unwrap();
+ apply_transaction(
+ state,
+ &Transaction::CreateLocalTestUnitBalance {
+ account_id: account_id.clone(),
+ owner: BRIDGE_PILOT_ACCOUNT_OWNER.to_string(),
+ },
+ )
+ .unwrap();
+ account_id
+}
+
+fn live_bridge_credit_tx(
+ log_index: u64,
+ amount_units: u64,
+ source_chain_id: &str,
+ source_token: &str,
+ max_deposit_amount_units: u64,
+ total_cap_amount_units: u64,
+ supported_tokens: &[&str],
+ confirmation_satisfied: bool,
+ credit_id_override: Option,
+) -> Transaction {
+ let account_id = LIVE_FLOWCHAIN_RECIPIENT.to_string();
+ let deposit_id = live_deposit_id(log_index, source_chain_id, source_token);
+ let observation_id = live_observation_id(log_index, source_chain_id, source_token);
+ let replay_key = deterministic_bridge_replay_key(
+ source_chain_id,
+ LIVE_SOURCE_CONTRACT,
+ LIVE_BRIDGE_TX_HASH,
+ log_index,
+ &deposit_id,
+ );
+ let credit_id = credit_id_override.unwrap_or_else(|| {
+ deterministic_bridge_credit_id(
+ &observation_id,
+ &deposit_id,
+ &replay_key,
+ source_chain_id,
+ LIVE_SOURCE_CONTRACT,
+ LIVE_BRIDGE_TX_HASH,
+ log_index,
+ )
+ });
+ Transaction::CreditBridgeFromBaseEvent {
+ bridge_credit_id: credit_id.clone(),
+ receipt_id: credit_id,
+ account_id,
+ flowchain_recipient: LIVE_FLOWCHAIN_RECIPIENT.to_string(),
+ asset_id: LOCAL_TEST_UNIT_ASSET_ID.to_string(),
+ source_token: source_token.to_string(),
+ amount_units,
+ source_chain_id: source_chain_id.to_string(),
+ source_contract: LIVE_SOURCE_CONTRACT.to_string(),
+ tx_hash: LIVE_BRIDGE_TX_HASH.to_string(),
+ log_index,
+ deposit_id,
+ observation_id,
+ replay_key,
+ memo: "live Base bridge credit".to_string(),
+ local_only: false,
+ production_ready: true,
+ confirmation_proof: Some(BridgeConfirmationProof {
+ depth: 5,
+ satisfied: confirmation_satisfied,
+ latest_block_number: Some("112".to_string()),
+ required_confirmed_block_number: Some("107".to_string()),
+ requested_to_block: Some("100".to_string()),
+ }),
+ pilot_cap_proof: Some(BridgePilotCapProof {
+ approved_lockbox: true,
+ operator_acknowledged: true,
+ no_secrets: true,
+ max_usd: Some("1".to_string()),
+ max_deposit_amount_units,
+ total_cap_amount_units,
+ pilot_mode_tag: Some(
+ "0x8edc10ba20d09d2f920c2135ea53baaa72ec90df339d57248f096ca150771a6e".to_string(),
+ ),
+ supported_tokens: supported_tokens
+ .iter()
+ .map(|token| token.to_ascii_lowercase())
+ .collect(),
+ }),
+ }
+}
+
+fn live_deposit_id(log_index: u64, source_chain_id: &str, source_token: &str) -> String {
+ keccak_hex(format!("live-deposit:{source_chain_id}:{source_token}:{log_index}").as_bytes())
+}
+
+fn live_observation_id(log_index: u64, source_chain_id: &str, source_token: &str) -> String {
+ keccak_hex(format!("live-observation:{source_chain_id}:{source_token}:{log_index}").as_bytes())
+}
+
+fn live_replay_key(log_index: u64, source_chain_id: &str, source_token: &str) -> String {
+ deterministic_bridge_replay_key(
+ source_chain_id,
+ LIVE_SOURCE_CONTRACT,
+ LIVE_BRIDGE_TX_HASH,
+ log_index,
+ &live_deposit_id(log_index, source_chain_id, source_token),
+ )
+}
+
+fn live_credit_id(log_index: u64, source_chain_id: &str, source_token: &str) -> String {
+ let observation_id = live_observation_id(log_index, source_chain_id, source_token);
+ let deposit_id = live_deposit_id(log_index, source_chain_id, source_token);
+ let replay_key = live_replay_key(log_index, source_chain_id, source_token);
+ deterministic_bridge_credit_id(
+ &observation_id,
+ &deposit_id,
+ &replay_key,
+ source_chain_id,
+ LIVE_SOURCE_CONTRACT,
+ LIVE_BRIDGE_TX_HASH,
+ log_index,
+ )
+}
+
+fn bridge_credit_id_and_replay_key(tx: &Transaction) -> (String, String) {
+ let Transaction::CreditBridgeFromBaseEvent {
+ bridge_credit_id,
+ replay_key,
+ ..
+ } = tx
+ else {
+ unreachable!("expected bridge credit transaction")
+ };
+ (bridge_credit_id.clone(), replay_key.clone())
+}
+
+fn assert_rejected_without_state_change(
+ state: &mut flowmemory_devnet::model::ChainState,
+ tx: &Transaction,
+ expected: DevnetError,
+) {
+ let before = state_root(state);
+ assert_eq!(apply_transaction(state, tx), Err(expected));
+ assert_eq!(state_root(state), before);
+ assert!(state.bridge_credits.is_empty());
+ assert!(state.bridge_credit_receipts.is_empty());
+ assert!(state.bridge_replay_index.is_empty());
+}
+
fn repo_root() -> std::path::PathBuf {
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
diff --git a/crypto/.gitignore b/crypto/.gitignore
index 4e322039..fb1168d2 100644
--- a/crypto/.gitignore
+++ b/crypto/.gitignore
@@ -2,5 +2,6 @@ node_modules/
coverage/
.nyc_output/
.wallet/
+devnet/local/
*.vault.local.json
*.wallet.local.json
diff --git a/crypto/fixtures/product-testnet-transactions.json b/crypto/fixtures/product-testnet-transactions.json
index b0dac2fe..ea392195 100644
--- a/crypto/fixtures/product-testnet-transactions.json
+++ b/crypto/fixtures/product-testnet-transactions.json
@@ -245,6 +245,12 @@
"sourceChainId": 31337,
"destinationChainId": 84532,
"token": "0x3333333333333333333333333333333333333333",
+ "asset": {
+ "sourceChainId": 31337,
+ "sourceToken": "0x3333333333333333333333333333333333333333",
+ "destinationAssetId": "0xfb24ae62df7271f2218d0ac5cc0df832209752d4a03483fc5c9b25b548998a07",
+ "decimals": 18
+ },
"amount": "5000000",
"flowchainAccount": "0x43df29be6dcbf171c042f86227b45acf58938e981c539759335d76798778fde3",
"baseRecipient": "0x4444444444444444444444444444444444444444",
@@ -264,6 +270,12 @@
"sourceChainId": 31337,
"destinationChainId": 84532,
"token": "0x3333333333333333333333333333333333333333",
+ "asset": {
+ "sourceChainId": 31337,
+ "sourceToken": "0x3333333333333333333333333333333333333333",
+ "destinationAssetId": "0xfb24ae62df7271f2218d0ac5cc0df832209752d4a03483fc5c9b25b548998a07",
+ "decimals": 18
+ },
"amount": "5000000",
"flowchainAccount": "0x43df29be6dcbf171c042f86227b45acf58938e981c539759335d76798778fde3",
"baseRecipient": "0x4444444444444444444444444444444444444444",
@@ -593,9 +605,9 @@
"schemaPath": "../../schemas/flowmemory/local-transaction-envelope.schema.json",
"function": "localTransactionEnvelopeHash",
"expected": {
- "envelopeId": "0x6ba95e7e98808cdeef4cd7aeae9cd88410ae66087d5dd4c5997e440923760871",
- "signingDigest": "0x549a8d18f1531138ac49e4a4afb869b041222ce8c1c2a611fa3b4ad0ce48589c",
- "payloadHash": "0xd24bfc9c616f340a8cc26e665043537bbf57dbcf5b2d5e65c885a8de517f0af4"
+ "envelopeId": "0x64dcf916c58ffc75d052410acd64955cd79eeed81575cdcfc858926b1cedad45",
+ "signingDigest": "0xa5e7b46843ef07f50446155fa57c7b6275a7e03d022c23778923919e48885134",
+ "payloadHash": "0xadf2f55a0f5d7221f7e0bcff127e031a6552dce5be88ec562dc868bce8bd355d"
},
"input": {
"chainId": "31337",
@@ -604,14 +616,14 @@
"signerKeyId": "0x8ebb83dff68eac8dd9a13ad4ef6e771b696d492ee7489c20ec8ea183fd66c208",
"signerRole": 2,
"nonce": "8",
- "payloadHash": "0xd24bfc9c616f340a8cc26e665043537bbf57dbcf5b2d5e65c885a8de517f0af4",
+ "payloadHash": "0xadf2f55a0f5d7221f7e0bcff127e031a6552dce5be88ec562dc868bce8bd355d",
"objectId": "0x7be29323cef21fc50c828a7e18d125056c82e5735cb7e1d47f9efc40e2b77c9a",
"objectTypeHash": "0xf54d0a70abd4a36a0d64f6b0704100bc0e86970d2d72d79613d7a033d083a66a",
"issuedAtUnixMs": "1778702400000"
},
"envelope": {
"schema": "flowchain.local_transaction_envelope.v0",
- "envelopeId": "0x6ba95e7e98808cdeef4cd7aeae9cd88410ae66087d5dd4c5997e440923760871",
+ "envelopeId": "0x64dcf916c58ffc75d052410acd64955cd79eeed81575cdcfc858926b1cedad45",
"domain": "flowchain.local-alpha.v0.local-transaction-envelope:chain:31337",
"domainSeparator": "0xff516fcab184623fe60f12b4d1e0430a57e6d18685d1356887ada50f5f20b36c",
"chainId": "31337",
@@ -625,10 +637,10 @@
"objectType": "bridge_withdrawal_intent",
"objectTypeHash": "0xf54d0a70abd4a36a0d64f6b0704100bc0e86970d2d72d79613d7a033d083a66a",
"objectId": "0x7be29323cef21fc50c828a7e18d125056c82e5735cb7e1d47f9efc40e2b77c9a",
- "payloadHash": "0xd24bfc9c616f340a8cc26e665043537bbf57dbcf5b2d5e65c885a8de517f0af4",
+ "payloadHash": "0xadf2f55a0f5d7221f7e0bcff127e031a6552dce5be88ec562dc868bce8bd355d",
"issuedAtUnixMs": "1778702400000",
- "signingDigest": "0x549a8d18f1531138ac49e4a4afb869b041222ce8c1c2a611fa3b4ad0ce48589c",
- "signature": "0xd7fb65246dae96d3676db6b13eef6ec4fa4ae823e47d91c51f1b788e1afdf3ac0496321ff302de87ca52c2ca49cffb81543261b89e31f23b4200790d844c0174"
+ "signingDigest": "0xa5e7b46843ef07f50446155fa57c7b6275a7e03d022c23778923919e48885134",
+ "signature": "0xb68abd0a2c97bd04aeb112ef8619135a800e15f54b69a4329d27b72a3f2cdbc003b9e5df69c674eecfe8c21ec420fb4b93c833fb7e7ba47cfb89921c0a102a7d"
}
}
],
diff --git a/crypto/fixtures/production-l1-vectors.json b/crypto/fixtures/production-l1-vectors.json
new file mode 100644
index 00000000..972b668b
--- /dev/null
+++ b/crypto/fixtures/production-l1-vectors.json
@@ -0,0 +1,1166 @@
+{
+ "schema": "flowmemory.crypto.production-l1-vectors.v0",
+ "chainId": "31337",
+ "networkProfile": "local-chain",
+ "issuedAtUnixMs": "1778702400000",
+ "expiresAtUnixMs": "1778706000000",
+ "boundary": "Deterministic local/private production-L1-shaped crypto vectors. Fixtures contain public test metadata, documents, signatures, and expected validation failures only.",
+ "accounts": {
+ "user": {
+ "schema": "flowchain.public_account_metadata.v0",
+ "label": "production-l1-user",
+ "role": "user",
+ "roleCode": 10,
+ "roleGated": false,
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "publicKey": "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
+ "publicKeyHash": "0xb1d6c5d0b4324108b4e94bd2defa719507f6d0ca0bcbffd934748b6a087edecb",
+ "address": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "accountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "signerKeyId": "0x2fc3666dfc5634ead760c895a14fda3afc55b342794962265c8bf7e3deb9e096",
+ "createdAtUnixMs": "1778702400000",
+ "active": true
+ },
+ "recipient": {
+ "schema": "flowchain.public_account_metadata.v0",
+ "label": "production-l1-user",
+ "role": "user",
+ "roleCode": 10,
+ "roleGated": false,
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "publicKey": "0x02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
+ "publicKeyHash": "0x22e038c39f4c3424adc64bca62eabd4280afbc8eb70019f9c59d1324b671dcb2",
+ "address": "0xa58f4a2b64d9c5287190a5eecade24044dcc1a25",
+ "accountId": "0xfc3fd2b4d0c7a318201db17cd10f517d9f218defe1f3a9fd243692d156d70dd2",
+ "signerKeyId": "0xe36caee388b1f4f5c8e02476dd75545a886db1e00d9a7bad9c88f83ba73ea8c7",
+ "createdAtUnixMs": "1778702400000",
+ "active": true
+ },
+ "validator": {
+ "schema": "flowchain.public_account_metadata.v0",
+ "label": "production-l1-validator",
+ "role": "validator",
+ "roleCode": 11,
+ "roleGated": true,
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "publicKey": "0x02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
+ "publicKeyHash": "0x7241cc1ffb80449f623c84798b9546ad2e164fe762f12369081fa2138063440f",
+ "address": "0x7458689c2bda5e6dffc26967b9cccf5b83bade66",
+ "accountId": "0x18064db99c33bd271aa2f94835f67686d609463964b9c5ea86d98071b1111c9d",
+ "signerKeyId": "0xce800048eb202fba5ee063466b35f7375e8ea0b2ac85eaa047abece7220e70dc",
+ "createdAtUnixMs": "1778702400000",
+ "active": true
+ },
+ "bridgeRelayer": {
+ "schema": "flowchain.public_account_metadata.v0",
+ "label": "production-l1-bridgeRelayer",
+ "role": "bridgeRelayer",
+ "roleCode": 12,
+ "roleGated": true,
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "publicKey": "0x02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13",
+ "publicKeyHash": "0xdcb8568a38409e812848ce118d114fad2f14dc41709612a39fecb69446041670",
+ "address": "0x3411d1ee158d4c87f872469953e4957ffeacddcc",
+ "accountId": "0x6124d765a1b840a520919a09c35bbda21f7a9efc896ca329c4c69af537838c27",
+ "signerKeyId": "0xd9f39d8141642633edd42419a70283271ad9a6f26371747c1d9beafcaf9f3454",
+ "createdAtUnixMs": "1778702400000",
+ "active": true
+ },
+ "bridgeReleaseAuthority": {
+ "schema": "flowchain.public_account_metadata.v0",
+ "label": "production-l1-bridgeReleaseAuthority",
+ "role": "bridgeReleaseAuthority",
+ "roleCode": 13,
+ "roleGated": true,
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "publicKey": "0x022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4",
+ "publicKeyHash": "0x3478985eae1eee2fbb32cfeb703c4fae860e6e300a08b5afbf23424f7fd582e6",
+ "address": "0xb8d39c1dc062e9ce826020d0bf0ac2a3d92c52a3",
+ "accountId": "0x40ea93e387b044dbb56c9e543ec503857a31c45aead019cf6088e60f1ceb2ac1",
+ "signerKeyId": "0x4ae4fee7df34c54114adc91abe9fe874839b9fbbb23186939256b7681ad5cf67",
+ "createdAtUnixMs": "1778702400000",
+ "active": true
+ },
+ "emergencyOperator": {
+ "schema": "flowchain.public_account_metadata.v0",
+ "label": "production-l1-emergencyOperator",
+ "role": "emergencyOperator",
+ "roleCode": 14,
+ "roleGated": true,
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "publicKey": "0x03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556",
+ "publicKeyHash": "0x0414c6f06c44f3f4b47e72161f03ec34ca7fe59dc62c6e7558169e728914440e",
+ "address": "0x07e7512973b933165e6543f50aef2593c705d625",
+ "accountId": "0x6697720bec4962fccaaf3e098e6dfe38cae303dda87ac84863abd84b3bef8dec",
+ "signerKeyId": "0x957363d7aed4146b13afb78f101a7ac6682901b3d6ba6e36164073959c8be5fd",
+ "createdAtUnixMs": "1778702400000",
+ "active": true
+ }
+ },
+ "bridgeSourceEvent": {
+ "sourceChainId": "8453",
+ "lockbox": "0x1111111111111111111111111111111111111111",
+ "token": "0x2222222222222222222222222222222222222222",
+ "depositor": "0x3333333333333333333333333333333333333333",
+ "recipient": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "amount": "25000000",
+ "txHash": "0xf5841799df5ea9c2b8dfcfcbbd5fb6915a68075d29b2ee91bc9bbf1d792143f9",
+ "logIndex": "7",
+ "blockNumber": "45955540",
+ "eventNonce": "1"
+ },
+ "hashHelpers": {
+ "transactionId": "0x8434b0e00b03709b812c040565671c17a0e99922c556530c2a7ec882fb27fe78",
+ "blockHash": "0xf2cac01d94c98df96a689e9462828a46957853818fb6ac18ddcb0b886c513de9",
+ "txRoot": "0x0102ac1e88b75e52263d799e80ab3fec362ebaba16f9a028847c1142f307fa61",
+ "receiptRoot": "0xda05aba2e8eb534fe3ddfd41f9943427383451da414360f653ded77d7788dc91",
+ "eventRoot": "0x79f1cea508f73aabb709ced7e26c7766461aed99d83755bf46e39985a5a46d86",
+ "accountStateRoot": "0x940d2538669b56dc34b7dc946c09e928f24298673423d57df8779c705cc02e8b",
+ "tokenStateRoot": "0x37da78c25557c97df14386cc788b9590061764c35c460c8f4efc189ef2232962",
+ "dexStateRoot": "0x63be9d3a29c288d4f2dcef45e99cbd93875a902d3765ed56985b2748fc942d8b",
+ "bridgeObservationId": "0x738708e90f51749d433ad593bb5d10a1d6c34fe7470e418007f261b295286d0e",
+ "bridgeCreditId": "0x3c14dea922babb89523ff15036914734f22933eaeb87ba15b4fed0c4044763e3",
+ "withdrawalIntentId": "0x285142e2ab4c97d1a2793ce1189337a2fe635175d0a9443a91b9e8eebd26d935",
+ "finalityReceiptId": "0x2a9a8f1c26070d56176e9c07b8e35a54a9aa8ee7dada33a2d279dd6d1d748fcc",
+ "replayKeys": {
+ "accountNonce": "0x9e12ec4c6a69f58e84a13b8e105ed70f2c88a24ffa6304782fa87137465f59de",
+ "roleScopedNonce": "0xd80523d183e98157147281a1e57ecb76a11616c3fbd28977061147eda9101599",
+ "bridgeSourceEvent": "0x738708e90f51749d433ad593bb5d10a1d6c34fe7470e418007f261b295286d0e",
+ "withdrawalIntent": "0x285142e2ab4c97d1a2793ce1189337a2fe635175d0a9443a91b9e8eebd26d935",
+ "finalityVote": "0x467821617cc7eb72fa76c03e85d4ed95ff222624a894c2ff4dfe8977275730ea"
+ }
+ },
+ "positive": [
+ {
+ "name": "wallet-transfer",
+ "document": {
+ "schema": "flowchain.product_transfer.v0",
+ "transferId": "0x369b6f050b5ae930fa5d4af76c785bdef58e3b9143e75c302cf4d05e4ad11c12",
+ "fromAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "toAccountId": "0xfc3fd2b4d0c7a318201db17cd10f517d9f218defe1f3a9fd243692d156d70dd2",
+ "assetId": "0xd9e00f87e32bb273960e26dabf7a0632487b3c100f97126ce6e5f6c7500336ed",
+ "amount": "1000000",
+ "accountNonce": "1",
+ "deadlineBlock": "120",
+ "memoHash": "0xbb58cac7e385eacd5944e7ce0a8d3d4666e024a021fc42380afd52b877f1f4de"
+ },
+ "envelope": {
+ "schema": "flowchain.local_transaction_envelope.v0",
+ "schemaVersion": 1,
+ "networkProfile": "local-chain",
+ "networkProfileHash": "0xbd64b702ecdbcab44fabefc7e5836219cde02f5d6a3f0abc0026dcdb9b46a56a",
+ "envelopeId": "0x99f892b8621a80b34ee71cc6585c3b453212c0af6ff01e02bb26d98a18d3fe7b",
+ "domain": "flowchain.production-l1.v0.transaction-envelope:profile:local-chain:chain:31337",
+ "domainSeparator": "0x6e2e8ddd3eba28f318b038b64534e672e57324225ac7e8ae19729dfda1f7a2a8",
+ "chainId": "31337",
+ "nonce": "1",
+ "signerId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "signerKeyId": "0x2fc3666dfc5634ead760c895a14fda3afc55b342794962265c8bf7e3deb9e096",
+ "signerRole": "user",
+ "signerRoleCode": 10,
+ "publicKey": "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "objectSchema": "flowchain.product_transfer.v0",
+ "objectType": "product_transfer",
+ "payloadType": "wallet_transfer",
+ "payloadTypeHash": "0x14480d5cbd3e854884a374919b17d1915f1951385d1da6ed95686bac3f1d5a64",
+ "objectTypeHash": "0xff7912cf8351cc2aa97d9d9a9e583a3b449c5f9902914185f288c00b83a458a1",
+ "objectId": "0x369b6f050b5ae930fa5d4af76c785bdef58e3b9143e75c302cf4d05e4ad11c12",
+ "payloadHash": "0x262d3c9eb6dca796cd82ac1087642f76fe08445a8415554def3e26fe3c5e48fe",
+ "issuedAtUnixMs": "1778702400000",
+ "expiresAtUnixMs": "1778706000000",
+ "localExecutionCost": {
+ "unit": "local-compute",
+ "amount": "0",
+ "metering": "not-metered-local-private-testnet"
+ },
+ "localExecutionCostHash": "0x6cad238c63ddf255495af76197783d3392f61da8bb61a7943bd4b79ed3745fb8",
+ "fee": {
+ "assetId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "amount": "0",
+ "policy": "no-value-local-private-testnet"
+ },
+ "feeHash": "0x84226d772ddfcad0ed9437e0dbccdf42f4b05660112b16fdd53b089b83781b93",
+ "signatureAlgorithm": "secp256k1-keccak256-eip712-local-v0",
+ "signatureAlgorithmHash": "0x1a6f483c4f6880a162cefa906f3dd9dc228115e89c04c539d63601752d3d57ae",
+ "signingDigest": "0x36150db99bef7d2b0d7c10c9284f3c12840517278bd72f8eee8dfa5c83e1e850",
+ "signature": "0x9aaa403f876be1ce5a9584a7e8d0fedc6610880587ee74b8c6821183d35c40b15b6038c00c1df5a3692bbf01564616ba8ac44727c856f64f913c530dd19499fb",
+ "transactionId": "0x8434b0e00b03709b812c040565671c17a0e99922c556530c2a7ec882fb27fe78"
+ },
+ "expected": {
+ "objectId": "0x369b6f050b5ae930fa5d4af76c785bdef58e3b9143e75c302cf4d05e4ad11c12",
+ "payloadHash": "0x262d3c9eb6dca796cd82ac1087642f76fe08445a8415554def3e26fe3c5e48fe",
+ "envelopeId": "0x99f892b8621a80b34ee71cc6585c3b453212c0af6ff01e02bb26d98a18d3fe7b",
+ "signingDigest": "0x36150db99bef7d2b0d7c10c9284f3c12840517278bd72f8eee8dfa5c83e1e850",
+ "transactionId": "0x8434b0e00b03709b812c040565671c17a0e99922c556530c2a7ec882fb27fe78"
+ },
+ "runtime": {
+ "ok": true,
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "signerAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "payloadHash": "0x262d3c9eb6dca796cd82ac1087642f76fe08445a8415554def3e26fe3c5e48fe",
+ "transactionId": "0x8434b0e00b03709b812c040565671c17a0e99922c556530c2a7ec882fb27fe78",
+ "nonce": "1",
+ "chainId": "31337",
+ "networkProfile": "local-chain"
+ }
+ },
+ {
+ "name": "faucet-test-funding",
+ "document": {
+ "schema": "flowchain.local_balance_record.v0",
+ "balanceRecordId": "0xbd420a17555e7d08c713a288ae4eae7cb16bb9dacbfe5d2e54490540f89bdab3",
+ "accountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "assetId": "0xd9e00f87e32bb273960e26dabf7a0632487b3c100f97126ce6e5f6c7500336ed",
+ "availableAmount": "100000000",
+ "lockedAmount": "0",
+ "lastCreditId": "0x019fe6f7ca21dc8af017bc426bbdcc6dd281e76e81154219cb31955569866070",
+ "lastWithdrawalId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "stateRoot": "0x5d6166b6be00cfac6afa7c82eb8dd6d0ba9052b7e7cdfd2b934d38890085f52a",
+ "updatedAtBlockNumber": "1",
+ "nonce": "0x99eda03585f661caef8f2a64186eff8d72669a5c191a835810624de6964d72c1"
+ },
+ "envelope": {
+ "schema": "flowchain.local_transaction_envelope.v0",
+ "schemaVersion": 1,
+ "networkProfile": "local-chain",
+ "networkProfileHash": "0xbd64b702ecdbcab44fabefc7e5836219cde02f5d6a3f0abc0026dcdb9b46a56a",
+ "envelopeId": "0xf2c49bd4525428b86df8585f605278599c74b8bb5dfcda2836d6b6a17430d94d",
+ "domain": "flowchain.production-l1.v0.transaction-envelope:profile:local-chain:chain:31337",
+ "domainSeparator": "0x6e2e8ddd3eba28f318b038b64534e672e57324225ac7e8ae19729dfda1f7a2a8",
+ "chainId": "31337",
+ "nonce": "2",
+ "signerId": "0x6697720bec4962fccaaf3e098e6dfe38cae303dda87ac84863abd84b3bef8dec",
+ "signerKeyId": "0x957363d7aed4146b13afb78f101a7ac6682901b3d6ba6e36164073959c8be5fd",
+ "signerRole": "emergencyOperator",
+ "signerRoleCode": 14,
+ "publicKey": "0x03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556",
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "signerAddress": "0x07e7512973b933165e6543f50aef2593c705d625",
+ "objectSchema": "flowchain.local_balance_record.v0",
+ "objectType": "local_balance_record",
+ "payloadType": "faucet_test_funding",
+ "payloadTypeHash": "0x50319e8f9d5d22e34fdc5cd02705f68ac04f0376268f30c35b445df65e5c4313",
+ "objectTypeHash": "0x70eff29faa99904e952128eb87e6d95d13548d1b44f4be4c97d036d84811d569",
+ "objectId": "0xbd420a17555e7d08c713a288ae4eae7cb16bb9dacbfe5d2e54490540f89bdab3",
+ "payloadHash": "0xf715e1395239acee9e98f5a4dbe122a231b54a55c90eba8ae93269e8cf3f9b41",
+ "issuedAtUnixMs": "1778702400000",
+ "expiresAtUnixMs": "1778706000000",
+ "localExecutionCost": {
+ "unit": "local-compute",
+ "amount": "0",
+ "metering": "not-metered-local-private-testnet"
+ },
+ "localExecutionCostHash": "0x6cad238c63ddf255495af76197783d3392f61da8bb61a7943bd4b79ed3745fb8",
+ "fee": {
+ "assetId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "amount": "0",
+ "policy": "no-value-local-private-testnet"
+ },
+ "feeHash": "0x84226d772ddfcad0ed9437e0dbccdf42f4b05660112b16fdd53b089b83781b93",
+ "signatureAlgorithm": "secp256k1-keccak256-eip712-local-v0",
+ "signatureAlgorithmHash": "0x1a6f483c4f6880a162cefa906f3dd9dc228115e89c04c539d63601752d3d57ae",
+ "signingDigest": "0x154cd5066783c1ae0e44be7896e001256cb73633ac75657fee6aec293439eb86",
+ "signature": "0xab466f41e774a13925d178674bf31383cd47779a4ebb4ba169a437c1d8603731725cccd91050d57f6706039a7254781fc97cbc634fe286fd8b1369e89db5c638",
+ "transactionId": "0x18249b6d3e8cd4fd151c46c3d24cb484069470deb4f917849efecbfe780f25e1"
+ },
+ "expected": {
+ "objectId": "0xbd420a17555e7d08c713a288ae4eae7cb16bb9dacbfe5d2e54490540f89bdab3",
+ "payloadHash": "0xf715e1395239acee9e98f5a4dbe122a231b54a55c90eba8ae93269e8cf3f9b41",
+ "envelopeId": "0xf2c49bd4525428b86df8585f605278599c74b8bb5dfcda2836d6b6a17430d94d",
+ "signingDigest": "0x154cd5066783c1ae0e44be7896e001256cb73633ac75657fee6aec293439eb86",
+ "transactionId": "0x18249b6d3e8cd4fd151c46c3d24cb484069470deb4f917849efecbfe780f25e1"
+ },
+ "runtime": {
+ "ok": true,
+ "signerAddress": "0x07e7512973b933165e6543f50aef2593c705d625",
+ "signerAccountId": "0x6697720bec4962fccaaf3e098e6dfe38cae303dda87ac84863abd84b3bef8dec",
+ "payloadHash": "0xf715e1395239acee9e98f5a4dbe122a231b54a55c90eba8ae93269e8cf3f9b41",
+ "transactionId": "0x18249b6d3e8cd4fd151c46c3d24cb484069470deb4f917849efecbfe780f25e1",
+ "nonce": "2",
+ "chainId": "31337",
+ "networkProfile": "local-chain"
+ }
+ },
+ {
+ "name": "token-launch",
+ "document": {
+ "schema": "flowchain.product_token_launch.v0",
+ "tokenLaunchId": "0x519b9cdb91ecf5d4160811dda30178ccc158347cbb9e02e8da3c9e296621c941",
+ "issuerAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "tokenId": "0xf75726e75be7373f3b97142c5401342b778a0984950588839ccb28c5ce7a94b5",
+ "symbolHash": "0x08ef225fc646fe5e7560ce95c34f8210e5d852f8265fb6abd8f7ebecbf2995dc",
+ "nameHash": "0x1348a092cba61244feaa3371f8655c41ed2c4f8daee210817aa520029fc69b20",
+ "metadataHash": "0xc95a41ec87e224c5ff8b59bd092baa0229a65c98df633927d4dca391cdf489d8",
+ "decimals": 6,
+ "initialSupply": "1000000000",
+ "recipientAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "accountNonce": "2",
+ "launchPolicyHash": "0x363baf541f4bbcc92b7caba7b0729682d28c3cc6e1b80e31ef0764ed6e74da74"
+ },
+ "envelope": {
+ "schema": "flowchain.local_transaction_envelope.v0",
+ "schemaVersion": 1,
+ "networkProfile": "local-chain",
+ "networkProfileHash": "0xbd64b702ecdbcab44fabefc7e5836219cde02f5d6a3f0abc0026dcdb9b46a56a",
+ "envelopeId": "0x1d6bdea3b4bd32e9d202e72c1ac0957925b199e89076490417d071314438a192",
+ "domain": "flowchain.production-l1.v0.transaction-envelope:profile:local-chain:chain:31337",
+ "domainSeparator": "0x6e2e8ddd3eba28f318b038b64534e672e57324225ac7e8ae19729dfda1f7a2a8",
+ "chainId": "31337",
+ "nonce": "3",
+ "signerId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "signerKeyId": "0x2fc3666dfc5634ead760c895a14fda3afc55b342794962265c8bf7e3deb9e096",
+ "signerRole": "user",
+ "signerRoleCode": 10,
+ "publicKey": "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "objectSchema": "flowchain.product_token_launch.v0",
+ "objectType": "product_token_launch",
+ "payloadType": "token_launch",
+ "payloadTypeHash": "0x4359986e3dc22eaf286230ed5f18779979921ad6e648d481b72973fc6a95406e",
+ "objectTypeHash": "0x282fc65a3a72762f82f7b5296b9589071c401feb5271af7478605fafb7fc9a8b",
+ "objectId": "0x519b9cdb91ecf5d4160811dda30178ccc158347cbb9e02e8da3c9e296621c941",
+ "payloadHash": "0xbc78f4b1460b8ca0a3c0af67305b260bb98b55fc225844bd612d90ca0e5559e8",
+ "issuedAtUnixMs": "1778702400000",
+ "expiresAtUnixMs": "1778706000000",
+ "localExecutionCost": {
+ "unit": "local-compute",
+ "amount": "0",
+ "metering": "not-metered-local-private-testnet"
+ },
+ "localExecutionCostHash": "0x6cad238c63ddf255495af76197783d3392f61da8bb61a7943bd4b79ed3745fb8",
+ "fee": {
+ "assetId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "amount": "0",
+ "policy": "no-value-local-private-testnet"
+ },
+ "feeHash": "0x84226d772ddfcad0ed9437e0dbccdf42f4b05660112b16fdd53b089b83781b93",
+ "signatureAlgorithm": "secp256k1-keccak256-eip712-local-v0",
+ "signatureAlgorithmHash": "0x1a6f483c4f6880a162cefa906f3dd9dc228115e89c04c539d63601752d3d57ae",
+ "signingDigest": "0x7d211ad03eb946a51d42b8d8c1df97f3009c479d04266c527dc293ce23298e71",
+ "signature": "0x471053568b74dd634f59899e2198a62e9dd7ec40bf6886b6955938377bad2ecd35e31726453d233c177ab2b2abd3664bf0b83fb649d536fd3706fcf5c0033037",
+ "transactionId": "0x7e2874ae8d3533cfb6743cc9d0d36183b5b4913be91e6de4f407d20c463dd4ea"
+ },
+ "expected": {
+ "objectId": "0x519b9cdb91ecf5d4160811dda30178ccc158347cbb9e02e8da3c9e296621c941",
+ "payloadHash": "0xbc78f4b1460b8ca0a3c0af67305b260bb98b55fc225844bd612d90ca0e5559e8",
+ "envelopeId": "0x1d6bdea3b4bd32e9d202e72c1ac0957925b199e89076490417d071314438a192",
+ "signingDigest": "0x7d211ad03eb946a51d42b8d8c1df97f3009c479d04266c527dc293ce23298e71",
+ "transactionId": "0x7e2874ae8d3533cfb6743cc9d0d36183b5b4913be91e6de4f407d20c463dd4ea"
+ },
+ "runtime": {
+ "ok": true,
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "signerAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "payloadHash": "0xbc78f4b1460b8ca0a3c0af67305b260bb98b55fc225844bd612d90ca0e5559e8",
+ "transactionId": "0x7e2874ae8d3533cfb6743cc9d0d36183b5b4913be91e6de4f407d20c463dd4ea",
+ "nonce": "3",
+ "chainId": "31337",
+ "networkProfile": "local-chain"
+ }
+ },
+ {
+ "name": "token-transfer",
+ "document": {
+ "schema": "flowchain.product_transfer.v0",
+ "transferId": "0xd341dc3c529c603c854265e44f521bd621cf11824745aeb40db6b8452667b13d",
+ "fromAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "toAccountId": "0xfc3fd2b4d0c7a318201db17cd10f517d9f218defe1f3a9fd243692d156d70dd2",
+ "assetId": "0xf75726e75be7373f3b97142c5401342b778a0984950588839ccb28c5ce7a94b5",
+ "amount": "5000000",
+ "accountNonce": "3",
+ "deadlineBlock": "140",
+ "memoHash": "0x512017e178db49339ab23460cd81b735121256b0d5e5f3c4fd4a52e8ee7e0fec"
+ },
+ "envelope": {
+ "schema": "flowchain.local_transaction_envelope.v0",
+ "schemaVersion": 1,
+ "networkProfile": "local-chain",
+ "networkProfileHash": "0xbd64b702ecdbcab44fabefc7e5836219cde02f5d6a3f0abc0026dcdb9b46a56a",
+ "envelopeId": "0x665b97b292695749f9126d570eaaa6b41de6fd44200d9718c955cec77336544a",
+ "domain": "flowchain.production-l1.v0.transaction-envelope:profile:local-chain:chain:31337",
+ "domainSeparator": "0x6e2e8ddd3eba28f318b038b64534e672e57324225ac7e8ae19729dfda1f7a2a8",
+ "chainId": "31337",
+ "nonce": "4",
+ "signerId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "signerKeyId": "0x2fc3666dfc5634ead760c895a14fda3afc55b342794962265c8bf7e3deb9e096",
+ "signerRole": "user",
+ "signerRoleCode": 10,
+ "publicKey": "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "objectSchema": "flowchain.product_transfer.v0",
+ "objectType": "product_transfer",
+ "payloadType": "token_transfer",
+ "payloadTypeHash": "0x85f15e1b7cd56792dde24eb24a8d705ce45aaae89a91f00b953d03371b6bc0ab",
+ "objectTypeHash": "0xff7912cf8351cc2aa97d9d9a9e583a3b449c5f9902914185f288c00b83a458a1",
+ "objectId": "0xd341dc3c529c603c854265e44f521bd621cf11824745aeb40db6b8452667b13d",
+ "payloadHash": "0xfd5146595e75a91cbd470e247675c402dd04cd5ef29608ce632a1d880801d601",
+ "issuedAtUnixMs": "1778702400000",
+ "expiresAtUnixMs": "1778706000000",
+ "localExecutionCost": {
+ "unit": "local-compute",
+ "amount": "0",
+ "metering": "not-metered-local-private-testnet"
+ },
+ "localExecutionCostHash": "0x6cad238c63ddf255495af76197783d3392f61da8bb61a7943bd4b79ed3745fb8",
+ "fee": {
+ "assetId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "amount": "0",
+ "policy": "no-value-local-private-testnet"
+ },
+ "feeHash": "0x84226d772ddfcad0ed9437e0dbccdf42f4b05660112b16fdd53b089b83781b93",
+ "signatureAlgorithm": "secp256k1-keccak256-eip712-local-v0",
+ "signatureAlgorithmHash": "0x1a6f483c4f6880a162cefa906f3dd9dc228115e89c04c539d63601752d3d57ae",
+ "signingDigest": "0x523a15557f55c3c790b3a02e2209e4c4ad7fc63d3e0b97ce99c5e7c1cd747b1b",
+ "signature": "0xbba988c8da507cf7af5db6ea4e11b144789c537fce58e86d21ee097e79f14dae5034df47b7708a949b29978b85af7fb7ff392795e8f9d3ebf2733f677ae25a18",
+ "transactionId": "0x81ebcad01ac1b04190abc5bde2d7640c9451ffabfdcb5efd3cad1172eb15cb80"
+ },
+ "expected": {
+ "objectId": "0xd341dc3c529c603c854265e44f521bd621cf11824745aeb40db6b8452667b13d",
+ "payloadHash": "0xfd5146595e75a91cbd470e247675c402dd04cd5ef29608ce632a1d880801d601",
+ "envelopeId": "0x665b97b292695749f9126d570eaaa6b41de6fd44200d9718c955cec77336544a",
+ "signingDigest": "0x523a15557f55c3c790b3a02e2209e4c4ad7fc63d3e0b97ce99c5e7c1cd747b1b",
+ "transactionId": "0x81ebcad01ac1b04190abc5bde2d7640c9451ffabfdcb5efd3cad1172eb15cb80"
+ },
+ "runtime": {
+ "ok": true,
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "signerAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "payloadHash": "0xfd5146595e75a91cbd470e247675c402dd04cd5ef29608ce632a1d880801d601",
+ "transactionId": "0x81ebcad01ac1b04190abc5bde2d7640c9451ffabfdcb5efd3cad1172eb15cb80",
+ "nonce": "4",
+ "chainId": "31337",
+ "networkProfile": "local-chain"
+ }
+ },
+ {
+ "name": "pool-create",
+ "document": {
+ "schema": "flowchain.product_pool_create.v0",
+ "poolCreateId": "0x9187c32e0fef30f688b5607684c255e4cc33e499ceae4f241d324ef77e373846",
+ "creatorAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "poolId": "0x95b03fea81dc352314e5a353a210de8da7f47804fd01a3afc66abb4d98e6e3ef",
+ "baseAssetId": "0xd9e00f87e32bb273960e26dabf7a0632487b3c100f97126ce6e5f6c7500336ed",
+ "quoteAssetId": "0xf75726e75be7373f3b97142c5401342b778a0984950588839ccb28c5ce7a94b5",
+ "feeBps": 30,
+ "tickSpacing": 1,
+ "metadataHash": "0x5653cb61bf0bcbfe2dc8afc65d8ff4cf6d840ea95ead1ca9d12b38c187f865f9",
+ "accountNonce": "4"
+ },
+ "envelope": {
+ "schema": "flowchain.local_transaction_envelope.v0",
+ "schemaVersion": 1,
+ "networkProfile": "local-chain",
+ "networkProfileHash": "0xbd64b702ecdbcab44fabefc7e5836219cde02f5d6a3f0abc0026dcdb9b46a56a",
+ "envelopeId": "0x3298d36611afbb13d4a965b7c1a812540eeca817baee749275fb2c097be892af",
+ "domain": "flowchain.production-l1.v0.transaction-envelope:profile:local-chain:chain:31337",
+ "domainSeparator": "0x6e2e8ddd3eba28f318b038b64534e672e57324225ac7e8ae19729dfda1f7a2a8",
+ "chainId": "31337",
+ "nonce": "5",
+ "signerId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "signerKeyId": "0x2fc3666dfc5634ead760c895a14fda3afc55b342794962265c8bf7e3deb9e096",
+ "signerRole": "user",
+ "signerRoleCode": 10,
+ "publicKey": "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "objectSchema": "flowchain.product_pool_create.v0",
+ "objectType": "product_pool_create",
+ "payloadType": "pool_create",
+ "payloadTypeHash": "0xa5759fd4e266c95d06a76b5435b106c4f2731656e2bf9f418384a461044b5985",
+ "objectTypeHash": "0xbb71a7fba11a7dc5490eccc49d8aa29ee3e2df572ac13500d643c23cb42ea2dc",
+ "objectId": "0x9187c32e0fef30f688b5607684c255e4cc33e499ceae4f241d324ef77e373846",
+ "payloadHash": "0xf23ce658205e8c07b3c30606f570dab605213f6be3c623ca77329e34a6af898a",
+ "issuedAtUnixMs": "1778702400000",
+ "expiresAtUnixMs": "1778706000000",
+ "localExecutionCost": {
+ "unit": "local-compute",
+ "amount": "0",
+ "metering": "not-metered-local-private-testnet"
+ },
+ "localExecutionCostHash": "0x6cad238c63ddf255495af76197783d3392f61da8bb61a7943bd4b79ed3745fb8",
+ "fee": {
+ "assetId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "amount": "0",
+ "policy": "no-value-local-private-testnet"
+ },
+ "feeHash": "0x84226d772ddfcad0ed9437e0dbccdf42f4b05660112b16fdd53b089b83781b93",
+ "signatureAlgorithm": "secp256k1-keccak256-eip712-local-v0",
+ "signatureAlgorithmHash": "0x1a6f483c4f6880a162cefa906f3dd9dc228115e89c04c539d63601752d3d57ae",
+ "signingDigest": "0x35b08bce68f18f4ab9aa722ff39f925fe9a9e0e947cc3687bcb875e6d1324ca0",
+ "signature": "0xb2d3e64c19dfbcee5c73bcdc1f4c7b0ba00bbe99deca0ca0725d937aaa3681cb6e562e83029600e60ed726b426757aa0daf7dab2cfc96d92b15cbbe7297b534b",
+ "transactionId": "0x4dbfa6c6354287fbc0621ae2167546095f34d185d79085a1453c538d4fcc417b"
+ },
+ "expected": {
+ "objectId": "0x9187c32e0fef30f688b5607684c255e4cc33e499ceae4f241d324ef77e373846",
+ "payloadHash": "0xf23ce658205e8c07b3c30606f570dab605213f6be3c623ca77329e34a6af898a",
+ "envelopeId": "0x3298d36611afbb13d4a965b7c1a812540eeca817baee749275fb2c097be892af",
+ "signingDigest": "0x35b08bce68f18f4ab9aa722ff39f925fe9a9e0e947cc3687bcb875e6d1324ca0",
+ "transactionId": "0x4dbfa6c6354287fbc0621ae2167546095f34d185d79085a1453c538d4fcc417b"
+ },
+ "runtime": {
+ "ok": true,
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "signerAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "payloadHash": "0xf23ce658205e8c07b3c30606f570dab605213f6be3c623ca77329e34a6af898a",
+ "transactionId": "0x4dbfa6c6354287fbc0621ae2167546095f34d185d79085a1453c538d4fcc417b",
+ "nonce": "5",
+ "chainId": "31337",
+ "networkProfile": "local-chain"
+ }
+ },
+ {
+ "name": "add-liquidity",
+ "document": {
+ "schema": "flowchain.product_add_liquidity.v0",
+ "addLiquidityId": "0x988bb77d010bf1bd9215da262506817bcc73d8b14a3fd9ab610924eaceb0a42e",
+ "providerAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "poolId": "0x95b03fea81dc352314e5a353a210de8da7f47804fd01a3afc66abb4d98e6e3ef",
+ "baseAmount": "25000000",
+ "quoteAmount": "250000000",
+ "minLiquidityTokens": "1",
+ "deadlineBlock": "160",
+ "accountNonce": "5"
+ },
+ "envelope": {
+ "schema": "flowchain.local_transaction_envelope.v0",
+ "schemaVersion": 1,
+ "networkProfile": "local-chain",
+ "networkProfileHash": "0xbd64b702ecdbcab44fabefc7e5836219cde02f5d6a3f0abc0026dcdb9b46a56a",
+ "envelopeId": "0x77bbe86def9005c84e2734de8484dc621c08d681aa67bf3671fa795339455773",
+ "domain": "flowchain.production-l1.v0.transaction-envelope:profile:local-chain:chain:31337",
+ "domainSeparator": "0x6e2e8ddd3eba28f318b038b64534e672e57324225ac7e8ae19729dfda1f7a2a8",
+ "chainId": "31337",
+ "nonce": "6",
+ "signerId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "signerKeyId": "0x2fc3666dfc5634ead760c895a14fda3afc55b342794962265c8bf7e3deb9e096",
+ "signerRole": "user",
+ "signerRoleCode": 10,
+ "publicKey": "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "objectSchema": "flowchain.product_add_liquidity.v0",
+ "objectType": "product_add_liquidity",
+ "payloadType": "add_liquidity",
+ "payloadTypeHash": "0x2acfb12ff9e08412ec5009c65ea06e727119ad948d25c8a8cc2c86fec4adee70",
+ "objectTypeHash": "0x070a522fa99720718c247cf285632c232bfe50da960e5c1f2838e15c210386a0",
+ "objectId": "0x988bb77d010bf1bd9215da262506817bcc73d8b14a3fd9ab610924eaceb0a42e",
+ "payloadHash": "0x35ec4301e17126fc0bea037261ff7b5cc17aa3e3069211643547b9d53dcbfd9d",
+ "issuedAtUnixMs": "1778702400000",
+ "expiresAtUnixMs": "1778706000000",
+ "localExecutionCost": {
+ "unit": "local-compute",
+ "amount": "0",
+ "metering": "not-metered-local-private-testnet"
+ },
+ "localExecutionCostHash": "0x6cad238c63ddf255495af76197783d3392f61da8bb61a7943bd4b79ed3745fb8",
+ "fee": {
+ "assetId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "amount": "0",
+ "policy": "no-value-local-private-testnet"
+ },
+ "feeHash": "0x84226d772ddfcad0ed9437e0dbccdf42f4b05660112b16fdd53b089b83781b93",
+ "signatureAlgorithm": "secp256k1-keccak256-eip712-local-v0",
+ "signatureAlgorithmHash": "0x1a6f483c4f6880a162cefa906f3dd9dc228115e89c04c539d63601752d3d57ae",
+ "signingDigest": "0x83cbc77c564ef631ce8d18fb3eb4bc2385e153ca17dba99a56eb1ee8a15c247f",
+ "signature": "0x4cf2e2fee69eaec2d54830b3707d2bc3c7d554c2810db9b3b1cdc33cfa3d27581627a7b086ea38a203ba82803f90ec6f5d24ca79e889505812fd90ad8292b7b3",
+ "transactionId": "0x066a68e4052a5e661856be50f77413789b7ff0b0a239b4fd62975d5ca8a0a566"
+ },
+ "expected": {
+ "objectId": "0x988bb77d010bf1bd9215da262506817bcc73d8b14a3fd9ab610924eaceb0a42e",
+ "payloadHash": "0x35ec4301e17126fc0bea037261ff7b5cc17aa3e3069211643547b9d53dcbfd9d",
+ "envelopeId": "0x77bbe86def9005c84e2734de8484dc621c08d681aa67bf3671fa795339455773",
+ "signingDigest": "0x83cbc77c564ef631ce8d18fb3eb4bc2385e153ca17dba99a56eb1ee8a15c247f",
+ "transactionId": "0x066a68e4052a5e661856be50f77413789b7ff0b0a239b4fd62975d5ca8a0a566"
+ },
+ "runtime": {
+ "ok": true,
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "signerAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "payloadHash": "0x35ec4301e17126fc0bea037261ff7b5cc17aa3e3069211643547b9d53dcbfd9d",
+ "transactionId": "0x066a68e4052a5e661856be50f77413789b7ff0b0a239b4fd62975d5ca8a0a566",
+ "nonce": "6",
+ "chainId": "31337",
+ "networkProfile": "local-chain"
+ }
+ },
+ {
+ "name": "remove-liquidity",
+ "document": {
+ "schema": "flowchain.product_remove_liquidity.v0",
+ "removeLiquidityId": "0x5af6c2ca52a5041ea3630996e7fd8cf34a6ad3a38193da89dd1543fd3601d7ae",
+ "providerAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "poolId": "0x95b03fea81dc352314e5a353a210de8da7f47804fd01a3afc66abb4d98e6e3ef",
+ "liquidityTokens": "1000",
+ "minBaseAmount": "1",
+ "minQuoteAmount": "1",
+ "deadlineBlock": "180",
+ "accountNonce": "6"
+ },
+ "envelope": {
+ "schema": "flowchain.local_transaction_envelope.v0",
+ "schemaVersion": 1,
+ "networkProfile": "local-chain",
+ "networkProfileHash": "0xbd64b702ecdbcab44fabefc7e5836219cde02f5d6a3f0abc0026dcdb9b46a56a",
+ "envelopeId": "0x4293d7f58806463b0c24084e932601cf1455ae99fdc31014e69db3dda20a246e",
+ "domain": "flowchain.production-l1.v0.transaction-envelope:profile:local-chain:chain:31337",
+ "domainSeparator": "0x6e2e8ddd3eba28f318b038b64534e672e57324225ac7e8ae19729dfda1f7a2a8",
+ "chainId": "31337",
+ "nonce": "7",
+ "signerId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "signerKeyId": "0x2fc3666dfc5634ead760c895a14fda3afc55b342794962265c8bf7e3deb9e096",
+ "signerRole": "user",
+ "signerRoleCode": 10,
+ "publicKey": "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "objectSchema": "flowchain.product_remove_liquidity.v0",
+ "objectType": "product_remove_liquidity",
+ "payloadType": "remove_liquidity",
+ "payloadTypeHash": "0x3ae875d1c86df033547c5c7839d8b6e3641de29ee1f708bbce99743b34272ada",
+ "objectTypeHash": "0x77cc0f9dc5f736d17097e04f960fd9f20a67067ad1d9acfb3a2f779c8ff32ba1",
+ "objectId": "0x5af6c2ca52a5041ea3630996e7fd8cf34a6ad3a38193da89dd1543fd3601d7ae",
+ "payloadHash": "0x9aa9ade71210e7e9bfa9b53a9c5621912f81a6de2e168860230e83e801b018ed",
+ "issuedAtUnixMs": "1778702400000",
+ "expiresAtUnixMs": "1778706000000",
+ "localExecutionCost": {
+ "unit": "local-compute",
+ "amount": "0",
+ "metering": "not-metered-local-private-testnet"
+ },
+ "localExecutionCostHash": "0x6cad238c63ddf255495af76197783d3392f61da8bb61a7943bd4b79ed3745fb8",
+ "fee": {
+ "assetId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "amount": "0",
+ "policy": "no-value-local-private-testnet"
+ },
+ "feeHash": "0x84226d772ddfcad0ed9437e0dbccdf42f4b05660112b16fdd53b089b83781b93",
+ "signatureAlgorithm": "secp256k1-keccak256-eip712-local-v0",
+ "signatureAlgorithmHash": "0x1a6f483c4f6880a162cefa906f3dd9dc228115e89c04c539d63601752d3d57ae",
+ "signingDigest": "0x4fdb374cc91e119923352f9189233c89c870b6076e75d6b6e942ab28f3803430",
+ "signature": "0xe87f9970888134d1d55cae1db0bfb84580bc46409bce080b9e291747932bc820691c5a034598d0dc2b938a314fadbb920bf752b27f071a1cb22e165c15177bd7",
+ "transactionId": "0x86da263a7b64dc03c9677c4f16befbc40645e4b22bda689cd96b659f92c3a57b"
+ },
+ "expected": {
+ "objectId": "0x5af6c2ca52a5041ea3630996e7fd8cf34a6ad3a38193da89dd1543fd3601d7ae",
+ "payloadHash": "0x9aa9ade71210e7e9bfa9b53a9c5621912f81a6de2e168860230e83e801b018ed",
+ "envelopeId": "0x4293d7f58806463b0c24084e932601cf1455ae99fdc31014e69db3dda20a246e",
+ "signingDigest": "0x4fdb374cc91e119923352f9189233c89c870b6076e75d6b6e942ab28f3803430",
+ "transactionId": "0x86da263a7b64dc03c9677c4f16befbc40645e4b22bda689cd96b659f92c3a57b"
+ },
+ "runtime": {
+ "ok": true,
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "signerAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "payloadHash": "0x9aa9ade71210e7e9bfa9b53a9c5621912f81a6de2e168860230e83e801b018ed",
+ "transactionId": "0x86da263a7b64dc03c9677c4f16befbc40645e4b22bda689cd96b659f92c3a57b",
+ "nonce": "7",
+ "chainId": "31337",
+ "networkProfile": "local-chain"
+ }
+ },
+ {
+ "name": "swap",
+ "document": {
+ "schema": "flowchain.product_swap.v0",
+ "swapId": "0xe4ef0618e0ec69d6a7f90a982804259d6ae2033d61aa86282c4b3e227d6f62f9",
+ "traderAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "poolId": "0x95b03fea81dc352314e5a353a210de8da7f47804fd01a3afc66abb4d98e6e3ef",
+ "assetInId": "0xd9e00f87e32bb273960e26dabf7a0632487b3c100f97126ce6e5f6c7500336ed",
+ "assetOutId": "0xf75726e75be7373f3b97142c5401342b778a0984950588839ccb28c5ce7a94b5",
+ "amountIn": "100000",
+ "minAmountOut": "900000",
+ "deadlineBlock": "200",
+ "accountNonce": "7"
+ },
+ "envelope": {
+ "schema": "flowchain.local_transaction_envelope.v0",
+ "schemaVersion": 1,
+ "networkProfile": "local-chain",
+ "networkProfileHash": "0xbd64b702ecdbcab44fabefc7e5836219cde02f5d6a3f0abc0026dcdb9b46a56a",
+ "envelopeId": "0xc0f58480a98ce8b64371efcf211b3699773ab98772d7c59d1a58869c5302e4c1",
+ "domain": "flowchain.production-l1.v0.transaction-envelope:profile:local-chain:chain:31337",
+ "domainSeparator": "0x6e2e8ddd3eba28f318b038b64534e672e57324225ac7e8ae19729dfda1f7a2a8",
+ "chainId": "31337",
+ "nonce": "8",
+ "signerId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "signerKeyId": "0x2fc3666dfc5634ead760c895a14fda3afc55b342794962265c8bf7e3deb9e096",
+ "signerRole": "user",
+ "signerRoleCode": 10,
+ "publicKey": "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "objectSchema": "flowchain.product_swap.v0",
+ "objectType": "product_swap",
+ "payloadType": "swap",
+ "payloadTypeHash": "0x695543c3708653cda9d418b4ccd3be11368e40636c10c44b18cfe756b6d88b29",
+ "objectTypeHash": "0x56a77131aee0fc37d8111b2b093f71aade4bc34a257b7f77b305796f4895f398",
+ "objectId": "0xe4ef0618e0ec69d6a7f90a982804259d6ae2033d61aa86282c4b3e227d6f62f9",
+ "payloadHash": "0x74a9c1c1aabfa25de33d1893b8614b56b0606efa06058c920880e2b9ae96325b",
+ "issuedAtUnixMs": "1778702400000",
+ "expiresAtUnixMs": "1778706000000",
+ "localExecutionCost": {
+ "unit": "local-compute",
+ "amount": "0",
+ "metering": "not-metered-local-private-testnet"
+ },
+ "localExecutionCostHash": "0x6cad238c63ddf255495af76197783d3392f61da8bb61a7943bd4b79ed3745fb8",
+ "fee": {
+ "assetId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "amount": "0",
+ "policy": "no-value-local-private-testnet"
+ },
+ "feeHash": "0x84226d772ddfcad0ed9437e0dbccdf42f4b05660112b16fdd53b089b83781b93",
+ "signatureAlgorithm": "secp256k1-keccak256-eip712-local-v0",
+ "signatureAlgorithmHash": "0x1a6f483c4f6880a162cefa906f3dd9dc228115e89c04c539d63601752d3d57ae",
+ "signingDigest": "0xf2a56cd6205e6e1227ee74abba3ac4f9e3c442061323aadb3a791af51b41b78b",
+ "signature": "0xa92b972a465c49d0920719a4b56b94c77fb7ddb1b5cee0687ba3a08d090d06853140fe49e9599d40b8eba1f83243c36431b00ec81a757a96d3691f181d12e2a1",
+ "transactionId": "0xf437b76527af5a6ccbf37c7a86aff67ceb348a2c34b119e3b05cd49b2860da1a"
+ },
+ "expected": {
+ "objectId": "0xe4ef0618e0ec69d6a7f90a982804259d6ae2033d61aa86282c4b3e227d6f62f9",
+ "payloadHash": "0x74a9c1c1aabfa25de33d1893b8614b56b0606efa06058c920880e2b9ae96325b",
+ "envelopeId": "0xc0f58480a98ce8b64371efcf211b3699773ab98772d7c59d1a58869c5302e4c1",
+ "signingDigest": "0xf2a56cd6205e6e1227ee74abba3ac4f9e3c442061323aadb3a791af51b41b78b",
+ "transactionId": "0xf437b76527af5a6ccbf37c7a86aff67ceb348a2c34b119e3b05cd49b2860da1a"
+ },
+ "runtime": {
+ "ok": true,
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "signerAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "payloadHash": "0x74a9c1c1aabfa25de33d1893b8614b56b0606efa06058c920880e2b9ae96325b",
+ "transactionId": "0xf437b76527af5a6ccbf37c7a86aff67ceb348a2c34b119e3b05cd49b2860da1a",
+ "nonce": "8",
+ "chainId": "31337",
+ "networkProfile": "local-chain"
+ }
+ },
+ {
+ "name": "bridge-credit-authority",
+ "document": {
+ "schema": "flowchain.bridge_credit.v0",
+ "creditId": "0xfdf8329f54e438c79a8a58675fa3f6bcbe8354d6912c98651243e2d7c70455d5",
+ "depositId": "0x22f32eabca7b5fe45048405f9e79634026f035eebaf70dd8603d1eae1154ec07",
+ "recipient": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "assetId": "0xf75726e75be7373f3b97142c5401342b778a0984950588839ccb28c5ce7a94b5",
+ "amount": "25000000",
+ "creditedAtBlockNumber": "8",
+ "creditedAtUnixMs": "1778702400000",
+ "status": "credited",
+ "statusCode": 3,
+ "nonce": "0xcbf3e8d5823b4b44240a6af0e119e3a56991bade0739babadc1efcde7744071c"
+ },
+ "envelope": {
+ "schema": "flowchain.local_transaction_envelope.v0",
+ "schemaVersion": 1,
+ "networkProfile": "local-chain",
+ "networkProfileHash": "0xbd64b702ecdbcab44fabefc7e5836219cde02f5d6a3f0abc0026dcdb9b46a56a",
+ "envelopeId": "0x9e9d285d55c42e3ebfe63946042e2ec0cc8bb2d213f17ae7a4e5f2e41c2c6ec2",
+ "domain": "flowchain.production-l1.v0.transaction-envelope:profile:local-chain:chain:31337",
+ "domainSeparator": "0x6e2e8ddd3eba28f318b038b64534e672e57324225ac7e8ae19729dfda1f7a2a8",
+ "chainId": "31337",
+ "nonce": "9",
+ "signerId": "0x40ea93e387b044dbb56c9e543ec503857a31c45aead019cf6088e60f1ceb2ac1",
+ "signerKeyId": "0x4ae4fee7df34c54114adc91abe9fe874839b9fbbb23186939256b7681ad5cf67",
+ "signerRole": "bridgeReleaseAuthority",
+ "signerRoleCode": 13,
+ "publicKey": "0x022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4",
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "signerAddress": "0xb8d39c1dc062e9ce826020d0bf0ac2a3d92c52a3",
+ "objectSchema": "flowchain.bridge_credit.v0",
+ "objectType": "bridge_credit",
+ "payloadType": "bridge_credit",
+ "payloadTypeHash": "0x99c0a6dcd28db25cdab5deb9c9b59cca3e5aa98b434f03645a6b6d5e08bb13be",
+ "objectTypeHash": "0x5c492a94b36aa3beb3b9ceb9dc5124464beeba9ac9fd2d04f88118cf73f3e912",
+ "objectId": "0xfdf8329f54e438c79a8a58675fa3f6bcbe8354d6912c98651243e2d7c70455d5",
+ "payloadHash": "0x201b8fad58b4f58b91ab870833da8e8009bf80f0b3b60b1a787f14e0a29961c2",
+ "issuedAtUnixMs": "1778702400000",
+ "expiresAtUnixMs": "1778706000000",
+ "localExecutionCost": {
+ "unit": "local-compute",
+ "amount": "0",
+ "metering": "not-metered-local-private-testnet"
+ },
+ "localExecutionCostHash": "0x6cad238c63ddf255495af76197783d3392f61da8bb61a7943bd4b79ed3745fb8",
+ "fee": {
+ "assetId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "amount": "0",
+ "policy": "no-value-local-private-testnet"
+ },
+ "feeHash": "0x84226d772ddfcad0ed9437e0dbccdf42f4b05660112b16fdd53b089b83781b93",
+ "signatureAlgorithm": "secp256k1-keccak256-eip712-local-v0",
+ "signatureAlgorithmHash": "0x1a6f483c4f6880a162cefa906f3dd9dc228115e89c04c539d63601752d3d57ae",
+ "signingDigest": "0x38ed9b76d7684f8708ff1376dedfdf71b3cb81a58f1691325a629c2b6d04bc18",
+ "signature": "0xcc7adfd27b431abbb8e95f029fe1c89d7450cd5d23edd696df8d5b5cdbc6e58161c982d0bcb28c95687745bb5532139df7fdb89148879ff561c529bf92ce3834",
+ "transactionId": "0x8bfeeda684a9e9d7680bdef28820b7345c5724d25b50a43cfc0dbf9ad8b106dc"
+ },
+ "expected": {
+ "objectId": "0xfdf8329f54e438c79a8a58675fa3f6bcbe8354d6912c98651243e2d7c70455d5",
+ "payloadHash": "0x201b8fad58b4f58b91ab870833da8e8009bf80f0b3b60b1a787f14e0a29961c2",
+ "envelopeId": "0x9e9d285d55c42e3ebfe63946042e2ec0cc8bb2d213f17ae7a4e5f2e41c2c6ec2",
+ "signingDigest": "0x38ed9b76d7684f8708ff1376dedfdf71b3cb81a58f1691325a629c2b6d04bc18",
+ "transactionId": "0x8bfeeda684a9e9d7680bdef28820b7345c5724d25b50a43cfc0dbf9ad8b106dc"
+ },
+ "runtime": {
+ "ok": true,
+ "signerAddress": "0xb8d39c1dc062e9ce826020d0bf0ac2a3d92c52a3",
+ "signerAccountId": "0x40ea93e387b044dbb56c9e543ec503857a31c45aead019cf6088e60f1ceb2ac1",
+ "payloadHash": "0x201b8fad58b4f58b91ab870833da8e8009bf80f0b3b60b1a787f14e0a29961c2",
+ "transactionId": "0x8bfeeda684a9e9d7680bdef28820b7345c5724d25b50a43cfc0dbf9ad8b106dc",
+ "nonce": "9",
+ "chainId": "31337",
+ "networkProfile": "local-chain"
+ }
+ },
+ {
+ "name": "withdrawal-intent",
+ "document": {
+ "schema": "flowmemory.bridge_withdrawal_intent.v0",
+ "withdrawalIntentId": "0x055fd9650e3005276ba93e5e0355aa73ade35054413e25f42091b009d21997ec",
+ "creditId": "0x3c14dea922babb89523ff15036914734f22933eaeb87ba15b4fed0c4044763e3",
+ "depositId": "0x22f32eabca7b5fe45048405f9e79634026f035eebaf70dd8603d1eae1154ec07",
+ "sourceChainId": 31337,
+ "destinationChainId": 8453,
+ "token": "0x2222222222222222222222222222222222222222",
+ "amount": "10000000",
+ "flowchainAccount": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "baseRecipient": "0x4444444444444444444444444444444444444444",
+ "status": "requested",
+ "requestedAt": "2026-05-13T23:00:00.000Z",
+ "testMode": true,
+ "broadcast": false,
+ "releasePolicy": "test_record_only",
+ "productionReady": false
+ },
+ "envelope": {
+ "schema": "flowchain.local_transaction_envelope.v0",
+ "schemaVersion": 1,
+ "networkProfile": "local-chain",
+ "networkProfileHash": "0xbd64b702ecdbcab44fabefc7e5836219cde02f5d6a3f0abc0026dcdb9b46a56a",
+ "envelopeId": "0x91976b3cdc5cff4589a25a1b7db75f434da7f9c61c8576efb23ff77ec12070e4",
+ "domain": "flowchain.production-l1.v0.transaction-envelope:profile:local-chain:chain:31337",
+ "domainSeparator": "0x6e2e8ddd3eba28f318b038b64534e672e57324225ac7e8ae19729dfda1f7a2a8",
+ "chainId": "31337",
+ "nonce": "10",
+ "signerId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "signerKeyId": "0x2fc3666dfc5634ead760c895a14fda3afc55b342794962265c8bf7e3deb9e096",
+ "signerRole": "user",
+ "signerRoleCode": 10,
+ "publicKey": "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "objectSchema": "flowmemory.bridge_withdrawal_intent.v0",
+ "objectType": "bridge_withdrawal_intent",
+ "payloadType": "withdrawal_intent",
+ "payloadTypeHash": "0x99b4e331e591a8195b46b135b3fdfbcb90e7e7c95f7479d55c6ff4b29e6d0cc4",
+ "objectTypeHash": "0xf54d0a70abd4a36a0d64f6b0704100bc0e86970d2d72d79613d7a033d083a66a",
+ "objectId": "0x055fd9650e3005276ba93e5e0355aa73ade35054413e25f42091b009d21997ec",
+ "payloadHash": "0x7103a19da3dc42366377840ef816144c99e1151861590bc0f7cac316ffdc6628",
+ "issuedAtUnixMs": "1778702400000",
+ "expiresAtUnixMs": "1778706000000",
+ "localExecutionCost": {
+ "unit": "local-compute",
+ "amount": "0",
+ "metering": "not-metered-local-private-testnet"
+ },
+ "localExecutionCostHash": "0x6cad238c63ddf255495af76197783d3392f61da8bb61a7943bd4b79ed3745fb8",
+ "fee": {
+ "assetId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "amount": "0",
+ "policy": "no-value-local-private-testnet"
+ },
+ "feeHash": "0x84226d772ddfcad0ed9437e0dbccdf42f4b05660112b16fdd53b089b83781b93",
+ "signatureAlgorithm": "secp256k1-keccak256-eip712-local-v0",
+ "signatureAlgorithmHash": "0x1a6f483c4f6880a162cefa906f3dd9dc228115e89c04c539d63601752d3d57ae",
+ "signingDigest": "0x24b72c559647bff3f839f065a7f90d3222cfca896632ef33c2676e2e6d1ab922",
+ "signature": "0x55ff3ff9962dc359b95f2c54561c92547d09d98ba9d2697aabedb4f7401e9b2f514c61d92abe27eb34c0e4efe5610e3edfb1b4f5cdd85147c0b483d334d52bda",
+ "transactionId": "0xfb9d966380c2ea63c992d9d46d49cdb35345de52dfb84655bc30363259176c96"
+ },
+ "expected": {
+ "objectId": "0x055fd9650e3005276ba93e5e0355aa73ade35054413e25f42091b009d21997ec",
+ "payloadHash": "0x7103a19da3dc42366377840ef816144c99e1151861590bc0f7cac316ffdc6628",
+ "envelopeId": "0x91976b3cdc5cff4589a25a1b7db75f434da7f9c61c8576efb23ff77ec12070e4",
+ "signingDigest": "0x24b72c559647bff3f839f065a7f90d3222cfca896632ef33c2676e2e6d1ab922",
+ "transactionId": "0xfb9d966380c2ea63c992d9d46d49cdb35345de52dfb84655bc30363259176c96"
+ },
+ "runtime": {
+ "ok": true,
+ "signerAddress": "0xfd705a53418d74f973461f4360da507b319710eb",
+ "signerAccountId": "0xd73d877ada523f7171fec59bb80a282b1c697f5ee07d126a8f0aacc0e3e28ed3",
+ "payloadHash": "0x7103a19da3dc42366377840ef816144c99e1151861590bc0f7cac316ffdc6628",
+ "transactionId": "0xfb9d966380c2ea63c992d9d46d49cdb35345de52dfb84655bc30363259176c96",
+ "nonce": "10",
+ "chainId": "31337",
+ "networkProfile": "local-chain"
+ }
+ },
+ {
+ "name": "validator-finality",
+ "document": {
+ "schema": "flowchain.finality_receipt.v0",
+ "finalityReceiptId": "0xbe62b59a3b3a433caa8d38c0ce5f83f37ab04c6022d6583094acf6bb3dd27801",
+ "receiptId": "0x70e23a370e24a7a194fb1b00fd90bdef23a0847c8711b6b14fea6fcd92673a2c",
+ "reportId": "0xee159907e6d778e434ba02306cf79d953ed6b1f20ede7bf4a20b234c4bf1ba96",
+ "challengeRoot": "0xda2c97d6b2dad4b7f04d5024b79921a5cfbb09936a81199a775cc9da13d570b7",
+ "finalityState": "finalized",
+ "finalityStateCode": 6,
+ "finalizedAtUnixMs": "1778702400000",
+ "finalizedBlockNumber": "9",
+ "finalizedBlockHash": "0x15e77563faf61d5b643c1cd563c8dc86fdb1d2a16b27e26868707041b996c7f6",
+ "policyHash": "0x48706bd548d6d1775a36b3bb9ec4e43e66aee9efc4ca610de66426f1208f3e95"
+ },
+ "envelope": {
+ "schema": "flowchain.local_transaction_envelope.v0",
+ "schemaVersion": 1,
+ "networkProfile": "local-chain",
+ "networkProfileHash": "0xbd64b702ecdbcab44fabefc7e5836219cde02f5d6a3f0abc0026dcdb9b46a56a",
+ "envelopeId": "0x071c2ff1c3160e4eb0087275c891cf17d3a2dc46bd15d6ca378f4099541a7d2e",
+ "domain": "flowchain.production-l1.v0.transaction-envelope:profile:local-chain:chain:31337",
+ "domainSeparator": "0x6e2e8ddd3eba28f318b038b64534e672e57324225ac7e8ae19729dfda1f7a2a8",
+ "chainId": "31337",
+ "nonce": "11",
+ "signerId": "0x18064db99c33bd271aa2f94835f67686d609463964b9c5ea86d98071b1111c9d",
+ "signerKeyId": "0xce800048eb202fba5ee063466b35f7375e8ea0b2ac85eaa047abece7220e70dc",
+ "signerRole": "validator",
+ "signerRoleCode": 11,
+ "publicKey": "0x02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
+ "publicKeyEncoding": "secp256k1-compressed-hex",
+ "signerAddress": "0x7458689c2bda5e6dffc26967b9cccf5b83bade66",
+ "objectSchema": "flowchain.finality_receipt.v0",
+ "objectType": "finality_receipt",
+ "payloadType": "validator_finality",
+ "payloadTypeHash": "0x5b50880335f071fa2b331aeea2e1cabff90461fafda06f5764afb839082479b8",
+ "objectTypeHash": "0x381c5cc90ae4f9e807ffbefd2190f856678a0a7d4ff518970bf95859c5c29560",
+ "objectId": "0xbe62b59a3b3a433caa8d38c0ce5f83f37ab04c6022d6583094acf6bb3dd27801",
+ "payloadHash": "0x65be232fde1d36406be9f523ce10d6c3a9f4d5f8dbc6da1cfa1bdada4120991f",
+ "issuedAtUnixMs": "1778702400000",
+ "expiresAtUnixMs": "1778706000000",
+ "localExecutionCost": {
+ "unit": "local-compute",
+ "amount": "0",
+ "metering": "not-metered-local-private-testnet"
+ },
+ "localExecutionCostHash": "0x6cad238c63ddf255495af76197783d3392f61da8bb61a7943bd4b79ed3745fb8",
+ "fee": {
+ "assetId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "amount": "0",
+ "policy": "no-value-local-private-testnet"
+ },
+ "feeHash": "0x84226d772ddfcad0ed9437e0dbccdf42f4b05660112b16fdd53b089b83781b93",
+ "signatureAlgorithm": "secp256k1-keccak256-eip712-local-v0",
+ "signatureAlgorithmHash": "0x1a6f483c4f6880a162cefa906f3dd9dc228115e89c04c539d63601752d3d57ae",
+ "signingDigest": "0xe9accb787c8f8f0c068c7517184b5821808cede556b3af60b05cfe787e6a8301",
+ "signature": "0xfe24a96028f74f2a83801aa6d0e76dc7e07cebc322b60ce81ec65099e6a708250281cd202d9fe1bfc069d02901fdafae2bd6e883b97600d7dc1945299d3f5360",
+ "transactionId": "0x480b1f060abfcc6d82212871438b030192c86260c71352f27d92f35424f3936b"
+ },
+ "expected": {
+ "objectId": "0xbe62b59a3b3a433caa8d38c0ce5f83f37ab04c6022d6583094acf6bb3dd27801",
+ "payloadHash": "0x65be232fde1d36406be9f523ce10d6c3a9f4d5f8dbc6da1cfa1bdada4120991f",
+ "envelopeId": "0x071c2ff1c3160e4eb0087275c891cf17d3a2dc46bd15d6ca378f4099541a7d2e",
+ "signingDigest": "0xe9accb787c8f8f0c068c7517184b5821808cede556b3af60b05cfe787e6a8301",
+ "transactionId": "0x480b1f060abfcc6d82212871438b030192c86260c71352f27d92f35424f3936b"
+ },
+ "runtime": {
+ "ok": true,
+ "signerAddress": "0x7458689c2bda5e6dffc26967b9cccf5b83bade66",
+ "signerAccountId": "0x18064db99c33bd271aa2f94835f67686d609463964b9c5ea86d98071b1111c9d",
+ "payloadHash": "0x65be232fde1d36406be9f523ce10d6c3a9f4d5f8dbc6da1cfa1bdada4120991f",
+ "transactionId": "0x480b1f060abfcc6d82212871438b030192c86260c71352f27d92f35424f3936b",
+ "nonce": "11",
+ "chainId": "31337",
+ "networkProfile": "local-chain"
+ }
+ }
+ ],
+ "negative": [
+ {
+ "name": "production-l1.wrong-chain-id",
+ "base": "wallet-transfer",
+ "mutation": {
+ "context": {
+ "chainId": "31338"
+ }
+ },
+ "primaryFailureCode": "wrong-chain-id",
+ "expectFailureCodes": [
+ "wrong-chain-id"
+ ]
+ },
+ {
+ "name": "production-l1.wrong-network-profile",
+ "base": "wallet-transfer",
+ "mutation": {
+ "context": {
+ "networkProfile": "private-lan"
+ }
+ },
+ "primaryFailureCode": "wrong-network-profile",
+ "expectFailureCodes": [
+ "wrong-network-profile"
+ ]
+ },
+ {
+ "name": "production-l1.wrong-domain",
+ "base": "wallet-transfer",
+ "mutation": {
+ "envelope": {
+ "domain": "flowchain.production-l1.v0.transaction-envelope:profile:private-lan:chain:31337"
+ }
+ },
+ "primaryFailureCode": "wrong-domain",
+ "expectFailureCodes": [
+ "wrong-domain"
+ ]
+ },
+ {
+ "name": "production-l1.wrong-signer",
+ "base": "wallet-transfer",
+ "mutation": {
+ "envelope": {
+ "signerId": "0xfc3fd2b4d0c7a318201db17cd10f517d9f218defe1f3a9fd243692d156d70dd2"
+ }
+ },
+ "primaryFailureCode": "wrong-signer",
+ "expectFailureCodes": [
+ "bad-envelope-digest",
+ "bad-envelope-id",
+ "wrong-signer"
+ ]
+ },
+ {
+ "name": "production-l1.wrong-signer-role",
+ "base": "bridge-credit-authority",
+ "mutation": {
+ "envelope": {
+ "signerRole": "user",
+ "signerRoleCode": 10
+ }
+ },
+ "primaryFailureCode": "wrong-signer",
+ "expectFailureCodes": [
+ "bad-envelope-digest",
+ "bad-envelope-id",
+ "wrong-signer"
+ ]
+ },
+ {
+ "name": "production-l1.stale-nonce",
+ "base": "wallet-transfer",
+ "mutation": {
+ "context": {
+ "minimumNonce": "2"
+ }
+ },
+ "primaryFailureCode": "stale-nonce",
+ "expectFailureCodes": [
+ "stale-nonce"
+ ]
+ },
+ {
+ "name": "production-l1.duplicate-nonce",
+ "base": "wallet-transfer",
+ "mutation": {
+ "contextKind": "duplicate-nonce"
+ },
+ "primaryFailureCode": "duplicate-nonce",
+ "expectFailureCodes": [
+ "duplicate-nonce",
+ "replay"
+ ]
+ },
+ {
+ "name": "production-l1.duplicate-tx-id",
+ "base": "wallet-transfer",
+ "mutation": {
+ "contextKind": "duplicate-tx-id"
+ },
+ "primaryFailureCode": "duplicate-tx-id",
+ "expectFailureCodes": [
+ "duplicate-tx-id"
+ ]
+ },
+ {
+ "name": "production-l1.expired-tx",
+ "base": "wallet-transfer",
+ "mutation": {
+ "context": {
+ "nowUnixMs": "1778709600000"
+ }
+ },
+ "primaryFailureCode": "expired-tx",
+ "expectFailureCodes": [
+ "expired-tx"
+ ]
+ },
+ {
+ "name": "production-l1.mutated-payload",
+ "base": "swap",
+ "mutation": {
+ "document": {
+ "amountIn": "200000"
+ }
+ },
+ "primaryFailureCode": "bad-payload-hash",
+ "expectFailureCodes": [
+ "bad-object-id",
+ "bad-payload-hash"
+ ]
+ },
+ {
+ "name": "production-l1.malformed-public-key",
+ "base": "wallet-transfer",
+ "mutation": {
+ "envelope": {
+ "publicKey": "0x1234"
+ }
+ },
+ "primaryFailureCode": "malformed-public-key",
+ "expectFailureCodes": [
+ "malformed-public-key"
+ ]
+ },
+ {
+ "name": "production-l1.malformed-signature",
+ "base": "wallet-transfer",
+ "mutation": {
+ "envelope": {
+ "signature": "0x1234"
+ }
+ },
+ "primaryFailureCode": "malformed-signature",
+ "expectFailureCodes": [
+ "bad-transaction-id",
+ "malformed-id",
+ "malformed-signature"
+ ]
+ },
+ {
+ "name": "production-l1.malformed-root",
+ "base": "validator-finality",
+ "mutation": {
+ "document": {
+ "challengeRoot": "0x1234"
+ }
+ },
+ "primaryFailureCode": "malformed-root",
+ "expectFailureCodes": [
+ "malformed-id",
+ "malformed-root"
+ ]
+ },
+ {
+ "name": "production-l1.duplicate-bridge-source-event",
+ "base": "bridge-credit-authority",
+ "mutation": {
+ "contextKind": "duplicate-bridge-source-event"
+ },
+ "primaryFailureCode": "duplicate-bridge-source-event",
+ "expectFailureCodes": [
+ "duplicate-bridge-source-event"
+ ]
+ }
+ ]
+}
diff --git a/crypto/package.json b/crypto/package.json
index 2076af9c..5cc79be5 100644
--- a/crypto/package.json
+++ b/crypto/package.json
@@ -14,6 +14,10 @@
"./pilot-envelope-validation": {
"types": "./src/pilot-envelope-validation.d.ts",
"default": "./src/pilot-envelope-validation.js"
+ },
+ "./runtime-validation": {
+ "types": "./src/runtime-validation.d.ts",
+ "default": "./src/runtime-validation.js"
}
},
"scripts": {
@@ -22,21 +26,44 @@
"validate:vectors": "node src/validate-vectors.js",
"validate:local-alpha": "node src/validate-local-alpha-fixtures.js",
"validate:product-transactions": "node src/validate-product-testnet-fixtures.js",
+ "validate:production-l1-crypto": "node src/validate-production-l1-crypto.js",
"wallet:product-smoke": "node src/validate-product-testnet-fixtures.js",
"wallet:create": "node src/wallet-cli.js create",
+ "wallet:import": "node src/wallet-cli.js import",
+ "wallet:unlock": "node src/wallet-cli.js unlock",
+ "wallet:lock": "node src/wallet-cli.js lock",
"wallet:check": "node src/wallet-cli.js check",
"wallet:list": "node src/wallet-cli.js list",
"wallet:metadata": "node src/wallet-cli.js metadata",
+ "wallet:export-metadata": "node src/wallet-cli.js export-metadata",
+ "wallet:verify-metadata": "node src/wallet-cli.js verify-metadata",
"wallet:add-account": "node src/wallet-cli.js add-account",
"wallet:rotate": "node src/wallet-cli.js rotate",
"wallet:sign": "node src/wallet-cli.js sign",
"wallet:verify": "node src/wallet-cli.js verify",
+ "wallet:derive-metadata": "node src/wallet-cli.js derive-metadata",
+ "wallet:sign-transfer": "node src/wallet-cli.js sign-transfer",
+ "wallet:sign-token-launch": "node src/wallet-cli.js sign-token-launch",
+ "wallet:sign-token-transfer": "node src/wallet-cli.js sign-token-transfer",
+ "wallet:sign-pool-create": "node src/wallet-cli.js sign-pool-create",
+ "wallet:sign-add-liquidity": "node src/wallet-cli.js sign-add-liquidity",
+ "wallet:sign-remove-liquidity": "node src/wallet-cli.js sign-remove-liquidity",
+ "wallet:sign-swap": "node src/wallet-cli.js sign-swap",
+ "wallet:sign-withdrawal-intent": "node src/wallet-cli.js sign-withdrawal-intent",
+ "wallet:sign-finality": "node src/wallet-cli.js sign-finality",
+ "wallet:submit": "node src/wallet-cli.js submit",
+ "wallet:query": "node src/wallet-cli.js query",
+ "wallet:e2e": "node src/wallet-e2e.js",
+ "wallet:transfer:e2e": "node src/wallet-e2e.js --transfer-only",
+ "wallet:operator-bridge": "node src/operator-bridge-cli.js",
"wallet:pilot-config": "node src/pilot-wallet-cli.js config-from-env",
"wallet:pilot-metadata": "node src/pilot-wallet-cli.js metadata",
"wallet:pilot-sign": "node src/pilot-wallet-cli.js sign",
"wallet:pilot-verify": "node src/pilot-wallet-cli.js verify",
"wallet:pilot-next": "node src/pilot-wallet-cli.js next-commands",
- "wallet:pilot-e2e": "node src/pilot-wallet-e2e.js"
+ "wallet:pilot-e2e": "node src/pilot-wallet-e2e.js",
+ "scan:no-secrets": "node src/no-secret-scan.js",
+ "production-l1:vectors": "node src/production-l1-vector-cli.js"
},
"dependencies": {
"@noble/hashes": "2.2.0",
diff --git a/crypto/src/constants.js b/crypto/src/constants.js
index ff29ef14..2299559b 100644
--- a/crypto/src/constants.js
+++ b/crypto/src/constants.js
@@ -97,6 +97,20 @@ export const TYPE_STRINGS = Object.freeze({
"FlowChainLocalSignatureEnvelopeV0(bytes32 objectId,bytes32 objectTypeHash,bytes32 domainSeparator,bytes32 signerId,bytes32 signerKeyId,uint8 signerRole,uint64 sequence,uint64 issuedAtUnixMs,uint64 expiresAtUnixMs,bytes32 nonce)",
localTransactionEnvelopeV0:
"FlowChainLocalTransactionEnvelopeV0(uint256 chainId,bytes32 domainSeparator,bytes32 signerId,bytes32 signerKeyId,uint8 signerRole,uint64 nonce,bytes32 payloadHash,bytes32 objectId,bytes32 objectTypeHash,uint64 issuedAtUnixMs)",
+ localTransactionEnvelopeProductionL1V0:
+ "FlowChainLocalTransactionEnvelopeProductionL1V0(uint16 schemaVersion,uint256 chainId,bytes32 networkProfileHash,bytes32 domainSeparator,bytes32 signerId,bytes32 signerKeyId,uint8 signerRole,uint64 nonce,bytes32 payloadTypeHash,bytes32 payloadHash,bytes32 objectId,bytes32 objectTypeHash,uint64 issuedAtUnixMs,uint64 expiresAtUnixMs,bytes32 localExecutionCostHash,bytes32 feeHash,bytes32 signatureAlgorithmHash)",
+ flowchainTransactionIdV0:
+ "FlowChainTransactionIdV0(uint256 chainId,bytes32 networkProfileHash,bytes32 envelopeId,bytes32 payloadHash,bytes32 signatureHash)",
+ flowchainAccountIdV0:
+ "FlowChainAccountIdV0(bytes32 publicKeyHash,address flowchainAddress,bytes32 roleRoot)",
+ flowchainBridgeObservationV0:
+ "FlowChainBridgeObservationV0(uint256 sourceChainId,address lockbox,address token,address depositor,bytes32 recipient,uint256 amount,bytes32 txHash,uint32 logIndex,uint64 blockNumber,uint256 eventNonce)",
+ flowchainBridgeCreditV1:
+ "FlowChainBridgeCreditV1(bytes32 observationId,bytes32 localRecipient,uint256 localChainId,uint256 creditAmount)",
+ flowchainWithdrawalIntentV1:
+ "FlowChainWithdrawalIntentV1(uint256 localChainId,bytes32 accountId,bytes32 assetId,uint256 amount,uint64 nonce,bytes32 destinationHash)",
+ flowchainFinalityReceiptV1:
+ "FlowChainFinalityReceiptV1(uint256 chainId,uint64 blockNumber,bytes32 blockHash,bytes32 stateRoot,bytes32 validatorSetRoot,uint64 round,bytes32 voteRoot)",
eip712Domain:
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"
});
@@ -142,7 +156,20 @@ export const DOMAIN_STRINGS = Object.freeze({
hardwareSignalEnvelopeId: "flowchain.local-alpha.v0.hardware-signal-envelope.id",
controlPlaneProvenanceResponseId: "flowchain.local-alpha.v0.control-plane-provenance-response.id",
localSignatureEnvelope: "flowchain.local-alpha.v0.local-signature-envelope",
- localTransactionEnvelope: "flowchain.local-alpha.v0.local-transaction-envelope"
+ localTransactionEnvelope: "flowchain.local-alpha.v0.local-transaction-envelope",
+ productionL1TransactionEnvelope: "flowchain.production-l1.v0.transaction-envelope",
+ productionLocalChain: "flowchain.production-l1.v0.local-chain",
+ productionPrivateLan: "flowchain.production-l1.v0.private-lan",
+ productionBase8453PilotBridge: "flowchain.production-l1.v0.base-8453-pilot-bridge",
+ productionObjectLifecycle: "flowchain.production-l1.v0.object-lifecycle",
+ productionTokenDex: "flowchain.production-l1.v0.token-dex",
+ productionValidatorFinality: "flowchain.production-l1.v0.validator-finality",
+ productionAccountIdentity: "flowchain.production-l1.v0.account-identity",
+ productionAddress: "flowchain.production-l1.v0.address",
+ productionBridgeObservation: "flowchain.production-l1.v0.bridge-observation",
+ productionBridgeCredit: "flowchain.production-l1.v0.bridge-credit",
+ productionWithdrawalIntent: "flowchain.production-l1.v0.withdrawal-intent",
+ productionFinalityReceipt: "flowchain.production-l1.v0.finality-receipt"
});
export const MERKLE_SCHEME_V0 = "FM-MERKLE-KECCAK256-BINARY-V0";
@@ -210,7 +237,40 @@ export const LOCAL_ALPHA_SIGNER_ROLES = Object.freeze({
operator: 1,
agent: 2,
verifier: 3,
- hardware: 4
+ hardware: 4,
+ user: 10,
+ validator: 11,
+ bridgeRelayer: 12,
+ bridgeReleaseAuthority: 13,
+ emergencyOperator: 14
+});
+
+export const FLOWCHAIN_ACCOUNT_ROLES = Object.freeze({
+ user: {
+ code: LOCAL_ALPHA_SIGNER_ROLES.user,
+ roleGated: false,
+ description: "Normal account authority for user-owned local/private transactions."
+ },
+ validator: {
+ code: LOCAL_ALPHA_SIGNER_ROLES.validator,
+ roleGated: true,
+ description: "Validator/finality authority for local/private finality objects."
+ },
+ bridgeRelayer: {
+ code: LOCAL_ALPHA_SIGNER_ROLES.bridgeRelayer,
+ roleGated: true,
+ description: "Bridge observation submitter for source-event facts."
+ },
+ bridgeReleaseAuthority: {
+ code: LOCAL_ALPHA_SIGNER_ROLES.bridgeReleaseAuthority,
+ roleGated: true,
+ description: "Bridge credit and release authority for local/private bridge accounting."
+ },
+ emergencyOperator: {
+ code: LOCAL_ALPHA_SIGNER_ROLES.emergencyOperator,
+ roleGated: true,
+ description: "Emergency operator for pause, revoke, and recovery controls."
+ }
});
export const LOCAL_ALPHA_BRIDGE_STATUSES = Object.freeze({
diff --git a/crypto/src/identity.js b/crypto/src/identity.js
new file mode 100644
index 00000000..7553e4ce
--- /dev/null
+++ b/crypto/src/identity.js
@@ -0,0 +1,100 @@
+import * as secp from "@noble/secp256k1";
+
+import { FLOWCHAIN_ACCOUNT_ROLES, TYPE_STRINGS } from "./constants.js";
+import { hexToBytes, strip0x } from "./encoding.js";
+import { canonicalJsonHash, keccak256Hex, typedHash } from "./hashes.js";
+
+export const FLOWCHAIN_PUBLIC_KEY_ENCODING = "secp256k1-compressed-hex";
+
+export function normalizeFlowchainPublicKey(publicKey) {
+ const raw = hexToBytes(publicKey);
+ if (![33, 65].includes(raw.length)) {
+ throw new Error("malformed public key");
+ }
+ const point = secp.Point.fromHex(strip0x(publicKey));
+ point.assertValidity();
+ return `0x${point.toHex(true)}`;
+}
+
+export function flowchainPublicKeyHash(publicKey) {
+ return canonicalJsonHash({
+ schema: "flowchain.public_key.v0",
+ encoding: FLOWCHAIN_PUBLIC_KEY_ENCODING,
+ publicKey: normalizeFlowchainPublicKey(publicKey)
+ });
+}
+
+export function flowchainAddressFromPublicKey(publicKey) {
+ const publicKeyHash = flowchainPublicKeyHash(publicKey);
+ const digest = keccak256Hex(hexToBytes(publicKeyHash, 32));
+ return `0x${strip0x(digest).slice(-40)}`;
+}
+
+export function flowchainRoleMetadata(role) {
+ const metadata = FLOWCHAIN_ACCOUNT_ROLES[role];
+ if (!metadata) {
+ throw new Error(`unsupported FlowChain account role: ${role}`);
+ }
+ return {
+ schema: "flowchain.account_role_metadata.v0",
+ role,
+ roleCode: metadata.code,
+ roleGated: metadata.roleGated,
+ description: metadata.description
+ };
+}
+
+export function flowchainRoleRoot(role) {
+ return canonicalJsonHash(flowchainRoleMetadata(role));
+}
+
+export function flowchainAccountId({ publicKey, role = "user" }) {
+ const normalizedPublicKey = normalizeFlowchainPublicKey(publicKey);
+ return typedHash(TYPE_STRINGS.flowchainAccountIdV0, [
+ ["bytes32", flowchainPublicKeyHash(normalizedPublicKey)],
+ ["address", flowchainAddressFromPublicKey(normalizedPublicKey)],
+ ["bytes32", flowchainRoleRoot(role)]
+ ]);
+}
+
+export function flowchainSignerKeyId({ publicKey }) {
+ return canonicalJsonHash({
+ schema: "flowchain.signer_key.v0",
+ publicKeyEncoding: FLOWCHAIN_PUBLIC_KEY_ENCODING,
+ publicKey: normalizeFlowchainPublicKey(publicKey)
+ });
+}
+
+export function flowchainPublicAccountMetadata({ publicKey, role = "user", label, createdAtUnixMs, active = true }) {
+ const normalizedPublicKey = normalizeFlowchainPublicKey(publicKey);
+ const roleMetadata = flowchainRoleMetadata(role);
+ return {
+ schema: "flowchain.public_account_metadata.v0",
+ label,
+ role,
+ roleCode: roleMetadata.roleCode,
+ roleGated: roleMetadata.roleGated,
+ publicKeyEncoding: FLOWCHAIN_PUBLIC_KEY_ENCODING,
+ publicKey: normalizedPublicKey,
+ publicKeyHash: flowchainPublicKeyHash(normalizedPublicKey),
+ address: flowchainAddressFromPublicKey(normalizedPublicKey),
+ accountId: flowchainAccountId({ publicKey: normalizedPublicKey, role }),
+ signerKeyId: flowchainSignerKeyId({ publicKey: normalizedPublicKey }),
+ createdAtUnixMs,
+ active
+ };
+}
+
+export function assertFlowchainPublicMetadataContainsNoSecrets(value) {
+ const serialized = JSON.stringify(value);
+ if (
+ /privateKey|private_key|seedPhrase|seed phrase|mnemonic|ciphertext|authTag|password|rpc[-_]?credential|rpc[-_]?url|api[-_]?key|webhook/i.test(serialized) ||
+ /https:\/\/hooks\.slack\.com|https:\/\/discord\.com\/api\/webhooks/i.test(serialized)
+ ) {
+ throw new Error("public FlowChain metadata contains secret-shaped material");
+ }
+}
+
+export function isFlowchainRole(role) {
+ return Boolean(FLOWCHAIN_ACCOUNT_ROLES[role]);
+}
diff --git a/crypto/src/index.js b/crypto/src/index.js
index fca98f63..1db3c962 100644
--- a/crypto/src/index.js
+++ b/crypto/src/index.js
@@ -4,9 +4,14 @@ export * from "./domains.js";
export * from "./encoding.js";
export * from "./flowpulse.js";
export * from "./hashes.js";
+export * from "./identity.js";
export * from "./merkle.js";
export * from "./objects.js";
export * from "./pilot-envelope-validation.js";
export * from "./pilot-operator.js";
+export * from "./production-l1.js";
+export * from "./runtime-validation.js";
export * from "./transactions.js";
export * from "./wallet.js";
+export * from "./wallet-documents.js";
+export * from "./wallet-envelope.js";
diff --git a/crypto/src/no-secret-scan.js b/crypto/src/no-secret-scan.js
new file mode 100644
index 00000000..172c22a0
--- /dev/null
+++ b/crypto/src/no-secret-scan.js
@@ -0,0 +1,69 @@
+#!/usr/bin/env node
+import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
+import { resolve } from "node:path";
+
+const root = resolve(import.meta.dirname, "..", "..");
+const scanRoots = [
+ "crypto/fixtures",
+ "crypto/test",
+ "schemas/flowmemory",
+ "fixtures/crypto"
+];
+
+const patterns = [
+ {
+ name: "private-key-field",
+ pattern: /["']?privateKey["']?\s*[:=]\s*["']0x[0-9a-fA-F]{64}["']/i
+ },
+ {
+ name: "seed-or-mnemonic-field",
+ pattern: /["']?(seedPhrase|seed phrase|mnemonic)["']?\s*[:=]\s*["'][^"']{8,}["']/i
+ },
+ {
+ name: "rpc-url-with-secret-token",
+ pattern: /https?:\/\/[^\s"']*(rpc|alchemy|infura|quicknode|token|secret|key)[^\s"']*/i
+ },
+ {
+ name: "api-key-field",
+ pattern: /["']?(apiKey|api_key|API_KEY)["']?\s*[:=]\s*["'][A-Za-z0-9_\-]{16,}["']/i
+ },
+ {
+ name: "webhook-url",
+ pattern: /https:\/\/(hooks\.slack\.com|discord\.com\/api\/webhooks)\/[^\s"']+/i
+ }
+];
+
+const findings = [];
+for (const scanRoot of scanRoots) {
+ const absolute = resolve(root, scanRoot);
+ if (!existsSync(absolute)) {
+ continue;
+ }
+ for (const path of files(absolute)) {
+ const text = readFileSync(path, "utf8");
+ for (const { name, pattern } of patterns) {
+ if (pattern.test(text)) {
+ findings.push({ path, name });
+ }
+ }
+ }
+}
+
+if (findings.length > 0) {
+ console.error(JSON.stringify({ schema: "flowmemory.crypto.no_secret_scan.v0", ok: false, findings }, null, 2));
+ process.exitCode = 1;
+} else {
+ console.log(JSON.stringify({ schema: "flowmemory.crypto.no_secret_scan.v0", ok: true, scannedRoots: scanRoots }, null, 2));
+}
+
+function* files(dir) {
+ for (const entry of readdirSync(dir)) {
+ const path = resolve(dir, entry);
+ const stat = statSync(path);
+ if (stat.isDirectory()) {
+ yield* files(path);
+ } else if (/\.(json|js|ts|md)$/.test(path)) {
+ yield path;
+ }
+ }
+}
diff --git a/crypto/src/objects.js b/crypto/src/objects.js
index a6e17746..b5982718 100644
--- a/crypto/src/objects.js
+++ b/crypto/src/objects.js
@@ -823,7 +823,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "verifier_report",
idField: "reportId",
domainName: "verifierReportDigest",
- signerRoles: ["verifier"],
+ signerRoles: ["verifier", "validator"],
nonzeroFields: [
"reportId",
"reportSchemaHash",
@@ -899,7 +899,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "finality_receipt",
idField: "finalityReceiptId",
domainName: "finalityReceiptId",
- signerRoles: ["verifier"],
+ signerRoles: ["verifier", "validator"],
nonzeroFields: ["finalityReceiptId", "receiptId", "reportId", "challengeRoot", "policyHash"],
input: (document) => ({
receiptId: document.receiptId,
@@ -927,7 +927,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "bridge_deposit",
idField: "depositId",
domainName: "bridgeDepositId",
- signerRoles: ["operator"],
+ signerRoles: ["operator", "bridgeRelayer"],
nonzeroFields: [
"depositId",
"txHash",
@@ -960,7 +960,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "bridge_credit",
idField: "creditId",
domainName: "bridgeCreditId",
- signerRoles: ["operator"],
+ signerRoles: ["operator", "bridgeReleaseAuthority"],
nonzeroFields: ["creditId", "depositId", "recipient", "assetId", "nonce"],
input: (document) => ({
depositId: document.depositId,
@@ -981,7 +981,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "bridge_withdrawal",
idField: "withdrawalId",
domainName: "bridgeWithdrawalId",
- signerRoles: ["agent", "operator"],
+ signerRoles: ["agent", "operator", "user"],
nonzeroFields: ["withdrawalId", "accountId", "metadataHash", "nonce"],
input: (document) => ({
accountId: document.accountId,
@@ -1005,7 +1005,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "local_balance_record",
idField: "balanceRecordId",
domainName: "localBalanceRecordId",
- signerRoles: ["operator"],
+ signerRoles: ["operator", "emergencyOperator"],
nonzeroFields: ["balanceRecordId", "accountId", "assetId", "stateRoot", "nonce"],
input: (document) => ({
accountId: document.accountId,
@@ -1027,7 +1027,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "product_transfer",
idField: "transferId",
domainName: "productTransferId",
- signerRoles: ["agent"],
+ signerRoles: ["agent", "user"],
nonzeroFields: ["transferId", "fromAccountId", "toAccountId", "assetId"],
input: (document) => ({
fromAccountId: document.fromAccountId,
@@ -1047,7 +1047,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "product_token_launch",
idField: "tokenLaunchId",
domainName: "productTokenLaunchId",
- signerRoles: ["agent"],
+ signerRoles: ["agent", "user"],
nonzeroFields: [
"tokenLaunchId",
"issuerAccountId",
@@ -1079,7 +1079,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "product_pool_create",
idField: "poolCreateId",
domainName: "productPoolCreateId",
- signerRoles: ["agent"],
+ signerRoles: ["agent", "user"],
nonzeroFields: ["poolCreateId", "creatorAccountId", "poolId", "baseAssetId", "quoteAssetId", "metadataHash"],
input: (document) => ({
creatorAccountId: document.creatorAccountId,
@@ -1107,7 +1107,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "product_add_liquidity",
idField: "addLiquidityId",
domainName: "productAddLiquidityId",
- signerRoles: ["agent"],
+ signerRoles: ["agent", "user"],
nonzeroFields: ["addLiquidityId", "providerAccountId", "poolId"],
input: (document) => ({
providerAccountId: document.providerAccountId,
@@ -1127,7 +1127,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "product_remove_liquidity",
idField: "removeLiquidityId",
domainName: "productRemoveLiquidityId",
- signerRoles: ["agent"],
+ signerRoles: ["agent", "user"],
nonzeroFields: ["removeLiquidityId", "providerAccountId", "poolId"],
input: (document) => ({
providerAccountId: document.providerAccountId,
@@ -1147,7 +1147,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "product_swap",
idField: "swapId",
domainName: "productSwapId",
- signerRoles: ["agent"],
+ signerRoles: ["agent", "user"],
nonzeroFields: ["swapId", "traderAccountId", "poolId", "assetInId", "assetOutId"],
input: (document) => ({
traderAccountId: document.traderAccountId,
@@ -1168,7 +1168,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "product_bridge_credit_ack",
idField: "bridgeCreditAckId",
domainName: "productBridgeCreditAckId",
- signerRoles: ["agent"],
+ signerRoles: ["agent", "user"],
nonzeroFields: ["bridgeCreditAckId", "creditId", "depositId", "accountId", "assetId"],
input: (document) => ({
creditId: document.creditId,
@@ -1188,7 +1188,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "bridge_withdrawal_intent",
idField: "withdrawalIntentId",
domainName: "bridgeWithdrawalIntentId",
- signerRoles: ["agent"],
+ signerRoles: ["agent", "user"],
nonzeroFields: ["withdrawalIntentId", "creditId", "depositId", "flowchainAccount"],
input: (document) => ({
creditId: document.creditId,
@@ -1222,7 +1222,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "pilot_bridge_credit_ack",
idField: "pilotBridgeCreditAckId",
domainName: "pilotBridgeCreditAckId",
- signerRoles: ["operator"],
+ signerRoles: ["operator", "bridgeReleaseAuthority"],
nonzeroFields: [
"pilotBridgeCreditAckId",
"operatorId",
@@ -1259,7 +1259,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "pilot_withdrawal_intent",
idField: "pilotWithdrawalIntentId",
domainName: "pilotWithdrawalIntentId",
- signerRoles: ["operator"],
+ signerRoles: ["operator", "bridgeReleaseAuthority"],
nonzeroFields: [
"pilotWithdrawalIntentId",
"operatorId",
@@ -1300,7 +1300,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "pilot_release_evidence",
idField: "pilotReleaseEvidenceId",
domainName: "pilotReleaseEvidenceId",
- signerRoles: ["operator"],
+ signerRoles: ["operator", "bridgeReleaseAuthority"],
nonzeroFields: [
"pilotReleaseEvidenceId",
"operatorId",
@@ -1340,7 +1340,7 @@ export const LOCAL_ALPHA_OBJECT_DESCRIPTORS = Object.freeze({
objectType: "pilot_emergency_control",
idField: "pilotEmergencyControlId",
domainName: "pilotEmergencyControlId",
- signerRoles: ["operator"],
+ signerRoles: ["operator", "emergencyOperator"],
nonzeroFields: [
"pilotEmergencyControlId",
"operatorId",
diff --git a/crypto/src/operator-bridge-cli.js b/crypto/src/operator-bridge-cli.js
new file mode 100644
index 00000000..f3a30611
--- /dev/null
+++ b/crypto/src/operator-bridge-cli.js
@@ -0,0 +1,279 @@
+#!/usr/bin/env node
+
+const BASE_CHAIN_ID = 8453;
+const OPERATOR_ACK_VALUE = "I_UNDERSTAND_THIS_IS_CAPPED_BASE8453_OWNER_PILOT";
+const MAX_SINGLE_DEPOSIT_WEI = 100000000000000n;
+const MAX_TOTAL_CAP_WEI = 1000000000000000n;
+const REQUIRED_ENV_NAMES = Object.freeze([
+ "FLOWCHAIN_PILOT_OPERATOR_ACK",
+ "FLOWCHAIN_BASE8453_RPC_URL",
+ "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS",
+ "FLOWCHAIN_BASE8453_FROM_BLOCK",
+ "FLOWCHAIN_BASE8453_TO_BLOCK",
+ "FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI",
+ "FLOWCHAIN_PILOT_TOTAL_CAP_WEI",
+ "FLOWCHAIN_PILOT_WITHDRAWAL_RECIPIENT",
+ "FLOWCHAIN_PILOT_MAX_USD"
+]);
+
+const command = process.argv[2] ?? "env";
+const args = parseArgs(process.argv.slice(3));
+
+try {
+ if (command === "env") {
+ printJson({
+ schema: "flowchain.wallet_operator.base8453_env_names.v0",
+ baseChainId: BASE_CHAIN_ID,
+ operatorAckRequiredValue: OPERATOR_ACK_VALUE,
+ requiredEnvNames: REQUIRED_ENV_NAMES,
+ dryRunCommand: "npm run wallet:operator-bridge --prefix crypto -- env",
+ liveValidationCommand: "npm run wallet:operator-bridge --prefix crypto -- validate --live",
+ boundary: "Env names only. Secret values are read from the local shell and are never printed."
+ });
+ } else if (command === "validate") {
+ const result = await validateOperatorEnv({ live: args.live === true || args.live === "true" });
+ printJson(result);
+ process.exitCode = result.valid ? 0 : 1;
+ } else if (command === "prepare-deposit-evidence") {
+ printJson(prepareDepositEvidence());
+ } else if (command === "prepare-release-evidence") {
+ printJson(prepareReleaseEvidence());
+ } else {
+ usage();
+ process.exitCode = 1;
+ }
+} catch (error) {
+ console.error(error instanceof Error ? error.message : String(error));
+ process.exitCode = 1;
+}
+
+async function validateOperatorEnv({ live }) {
+ const errors = [];
+ const envPresence = Object.fromEntries(REQUIRED_ENV_NAMES.map((name) => [name, hasEnv(name)]));
+ const lockbox = envValue("FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS");
+ const lockboxAddressValid = !lockbox || isEthAddress(lockbox);
+ if (!lockboxAddressValid) {
+ errors.push("bad-lockbox-address");
+ }
+ const withdrawalRecipient = envValue("FLOWCHAIN_PILOT_WITHDRAWAL_RECIPIENT");
+ const withdrawalRecipientValid = !withdrawalRecipient || isEthAddress(withdrawalRecipient);
+ if (!withdrawalRecipientValid) {
+ errors.push("bad-withdrawal-recipient");
+ }
+ const capResult = validateCaps();
+ errors.push(...capResult.errors);
+ const operatorAckPresent = envValue("FLOWCHAIN_PILOT_OPERATOR_ACK") === OPERATOR_ACK_VALUE;
+ if (!operatorAckPresent && live) {
+ errors.push("missing-operator-ack");
+ }
+
+ let chainIdValid = live ? false : null;
+ if (live) {
+ if (!hasEnv("FLOWCHAIN_BASE8453_RPC_URL")) {
+ errors.push("missing-rpc-url");
+ } else {
+ chainIdValid = await validateBaseChainId(envValue("FLOWCHAIN_BASE8453_RPC_URL"));
+ if (!chainIdValid) {
+ errors.push("wrong-chain-id");
+ }
+ }
+ }
+
+ return {
+ schema: "flowchain.wallet_operator.base8453_validation.v0",
+ valid: errors.length === 0,
+ live,
+ baseChainId: BASE_CHAIN_ID,
+ envPresence,
+ rpcConfigured: hasEnv("FLOWCHAIN_BASE8453_RPC_URL"),
+ rpcValuePrinted: false,
+ chainIdValid,
+ lockboxAddressValid,
+ withdrawalRecipientValid,
+ capCheck: capResult.check,
+ operatorAckPresent,
+ dryRunCommands: dryRunCommands(),
+ liveCommands: liveCommands(),
+ errors: [...new Set(errors)]
+ };
+}
+
+function prepareDepositEvidence() {
+ const lockbox = args["lockbox-address"] || envValue("FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS") || "";
+ if (!lockbox.startsWith("<") && !isEthAddress(lockbox)) {
+ throw new Error("lockbox address must be a 20-byte hex address");
+ }
+ return {
+ schema: "flowchain.wallet_operator.bridge_deposit_evidence_commands.v0",
+ baseChainId: BASE_CHAIN_ID,
+ lockboxAddressFormat: lockbox.startsWith("<") ? "placeholder" : "valid",
+ requiredEnvNames: REQUIRED_ENV_NAMES,
+ dryRunCommands: [
+ "npm run wallet:operator-bridge --prefix crypto -- env",
+ "npm run wallet:operator-bridge --prefix crypto -- validate"
+ ],
+ liveCommands: [
+ "npm run wallet:operator-bridge --prefix crypto -- validate --live",
+ "npm run flowchain:real-value-pilot -- --Mode Live --Action Observe"
+ ],
+ evidenceOutputs: [
+ "devnet/local/real-value-pilot/evidence/base8453-observation.json",
+ "devnet/local/real-value-pilot/evidence/base8453-credit-pending.json",
+ "devnet/local/real-value-pilot/evidence/base8453-handoff-pending.json"
+ ],
+ rpcValuePrinted: false,
+ broadcast: false
+ };
+}
+
+function prepareReleaseEvidence() {
+ const recipient = args["base-recipient"] || envValue("FLOWCHAIN_PILOT_WITHDRAWAL_RECIPIENT") || "";
+ if (!recipient.startsWith("<") && !isEthAddress(recipient)) {
+ throw new Error("withdrawal recipient must be a 20-byte Base address");
+ }
+ return {
+ schema: "flowchain.wallet_operator.bridge_release_evidence_commands.v0",
+ baseChainId: BASE_CHAIN_ID,
+ recipientAddressFormat: recipient.startsWith("<") ? "placeholder" : "valid",
+ requiredEnvNames: REQUIRED_ENV_NAMES,
+ dryRunCommands: [
+ "npm run wallet:operator-bridge --prefix crypto -- validate",
+ "npm run wallet:e2e --prefix crypto"
+ ],
+ liveCommands: [
+ "npm run wallet:operator-bridge --prefix crypto -- validate --live",
+ "npm run flowchain:real-value-pilot -- --Mode Live --Action Withdraw",
+ "npm run flowchain:real-value-pilot:export"
+ ],
+ evidenceOutputs: [
+ "devnet/local/real-value-pilot/evidence/base8453-withdrawal-intent.json",
+ "devnet/local/real-value-pilot/evidence/base8453-handoff-with-withdrawal.json"
+ ],
+ rpcValuePrinted: false,
+ broadcast: false
+ };
+}
+
+async function validateBaseChainId(rpcUrl) {
+ let url;
+ try {
+ url = new URL(rpcUrl);
+ } catch {
+ throw new Error("FLOWCHAIN_BASE8453_RPC_URL must be an absolute HTTP(S) URL");
+ }
+ if (!["http:", "https:"].includes(url.protocol)) {
+ throw new Error("FLOWCHAIN_BASE8453_RPC_URL must use HTTP(S)");
+ }
+ let response;
+ try {
+ response = await fetch(rpcUrl, {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_chainId", params: [] })
+ });
+ } catch {
+ throw new Error("could not read eth_chainId from FLOWCHAIN_BASE8453_RPC_URL; endpoint value was not printed");
+ }
+ const body = await response.json();
+ if (body.error || typeof body.result !== "string" || !/^0x[0-9a-fA-F]+$/.test(body.result)) {
+ throw new Error("eth_chainId returned an invalid response; endpoint value was not printed");
+ }
+ return Number.parseInt(body.result.slice(2), 16) === BASE_CHAIN_ID;
+}
+
+function validateCaps() {
+ const errors = [];
+ const maxDeposit = parseOptionalUint("FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI");
+ const totalCap = parseOptionalUint("FLOWCHAIN_PILOT_TOTAL_CAP_WEI");
+ if (maxDeposit === null || totalCap === null) {
+ errors.push("missing-cap-values");
+ } else {
+ if (maxDeposit <= 0n || maxDeposit > MAX_SINGLE_DEPOSIT_WEI) {
+ errors.push("bad-max-deposit-cap");
+ }
+ if (totalCap <= 0n || totalCap > MAX_TOTAL_CAP_WEI || totalCap < maxDeposit) {
+ errors.push("bad-total-cap");
+ }
+ }
+ return {
+ errors,
+ check: {
+ configured: maxDeposit !== null && totalCap !== null,
+ maxDepositWei: maxDeposit?.toString() ?? null,
+ totalCapWei: totalCap?.toString() ?? null,
+ maxSingleDepositLimitWei: MAX_SINGLE_DEPOSIT_WEI.toString(),
+ totalCapLimitWei: MAX_TOTAL_CAP_WEI.toString()
+ }
+ };
+}
+
+function parseOptionalUint(name) {
+ const value = envValue(name);
+ if (!value) {
+ return null;
+ }
+ if (!/^[0-9]+$/.test(value)) {
+ return -1n;
+ }
+ return BigInt(value);
+}
+
+function dryRunCommands() {
+ return [
+ "npm run wallet:operator-bridge --prefix crypto -- env",
+ "npm run wallet:operator-bridge --prefix crypto -- validate",
+ "npm run wallet:operator-bridge --prefix crypto -- prepare-deposit-evidence",
+ "npm run wallet:operator-bridge --prefix crypto -- prepare-release-evidence"
+ ];
+}
+
+function liveCommands() {
+ return [
+ "npm run wallet:operator-bridge --prefix crypto -- validate --live",
+ "npm run flowchain:real-value-pilot -- --Mode Live --Action Observe",
+ "npm run flowchain:real-value-pilot -- --Mode Live --Action Withdraw"
+ ];
+}
+
+function hasEnv(name) {
+ return envValue(name) !== "";
+}
+
+function envValue(name) {
+ return process.env[name] ?? "";
+}
+
+function isEthAddress(value) {
+ return /^0x[0-9a-fA-F]{40}$/.test(value);
+}
+
+function parseArgs(argv) {
+ const parsed = {};
+ for (let i = 0; i < argv.length; i += 1) {
+ const arg = argv[i];
+ if (!arg.startsWith("--")) {
+ continue;
+ }
+ const key = arg.slice(2);
+ const next = argv[i + 1];
+ if (!next || next.startsWith("--")) {
+ parsed[key] = true;
+ } else {
+ parsed[key] = next;
+ i += 1;
+ }
+ }
+ return parsed;
+}
+
+function printJson(value) {
+ console.log(JSON.stringify(value, null, 2));
+}
+
+function usage() {
+ console.error(`Usage:
+ node src/operator-bridge-cli.js env
+ node src/operator-bridge-cli.js validate [--live]
+ node src/operator-bridge-cli.js prepare-deposit-evidence [--lockbox-address <0x...>]
+ node src/operator-bridge-cli.js prepare-release-evidence [--base-recipient <0x...>]`);
+}
diff --git a/crypto/src/production-l1-vector-cli.js b/crypto/src/production-l1-vector-cli.js
new file mode 100644
index 00000000..8612eead
--- /dev/null
+++ b/crypto/src/production-l1-vector-cli.js
@@ -0,0 +1,33 @@
+#!/usr/bin/env node
+import { writeFileSync } from "node:fs";
+
+import { buildProductionL1Vectors } from "./production-l1-vectors.js";
+
+const args = parseArgs(process.argv.slice(2));
+const vectors = await buildProductionL1Vectors();
+const serialized = `${JSON.stringify(vectors, null, 2)}\n`;
+
+if (args.out) {
+ writeFileSync(args.out, serialized);
+} else {
+ process.stdout.write(serialized);
+}
+
+function parseArgs(argv) {
+ const parsed = {};
+ for (let i = 0; i < argv.length; i += 1) {
+ const arg = argv[i];
+ if (!arg.startsWith("--")) {
+ continue;
+ }
+ const key = arg.slice(2);
+ const next = argv[i + 1];
+ if (!next || next.startsWith("--")) {
+ parsed[key] = true;
+ } else {
+ parsed[key] = next;
+ i += 1;
+ }
+ }
+ return parsed;
+}
diff --git a/crypto/src/production-l1-vectors.js b/crypto/src/production-l1-vectors.js
new file mode 100644
index 00000000..3386567e
--- /dev/null
+++ b/crypto/src/production-l1-vectors.js
@@ -0,0 +1,582 @@
+import { bytesToHex } from "./encoding.js";
+import { signDigest, publicKeyFromPrivateKey } from "./attestations.js";
+import {
+ LOCAL_ALPHA_BRIDGE_STATUSES,
+ LOCAL_ALPHA_FINALITY_STATES,
+ ZERO_BYTES32
+} from "./constants.js";
+import {
+ bridgeCreditId,
+ bridgeWithdrawalIntentId,
+ finalityReceiptId,
+ localAlphaObjectId,
+ localBalanceRecordId,
+ productAddLiquidityId,
+ productPoolCreateId,
+ productRemoveLiquidityId,
+ productSwapId,
+ productTokenLaunchId,
+ productTransferId
+} from "./objects.js";
+import { buildUnsignedLocalTransactionEnvelope } from "./transactions.js";
+import {
+ accountNonceReplayKey,
+ bridgeSourceEventReplayKey,
+ finalityVoteReplayKey,
+ flowchainAccountStateRoot,
+ flowchainBlockHash,
+ flowchainBridgeCreditId,
+ flowchainBridgeObservationId,
+ flowchainDexStateRoot,
+ flowchainEventRoot,
+ flowchainFinalityReceiptId,
+ flowchainReceiptRoot,
+ flowchainTokenStateRoot,
+ flowchainTransactionId,
+ flowchainTxRoot,
+ flowchainWithdrawalIntentId,
+ roleScopedNonceReplayKey,
+ withdrawalIntentReplayKey
+} from "./production-l1.js";
+import {
+ flowchainAccountId,
+ flowchainPublicAccountMetadata,
+ flowchainSignerKeyId
+} from "./identity.js";
+import { canonicalJsonHash, keccakUtf8 } from "./hashes.js";
+import { verifyFlowchainEnvelope } from "./runtime-validation.js";
+
+const CHAIN_ID = "31337";
+const NETWORK_PROFILE = "local-chain";
+const ISSUED_AT_UNIX_MS = "1778702400000";
+const EXPIRES_AT_UNIX_MS = "1778706000000";
+const BASE_8453_CHAIN_ID = "8453";
+
+export async function buildProductionL1Vectors() {
+ const accounts = {
+ user: account("user", 1),
+ recipient: account("user", 2),
+ validator: account("validator", 3),
+ bridgeRelayer: account("bridgeRelayer", 4),
+ bridgeReleaseAuthority: account("bridgeReleaseAuthority", 5),
+ emergencyOperator: account("emergencyOperator", 6)
+ };
+
+ const assets = {
+ native: keccakUtf8("flowchain.asset.native-test-unit.v0"),
+ token: keccakUtf8("flowchain.asset.token.launch-demo.v0")
+ };
+ const poolId = keccakUtf8("flowchain.pool.native-token.demo");
+ const bridgeSourceEvent = {
+ sourceChainId: BASE_8453_CHAIN_ID,
+ lockbox: "0x1111111111111111111111111111111111111111",
+ token: "0x2222222222222222222222222222222222222222",
+ depositor: "0x3333333333333333333333333333333333333333",
+ recipient: accounts.user.accountId,
+ amount: "25000000",
+ txHash: keccakUtf8("base-8453-lock-tx"),
+ logIndex: "7",
+ blockNumber: "45955540",
+ eventNonce: "1"
+ };
+ const bridgeObservationId = flowchainBridgeObservationId(bridgeSourceEvent);
+ const canonicalBridgeCreditId = flowchainBridgeCreditId({
+ observationId: bridgeObservationId,
+ localRecipient: accounts.user.accountId,
+ localChainId: CHAIN_ID,
+ creditAmount: bridgeSourceEvent.amount
+ });
+ const depositId = keccakUtf8("bridge-deposit:base-8453:demo");
+
+ const documents = {
+ walletTransfer: productTransferDocument({
+ fromAccountId: accounts.user.accountId,
+ toAccountId: accounts.recipient.accountId,
+ assetId: assets.native,
+ amount: "1000000",
+ accountNonce: "1",
+ deadlineBlock: "120",
+ memoHash: keccakUtf8("wallet transfer")
+ }),
+ faucetFunding: localBalanceRecordDocument({
+ accountId: accounts.user.accountId,
+ assetId: assets.native,
+ availableAmount: "100000000",
+ lockedAmount: "0",
+ lastCreditId: keccakUtf8("faucet-credit"),
+ lastWithdrawalId: ZERO_BYTES32,
+ stateRoot: keccakUtf8("state:faucet-funded"),
+ updatedAtBlockNumber: "1",
+ nonce: keccakUtf8("local-balance:faucet:nonce")
+ }),
+ tokenLaunch: productTokenLaunchDocument({
+ issuerAccountId: accounts.user.accountId,
+ tokenId: assets.token,
+ symbolHash: keccakUtf8("FLOWT"),
+ nameHash: keccakUtf8("FlowChain Test Token"),
+ metadataHash: keccakUtf8("token metadata"),
+ decimals: 6,
+ initialSupply: "1000000000",
+ recipientAccountId: accounts.user.accountId,
+ accountNonce: "2",
+ launchPolicyHash: keccakUtf8("launch policy")
+ }),
+ tokenTransfer: productTransferDocument({
+ fromAccountId: accounts.user.accountId,
+ toAccountId: accounts.recipient.accountId,
+ assetId: assets.token,
+ amount: "5000000",
+ accountNonce: "3",
+ deadlineBlock: "140",
+ memoHash: keccakUtf8("token transfer")
+ }),
+ poolCreate: productPoolCreateDocument({
+ creatorAccountId: accounts.user.accountId,
+ poolId,
+ baseAssetId: assets.native,
+ quoteAssetId: assets.token,
+ feeBps: 30,
+ tickSpacing: 1,
+ metadataHash: keccakUtf8("pool metadata"),
+ accountNonce: "4"
+ }),
+ addLiquidity: productAddLiquidityDocument({
+ providerAccountId: accounts.user.accountId,
+ poolId,
+ baseAmount: "25000000",
+ quoteAmount: "250000000",
+ minLiquidityTokens: "1",
+ deadlineBlock: "160",
+ accountNonce: "5"
+ }),
+ removeLiquidity: productRemoveLiquidityDocument({
+ providerAccountId: accounts.user.accountId,
+ poolId,
+ liquidityTokens: "1000",
+ minBaseAmount: "1",
+ minQuoteAmount: "1",
+ deadlineBlock: "180",
+ accountNonce: "6"
+ }),
+ swap: productSwapDocument({
+ traderAccountId: accounts.user.accountId,
+ poolId,
+ assetInId: assets.native,
+ assetOutId: assets.token,
+ amountIn: "100000",
+ minAmountOut: "900000",
+ deadlineBlock: "200",
+ accountNonce: "7"
+ }),
+ bridgeCredit: bridgeCreditDocument({
+ depositId,
+ recipient: accounts.user.accountId,
+ assetId: assets.token,
+ amount: bridgeSourceEvent.amount,
+ creditedAtBlockNumber: "8",
+ creditedAtUnixMs: ISSUED_AT_UNIX_MS,
+ status: "credited",
+ statusCode: LOCAL_ALPHA_BRIDGE_STATUSES.credited,
+ nonce: keccakUtf8("bridge-credit:nonce")
+ }),
+ withdrawalIntent: bridgeWithdrawalIntentDocument({
+ creditId: canonicalBridgeCreditId,
+ depositId,
+ sourceChainId: Number(CHAIN_ID),
+ destinationChainId: 8453,
+ token: bridgeSourceEvent.token,
+ amount: "10000000",
+ flowchainAccount: accounts.user.accountId,
+ baseRecipient: "0x4444444444444444444444444444444444444444",
+ status: "requested",
+ requestedAt: "2026-05-13T23:00:00.000Z",
+ testMode: true,
+ broadcast: false,
+ releasePolicy: "test_record_only",
+ productionReady: false
+ }),
+ finality: finalityReceiptDocument({
+ receiptId: keccakUtf8("receipt:finality"),
+ reportId: keccakUtf8("report:finality"),
+ challengeRoot: keccakUtf8("challenge-root:empty"),
+ finalityState: "finalized",
+ finalityStateCode: LOCAL_ALPHA_FINALITY_STATES.finalized,
+ finalizedAtUnixMs: ISSUED_AT_UNIX_MS,
+ finalizedBlockNumber: "9",
+ finalizedBlockHash: keccakUtf8("block:9"),
+ policyHash: keccakUtf8("finality-policy")
+ })
+ };
+
+ const positives = [
+ await signedVector("wallet-transfer", documents.walletTransfer, accounts.user, "1", "wallet_transfer"),
+ await signedVector("faucet-test-funding", documents.faucetFunding, accounts.emergencyOperator, "2", "faucet_test_funding"),
+ await signedVector("token-launch", documents.tokenLaunch, accounts.user, "3", "token_launch"),
+ await signedVector("token-transfer", documents.tokenTransfer, accounts.user, "4", "token_transfer"),
+ await signedVector("pool-create", documents.poolCreate, accounts.user, "5", "pool_create"),
+ await signedVector("add-liquidity", documents.addLiquidity, accounts.user, "6", "add_liquidity"),
+ await signedVector("remove-liquidity", documents.removeLiquidity, accounts.user, "7", "remove_liquidity"),
+ await signedVector("swap", documents.swap, accounts.user, "8", "swap"),
+ await signedVector("bridge-credit-authority", documents.bridgeCredit, accounts.bridgeReleaseAuthority, "9", "bridge_credit"),
+ await signedVector("withdrawal-intent", documents.withdrawalIntent, accounts.user, "10", "withdrawal_intent"),
+ await signedVector("validator-finality", documents.finality, accounts.validator, "11", "validator_finality")
+ ];
+
+ const hashHelpers = buildHashHelperVectors({
+ positives,
+ accounts,
+ assets,
+ poolId,
+ bridgeSourceEvent,
+ bridgeObservationId,
+ canonicalBridgeCreditId
+ });
+ const negatives = buildNegativeVectors({ positives, accounts, bridgeSourceEvent });
+
+ return {
+ schema: "flowmemory.crypto.production-l1-vectors.v0",
+ chainId: CHAIN_ID,
+ networkProfile: NETWORK_PROFILE,
+ issuedAtUnixMs: ISSUED_AT_UNIX_MS,
+ expiresAtUnixMs: EXPIRES_AT_UNIX_MS,
+ boundary: "Deterministic local/private production-L1-shaped crypto vectors. Fixtures contain public test metadata, documents, signatures, and expected validation failures only.",
+ accounts: Object.fromEntries(
+ Object.entries(accounts).map(([name, value]) => [name, value.publicMetadata])
+ ),
+ bridgeSourceEvent,
+ hashHelpers,
+ positive: positives,
+ negative: negatives
+ };
+}
+
+function account(role, index) {
+ const privateKey = deterministicTestPrivateKey(index);
+ const publicKey = publicKeyFromPrivateKey(privateKey);
+ const publicMetadata = flowchainPublicAccountMetadata({
+ publicKey,
+ role,
+ label: `production-l1-${role}`,
+ createdAtUnixMs: ISSUED_AT_UNIX_MS
+ });
+ return {
+ role,
+ privateKey,
+ publicKey: publicMetadata.publicKey,
+ accountId: flowchainAccountId({ publicKey, role }),
+ signerKeyId: flowchainSignerKeyId({ publicKey }),
+ publicMetadata
+ };
+}
+
+async function signedVector(name, document, signer, nonce, payloadType) {
+ const unsigned = buildUnsignedLocalTransactionEnvelope({
+ document,
+ chainId: CHAIN_ID,
+ nonce,
+ signerId: signer.accountId,
+ signerKeyId: signer.signerKeyId,
+ signerRole: signer.role,
+ publicKey: signer.publicKey,
+ issuedAtUnixMs: ISSUED_AT_UNIX_MS,
+ expiresAtUnixMs: EXPIRES_AT_UNIX_MS,
+ networkProfile: NETWORK_PROFILE,
+ payloadType
+ });
+ const signature = await signDigest({ digest: unsigned.signingDigest, privateKey: signer.privateKey });
+ const envelope = {
+ ...unsigned,
+ signature
+ };
+ envelope.transactionId = flowchainTransactionId(envelope);
+ const runtime = verifyFlowchainEnvelope({
+ document,
+ envelope,
+ context: { chainId: CHAIN_ID, networkProfile: NETWORK_PROFILE, expectedNonce: nonce }
+ });
+ return {
+ name,
+ document,
+ envelope,
+ expected: {
+ objectId: localAlphaObjectId(document),
+ payloadHash: envelope.payloadHash,
+ envelopeId: envelope.envelopeId,
+ signingDigest: envelope.signingDigest,
+ transactionId: envelope.transactionId
+ },
+ runtime: {
+ ok: runtime.ok,
+ signerAddress: runtime.signerAddress,
+ signerAccountId: runtime.signerAccountId,
+ payloadHash: runtime.payloadHash,
+ transactionId: runtime.transactionId,
+ nonce: runtime.nonce,
+ chainId: runtime.chainId,
+ networkProfile: runtime.networkProfile
+ }
+ };
+}
+
+function buildHashHelperVectors({
+ positives,
+ accounts,
+ assets,
+ poolId,
+ bridgeSourceEvent,
+ bridgeObservationId,
+ canonicalBridgeCreditId
+}) {
+ const txRoot = flowchainTxRoot(positives.map((entry) => entry.envelope.transactionId));
+ const receiptRoot = flowchainReceiptRoot(positives.map((entry) => entry.expected.payloadHash));
+ const eventRoot = flowchainEventRoot([bridgeObservationId, canonicalBridgeCreditId]);
+ const accountStateRoot = flowchainAccountStateRoot(Object.values(accounts).map((entry) => entry.publicMetadata));
+ const tokenStateRoot = flowchainTokenStateRoot([assets.native, assets.token]);
+ const dexStateRoot = flowchainDexStateRoot([{ poolId, baseAssetId: assets.native, quoteAssetId: assets.token }]);
+ const blockHash = flowchainBlockHash({
+ chainId: CHAIN_ID,
+ networkProfile: NETWORK_PROFILE,
+ blockNumber: "9",
+ parentHash: keccakUtf8("block:8"),
+ txRoot,
+ receiptRoot,
+ eventRoot,
+ accountStateRoot,
+ tokenStateRoot,
+ dexStateRoot,
+ timestampUnixMs: ISSUED_AT_UNIX_MS
+ });
+ const withdrawalIntentInput = {
+ localChainId: CHAIN_ID,
+ accountId: accounts.user.accountId,
+ assetId: assets.token,
+ amount: "10000000",
+ nonce: "10",
+ destination: {
+ chainId: BASE_8453_CHAIN_ID,
+ recipient: "0x4444444444444444444444444444444444444444"
+ }
+ };
+ const finalityInput = {
+ chainId: CHAIN_ID,
+ blockNumber: "9",
+ blockHash,
+ stateRoot: accountStateRoot,
+ validatorSetRoot: canonicalJsonHash(Object.values(accounts).map((entry) => entry.accountId)),
+ round: "1",
+ voteRoot: flowchainReceiptRoot([accounts.validator.accountId, blockHash])
+ };
+ return {
+ transactionId: positives[0].envelope.transactionId,
+ blockHash,
+ txRoot,
+ receiptRoot,
+ eventRoot,
+ accountStateRoot,
+ tokenStateRoot,
+ dexStateRoot,
+ bridgeObservationId,
+ bridgeCreditId: canonicalBridgeCreditId,
+ withdrawalIntentId: flowchainWithdrawalIntentId(withdrawalIntentInput),
+ finalityReceiptId: flowchainFinalityReceiptId(finalityInput),
+ replayKeys: {
+ accountNonce: accountNonceReplayKey({
+ chainId: CHAIN_ID,
+ networkProfile: NETWORK_PROFILE,
+ accountId: accounts.user.accountId,
+ nonce: "1"
+ }),
+ roleScopedNonce: roleScopedNonceReplayKey({
+ chainId: CHAIN_ID,
+ networkProfile: NETWORK_PROFILE,
+ accountId: accounts.validator.accountId,
+ signerRole: "validator",
+ nonce: "11"
+ }),
+ bridgeSourceEvent: bridgeSourceEventReplayKey(bridgeSourceEvent),
+ withdrawalIntent: withdrawalIntentReplayKey(withdrawalIntentInput),
+ finalityVote: finalityVoteReplayKey({
+ chainId: CHAIN_ID,
+ validatorAccountId: accounts.validator.accountId,
+ blockHash,
+ round: "1",
+ voteType: "precommit"
+ })
+ }
+ };
+}
+
+function buildNegativeVectors({ positives, accounts, bridgeSourceEvent }) {
+ const byName = new Map(positives.map((entry) => [entry.name, entry]));
+ const specs = [
+ {
+ name: "wrong-chain-id",
+ base: "wallet-transfer",
+ context: { chainId: "31338" },
+ primaryFailureCode: "wrong-chain-id"
+ },
+ {
+ name: "wrong-network-profile",
+ base: "wallet-transfer",
+ context: { networkProfile: "private-lan" },
+ primaryFailureCode: "wrong-network-profile"
+ },
+ {
+ name: "wrong-domain",
+ base: "wallet-transfer",
+ envelope: { domain: "flowchain.production-l1.v0.transaction-envelope:profile:private-lan:chain:31337" },
+ primaryFailureCode: "wrong-domain"
+ },
+ {
+ name: "wrong-signer",
+ base: "wallet-transfer",
+ envelope: { signerId: accounts.recipient.accountId },
+ primaryFailureCode: "wrong-signer"
+ },
+ {
+ name: "wrong-signer-role",
+ base: "bridge-credit-authority",
+ envelope: { signerRole: "user", signerRoleCode: 10 },
+ primaryFailureCode: "wrong-signer"
+ },
+ {
+ name: "stale-nonce",
+ base: "wallet-transfer",
+ context: { minimumNonce: "2" },
+ primaryFailureCode: "stale-nonce"
+ },
+ {
+ name: "duplicate-nonce",
+ base: "wallet-transfer",
+ contextFactory(entry) {
+ return { seenNonces: new Set([`${entry.envelope.chainId}:${entry.envelope.networkProfile}:${entry.envelope.signerId}:${entry.envelope.signerRole}:${entry.envelope.nonce}`]) };
+ },
+ primaryFailureCode: "duplicate-nonce"
+ },
+ {
+ name: "duplicate-tx-id",
+ base: "wallet-transfer",
+ contextFactory(entry) {
+ return { seenTransactionIds: new Set([entry.envelope.transactionId]) };
+ },
+ primaryFailureCode: "duplicate-tx-id"
+ },
+ {
+ name: "expired-tx",
+ base: "wallet-transfer",
+ context: { nowUnixMs: "1778709600000" },
+ primaryFailureCode: "expired-tx"
+ },
+ {
+ name: "mutated-payload",
+ base: "swap",
+ document: { amountIn: "200000" },
+ primaryFailureCode: "bad-payload-hash"
+ },
+ {
+ name: "malformed-public-key",
+ base: "wallet-transfer",
+ envelope: { publicKey: "0x1234" },
+ primaryFailureCode: "malformed-public-key"
+ },
+ {
+ name: "malformed-signature",
+ base: "wallet-transfer",
+ envelope: { signature: "0x1234" },
+ primaryFailureCode: "malformed-signature"
+ },
+ {
+ name: "malformed-root",
+ base: "validator-finality",
+ document: { challengeRoot: "0x1234" },
+ primaryFailureCode: "malformed-root"
+ },
+ {
+ name: "duplicate-bridge-source-event",
+ base: "bridge-credit-authority",
+ contextFactory() {
+ return {
+ bridgeSourceEvent,
+ seenBridgeSourceEvents: new Set([bridgeSourceEventReplayKey(bridgeSourceEvent)])
+ };
+ },
+ primaryFailureCode: "duplicate-bridge-source-event"
+ }
+ ];
+ return specs.map((spec) => {
+ const base = byName.get(spec.base);
+ const document = { ...base.document, ...(spec.document ?? {}) };
+ const envelope = { ...base.envelope, ...(spec.envelope ?? {}) };
+ const context = {
+ chainId: CHAIN_ID,
+ networkProfile: NETWORK_PROFILE,
+ expectedNonce: envelope.nonce,
+ ...(spec.context ?? {}),
+ ...(spec.contextFactory?.(base) ?? {})
+ };
+ const result = verifyFlowchainEnvelope({ document, envelope, context });
+ return {
+ name: `production-l1.${spec.name}`,
+ base: spec.base,
+ mutation: {
+ document: spec.document,
+ envelope: spec.envelope,
+ context: spec.context,
+ contextKind: spec.contextFactory ? spec.name : undefined
+ },
+ primaryFailureCode: spec.primaryFailureCode,
+ expectFailureCodes: result.failureCodes.sort()
+ };
+ });
+}
+
+function productTransferDocument(input) {
+ return { schema: "flowchain.product_transfer.v0", transferId: productTransferId(input), ...input };
+}
+
+function productTokenLaunchDocument(input) {
+ return { schema: "flowchain.product_token_launch.v0", tokenLaunchId: productTokenLaunchId(input), ...input };
+}
+
+function productPoolCreateDocument(input) {
+ return { schema: "flowchain.product_pool_create.v0", poolCreateId: productPoolCreateId(input), ...input };
+}
+
+function productAddLiquidityDocument(input) {
+ return { schema: "flowchain.product_add_liquidity.v0", addLiquidityId: productAddLiquidityId(input), ...input };
+}
+
+function productRemoveLiquidityDocument(input) {
+ return { schema: "flowchain.product_remove_liquidity.v0", removeLiquidityId: productRemoveLiquidityId(input), ...input };
+}
+
+function productSwapDocument(input) {
+ return { schema: "flowchain.product_swap.v0", swapId: productSwapId(input), ...input };
+}
+
+function localBalanceRecordDocument(input) {
+ return { schema: "flowchain.local_balance_record.v0", balanceRecordId: localBalanceRecordId(input), ...input };
+}
+
+function bridgeCreditDocument(input) {
+ const creditId = bridgeCreditId({ ...input, status: input.statusCode });
+ return { schema: "flowchain.bridge_credit.v0", creditId, ...input };
+}
+
+function bridgeWithdrawalIntentDocument(input) {
+ return { schema: "flowmemory.bridge_withdrawal_intent.v0", withdrawalIntentId: bridgeWithdrawalIntentId(input), ...input };
+}
+
+function finalityReceiptDocument(input) {
+ const idInput = {
+ ...input,
+ finalityState: input.finalityStateCode
+ };
+ return { schema: "flowchain.finality_receipt.v0", finalityReceiptId: finalityReceiptId(idInput), ...input };
+}
+
+function deterministicTestPrivateKey(index) {
+ const bytes = new Uint8Array(32);
+ bytes[31] = index;
+ return bytesToHex(bytes);
+}
diff --git a/crypto/src/production-l1.js b/crypto/src/production-l1.js
new file mode 100644
index 00000000..0ac7ca9e
--- /dev/null
+++ b/crypto/src/production-l1.js
@@ -0,0 +1,221 @@
+import { DOMAIN_STRINGS, TYPE_STRINGS } from "./constants.js";
+import { canonicalJsonHash, keccakUtf8, typedHash } from "./hashes.js";
+import { hexToBytes } from "./encoding.js";
+import { merkleRoot } from "./merkle.js";
+
+export const FLOWCHAIN_NETWORK_PROFILES = Object.freeze({
+ localChain: "local-chain",
+ privateLan: "private-lan",
+ base8453PilotBridge: "base-8453-pilot-bridge"
+});
+
+export const FLOWCHAIN_DOMAIN_SEPARATORS = Object.freeze({
+ localChain: DOMAIN_STRINGS.productionLocalChain,
+ privateLan: DOMAIN_STRINGS.productionPrivateLan,
+ base8453PilotBridge: DOMAIN_STRINGS.productionBase8453PilotBridge,
+ objectLifecycle: DOMAIN_STRINGS.productionObjectLifecycle,
+ tokenDex: DOMAIN_STRINGS.productionTokenDex,
+ validatorFinality: DOMAIN_STRINGS.productionValidatorFinality
+});
+
+export function flowchainNetworkProfileHash(networkProfile) {
+ assertNetworkProfile(networkProfile);
+ return keccakUtf8(networkProfile);
+}
+
+export function flowchainProductionDomain({ chainId, networkProfile }) {
+ assertNetworkProfile(networkProfile);
+ return `${DOMAIN_STRINGS.productionL1TransactionEnvelope}:profile:${networkProfile}:chain:${chainId}`;
+}
+
+export function flowchainProductionDomainSeparator({ chainId, networkProfile }) {
+ return canonicalJsonHash({
+ domain: DOMAIN_STRINGS.productionL1TransactionEnvelope,
+ chainId: String(chainId),
+ networkProfile
+ });
+}
+
+export function flowchainTransactionId(envelope) {
+ const networkProfile = envelope.networkProfile ?? FLOWCHAIN_NETWORK_PROFILES.localChain;
+ return typedHash(TYPE_STRINGS.flowchainTransactionIdV0, [
+ ["uint256", envelope.chainId],
+ ["bytes32", flowchainNetworkProfileHash(networkProfile)],
+ ["bytes32", envelope.envelopeId],
+ ["bytes32", envelope.payloadHash],
+ ["bytes32", canonicalJsonHash({ signature: envelope.signature ?? "" })]
+ ]);
+}
+
+export function flowchainTxRoot(transactions) {
+ return rootFromItems("flowchain.production-l1.v0.tx-root", transactions);
+}
+
+export function flowchainReceiptRoot(receipts) {
+ return rootFromItems("flowchain.production-l1.v0.receipt-root", receipts);
+}
+
+export function flowchainEventRoot(events) {
+ return rootFromItems("flowchain.production-l1.v0.event-root", events);
+}
+
+export function flowchainAccountStateRoot(accounts) {
+ return rootFromItems("flowchain.production-l1.v0.account-state-root", accounts);
+}
+
+export function flowchainTokenStateRoot(tokens) {
+ return rootFromItems("flowchain.production-l1.v0.token-state-root", tokens);
+}
+
+export function flowchainDexStateRoot(pools) {
+ return rootFromItems("flowchain.production-l1.v0.dex-state-root", pools);
+}
+
+export function flowchainBlockHash(input) {
+ return canonicalJsonHash({
+ domain: "flowchain.production-l1.v0.block-hash",
+ chainId: String(input.chainId),
+ networkProfile: input.networkProfile,
+ blockNumber: String(input.blockNumber),
+ parentHash: input.parentHash,
+ txRoot: input.txRoot,
+ receiptRoot: input.receiptRoot,
+ eventRoot: input.eventRoot,
+ accountStateRoot: input.accountStateRoot,
+ tokenStateRoot: input.tokenStateRoot,
+ dexStateRoot: input.dexStateRoot,
+ timestampUnixMs: String(input.timestampUnixMs)
+ });
+}
+
+export function flowchainBridgeObservationId({
+ sourceChainId,
+ lockbox,
+ token,
+ depositor,
+ recipient,
+ amount,
+ txHash,
+ logIndex,
+ blockNumber,
+ eventNonce = "0"
+}) {
+ return typedHash(TYPE_STRINGS.flowchainBridgeObservationV0, [
+ ["uint256", sourceChainId],
+ ["address", lockbox],
+ ["address", token],
+ ["address", depositor],
+ ["bytes32", recipient],
+ ["uint256", amount],
+ ["bytes32", txHash],
+ ["uint32", logIndex],
+ ["uint64", blockNumber],
+ ["uint256", eventNonce]
+ ]);
+}
+
+export function flowchainBridgeCreditId({ observationId, localRecipient, localChainId, creditAmount }) {
+ return typedHash(TYPE_STRINGS.flowchainBridgeCreditV1, [
+ ["bytes32", observationId],
+ ["bytes32", localRecipient],
+ ["uint256", localChainId],
+ ["uint256", creditAmount]
+ ]);
+}
+
+export function flowchainWithdrawalIntentId({ localChainId, accountId, assetId, amount, nonce, destination }) {
+ return typedHash(TYPE_STRINGS.flowchainWithdrawalIntentV1, [
+ ["uint256", localChainId],
+ ["bytes32", accountId],
+ ["bytes32", assetId],
+ ["uint256", amount],
+ ["uint64", nonce],
+ ["bytes32", canonicalJsonHash(destination)]
+ ]);
+}
+
+export function flowchainFinalityReceiptId({
+ chainId,
+ blockNumber,
+ blockHash,
+ stateRoot,
+ validatorSetRoot,
+ round,
+ voteRoot
+}) {
+ return typedHash(TYPE_STRINGS.flowchainFinalityReceiptV1, [
+ ["uint256", chainId],
+ ["uint64", blockNumber],
+ ["bytes32", blockHash],
+ ["bytes32", stateRoot],
+ ["bytes32", validatorSetRoot],
+ ["uint64", round],
+ ["bytes32", voteRoot]
+ ]);
+}
+
+export function accountNonceReplayKey({ chainId, networkProfile, accountId, nonce }) {
+ return canonicalJsonHash({
+ domain: "flowchain.production-l1.v0.account-nonce-replay-key",
+ chainId: String(chainId),
+ networkProfile,
+ accountId,
+ nonce: String(nonce)
+ });
+}
+
+export function roleScopedNonceReplayKey({ chainId, networkProfile, accountId, signerRole, nonce }) {
+ return canonicalJsonHash({
+ domain: "flowchain.production-l1.v0.role-scoped-nonce-replay-key",
+ chainId: String(chainId),
+ networkProfile,
+ accountId,
+ signerRole,
+ nonce: String(nonce)
+ });
+}
+
+export function bridgeSourceEventReplayKey(input) {
+ return flowchainBridgeObservationId(input);
+}
+
+export function withdrawalIntentReplayKey(input) {
+ return flowchainWithdrawalIntentId(input);
+}
+
+export function finalityVoteReplayKey({ chainId, validatorAccountId, blockHash, round, voteType }) {
+ return canonicalJsonHash({
+ domain: "flowchain.production-l1.v0.finality-vote-replay-key",
+ chainId: String(chainId),
+ validatorAccountId,
+ blockHash,
+ round: String(round),
+ voteType
+ });
+}
+
+function rootFromItems(domain, items) {
+ if (!Array.isArray(items)) {
+ throw new Error("root items must be an array");
+ }
+ const leaves = items.map((item) => (isHex32(item) ? item.toLowerCase() : canonicalJsonHash({ domain, item })));
+ return merkleRoot(leaves);
+}
+
+function assertNetworkProfile(networkProfile) {
+ if (!Object.values(FLOWCHAIN_NETWORK_PROFILES).includes(networkProfile)) {
+ throw new Error(`unsupported FlowChain network profile: ${networkProfile}`);
+ }
+}
+
+function isHex32(value) {
+ if (typeof value !== "string") {
+ return false;
+ }
+ try {
+ hexToBytes(value, 32);
+ return true;
+ } catch {
+ return false;
+ }
+}
diff --git a/crypto/src/runtime-validation.d.ts b/crypto/src/runtime-validation.d.ts
new file mode 100644
index 00000000..a7b89ad6
--- /dev/null
+++ b/crypto/src/runtime-validation.d.ts
@@ -0,0 +1,27 @@
+export interface FlowchainRuntimeVerifyInput {
+ document: Record;
+ envelope: Record;
+ context?: Record;
+}
+
+export interface FlowchainRuntimeVerifyResult {
+ schema: "flowchain.runtime_verify_result.v0";
+ ok: boolean;
+ failureCodes: string[];
+ signerAddress?: string;
+ signerAccountId?: string;
+ signerPublicIdentity?: Record;
+ payloadHash?: string;
+ transactionId?: string;
+ envelopeId?: string;
+ signingDigest?: string;
+ nonce?: string | number;
+ chainId?: string | number;
+ networkProfile?: string;
+ payloadType?: string;
+ signerRole?: string;
+ signerKeyId?: string;
+ envelopePayload?: Record | null;
+}
+
+export function verifyFlowchainEnvelope(input: FlowchainRuntimeVerifyInput): FlowchainRuntimeVerifyResult;
diff --git a/crypto/src/runtime-validation.js b/crypto/src/runtime-validation.js
new file mode 100644
index 00000000..d95f5706
--- /dev/null
+++ b/crypto/src/runtime-validation.js
@@ -0,0 +1,124 @@
+import {
+ localTransactionEnvelopeInput,
+ localTransactionReplayKey,
+ validateLocalTransactionEnvelope
+} from "./transactions.js";
+import {
+ bridgeSourceEventReplayKey,
+ flowchainTransactionId
+} from "./production-l1.js";
+import {
+ flowchainAccountId,
+ flowchainAddressFromPublicKey,
+ flowchainPublicKeyHash,
+ isFlowchainRole,
+ normalizeFlowchainPublicKey
+} from "./identity.js";
+
+export function verifyFlowchainEnvelope({ document, envelope, context = {} }) {
+ const base = validateLocalTransactionEnvelope({
+ document,
+ envelope,
+ context: {
+ ...context,
+ requireCanonical: context.requireCanonical ?? true
+ }
+ });
+ const failureCodes = new Set(base.errors);
+
+ for (const rootError of malformedRootCodes(document)) {
+ failureCodes.add(rootError);
+ }
+
+ if (context.seenReplayKeys?.has?.(localTransactionReplayKey(envelope))) {
+ failureCodes.add("duplicate-nonce");
+ }
+
+ if (context.bridgeSourceEvent && context.seenBridgeSourceEvents?.has?.(bridgeSourceEventReplayKey(context.bridgeSourceEvent))) {
+ failureCodes.add("duplicate-bridge-source-event");
+ }
+
+ const signerIdentity = deriveSignerIdentity(envelope);
+ for (const error of signerIdentity.errors) {
+ failureCodes.add(error);
+ }
+
+ const transactionId = envelope?.signature ? flowchainTransactionId(envelope) : envelope?.transactionId;
+ if (context.seenTransactionIds?.has?.(transactionId)) {
+ failureCodes.add("duplicate-tx-id");
+ }
+
+ const payload = safeEnvelopePayload(envelope);
+
+ return {
+ schema: "flowchain.runtime_verify_result.v0",
+ ok: failureCodes.size === 0,
+ failureCodes: [...failureCodes],
+ signerAddress: signerIdentity.address,
+ signerAccountId: signerIdentity.accountId,
+ signerPublicIdentity: signerIdentity.publicIdentity,
+ payloadHash: envelope?.payloadHash,
+ transactionId,
+ envelopeId: envelope?.envelopeId,
+ signingDigest: envelope?.signingDigest,
+ nonce: envelope?.nonce,
+ chainId: envelope?.chainId,
+ networkProfile: envelope?.networkProfile,
+ payloadType: envelope?.payloadType,
+ signerRole: envelope?.signerRole,
+ signerKeyId: envelope?.signerKeyId,
+ envelopePayload: payload
+ };
+}
+
+function deriveSignerIdentity(envelope) {
+ const errors = [];
+ if (!envelope?.publicKey) {
+ return { errors: ["missing-signer"] };
+ }
+ try {
+ const publicKey = normalizeFlowchainPublicKey(envelope.publicKey);
+ const address = flowchainAddressFromPublicKey(publicKey);
+ const accountId = isFlowchainRole(envelope.signerRole)
+ ? flowchainAccountId({ publicKey, role: envelope.signerRole })
+ : envelope.signerId;
+ return {
+ errors,
+ address,
+ accountId,
+ publicIdentity: {
+ schema: "flowchain.runtime_public_identity.v0",
+ publicKey,
+ publicKeyHash: flowchainPublicKeyHash(publicKey),
+ address,
+ accountId,
+ signerRole: envelope.signerRole,
+ signerRoleCode: envelope.signerRoleCode,
+ signerKeyId: envelope.signerKeyId
+ }
+ };
+ } catch {
+ return { errors: ["malformed-public-key"] };
+ }
+}
+
+function malformedRootCodes(document) {
+ if (!document || typeof document !== "object") {
+ return [];
+ }
+ const errors = [];
+ for (const [key, value] of Object.entries(document)) {
+ if (key.toLowerCase().includes("root") && typeof value === "string" && !/^0x[0-9a-fA-F]{64}$/.test(value)) {
+ errors.push("malformed-root");
+ }
+ }
+ return errors;
+}
+
+function safeEnvelopePayload(envelope) {
+ try {
+ return localTransactionEnvelopeInput(envelope);
+ } catch {
+ return null;
+ }
+}
diff --git a/crypto/src/transactions.js b/crypto/src/transactions.js
index 9899fb33..87286d48 100644
--- a/crypto/src/transactions.js
+++ b/crypto/src/transactions.js
@@ -2,35 +2,58 @@ import { DOMAIN_STRINGS, LOCAL_ALPHA_SIGNER_ROLES, TYPE_STRINGS, ZERO_BYTES32 }
import { verifyDigest } from "./attestations.js";
import { eip712Digest } from "./flowpulse.js";
import { canonicalJsonHash, domainSeparator, keccakUtf8, typedHash } from "./hashes.js";
+import {
+ FLOWCHAIN_NETWORK_PROFILES,
+ flowchainNetworkProfileHash,
+ flowchainProductionDomain,
+ flowchainProductionDomainSeparator,
+ flowchainTransactionId
+} from "./production-l1.js";
+import {
+ flowchainAccountId,
+ flowchainAddressFromPublicKey,
+ isFlowchainRole,
+ normalizeFlowchainPublicKey
+} from "./identity.js";
import {
localAlphaObjectDescriptor,
localAlphaObjectId,
localAlphaObjectTypeHash
} from "./objects.js";
-export function localTransactionEnvelopeHash({
- chainId,
- domainSeparator,
- signerId,
- signerKeyId,
- signerRole,
- nonce,
- payloadHash,
- objectId,
- objectTypeHash,
- issuedAtUnixMs
-}) {
+export function localTransactionEnvelopeHash(input) {
+ if (isProductionL1EnvelopeInput(input)) {
+ return typedHash(TYPE_STRINGS.localTransactionEnvelopeProductionL1V0, [
+ ["uint16", input.schemaVersion],
+ ["uint256", input.chainId],
+ ["bytes32", input.networkProfileHash ?? flowchainNetworkProfileHash(input.networkProfile)],
+ ["bytes32", input.domainSeparator],
+ ["bytes32", input.signerId],
+ ["bytes32", input.signerKeyId],
+ ["uint8", input.signerRole],
+ ["uint64", input.nonce],
+ ["bytes32", input.payloadTypeHash ?? keccakUtf8(input.payloadType)],
+ ["bytes32", input.payloadHash],
+ ["bytes32", input.objectId],
+ ["bytes32", input.objectTypeHash],
+ ["uint64", input.issuedAtUnixMs],
+ ["uint64", input.expiresAtUnixMs],
+ ["bytes32", input.localExecutionCostHash ?? canonicalJsonHash(input.localExecutionCost ?? defaultLocalExecutionCost())],
+ ["bytes32", input.feeHash ?? canonicalJsonHash(input.fee ?? defaultFee())],
+ ["bytes32", input.signatureAlgorithmHash ?? keccakUtf8(input.signatureAlgorithm)]
+ ]);
+ }
return typedHash(TYPE_STRINGS.localTransactionEnvelopeV0, [
- ["uint256", chainId],
- ["bytes32", domainSeparator],
- ["bytes32", signerId],
- ["bytes32", signerKeyId],
- ["uint8", signerRole],
- ["uint64", nonce],
- ["bytes32", payloadHash],
- ["bytes32", objectId],
- ["bytes32", objectTypeHash],
- ["uint64", issuedAtUnixMs]
+ ["uint256", input.chainId],
+ ["bytes32", input.domainSeparator],
+ ["bytes32", input.signerId],
+ ["bytes32", input.signerKeyId],
+ ["uint8", input.signerRole],
+ ["uint64", input.nonce],
+ ["bytes32", input.payloadHash],
+ ["bytes32", input.objectId],
+ ["bytes32", input.objectTypeHash],
+ ["uint64", input.issuedAtUnixMs]
]);
}
@@ -45,7 +68,7 @@ export function localTransactionEnvelopePayload(input) {
}
export function localTransactionEnvelopeInput(envelope) {
- return {
+ const input = {
chainId: envelope.chainId,
domainSeparator: envelope.domainSeparator,
signerId: envelope.signerId,
@@ -57,17 +80,51 @@ export function localTransactionEnvelopeInput(envelope) {
objectTypeHash: envelope.objectTypeHash,
issuedAtUnixMs: envelope.issuedAtUnixMs
};
+ if (isProductionL1EnvelopeInput(envelope)) {
+ return {
+ schemaVersion: envelope.schemaVersion,
+ chainId: envelope.chainId,
+ networkProfile: envelope.networkProfile,
+ networkProfileHash: envelope.networkProfileHash,
+ domainSeparator: envelope.domainSeparator,
+ signerId: envelope.signerId,
+ signerKeyId: envelope.signerKeyId,
+ signerRole: envelope.signerRoleCode,
+ nonce: envelope.nonce,
+ payloadType: envelope.payloadType,
+ payloadTypeHash: envelope.payloadTypeHash,
+ payloadHash: envelope.payloadHash,
+ objectId: envelope.objectId,
+ objectTypeHash: envelope.objectTypeHash,
+ issuedAtUnixMs: envelope.issuedAtUnixMs,
+ expiresAtUnixMs: envelope.expiresAtUnixMs,
+ localExecutionCostHash: envelope.localExecutionCostHash,
+ feeHash: envelope.feeHash,
+ signatureAlgorithm: envelope.signatureAlgorithm,
+ signatureAlgorithmHash: envelope.signatureAlgorithmHash
+ };
+ }
+ return input;
}
export function localTransactionReplayKey(envelope) {
+ if (envelope?.networkProfile) {
+ return `${envelope.chainId}:${envelope.networkProfile}:${envelope.signerId}:${envelope.signerRole}:${envelope.nonce}`;
+ }
return `${envelope.chainId}:${envelope.domain}:${envelope.signerId}:${envelope.nonce}`;
}
-export function localTransactionDomain(chainId) {
+export function localTransactionDomain(chainId, networkProfile) {
+ if (networkProfile) {
+ return flowchainProductionDomain({ chainId, networkProfile });
+ }
return `${DOMAIN_STRINGS.localTransactionEnvelope}:chain:${chainId}`;
}
-export function localTransactionDomainSeparator(chainId) {
+export function localTransactionDomainSeparator(chainId, networkProfile) {
+ if (networkProfile) {
+ return flowchainProductionDomainSeparator({ chainId, networkProfile });
+ }
return keccakUtf8(localTransactionDomain(chainId));
}
@@ -79,7 +136,14 @@ export function buildUnsignedLocalTransactionEnvelope({
signerKeyId,
signerRole,
publicKey,
- issuedAtUnixMs
+ issuedAtUnixMs,
+ expiresAtUnixMs,
+ networkProfile = FLOWCHAIN_NETWORK_PROFILES.localChain,
+ payloadType,
+ localExecutionCost = defaultLocalExecutionCost(),
+ fee = defaultFee(),
+ signatureAlgorithm = "secp256k1-keccak256-eip712-local-v0",
+ canonical = true
}) {
const descriptor = localAlphaObjectDescriptor(document?.schema);
if (!descriptor) {
@@ -92,10 +156,24 @@ export function buildUnsignedLocalTransactionEnvelope({
const objectId = localAlphaObjectId(document);
const objectTypeHash = localAlphaObjectTypeHash(document.schema);
- const domain = localTransactionDomain(chainId);
+ const domain = localTransactionDomain(chainId, canonical ? networkProfile : undefined);
const envelopeInput = {
+ ...(canonical
+ ? {
+ schemaVersion: 1,
+ networkProfile,
+ networkProfileHash: flowchainNetworkProfileHash(networkProfile),
+ payloadType: payloadType ?? descriptor.objectType,
+ payloadTypeHash: keccakUtf8(payloadType ?? descriptor.objectType),
+ expiresAtUnixMs: expiresAtUnixMs ?? defaultExpiresAtUnixMs(issuedAtUnixMs),
+ localExecutionCostHash: canonicalJsonHash(localExecutionCost),
+ feeHash: canonicalJsonHash(fee),
+ signatureAlgorithm,
+ signatureAlgorithmHash: keccakUtf8(signatureAlgorithm)
+ }
+ : {}),
chainId,
- domainSeparator: localTransactionDomainSeparator(chainId),
+ domainSeparator: localTransactionDomainSeparator(chainId, canonical ? networkProfile : undefined),
signerId,
signerKeyId,
signerRole: signerRoleCode,
@@ -109,6 +187,13 @@ export function buildUnsignedLocalTransactionEnvelope({
return {
schema: "flowchain.local_transaction_envelope.v0",
+ ...(canonical
+ ? {
+ schemaVersion: envelopeInput.schemaVersion,
+ networkProfile,
+ networkProfileHash: envelopeInput.networkProfileHash
+ }
+ : {}),
envelopeId: payload.structHash,
domain,
domainSeparator: envelopeInput.domainSeparator,
@@ -118,13 +203,36 @@ export function buildUnsignedLocalTransactionEnvelope({
signerKeyId,
signerRole,
signerRoleCode,
- publicKey,
+ publicKey: canonical ? normalizeFlowchainPublicKey(publicKey) : publicKey,
+ ...(canonical
+ ? {
+ publicKeyEncoding: "secp256k1-compressed-hex",
+ signerAddress: flowchainAddressFromPublicKey(publicKey)
+ }
+ : {}),
objectSchema: document.schema,
objectType: descriptor.objectType,
+ ...(canonical
+ ? {
+ payloadType: envelopeInput.payloadType,
+ payloadTypeHash: envelopeInput.payloadTypeHash
+ }
+ : {}),
objectTypeHash,
objectId,
payloadHash: envelopeInput.payloadHash,
issuedAtUnixMs,
+ ...(canonical
+ ? {
+ expiresAtUnixMs: envelopeInput.expiresAtUnixMs,
+ localExecutionCost,
+ localExecutionCostHash: envelopeInput.localExecutionCostHash,
+ fee,
+ feeHash: envelopeInput.feeHash,
+ signatureAlgorithm,
+ signatureAlgorithmHash: envelopeInput.signatureAlgorithmHash
+ }
+ : {}),
signingDigest: payload.signingDigest
};
}
@@ -143,13 +251,20 @@ export function validateLocalTransactionEnvelope({
return { valid: false, errors: ["missing-signer"] };
}
- const expectedDomain = localTransactionDomain(envelope.chainId);
- const expectedDomainSeparator = localTransactionDomainSeparator(envelope.chainId);
+ const canonicalEnvelope = isProductionL1EnvelopeInput(envelope);
+ const expectedDomain = localTransactionDomain(envelope.chainId, canonicalEnvelope ? envelope.networkProfile : undefined);
+ const expectedDomainSeparator = localTransactionDomainSeparator(
+ envelope.chainId,
+ canonicalEnvelope ? envelope.networkProfile : undefined
+ );
const expectedRoleCode = LOCAL_ALPHA_SIGNER_ROLES[envelope.signerRole];
if (context.chainId !== undefined && String(envelope.chainId) !== String(context.chainId)) {
errors.push("wrong-chain-id");
}
+ if (context.networkProfile !== undefined && envelope.networkProfile !== context.networkProfile) {
+ errors.push("wrong-network-profile");
+ }
if (context.expectedNonce !== undefined && String(envelope.nonce) !== String(context.expectedNonce)) {
errors.push("wrong-nonce");
}
@@ -183,6 +298,16 @@ export function validateLocalTransactionEnvelope({
}
if (context.seenNonces?.has?.(localTransactionReplayKey(envelope))) {
errors.push("replay");
+ errors.push("duplicate-nonce");
+ }
+ if (context.minimumNonce !== undefined && BigInt(envelope.nonce) < BigInt(context.minimumNonce)) {
+ errors.push("stale-nonce");
+ }
+ if (context.expectedPayloadType !== undefined && envelope.payloadType !== context.expectedPayloadType) {
+ errors.push("wrong-payload-type");
+ }
+ if (context.requireCanonical && !canonicalEnvelope) {
+ errors.push("missing-canonical-field");
}
try {
@@ -210,6 +335,9 @@ export function validateLocalTransactionEnvelope({
if (envelope.payloadHash !== expectedPayloadHash) {
errors.push("bad-payload-hash");
}
+ if (canonicalEnvelope) {
+ validateProductionL1EnvelopeExtension(envelope, errors, context);
+ }
const input = localTransactionEnvelopeInput(envelope);
const expectedEnvelopeId = localTransactionEnvelopeHash(input);
@@ -232,7 +360,7 @@ export function validateLocalTransactionEnvelope({
errors.push("bad-signature");
}
} catch (error) {
- errors.push(/hex|bytes/i.test(String(error?.message)) ? "malformed-id" : "invalid-transaction");
+ errors.push(classifyTransactionError(error));
}
return {
@@ -244,3 +372,112 @@ export function validateLocalTransactionEnvelope({
function isHex32(value) {
return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
}
+
+function isProductionL1EnvelopeInput(input) {
+ return Boolean(input?.schemaVersion || input?.networkProfile || input?.payloadType || input?.expiresAtUnixMs);
+}
+
+function defaultLocalExecutionCost() {
+ return {
+ unit: "local-compute",
+ amount: "0",
+ metering: "not-metered-local-private-testnet"
+ };
+}
+
+function defaultFee() {
+ return {
+ assetId: ZERO_BYTES32,
+ amount: "0",
+ policy: "no-value-local-private-testnet"
+ };
+}
+
+function defaultExpiresAtUnixMs(issuedAtUnixMs) {
+ return (BigInt(issuedAtUnixMs) + 3_600_000n).toString();
+}
+
+function validateProductionL1EnvelopeExtension(envelope, errors, context) {
+ const required = [
+ "schemaVersion",
+ "networkProfile",
+ "networkProfileHash",
+ "payloadType",
+ "payloadTypeHash",
+ "expiresAtUnixMs",
+ "localExecutionCost",
+ "localExecutionCostHash",
+ "fee",
+ "feeHash",
+ "signatureAlgorithm",
+ "signatureAlgorithmHash"
+ ];
+ for (const field of required) {
+ if (envelope[field] === undefined || envelope[field] === null || envelope[field] === "") {
+ errors.push("missing-canonical-field");
+ }
+ }
+ if (envelope.schemaVersion !== 1) {
+ errors.push("wrong-schema-version");
+ }
+ if (envelope.networkProfileHash !== flowchainNetworkProfileHash(envelope.networkProfile)) {
+ errors.push("wrong-network-profile");
+ }
+ if (envelope.payloadTypeHash !== keccakUtf8(envelope.payloadType)) {
+ errors.push("wrong-payload-type");
+ }
+ if (envelope.localExecutionCostHash !== canonicalJsonHash(envelope.localExecutionCost)) {
+ errors.push("bad-local-execution-cost");
+ }
+ if (envelope.feeHash !== canonicalJsonHash(envelope.fee)) {
+ errors.push("bad-fee");
+ }
+ if (envelope.signatureAlgorithmHash !== keccakUtf8(envelope.signatureAlgorithm)) {
+ errors.push("bad-signature-algorithm");
+ }
+ if (BigInt(envelope.expiresAtUnixMs) < BigInt(envelope.issuedAtUnixMs)) {
+ errors.push("expired-tx");
+ }
+ if (context.nowUnixMs !== undefined && BigInt(envelope.expiresAtUnixMs) < BigInt(context.nowUnixMs)) {
+ errors.push("expired-tx");
+ }
+ try {
+ normalizeFlowchainPublicKey(envelope.publicKey);
+ } catch {
+ errors.push("malformed-public-key");
+ }
+ if (isFlowchainRole(envelope.signerRole)) {
+ const derivedSignerId = flowchainAccountId({ publicKey: envelope.publicKey, role: envelope.signerRole });
+ if (envelope.signerId !== derivedSignerId) {
+ errors.push("wrong-signer");
+ }
+ if (envelope.signerAddress && envelope.signerAddress !== flowchainAddressFromPublicKey(envelope.publicKey)) {
+ errors.push("wrong-signer");
+ }
+ }
+ if (envelope.signature && !/^0x[0-9a-fA-F]{128}$/.test(envelope.signature)) {
+ errors.push("malformed-signature");
+ }
+ if (envelope.signature) {
+ const expectedTransactionId = flowchainTransactionId(envelope);
+ if (envelope.transactionId && envelope.transactionId !== expectedTransactionId) {
+ errors.push("bad-transaction-id");
+ }
+ if (context.seenTransactionIds?.has?.(envelope.transactionId ?? expectedTransactionId)) {
+ errors.push("duplicate-tx-id");
+ }
+ }
+}
+
+function classifyTransactionError(error) {
+ if (/public key/i.test(String(error?.message))) {
+ return "malformed-public-key";
+ }
+ if (/signature/i.test(String(error?.message))) {
+ return "malformed-signature";
+ }
+ if (/hex|bytes/i.test(String(error?.message))) {
+ return "malformed-id";
+ }
+ return "invalid-transaction";
+}
diff --git a/crypto/src/validate-production-l1-crypto.js b/crypto/src/validate-production-l1-crypto.js
new file mode 100644
index 00000000..af865635
--- /dev/null
+++ b/crypto/src/validate-production-l1-crypto.js
@@ -0,0 +1,175 @@
+import assert from "node:assert/strict";
+import { readFileSync } from "node:fs";
+import { resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+import Ajv2020 from "ajv/dist/2020.js";
+import addFormats from "ajv-formats";
+
+import {
+ bridgeSourceEventReplayKey,
+ flowchainTransactionId,
+ localAlphaObjectId,
+ localTransactionReplayKey,
+ verifyFlowchainEnvelope
+} from "./index.js";
+
+const defaultFixturePath = resolve(import.meta.dirname, "..", "fixtures", "production-l1-vectors.json");
+
+export function validateProductionL1Crypto(fixturePath = defaultFixturePath) {
+ const fixture = readJson(fixturePath);
+ assert.equal(fixture.schema, "flowmemory.crypto.production-l1-vectors.v0");
+ assertNoSecrets(fixture);
+ assertRuntimeValidationHasNoWalletImport();
+
+ const fixtureDir = resolve(fixturePath, "..");
+ const ajv = new Ajv2020({ allErrors: true, strict: false });
+ addFormats(ajv);
+ const validators = new Map();
+
+ const positives = new Map();
+ for (const vector of fixture.positive) {
+ validateDocument({ ajv, validators, fixtureDir, document: vector.document, label: vector.name });
+ validateEnvelope({ ajv, validators, fixtureDir, envelope: vector.envelope, label: vector.name });
+ assert.equal(localAlphaObjectId(vector.document), vector.expected.objectId, `${vector.name} object id`);
+ assert.equal(vector.envelope.payloadHash, vector.expected.payloadHash, `${vector.name} payload hash`);
+ assert.equal(vector.envelope.envelopeId, vector.expected.envelopeId, `${vector.name} envelope id`);
+ assert.equal(vector.envelope.signingDigest, vector.expected.signingDigest, `${vector.name} signing digest`);
+ assert.equal(flowchainTransactionId(vector.envelope), vector.expected.transactionId, `${vector.name} tx id`);
+
+ const result = verifyFlowchainEnvelope({
+ document: vector.document,
+ envelope: vector.envelope,
+ context: {
+ chainId: fixture.chainId,
+ networkProfile: fixture.networkProfile,
+ expectedNonce: vector.envelope.nonce
+ }
+ });
+ assert.equal(result.ok, true, `${vector.name}: ${result.failureCodes.join(", ")}`);
+ assert.equal(result.transactionId, vector.expected.transactionId, `${vector.name} runtime tx id`);
+ assert.equal(result.payloadHash, vector.expected.payloadHash, `${vector.name} runtime payload hash`);
+ positives.set(vector.name, vector);
+ }
+
+ for (const vector of fixture.negative) {
+ const base = positives.get(vector.base);
+ assert.ok(base, `unknown negative base: ${vector.base}`);
+ const document = { ...base.document, ...(vector.mutation.document ?? {}) };
+ const envelope = { ...base.envelope, ...(vector.mutation.envelope ?? {}) };
+ const context = negativeContext({ fixture, base, envelope, vector });
+ const result = verifyFlowchainEnvelope({ document, envelope, context });
+ assert.equal(result.ok, false, vector.name);
+ assert.deepEqual(result.failureCodes.sort(), vector.expectFailureCodes, vector.name);
+ assert.ok(
+ result.failureCodes.includes(vector.primaryFailureCode),
+ `${vector.name} missing ${vector.primaryFailureCode}: ${result.failureCodes.join(", ")}`
+ );
+ }
+
+ return {
+ positive: fixture.positive.length,
+ negative: fixture.negative.length,
+ hashHelpers: Object.keys(fixture.hashHelpers).length,
+ schemas: validators.size
+ };
+}
+
+function validateDocument({ ajv, validators, fixtureDir, document, label }) {
+ const schemaPath = documentSchemaPath(document.schema);
+ const validate = schemaValidator({ ajv, validators, path: resolve(fixtureDir, schemaPath) });
+ if (!validate(document)) {
+ throw new Error(`${label} document failed schema validation: ${ajv.errorsText(validate.errors)}`);
+ }
+}
+
+function validateEnvelope({ ajv, validators, fixtureDir, envelope, label }) {
+ const validate = schemaValidator({
+ ajv,
+ validators,
+ path: resolve(fixtureDir, "../../schemas/flowmemory/local-transaction-envelope.schema.json")
+ });
+ if (!validate(envelope)) {
+ throw new Error(`${label} envelope failed schema validation: ${ajv.errorsText(validate.errors)}`);
+ }
+}
+
+function schemaValidator({ ajv, validators, path }) {
+ let validate = validators.get(path);
+ if (!validate) {
+ validate = ajv.compile(readJson(path));
+ validators.set(path, validate);
+ }
+ return validate;
+}
+
+function documentSchemaPath(schema) {
+ if (schema.startsWith("flowchain.product_")) {
+ return "../../schemas/flowmemory/product-transaction.schema.json";
+ }
+ const bySchema = {
+ "flowchain.local_balance_record.v0": "../../schemas/flowmemory/local-balance-record.schema.json",
+ "flowchain.bridge_credit.v0": "../../schemas/flowmemory/bridge-credit.schema.json",
+ "flowmemory.bridge_withdrawal_intent.v0": "../../schemas/flowmemory/bridge-withdrawal-intent.schema.json",
+ "flowchain.finality_receipt.v0": "../../schemas/flowmemory/finality-receipt.schema.json"
+ };
+ const path = bySchema[schema];
+ if (!path) {
+ throw new Error(`no schema path for ${schema}`);
+ }
+ return path;
+}
+
+function negativeContext({ fixture, base, envelope, vector }) {
+ const context = {
+ chainId: fixture.chainId,
+ networkProfile: fixture.networkProfile,
+ expectedNonce: envelope.nonce,
+ ...(vector.mutation.context ?? {})
+ };
+ switch (vector.mutation.contextKind) {
+ case "duplicate-nonce":
+ context.seenNonces = new Set([localTransactionReplayKey(base.envelope)]);
+ break;
+ case "duplicate-tx-id":
+ context.seenTransactionIds = new Set([base.envelope.transactionId]);
+ break;
+ case "duplicate-bridge-source-event":
+ context.bridgeSourceEvent = fixture.bridgeSourceEvent;
+ context.seenBridgeSourceEvents = new Set([bridgeSourceEventReplayKey(fixture.bridgeSourceEvent)]);
+ break;
+ default:
+ break;
+ }
+ return context;
+}
+
+function assertRuntimeValidationHasNoWalletImport() {
+ const runtimeSource = readFileSync(resolve(import.meta.dirname, "runtime-validation.js"), "utf8");
+ assert.doesNotMatch(runtimeSource, /wallet\.js|wallet-cli|vault/i, "runtime validation must not import wallet/vault code");
+}
+
+function assertNoSecrets(value) {
+ const serialized = JSON.stringify(value);
+ assert.doesNotMatch(
+ serialized,
+ /privateKey|private_key|seedPhrase|seed phrase|mnemonic|ciphertext|authTag|password|rpc[-_]?credential|rpc[-_]?url|api[-_]?key|webhook/i,
+ "production-L1 crypto fixture contains secret-shaped material"
+ );
+ assert.doesNotMatch(
+ serialized,
+ /https:\/\/hooks\.slack\.com|https:\/\/discord\.com\/api\/webhooks/i,
+ "production-L1 crypto fixture contains webhook-shaped material"
+ );
+}
+
+function readJson(path) {
+ return JSON.parse(readFileSync(path, "utf8"));
+}
+
+if (fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
+ const result = validateProductionL1Crypto(process.argv[2]);
+ console.log(
+ `FLOWCHAIN_PRODUCTION_L1_CRYPTO_OK positive=${result.positive} negative=${result.negative} hashHelpers=${result.hashHelpers} schemas=${result.schemas}`
+ );
+}
diff --git a/crypto/src/wallet-cli.js b/crypto/src/wallet-cli.js
index 41809c48..d885bbcf 100644
--- a/crypto/src/wallet-cli.js
+++ b/crypto/src/wallet-cli.js
@@ -1,56 +1,136 @@
#!/usr/bin/env node
-import { readFileSync, writeFileSync } from "node:fs";
+import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
+import { dirname, resolve } from "node:path";
+
import {
addEncryptedTestVaultAccount,
+ buildBridgeWithdrawalIntentDocument,
+ buildFinalityActionDocument,
+ buildProductAddLiquidityDocument,
+ buildProductPoolCreateDocument,
+ buildProductRemoveLiquidityDocument,
+ buildProductSwapDocument,
+ buildProductTokenLaunchDocument,
+ buildProductTransferDocument,
createEncryptedTestVault,
+ exportLocalWalletPublicMetadata,
exportVaultPublicMetadata,
listVaultPublicAccounts,
+ LOCAL_TEST_UNIT_ASSET_ID,
rotateEncryptedTestVaultAccount,
- signLocalTransactionWithVault,
+ signWalletDocumentWithVault,
unlockEncryptedTestVault,
- validateLocalTransactionEnvelope
+ validateLocalTransactionEnvelope,
+ validateLocalWalletPublicMetadata,
+ verifyWalletSignedEnvelope
} from "./index.js";
-const command = process.argv[2];
-const args = parseArgs(process.argv.slice(3));
+const { command, args } = parseCommand(process.argv.slice(2));
try {
if (command === "create") {
const vault = createEncryptedTestVault({
password: password(),
label: args.label ?? "local-operator",
- signerRole: args.role ?? "operator"
+ signerRole: args.role ?? "operator",
+ chainId: args["chain-id"] ?? "31337",
+ createdAtUnixMs: args["created-at-unix-ms"]
});
- writeOutput(args.vault, vault);
- console.log(JSON.stringify(exportVaultPublicMetadata(vault), null, 2));
- } else if (command === "check") {
+ writeJson(requiredOrDefault("vault", "devnet/local/wallet/operator-vault.local.json"), vault);
+ maybeWriteMetadata(vault);
+ printAccountSummary("flowchain.wallet.account_created.v0", vault.publicAccounts[0], { includePublicKey: true });
+ } else if (command === "import") {
+ const vaultPath = requiredOrDefault("vault", "devnet/local/wallet/imported-vault.local.json");
+ const privateKey = importedPrivateKey();
+ const vault = existsSync(vaultPath)
+ ? addEncryptedTestVaultAccount({
+ vault: readJson(vaultPath),
+ password: password(),
+ label: args.label ?? "imported-account",
+ signerRole: args.role ?? "agent",
+ privateKey,
+ chainId: args["chain-id"] ?? "31337",
+ createdAtUnixMs: args["created-at-unix-ms"]
+ })
+ : createEncryptedTestVault({
+ password: password(),
+ label: args.label ?? "imported-account",
+ signerRole: args.role ?? "agent",
+ privateKey,
+ chainId: args["chain-id"] ?? "31337",
+ createdAtUnixMs: args["created-at-unix-ms"]
+ });
+ writeJson(vaultPath, vault);
+ maybeWriteMetadata(vault);
+ printAccountSummary("flowchain.wallet.account_imported.v0", vault.publicAccounts.at(-1), { includePublicKey: false });
+ } else if (command === "unlock" || command === "check") {
const vault = readJson(required("vault"));
- const session = unlockEncryptedTestVault({ vault, password: password() });
+ const session = unlockVaultSafely(vault);
+ const markerPath = sessionPath();
+ writeJson(markerPath, {
+ schema: "flowchain.wallet.unlock_marker.v0",
+ vaultId: session.vaultId,
+ unlockedAtUnixMs: Date.now().toString(),
+ accountCount: session.accounts.length,
+ containsSecrets: false
+ });
console.log(JSON.stringify({
- schema: "flowmemory.crypto.local-test-vault-check.v0",
+ schema: "flowchain.wallet.unlock_result.v0",
vaultId: session.vaultId,
+ unlocked: true,
accountCount: session.accounts.length,
- publicAccountCount: session.publicAccounts.length,
- unlocked: true
+ sessionPath: markerPath,
+ containsSecrets: false
}, null, 2));
- } else if (command === "list") {
+ } else if (command === "lock") {
+ const markerPath = sessionPath();
+ if (existsSync(markerPath)) {
+ rmSync(markerPath, { force: true });
+ }
+ console.log(JSON.stringify({
+ schema: "flowchain.wallet.lock_result.v0",
+ locked: true,
+ sessionPath: markerPath
+ }, null, 2));
+ } else if (command === "list" || command === "list-accounts") {
const vault = readJson(required("vault"));
- unlockEncryptedTestVault({ vault, password: password() });
- console.log(JSON.stringify(listVaultPublicAccounts(vault), null, 2));
+ if (!args.public) {
+ unlockVaultSafely(vault);
+ }
+ console.log(JSON.stringify({
+ schema: "flowchain.wallet.account_list.v0",
+ vaultId: vault.vaultId,
+ locked: !existsSync(sessionPath()),
+ chainIds: [...new Set((vault.publicAccounts ?? []).map((account) => String(account.chainId ?? "31337")))],
+ accounts: listVaultPublicAccounts(vault)
+ }, null, 2));
} else if (command === "metadata") {
const vault = readJson(required("vault"));
console.log(JSON.stringify(exportVaultPublicMetadata(vault), null, 2));
- } else if (command === "add-account") {
+ } else if (command === "export-metadata") {
+ const vault = readJson(required("vault"));
+ const metadata = exportLocalWalletPublicMetadata(vault, { updatedAtUnixMs: args["updated-at-unix-ms"] });
+ writeOutput(args.out, metadata);
+ console.log(JSON.stringify(metadata, null, 2));
+ } else if (command === "verify-metadata") {
+ const metadata = readJson(required("metadata"));
+ const result = validateLocalWalletPublicMetadata(metadata, { expectedChainId: args["chain-id"] });
+ console.log(JSON.stringify(result, null, 2));
+ process.exitCode = result.valid ? 0 : 1;
+ } else if (command === "add-account" || command === "rotate-account") {
const vaultPath = required("vault");
const vault = readJson(vaultPath);
const updatedVault = addEncryptedTestVaultAccount({
vault,
password: password(),
label: args.label ?? "local-account",
- signerRole: args.role ?? "agent"
+ signerRole: args.role ?? "agent",
+ chainId: args["chain-id"] ?? vault.publicAccounts?.[0]?.chainId ?? "31337",
+ createdAtUnixMs: args["created-at-unix-ms"]
});
- writeOutput(vaultPath, updatedVault);
- console.log(JSON.stringify(exportVaultPublicMetadata(updatedVault), null, 2));
+ writeJson(vaultPath, updatedVault);
+ maybeWriteMetadata(updatedVault);
+ printAccountSummary("flowchain.wallet.account_added.v0", updatedVault.publicAccounts.at(-1), { includePublicKey: true });
} else if (command === "rotate") {
const vaultPath = required("vault");
const vault = readJson(vaultPath);
@@ -58,45 +138,140 @@ try {
vault,
password: password(),
signerKeyId: required("signer-key-id"),
- label: args.label
+ label: args.label,
+ chainId: args["chain-id"],
+ createdAtUnixMs: args["created-at-unix-ms"]
});
- writeOutput(vaultPath, updatedVault);
- console.log(JSON.stringify(exportVaultPublicMetadata(updatedVault), null, 2));
+ writeJson(vaultPath, updatedVault);
+ maybeWriteMetadata(updatedVault);
+ printAccountSummary("flowchain.wallet.account_rotated.v0", updatedVault.publicAccounts.at(-1), { includePublicKey: true });
} else if (command === "sign") {
- const vault = readJson(required("vault"));
const document = readJson(required("document"));
- const signerKeyId = args["signer-key-id"] ?? vault.publicAccounts[0]?.signerKeyId;
- const envelope = await signLocalTransactionWithVault({
- vault,
- password: password(),
- signerKeyId,
- document,
- chainId: required("chain-id"),
- nonce: required("nonce"),
- issuedAtUnixMs: args["issued-at-unix-ms"]
- });
- writeOutput(args.out, envelope);
- console.log(JSON.stringify(envelope, null, 2));
+ await signAndPrint(document);
+ } else if (command === "sign-transfer") {
+ await signAndPrint(buildProductTransferDocument({
+ fromAccountId: required("from"),
+ toAccountId: required("to"),
+ assetId: args["asset-id"] ?? LOCAL_TEST_UNIT_ASSET_ID,
+ amount: required("amount"),
+ accountNonce: args["account-nonce"] ?? required("nonce"),
+ deadlineBlock: args["deadline-block"] ?? "0",
+ memo: args.memo,
+ memoHash: args["memo-hash"]
+ }));
+ } else if (command === "sign-token-launch") {
+ await signAndPrint(buildProductTokenLaunchDocument({
+ issuerAccountId: required("owner"),
+ ownerAccountId: required("owner"),
+ symbol: required("symbol"),
+ name: required("name"),
+ supply: required("supply"),
+ decimals: args.decimals ?? 18,
+ accountNonce: args["account-nonce"] ?? required("nonce"),
+ tokenId: args["token-id"],
+ metadataHash: args["metadata-hash"],
+ launchPolicyHash: args["launch-policy-hash"]
+ }));
+ } else if (command === "sign-token-transfer") {
+ await signAndPrint(buildProductTransferDocument({
+ fromAccountId: required("from"),
+ toAccountId: required("to"),
+ assetId: required("token-id"),
+ amount: required("amount"),
+ accountNonce: args["account-nonce"] ?? required("nonce"),
+ deadlineBlock: args["deadline-block"] ?? "0",
+ memo: args.memo,
+ memoHash: args["memo-hash"]
+ }));
+ } else if (command === "sign-pool-create") {
+ await signAndPrint(buildProductPoolCreateDocument({
+ creatorAccountId: required("owner"),
+ baseAssetId: required("base-asset-id"),
+ quoteAssetId: required("quote-asset-id"),
+ baseReserve: required("base-reserve"),
+ quoteReserve: required("quote-reserve"),
+ feeBps: args["fee-bps"] ?? 30,
+ tickSpacing: args["tick-spacing"] ?? 1,
+ poolId: args["pool-id"],
+ metadataHash: args["metadata-hash"],
+ accountNonce: args["account-nonce"] ?? required("nonce")
+ }));
+ } else if (command === "sign-add-liquidity") {
+ await signAndPrint(buildProductAddLiquidityDocument({
+ providerAccountId: required("owner"),
+ poolId: required("pool-id"),
+ baseAmount: required("base-amount"),
+ quoteAmount: required("quote-amount"),
+ minLiquidityTokens: required("min-liquidity-tokens"),
+ deadlineBlock: required("deadline-block"),
+ accountNonce: args["account-nonce"] ?? required("nonce")
+ }));
+ } else if (command === "sign-remove-liquidity") {
+ await signAndPrint(buildProductRemoveLiquidityDocument({
+ providerAccountId: required("owner"),
+ poolId: required("pool-id"),
+ liquidityTokens: required("liquidity-tokens"),
+ minBaseAmount: required("min-base-amount"),
+ minQuoteAmount: required("min-quote-amount"),
+ deadlineBlock: required("deadline-block"),
+ accountNonce: args["account-nonce"] ?? required("nonce")
+ }));
+ } else if (command === "sign-swap") {
+ await signAndPrint(buildProductSwapDocument({
+ traderAccountId: required("owner"),
+ poolId: required("pool-id"),
+ assetInId: required("input-token-id"),
+ assetOutId: required("output-token-id"),
+ amountIn: required("input-amount"),
+ minAmountOut: required("minimum-output"),
+ deadlineBlock: required("deadline-block"),
+ accountNonce: args["account-nonce"] ?? required("nonce")
+ }));
+ } else if (command === "sign-withdrawal-intent") {
+ await signAndPrint(buildBridgeWithdrawalIntentDocument({
+ creditId: required("credit-id"),
+ depositId: required("deposit-id"),
+ sourceChainId: args["source-chain-id"] ?? required("chain-id"),
+ destinationChainId: args["destination-chain-id"] ?? 8453,
+ token: required("bridge-asset"),
+ amount: required("amount"),
+ flowchainAccount: required("account"),
+ baseRecipient: required("base-address"),
+ requestedAt: args["requested-at"]
+ }));
+ } else if (command === "sign-finality") {
+ await signAndPrint(buildFinalityActionDocument({
+ receiptId: required("receipt-id"),
+ reportId: required("report-id"),
+ challengeRoot: args["challenge-root"],
+ finalityState: args["finality-state-code"] ?? 6,
+ finalizedAtUnixMs: required("finalized-at-unix-ms"),
+ finalizedBlockNumber: required("finalized-block-number"),
+ finalizedBlockHash: required("finalized-block-hash"),
+ policyHash: required("policy-hash")
+ }));
} else if (command === "verify") {
- const document = readJson(required("document"));
- const envelope = readJson(required("envelope"));
- const context = {};
- if (args["chain-id"]) {
- context.chainId = args["chain-id"];
- }
- if (args["expected-nonce"]) {
- context.expectedNonce = args["expected-nonce"];
- }
- if (args["expected-signer-id"]) {
- context.expectedSignerId = args["expected-signer-id"];
+ if (!args.envelope && !args.document) {
+ runVerificationSmoke();
+ } else {
+ const envelope = readJson(required("envelope"));
+ const document = args.document ? readJson(args.document) : undefined;
+ const context = verificationContext(document);
+ const result = envelope.schema === "flowchain.wallet_signed_envelope.v0"
+ ? verifyWalletSignedEnvelope({ envelope, context })
+ : validateLocalTransactionEnvelope({ document, envelope, context });
+ console.log(JSON.stringify(result, null, 2));
+ process.exitCode = result.valid ? 0 : 1;
}
- const result = validateLocalTransactionEnvelope({
- document,
- envelope,
- context
- });
- console.log(JSON.stringify(result, null, 2));
- process.exitCode = result.valid ? 0 : 1;
+ } else if (command === "submit") {
+ const envelope = readJson(required("envelope"));
+ const response = await submitEnvelope(envelope);
+ console.log(JSON.stringify(response, null, 2));
+ process.exitCode = response.error ? 1 : 0;
+ } else if (command === "query") {
+ const response = await queryControlPlane(required("method"), args.params ? parseJsonValue(args.params) : {});
+ console.log(JSON.stringify(response, null, 2));
+ process.exitCode = response.error ? 1 : 0;
} else {
usage();
process.exitCode = 1;
@@ -106,6 +281,14 @@ try {
process.exitCode = 1;
}
+function parseCommand(argv) {
+ const rawCommand = argv[0];
+ if (rawCommand === "list" && argv[1] === "accounts") {
+ return { command: "list", args: parseArgs(argv.slice(2)) };
+ }
+ return { command: rawCommand, args: parseArgs(argv.slice(1)) };
+}
+
function parseArgs(argv) {
const parsed = {};
for (let i = 0; i < argv.length; i += 1) {
@@ -125,14 +308,189 @@ function parseArgs(argv) {
return parsed;
}
+async function signAndPrint(document) {
+ const vault = readJson(required("vault"));
+ const signerKeyId = args["signer-key-id"] ?? selectSignerKeyId(vault, args.from ?? args.owner ?? args.account);
+ const envelope = await signWalletDocumentWithVault({
+ vault,
+ password: password(),
+ signerKeyId,
+ document,
+ chainId: required("chain-id"),
+ nonce: required("nonce"),
+ issuedAtUnixMs: args["issued-at-unix-ms"],
+ expiresAtUnixMs: args["expires-at-unix-ms"] ?? null
+ });
+ const outPath = args.out ?? defaultEnvelopePath(envelope.txId);
+ writeJson(outPath, envelope);
+ console.log(JSON.stringify({
+ schema: "flowchain.wallet.sign_result.v0",
+ txId: envelope.txId,
+ chainId: envelope.chainId,
+ payloadType: envelope.payloadType,
+ signerAddress: envelope.signerAddress,
+ nonce: envelope.nonce,
+ envelopePath: outPath,
+ verification: envelope.verification
+ }, null, 2));
+}
+
+function selectSignerKeyId(vault, preferredAddress) {
+ const accounts = vault.publicAccounts ?? [];
+ if (preferredAddress) {
+ const account = accounts.find((candidate) =>
+ candidate.signerId === preferredAddress ||
+ candidate.accountId === preferredAddress ||
+ candidate.address === preferredAddress
+ );
+ if (!account) {
+ throw new Error("vault does not contain an active signer for the requested account");
+ }
+ return account.signerKeyId;
+ }
+ const account = accounts.find((candidate) => candidate.active !== false) ?? accounts[0];
+ if (!account) {
+ throw new Error("vault does not contain any signer accounts");
+ }
+ return account.signerKeyId;
+}
+
+function maybeWriteMetadata(vault) {
+ if (args["metadata-out"]) {
+ writeJson(args["metadata-out"], exportLocalWalletPublicMetadata(vault, { updatedAtUnixMs: args["updated-at-unix-ms"] }));
+ }
+}
+
+function printAccountSummary(schema, account, { includePublicKey }) {
+ const output = {
+ schema,
+ address: account.address ?? account.signerId
+ };
+ if (includePublicKey) {
+ output.publicKey = account.publicKey;
+ }
+ console.log(JSON.stringify(output, null, 2));
+}
+
function password() {
const value = args.password ?? process.env.FLOWMEMORY_TEST_WALLET_PASSWORD;
if (!value) {
- throw new Error("set --password or FLOWMEMORY_TEST_WALLET_PASSWORD for the local test vault");
+ throw new Error("set --password or FLOWMEMORY_TEST_WALLET_PASSWORD for the local wallet vault");
}
return value;
}
+function importedPrivateKey() {
+ if (args["private-key-env"]) {
+ const value = process.env[args["private-key-env"]];
+ if (!value) {
+ throw new Error(`environment variable ${args["private-key-env"]} is empty`);
+ }
+ return normalizePrivateKey(value);
+ }
+ if (args["private-key-file"]) {
+ return normalizePrivateKey(readFileSync(args["private-key-file"], "utf8"));
+ }
+ if (args["private-key-stdin"]) {
+ return normalizePrivateKey(readFileSync(0, "utf8"));
+ }
+ throw new Error("import requires --private-key-env, --private-key-file, or --private-key-stdin");
+}
+
+function normalizePrivateKey(value) {
+ const trimmed = String(value).trim();
+ if (!/^0x[0-9a-fA-F]{64}$/.test(trimmed)) {
+ throw new Error("imported private key must be a 32-byte hex value");
+ }
+ return trimmed;
+}
+
+function unlockVaultSafely(vault) {
+ try {
+ return unlockEncryptedTestVault({ vault, password: password() });
+ } catch {
+ throw new Error("vault unlock failed");
+ }
+}
+
+function sessionPath() {
+ return args["session-path"] ?? `${requiredOrDefault("vault", "devnet/local/wallet/operator-vault.local.json")}.session.local.json`;
+}
+
+function verificationContext(document) {
+ const context = {};
+ if (document) {
+ context.document = document;
+ }
+ if (args["chain-id"]) {
+ context.chainId = args["chain-id"];
+ }
+ if (args["expected-nonce"]) {
+ context.expectedNonce = args["expected-nonce"];
+ }
+ if (args["expected-signer-id"]) {
+ context.expectedSignerId = args["expected-signer-id"];
+ }
+ if (args["expected-signer-address"]) {
+ context.expectedSignerAddress = args["expected-signer-address"];
+ }
+ return context;
+}
+
+function runVerificationSmoke() {
+ const fixture = readJson(resolve(import.meta.dirname, "..", "fixtures", "product-testnet-transactions.json"));
+ const vector = fixture.transactions.positive[0];
+ const document = fixture.documents.positive.find((entry) => entry.name === vector.objectName).document;
+ const result = validateLocalTransactionEnvelope({
+ document,
+ envelope: vector.envelope,
+ context: { chainId: fixture.chainId, expectedNonce: vector.envelope.nonce }
+ });
+ console.log(JSON.stringify({
+ schema: "flowchain.wallet.verify_smoke.v0",
+ vector: vector.name,
+ txId: vector.envelope.envelopeId,
+ result
+ }, null, 2));
+ process.exitCode = result.valid ? 0 : 1;
+}
+
+async function submitEnvelope(envelope) {
+ const { dispatchJsonRpc, loadControlPlaneState } = await import("../../services/control-plane/src/index.ts");
+ const state = loadControlPlaneState({
+ txIntakePath: args["intake-path"] ?? "devnet/local/intake/transactions.ndjson"
+ });
+ return dispatchJsonRpc({
+ jsonrpc: "2.0",
+ id: 1,
+ method: "transaction_submit",
+ params: {
+ signedEnvelope: envelope,
+ submittedBy: args["submitted-by"] ?? "flowchain-wallet-cli"
+ }
+ }, { state });
+}
+
+async function queryControlPlane(method, params) {
+ const { dispatchJsonRpc, loadControlPlaneState } = await import("../../services/control-plane/src/index.ts");
+ const state = loadControlPlaneState({
+ txIntakePath: args["intake-path"] ?? "devnet/local/intake/transactions.ndjson"
+ });
+ return dispatchJsonRpc({
+ jsonrpc: "2.0",
+ id: 1,
+ method,
+ params
+ }, { state });
+}
+
+function parseJsonValue(value) {
+ if (existsSync(value)) {
+ return readJson(value);
+ }
+ return JSON.parse(value);
+}
+
function required(name) {
if (!args[name]) {
throw new Error(`missing --${name}`);
@@ -140,24 +498,51 @@ function required(name) {
return args[name];
}
+function requiredOrDefault(name, defaultValue) {
+ return args[name] ?? defaultValue;
+}
+
function readJson(path) {
return JSON.parse(readFileSync(path, "utf8"));
}
function writeOutput(path, value) {
if (path) {
- writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
+ writeJson(path, value);
}
}
+function writeJson(path, value) {
+ const fullPath = resolve(path);
+ mkdirSync(dirname(fullPath), { recursive: true });
+ writeFileSync(fullPath, `${JSON.stringify(value, null, 2)}\n`);
+ return fullPath;
+}
+
+function defaultEnvelopePath(txId) {
+ return `devnet/local/wallet/envelopes/${txId}.json`;
+}
+
function usage() {
console.error(`Usage:
- node src/wallet-cli.js create --vault [--password ] [--label ] [--role operator]
- node src/wallet-cli.js check --vault [--password ]
- node src/wallet-cli.js list --vault [--password ]
- node src/wallet-cli.js metadata --vault
- node src/wallet-cli.js add-account --vault [--password ] [--label ] [--role agent]
- node src/wallet-cli.js rotate --vault --signer-key-id [--password ] [--label ]
- node src/wallet-cli.js sign --vault --document --chain-id --nonce [--signer-key-id ] [--issued-at-unix-ms ] [--out ]
- node src/wallet-cli.js verify --document --envelope [--chain-id ] [--expected-nonce ] [--expected-signer-id ]`);
+ node src/wallet-cli.js create --vault --chain-id [--metadata-out ]
+ node src/wallet-cli.js import --vault --private-key-env --chain-id
+ node src/wallet-cli.js unlock --vault
+ node src/wallet-cli.js lock --vault
+ node src/wallet-cli.js list accounts --vault [--public]
+ node src/wallet-cli.js export-metadata --vault --out
+ node src/wallet-cli.js verify-metadata --metadata [--chain-id ]
+ node src/wallet-cli.js add-account --vault --chain-id [--role agent]
+ node src/wallet-cli.js rotate --vault --signer-key-id
+ node src/wallet-cli.js sign-transfer --vault --from --to --amount --nonce --chain-id
+ node src/wallet-cli.js sign-token-launch --vault --owner --symbol --name --supply --nonce --chain-id
+ node src/wallet-cli.js sign-token-transfer --vault --from --to --token-id --amount --nonce --chain-id
+ node src/wallet-cli.js sign-pool-create --vault --owner --base-asset-id --quote-asset-id --base-reserve --quote-reserve --nonce --chain-id
+ node src/wallet-cli.js sign-add-liquidity --vault --owner --pool-id --base-amount --quote-amount --min-liquidity-tokens --deadline-block --nonce --chain-id
+ node src/wallet-cli.js sign-remove-liquidity --vault --owner --pool-id --liquidity-tokens --min-base-amount --min-quote-amount --deadline-block --nonce --chain-id
+ node src/wallet-cli.js sign-swap --vault --owner --pool-id --input-token-id --output-token-id --input-amount --minimum-output --deadline-block --nonce --chain-id
+ node src/wallet-cli.js sign-withdrawal-intent --vault --account --base-address <0x...> --amount --bridge-asset <0x...> --credit-id --deposit-id --nonce --chain-id
+ node src/wallet-cli.js verify --envelope [--chain-id ] [--expected-nonce ]
+ node src/wallet-cli.js submit --envelope [--intake-path ]
+ node src/wallet-cli.js query --method [--params ]`);
}
diff --git a/crypto/src/wallet-documents.js b/crypto/src/wallet-documents.js
new file mode 100644
index 00000000..b511f098
--- /dev/null
+++ b/crypto/src/wallet-documents.js
@@ -0,0 +1,341 @@
+import { ZERO_BYTES32 } from "./constants.js";
+import { keccakUtf8 } from "./hashes.js";
+import {
+ bridgeWithdrawalIntentId,
+ finalityReceiptId,
+ productAddLiquidityId,
+ productPoolCreateId,
+ productRemoveLiquidityId,
+ productSwapId,
+ productTokenLaunchId,
+ productTransferId
+} from "./objects.js";
+
+export const LOCAL_TEST_UNIT_ASSET_ID = keccakUtf8("flowchain.asset.local-test-unit.v0");
+
+export function buildProductTransferDocument({
+ fromAccountId,
+ toAccountId,
+ assetId = LOCAL_TEST_UNIT_ASSET_ID,
+ amount,
+ accountNonce,
+ deadlineBlock = "0",
+ memo,
+ memoHash
+}) {
+ const document = {
+ schema: "flowchain.product_transfer.v0",
+ transferId: ZERO_BYTES32,
+ fromAccountId: requireHex32(fromAccountId, "fromAccountId"),
+ toAccountId: requireHex32(toAccountId, "toAccountId"),
+ assetId: requireHex32(assetId, "assetId"),
+ amount: requirePositiveUintString(amount, "amount"),
+ accountNonce: requireUintString(accountNonce, "accountNonce"),
+ deadlineBlock: requireUintString(deadlineBlock, "deadlineBlock"),
+ memoHash: memoHash ? requireHex32(memoHash, "memoHash") : keccakUtf8(memo ?? "")
+ };
+ document.transferId = productTransferId(document);
+ return document;
+}
+
+export function buildProductTokenLaunchDocument({
+ issuerAccountId,
+ symbol,
+ name,
+ supply,
+ ownerAccountId,
+ recipientAccountId = ownerAccountId,
+ decimals = 18,
+ accountNonce,
+ tokenId,
+ metadataHash,
+ launchPolicyHash
+}) {
+ const normalizedSymbol = normalizeTokenSymbol(symbol);
+ const normalizedName = requireNonEmptyString(name, "name");
+ const document = {
+ schema: "flowchain.product_token_launch.v0",
+ tokenLaunchId: ZERO_BYTES32,
+ issuerAccountId: requireHex32(issuerAccountId, "issuerAccountId"),
+ tokenId: tokenId ? requireHex32(tokenId, "tokenId") : keccakUtf8(`flowchain.product-token:${normalizedSymbol}:${normalizedName}`),
+ symbolHash: keccakUtf8(normalizedSymbol),
+ nameHash: keccakUtf8(normalizedName),
+ metadataHash: metadataHash ? requireHex32(metadataHash, "metadataHash") : keccakUtf8(`metadata:${normalizedSymbol}:${normalizedName}`),
+ decimals: normalizeSmallInteger(decimals, "decimals", 0, 18),
+ initialSupply: requirePositiveUintString(supply, "supply"),
+ recipientAccountId: requireHex32(recipientAccountId, "recipientAccountId"),
+ accountNonce: requireUintString(accountNonce, "accountNonce"),
+ launchPolicyHash: launchPolicyHash ? requireHex32(launchPolicyHash, "launchPolicyHash") : keccakUtf8("flowchain.product-token.launch-policy.local")
+ };
+ document.tokenLaunchId = productTokenLaunchId(document);
+ return document;
+}
+
+export function buildProductPoolCreateDocument({
+ creatorAccountId,
+ baseAssetId,
+ quoteAssetId,
+ baseReserve,
+ quoteReserve,
+ poolId,
+ feeBps = 30,
+ tickSpacing = 1,
+ metadataHash,
+ accountNonce
+}) {
+ const base = requireHex32(baseAssetId, "baseAssetId");
+ const quote = requireHex32(quoteAssetId, "quoteAssetId");
+ if (base === quote) {
+ throw new Error("pool token pair must contain two different assets");
+ }
+ const reserveHash = keccakUtf8(JSON.stringify({
+ baseReserve: requirePositiveUintString(baseReserve, "baseReserve"),
+ quoteReserve: requirePositiveUintString(quoteReserve, "quoteReserve")
+ }));
+ const document = {
+ schema: "flowchain.product_pool_create.v0",
+ poolCreateId: ZERO_BYTES32,
+ creatorAccountId: requireHex32(creatorAccountId, "creatorAccountId"),
+ poolId: poolId ? requireHex32(poolId, "poolId") : keccakUtf8(`flowchain.pool:${base}:${quote}:${feeBps}:${tickSpacing}`),
+ baseAssetId: base,
+ quoteAssetId: quote,
+ feeBps: normalizeSmallInteger(feeBps, "feeBps", 0, 10000),
+ tickSpacing: normalizeSmallInteger(tickSpacing, "tickSpacing", 1, 1000000),
+ metadataHash: metadataHash ? requireHex32(metadataHash, "metadataHash") : reserveHash,
+ accountNonce: requireUintString(accountNonce, "accountNonce")
+ };
+ document.poolCreateId = productPoolCreateId(document);
+ return document;
+}
+
+export function buildProductAddLiquidityDocument({
+ providerAccountId,
+ poolId,
+ baseAmount,
+ quoteAmount,
+ minLiquidityTokens,
+ deadlineBlock,
+ accountNonce
+}) {
+ const document = {
+ schema: "flowchain.product_add_liquidity.v0",
+ addLiquidityId: ZERO_BYTES32,
+ providerAccountId: requireHex32(providerAccountId, "providerAccountId"),
+ poolId: requireHex32(poolId, "poolId"),
+ baseAmount: requirePositiveUintString(baseAmount, "baseAmount"),
+ quoteAmount: requirePositiveUintString(quoteAmount, "quoteAmount"),
+ minLiquidityTokens: requireUintString(minLiquidityTokens, "minLiquidityTokens"),
+ deadlineBlock: requireUintString(deadlineBlock, "deadlineBlock"),
+ accountNonce: requireUintString(accountNonce, "accountNonce")
+ };
+ document.addLiquidityId = productAddLiquidityId(document);
+ return document;
+}
+
+export function buildProductRemoveLiquidityDocument({
+ providerAccountId,
+ poolId,
+ liquidityTokens,
+ minBaseAmount,
+ minQuoteAmount,
+ deadlineBlock,
+ accountNonce
+}) {
+ const document = {
+ schema: "flowchain.product_remove_liquidity.v0",
+ removeLiquidityId: ZERO_BYTES32,
+ providerAccountId: requireHex32(providerAccountId, "providerAccountId"),
+ poolId: requireHex32(poolId, "poolId"),
+ liquidityTokens: requirePositiveUintString(liquidityTokens, "liquidityTokens"),
+ minBaseAmount: requireUintString(minBaseAmount, "minBaseAmount"),
+ minQuoteAmount: requireUintString(minQuoteAmount, "minQuoteAmount"),
+ deadlineBlock: requireUintString(deadlineBlock, "deadlineBlock"),
+ accountNonce: requireUintString(accountNonce, "accountNonce")
+ };
+ document.removeLiquidityId = productRemoveLiquidityId(document);
+ return document;
+}
+
+export function buildProductSwapDocument({
+ traderAccountId,
+ poolId,
+ assetInId,
+ assetOutId,
+ amountIn,
+ minAmountOut,
+ deadlineBlock,
+ accountNonce
+}) {
+ const assetIn = requireHex32(assetInId, "assetInId");
+ const assetOut = requireHex32(assetOutId, "assetOutId");
+ if (assetIn === assetOut) {
+ throw new Error("swap input and output assets must differ");
+ }
+ const document = {
+ schema: "flowchain.product_swap.v0",
+ swapId: ZERO_BYTES32,
+ traderAccountId: requireHex32(traderAccountId, "traderAccountId"),
+ poolId: requireHex32(poolId, "poolId"),
+ assetInId: assetIn,
+ assetOutId: assetOut,
+ amountIn: requirePositiveUintString(amountIn, "amountIn"),
+ minAmountOut: requireUintString(minAmountOut, "minAmountOut"),
+ deadlineBlock: requireUintString(deadlineBlock, "deadlineBlock"),
+ accountNonce: requireUintString(accountNonce, "accountNonce")
+ };
+ document.swapId = productSwapId(document);
+ return document;
+}
+
+export function buildBridgeWithdrawalIntentDocument({
+ creditId,
+ depositId,
+ sourceChainId,
+ destinationChainId = 8453,
+ token,
+ amount,
+ flowchainAccount,
+ baseRecipient,
+ requestedAt,
+ status = "requested",
+ testMode = true,
+ broadcast = false,
+ releasePolicy = "test_record_only",
+ productionReady = false
+}) {
+ const document = {
+ schema: "flowmemory.bridge_withdrawal_intent.v0",
+ withdrawalIntentId: ZERO_BYTES32,
+ creditId: requireHex32(creditId, "creditId"),
+ depositId: requireHex32(depositId, "depositId"),
+ sourceChainId: normalizeChainIdNumber(sourceChainId, "sourceChainId"),
+ destinationChainId: normalizeChainIdNumber(destinationChainId, "destinationChainId"),
+ token: requireEthAddress(token, "token"),
+ amount: requirePositiveUintString(amount, "amount"),
+ flowchainAccount: requireHex32(flowchainAccount, "flowchainAccount"),
+ baseRecipient: requireEthAddress(baseRecipient, "baseRecipient"),
+ status,
+ requestedAt: requestedAt ?? new Date(0).toISOString(),
+ testMode,
+ broadcast,
+ releasePolicy,
+ productionReady
+ };
+ document.withdrawalIntentId = bridgeWithdrawalIntentId(document);
+ return document;
+}
+
+export function buildFinalityActionDocument({
+ receiptId,
+ reportId,
+ challengeRoot = ZERO_BYTES32,
+ finalityState = 4,
+ finalizedAtUnixMs,
+ finalizedBlockNumber,
+ finalizedBlockHash,
+ policyHash
+}) {
+ const finalityStateCode = normalizeSmallInteger(finalityState, "finalityState", 1, 255);
+ const document = {
+ schema: "flowchain.finality_receipt.v0",
+ finalityReceiptId: ZERO_BYTES32,
+ receiptId: requireHex32(receiptId, "receiptId"),
+ reportId: requireHex32(reportId, "reportId"),
+ challengeRoot: requireHex32(challengeRoot, "challengeRoot"),
+ finalityState: finalityStateName(finalityStateCode),
+ finalityStateCode,
+ finalizedAtUnixMs: requireUintString(finalizedAtUnixMs, "finalizedAtUnixMs"),
+ finalizedBlockNumber: requireUintString(finalizedBlockNumber, "finalizedBlockNumber"),
+ finalizedBlockHash: requireHex32(finalizedBlockHash, "finalizedBlockHash"),
+ policyHash: requireHex32(policyHash, "policyHash")
+ };
+ document.finalityReceiptId = finalityReceiptId({
+ receiptId: document.receiptId,
+ reportId: document.reportId,
+ challengeRoot: document.challengeRoot,
+ finalityState: document.finalityStateCode,
+ finalizedAtUnixMs: document.finalizedAtUnixMs,
+ finalizedBlockNumber: document.finalizedBlockNumber,
+ finalizedBlockHash: document.finalizedBlockHash,
+ policyHash: document.policyHash
+ });
+ return document;
+}
+
+export function requireHex32(value, field) {
+ if (typeof value !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(value)) {
+ throw new Error(`${field} must be a 32-byte hex value`);
+ }
+ return value.toLowerCase();
+}
+
+export function requireEthAddress(value, field) {
+ if (typeof value !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(value)) {
+ throw new Error(`${field} must be a 20-byte hex address`);
+ }
+ return value.toLowerCase();
+}
+
+export function requireUintString(value, field) {
+ const normalized = String(value);
+ if (!/^[0-9]+$/.test(normalized)) {
+ throw new Error(`${field} must be an unsigned integer string`);
+ }
+ return normalized;
+}
+
+export function requirePositiveUintString(value, field) {
+ const normalized = requireUintString(value, field);
+ if (BigInt(normalized) <= 0n) {
+ throw new Error(`${field} must be positive`);
+ }
+ return normalized;
+}
+
+function normalizeTokenSymbol(value) {
+ const symbol = requireNonEmptyString(value, "symbol").toUpperCase();
+ if (!/^[A-Z][A-Z0-9]{1,15}$/.test(symbol)) {
+ throw new Error("token symbol must be 2-16 uppercase alphanumeric characters and start with a letter");
+ }
+ return symbol;
+}
+
+function requireNonEmptyString(value, field) {
+ if (typeof value !== "string" || value.trim().length === 0) {
+ throw new Error(`${field} is required`);
+ }
+ return value.trim();
+}
+
+function normalizeSmallInteger(value, field, min, max) {
+ const parsed = typeof value === "number" ? value : Number(value);
+ if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
+ throw new Error(`${field} must be an integer between ${min} and ${max}`);
+ }
+ return parsed;
+}
+
+function normalizeChainIdNumber(value, field) {
+ const parsed = typeof value === "number" ? value : Number(value);
+ if (!Number.isSafeInteger(parsed) || ![31337, 84532, 8453].includes(parsed)) {
+ throw new Error(`${field} must be one of 31337, 84532, or 8453`);
+ }
+ return parsed;
+}
+
+function finalityStateName(code) {
+ const names = {
+ 1: "provisional",
+ 2: "challengeable",
+ 3: "challenged",
+ 4: "accepted",
+ 5: "rejected",
+ 6: "finalized",
+ 7: "superseded",
+ 8: "reorged"
+ };
+ if (!names[code]) {
+ throw new Error("unsupported finality state code");
+ }
+ return names[code];
+}
diff --git a/crypto/src/wallet-e2e.js b/crypto/src/wallet-e2e.js
new file mode 100644
index 00000000..083505f5
--- /dev/null
+++ b/crypto/src/wallet-e2e.js
@@ -0,0 +1,359 @@
+#!/usr/bin/env node
+import assert from "node:assert/strict";
+import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
+import { resolve } from "node:path";
+
+import {
+ buildBridgeWithdrawalIntentDocument,
+ buildProductAddLiquidityDocument,
+ buildProductPoolCreateDocument,
+ buildProductRemoveLiquidityDocument,
+ buildProductSwapDocument,
+ buildProductTokenLaunchDocument,
+ buildProductTransferDocument,
+ createEncryptedTestVault,
+ exportLocalWalletPublicMetadata,
+ keccakUtf8,
+ LOCAL_TEST_UNIT_ASSET_ID,
+ signWalletDocumentWithVault,
+ validateLocalWalletPublicMetadata,
+ verifyWalletSignedEnvelope
+} from "./index.js";
+
+import { dispatchJsonRpc, loadControlPlaneState } from "../../services/control-plane/src/index.ts";
+
+const transferOnly = process.argv.includes("--transfer-only");
+const repoRoot = resolve(import.meta.dirname, "..", "..");
+const outDir = resolve(repoRoot, "devnet", "local", "production-l1-wallet", transferOnly ? "transfer-e2e" : "wallet-e2e");
+const chainId = "31337";
+const password = "wallet-e2e-password";
+const issuedAtUnixMs = "1778702400000";
+
+rmSync(outDir, { recursive: true, force: true });
+mkdirSync(outDir, { recursive: true });
+
+const walletA = createEncryptedTestVault({
+ password,
+ label: "wallet-a",
+ signerRole: "agent",
+ privateKey: "0x0000000000000000000000000000000000000000000000000000000000000001",
+ chainId,
+ createdAtUnixMs: issuedAtUnixMs
+});
+const walletB = createEncryptedTestVault({
+ password,
+ label: "wallet-b",
+ signerRole: "agent",
+ privateKey: "0x0000000000000000000000000000000000000000000000000000000000000002",
+ chainId,
+ createdAtUnixMs: issuedAtUnixMs
+});
+
+writeJson("wallet-a.vault.local.json", walletA);
+writeJson("wallet-b.vault.local.json", walletB);
+
+const accountA = walletA.publicAccounts[0];
+const accountB = walletB.publicAccounts[0];
+const metadataA = exportLocalWalletPublicMetadata(walletA, { updatedAtUnixMs: issuedAtUnixMs });
+const metadataB = exportLocalWalletPublicMetadata(walletB, { updatedAtUnixMs: issuedAtUnixMs });
+assert.equal(validateLocalWalletPublicMetadata(metadataA, { expectedChainId: chainId }).valid, true);
+assert.equal(validateLocalWalletPublicMetadata(metadataB, { expectedChainId: chainId }).valid, true);
+writeJson("wallet-a-public-metadata.json", metadataA);
+writeJson("wallet-b-public-metadata.json", metadataB);
+
+const balances = new Map();
+credit(accountA.address, LOCAL_TEST_UNIT_ASSET_ID, 1000000n);
+
+const transferDocument = buildProductTransferDocument({
+ fromAccountId: accountA.address,
+ toAccountId: accountB.address,
+ assetId: LOCAL_TEST_UNIT_ASSET_ID,
+ amount: "125000",
+ accountNonce: "1",
+ deadlineBlock: "25",
+ memo: "wallet-a-to-wallet-b-e2e"
+});
+const transferEnvelope = await signEnvelope(walletA, accountA.signerKeyId, transferDocument, "1");
+const transferReceipt = applyTransfer({
+ envelope: transferEnvelope,
+ from: accountA.address,
+ to: accountB.address,
+ assetId: LOCAL_TEST_UNIT_ASSET_ID,
+ amount: 125000n
+});
+writeJson(`envelopes/${transferEnvelope.txId}.json`, transferEnvelope);
+writeJson("transfer-receipt.json", transferReceipt);
+
+const txIntakePath = resolve(outDir, "api-transactions.ndjson");
+const controlPlaneState = loadControlPlaneState({ txIntakePath });
+const transferSubmit = dispatchJsonRpc({
+ jsonrpc: "2.0",
+ id: 1,
+ method: "transaction_submit",
+ params: {
+ signedEnvelope: transferEnvelope,
+ submittedBy: "wallet-e2e"
+ }
+}, { state: controlPlaneState });
+assert.equal(transferSubmit.result.accepted, true);
+
+const productProof = transferOnly ? null : await signProductAndDexActions({ controlPlaneState });
+const mempool = dispatchJsonRpc({ jsonrpc: "2.0", id: 3, method: "mempool_list" }, { state: controlPlaneState });
+assert.ok(mempool.result.count >= (transferOnly ? 1 : 2));
+
+const proof = {
+ schema: "flowchain.wallet_e2e_proof.v0",
+ transferOnly,
+ chainId,
+ wallets: {
+ walletA: publicWalletSummary(accountA),
+ walletB: publicWalletSummary(accountB)
+ },
+ funding: {
+ source: "local pilot credit fixture",
+ account: accountA.address,
+ assetId: LOCAL_TEST_UNIT_ASSET_ID,
+ amount: "1000000"
+ },
+ transfer: {
+ txId: transferEnvelope.txId,
+ envelopePath: relativeOut(`envelopes/${transferEnvelope.txId}.json`),
+ receipt: transferReceipt,
+ submitResult: transferSubmit.result
+ },
+ balances: {
+ before: transferReceipt.balancesBefore,
+ after: transferReceipt.balancesAfter
+ },
+ productDex: productProof,
+ controlPlane: {
+ txIntakePath,
+ mempoolCount: mempool.result.count
+ },
+ noSecretScan: assertNoPrivateMaterialInPublicOutputs(),
+ boundaries: [
+ "deterministic local test keys only",
+ "vault files are written under ignored devnet/local paths",
+ "public proofs contain public keys, addresses, signatures, tx ids, receipts, and balances only"
+ ]
+};
+writeJson("wallet-e2e-proof.json", proof);
+
+console.log(
+ `FLOWCHAIN_WALLET_E2E_OK transferTxId=${transferEnvelope.txId} walletA=${accountA.address} walletB=${accountB.address} apiMempool=${mempool.result.count}`
+);
+
+async function signProductAndDexActions({ controlPlaneState }) {
+ const tokenLaunch = buildProductTokenLaunchDocument({
+ issuerAccountId: accountA.address,
+ ownerAccountId: accountA.address,
+ symbol: "FLOWT",
+ name: "Flow Test Token",
+ supply: "1000000000000000000000",
+ accountNonce: "2"
+ });
+ const tokenTransfer = buildProductTransferDocument({
+ fromAccountId: accountA.address,
+ toAccountId: accountB.address,
+ assetId: tokenLaunch.tokenId,
+ amount: "25000000000000000000",
+ accountNonce: "3",
+ deadlineBlock: "30",
+ memo: "token-transfer-e2e"
+ });
+ const poolCreate = buildProductPoolCreateDocument({
+ creatorAccountId: accountA.address,
+ baseAssetId: LOCAL_TEST_UNIT_ASSET_ID,
+ quoteAssetId: tokenLaunch.tokenId,
+ baseReserve: "100000",
+ quoteReserve: "25000000000000000000",
+ accountNonce: "4"
+ });
+ const addLiquidity = buildProductAddLiquidityDocument({
+ providerAccountId: accountA.address,
+ poolId: poolCreate.poolId,
+ baseAmount: "100000",
+ quoteAmount: "25000000000000000000",
+ minLiquidityTokens: "1",
+ deadlineBlock: "35",
+ accountNonce: "5"
+ });
+ const swap = buildProductSwapDocument({
+ traderAccountId: accountA.address,
+ poolId: poolCreate.poolId,
+ assetInId: LOCAL_TEST_UNIT_ASSET_ID,
+ assetOutId: tokenLaunch.tokenId,
+ amountIn: "1000",
+ minAmountOut: "1",
+ deadlineBlock: "40",
+ accountNonce: "6"
+ });
+ const removeLiquidity = buildProductRemoveLiquidityDocument({
+ providerAccountId: accountA.address,
+ poolId: poolCreate.poolId,
+ liquidityTokens: "1",
+ minBaseAmount: "1",
+ minQuoteAmount: "1",
+ deadlineBlock: "45",
+ accountNonce: "7"
+ });
+ const withdrawalIntent = buildBridgeWithdrawalIntentDocument({
+ creditId: keccakUtf8("wallet-e2e-credit"),
+ depositId: keccakUtf8("wallet-e2e-deposit"),
+ sourceChainId: 31337,
+ destinationChainId: 8453,
+ token: "0x3333333333333333333333333333333333333333",
+ amount: "500000",
+ flowchainAccount: accountA.address,
+ baseRecipient: "0x4444444444444444444444444444444444444444",
+ requestedAt: "2026-05-14T00:00:00.000Z"
+ });
+
+ const documents = [
+ ["tokenLaunch", tokenLaunch, "2"],
+ ["tokenTransfer", tokenTransfer, "3"],
+ ["poolCreate", poolCreate, "4"],
+ ["addLiquidity", addLiquidity, "5"],
+ ["swap", swap, "6"],
+ ["removeLiquidity", removeLiquidity, "7"],
+ ["withdrawalIntent", withdrawalIntent, "8"]
+ ];
+ const envelopes = {};
+ for (const [name, document, nonce] of documents) {
+ const envelope = await signEnvelope(walletA, accountA.signerKeyId, document, nonce);
+ writeJson(`envelopes/${envelope.txId}.json`, envelope);
+ envelopes[name] = {
+ txId: envelope.txId,
+ payloadType: envelope.payloadType,
+ envelopePath: relativeOut(`envelopes/${envelope.txId}.json`),
+ verification: envelope.verification
+ };
+ }
+
+ const dexSubmit = dispatchJsonRpc({
+ jsonrpc: "2.0",
+ id: 2,
+ method: "transaction_submit",
+ params: {
+ signedEnvelope: readJson(resolve(outDir, envelopes.poolCreate.envelopePath)),
+ submittedBy: "wallet-e2e"
+ }
+ }, { state: controlPlaneState });
+ assert.equal(dexSubmit.result.accepted, true);
+
+ return {
+ tokenId: tokenLaunch.tokenId,
+ poolId: poolCreate.poolId,
+ signedActions: envelopes,
+ submittedDexTxId: dexSubmit.result.txId,
+ bridgeFundedFlow: {
+ fundingSource: "local pilot credit fixture",
+ creditedAccount: accountA.address,
+ withdrawalIntentTxId: envelopes.withdrawalIntent.txId,
+ buyOrSellActionTxId: envelopes.swap.txId
+ }
+ };
+}
+
+async function signEnvelope(vault, signerKeyId, document, nonce) {
+ const envelope = await signWalletDocumentWithVault({
+ vault,
+ password,
+ signerKeyId,
+ document,
+ chainId,
+ nonce,
+ issuedAtUnixMs
+ });
+ const verification = verifyWalletSignedEnvelope({ envelope, context: { chainId, expectedNonce: nonce } });
+ assert.equal(verification.valid, true, document.schema);
+ envelope.verification = verification;
+ return envelope;
+}
+
+function applyTransfer({ envelope, from, to, assetId, amount }) {
+ const before = {
+ from: balanceOf(from, assetId).toString(),
+ to: balanceOf(to, assetId).toString()
+ };
+ assert.ok(balanceOf(from, assetId) >= amount, "insufficient local E2E balance");
+ debit(from, assetId, amount);
+ credit(to, assetId, amount);
+ return {
+ schema: "flowchain.wallet_local_transfer_receipt.v0",
+ txId: envelope.txId,
+ status: "applied",
+ assetId,
+ amount: amount.toString(),
+ from,
+ to,
+ balancesBefore: before,
+ balancesAfter: {
+ from: balanceOf(from, assetId).toString(),
+ to: balanceOf(to, assetId).toString()
+ }
+ };
+}
+
+function publicWalletSummary(account) {
+ return {
+ label: account.label,
+ address: account.address,
+ publicKey: account.publicKey,
+ keyScheme: account.keyScheme,
+ chainId: account.chainId,
+ lastKnownNonce: account.lastKnownNonce
+ };
+}
+
+function credit(account, assetId, amount) {
+ balances.set(balanceKey(account, assetId), balanceOf(account, assetId) + amount);
+}
+
+function debit(account, assetId, amount) {
+ balances.set(balanceKey(account, assetId), balanceOf(account, assetId) - amount);
+}
+
+function balanceOf(account, assetId) {
+ return balances.get(balanceKey(account, assetId)) ?? 0n;
+}
+
+function balanceKey(account, assetId) {
+ return `${account}:${assetId}`;
+}
+
+function assertNoPrivateMaterialInPublicOutputs() {
+ const scanned = [
+ "wallet-a-public-metadata.json",
+ "wallet-b-public-metadata.json",
+ "transfer-receipt.json"
+ ];
+ for (const file of scanned) {
+ assertNoPrivateMaterial(readFileSync(resolve(outDir, file), "utf8"), file);
+ }
+ return {
+ scannedFiles: scanned.length,
+ forbiddenMarkersFound: 0,
+ vaultFilesScanned: false
+ };
+}
+
+function assertNoPrivateMaterial(text, label) {
+ assert.doesNotMatch(text, /"privateKey"\s*:|"ciphertext"\s*:|"authTag"\s*:|"password"\s*:|seedPhrase|mnemonic|BEGIN RSA PRIVATE KEY|BEGIN OPENSSH PRIVATE KEY/i, label);
+ assert.doesNotMatch(text, /https:\/\/hooks\.slack\.com|https:\/\/discord\.com\/api\/webhooks/i, label);
+}
+
+function writeJson(name, value) {
+ const path = resolve(outDir, name);
+ mkdirSync(resolve(path, ".."), { recursive: true });
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
+ return path;
+}
+
+function readJson(path) {
+ return JSON.parse(readFileSync(path, "utf8"));
+}
+
+function relativeOut(name) {
+ return name.replaceAll("\\", "/");
+}
diff --git a/crypto/src/wallet-envelope.js b/crypto/src/wallet-envelope.js
new file mode 100644
index 00000000..7cbdfa5f
--- /dev/null
+++ b/crypto/src/wallet-envelope.js
@@ -0,0 +1,211 @@
+import { ZERO_BYTES32 } from "./constants.js";
+import { canonicalJsonHash, keccakUtf8 } from "./hashes.js";
+import { localTransactionReplayKey, validateLocalTransactionEnvelope } from "./transactions.js";
+import { signLocalTransactionWithVault } from "./wallet.js";
+
+export const WALLET_SIGNED_ENVELOPE_SCHEMA = "flowchain.wallet_signed_envelope.v0";
+export const WALLET_ENVELOPE_VERIFICATION_SCHEMA = "flowchain.wallet_envelope_verification.v0";
+
+export async function signWalletDocumentWithVault({
+ vault,
+ password,
+ signerKeyId,
+ document,
+ chainId,
+ nonce,
+ issuedAtUnixMs = Date.now().toString(),
+ fee = null,
+ expiresAtUnixMs = null
+}) {
+ const localEnvelope = await signLocalTransactionWithVault({
+ vault,
+ password,
+ signerKeyId,
+ document,
+ chainId,
+ nonce,
+ issuedAtUnixMs
+ });
+ const signerAddress = localEnvelope.signerId;
+ const envelope = {
+ schema: WALLET_SIGNED_ENVELOPE_SCHEMA,
+ version: "0",
+ txId: localEnvelope.envelopeId,
+ chainId: String(localEnvelope.chainId),
+ payloadType: document.schema,
+ payload: document,
+ tx: document,
+ signerAddress,
+ signer: {
+ address: signerAddress,
+ signerId: localEnvelope.signerId,
+ signerKeyId: localEnvelope.signerKeyId,
+ signerRole: localEnvelope.signerRole,
+ publicKey: localEnvelope.publicKey,
+ publicKeyReference: localEnvelope.signerKeyId,
+ keyScheme: "secp256k1"
+ },
+ nonce: String(localEnvelope.nonce),
+ fee: fee ?? {
+ supported: false,
+ amount: "0",
+ assetId: ZERO_BYTES32
+ },
+ validity: {
+ issuedAtUnixMs: String(localEnvelope.issuedAtUnixMs),
+ expiresAtUnixMs,
+ expirationSupported: expiresAtUnixMs !== null
+ },
+ signature: localEnvelope.signature,
+ localEnvelope
+ };
+ envelope.verification = verifyWalletSignedEnvelope({ envelope, context: { chainId, expectedNonce: nonce } });
+ return envelope;
+}
+
+export function verifyWalletSignedEnvelope({ envelope, context = {} }) {
+ const errors = new Set();
+ if (!envelope || typeof envelope !== "object") {
+ return verificationOutput({ errors: ["missing-envelope"] });
+ }
+
+ if (envelope.schema === "flowchain.local_transaction_envelope.v0") {
+ return verifyLocalOnlyEnvelope({ envelope, context });
+ }
+
+ if (envelope.schema !== WALLET_SIGNED_ENVELOPE_SCHEMA) {
+ errors.add("wrong-envelope-schema");
+ }
+
+ const document = envelope.payload ?? envelope.tx;
+ const localEnvelope = envelope.localEnvelope;
+ if (!document || typeof document !== "object" || Array.isArray(document)) {
+ errors.add("missing-payload");
+ }
+ if (!localEnvelope || typeof localEnvelope !== "object" || Array.isArray(localEnvelope)) {
+ errors.add("missing-local-envelope");
+ }
+
+ let base = { valid: false, errors: ["missing-local-envelope"] };
+ if (document && localEnvelope) {
+ base = validateLocalTransactionEnvelope({
+ document,
+ envelope: localEnvelope,
+ context: {
+ chainId: context.chainId,
+ expectedNonce: context.expectedNonce,
+ expectedSignerId: context.expectedSignerId,
+ seenNonces: context.seenNonces
+ }
+ });
+ for (const error of base.errors) {
+ errors.add(error);
+ }
+ }
+
+ if (context.expectedPayloadType && envelope.payloadType !== context.expectedPayloadType) {
+ errors.add("wrong-payload-type");
+ }
+ if (context.chainId !== undefined && String(envelope.chainId) !== String(context.chainId)) {
+ errors.add("wrong-chain-id");
+ }
+ if (context.expectedNonce !== undefined && String(envelope.nonce) !== String(context.expectedNonce)) {
+ errors.add("wrong-nonce");
+ }
+ if (context.expectedSignerAddress && envelope.signerAddress !== context.expectedSignerAddress) {
+ errors.add("wrong-signer");
+ }
+ if (document && envelope.payloadType !== document.schema) {
+ errors.add("wrong-payload-type");
+ }
+ if (localEnvelope) {
+ if (envelope.txId !== localEnvelope.envelopeId || envelope.signature !== localEnvelope.signature) {
+ errors.add("bad-envelope-id");
+ }
+ if (
+ envelope.chainId !== String(localEnvelope.chainId) ||
+ envelope.nonce !== String(localEnvelope.nonce) ||
+ envelope.signerAddress !== localEnvelope.signerId
+ ) {
+ errors.add("envelope-metadata-mismatch");
+ }
+ if (envelope.signer?.publicKey !== localEnvelope.publicKey || envelope.signer?.signerKeyId !== localEnvelope.signerKeyId) {
+ errors.add("signer-metadata-mismatch");
+ }
+ }
+ if (envelope.signer?.publicKey && !isPublicKey(envelope.signer.publicKey)) {
+ errors.add("malformed-public-key");
+ }
+
+ const signerDerivedAddress = signerAddressFromPublicKey(envelope.signer?.publicKey ?? localEnvelope?.publicKey);
+ if (signerDerivedAddress && localEnvelope?.signerId && signerDerivedAddress !== localEnvelope.signerId) {
+ errors.add("public-key-mismatch");
+ }
+
+ return verificationOutput({
+ errors: [...errors],
+ signatureValid: base.valid && !base.errors.includes("bad-signature") && !base.errors.includes("missing-signer"),
+ chainIdMatch: !(errors.has("wrong-chain-id") || errors.has("wrong-domain")),
+ signerDerivedAddress,
+ payloadHash: document ? canonicalJsonHash(document) : null,
+ transactionId: envelope.txId ?? localEnvelope?.envelopeId ?? null,
+ replayKey: localEnvelope ? localTransactionReplayKey(localEnvelope) : null
+ });
+}
+
+function verifyLocalOnlyEnvelope({ envelope, context }) {
+ const errors = [];
+ if (!context.document) {
+ errors.push("missing-payload");
+ return verificationOutput({ errors, transactionId: envelope.envelopeId ?? null });
+ }
+ const base = validateLocalTransactionEnvelope({
+ document: context.document,
+ envelope,
+ context
+ });
+ return verificationOutput({
+ errors: base.errors,
+ signatureValid: base.valid && !base.errors.includes("bad-signature") && !base.errors.includes("missing-signer"),
+ chainIdMatch: !base.errors.includes("wrong-chain-id") && !base.errors.includes("wrong-domain"),
+ signerDerivedAddress: signerAddressFromPublicKey(envelope.publicKey),
+ payloadHash: canonicalJsonHash(context.document),
+ transactionId: envelope.envelopeId ?? null,
+ replayKey: localTransactionReplayKey(envelope)
+ });
+}
+
+function verificationOutput({
+ errors,
+ signatureValid = false,
+ chainIdMatch = false,
+ signerDerivedAddress = null,
+ payloadHash = null,
+ transactionId = null,
+ replayKey = null
+}) {
+ const uniqueErrors = [...new Set(errors)];
+ return {
+ schema: WALLET_ENVELOPE_VERIFICATION_SCHEMA,
+ valid: uniqueErrors.length === 0,
+ signatureValid,
+ chainIdMatch,
+ signerDerivedAddress,
+ payloadHash,
+ transactionId,
+ replayKey,
+ rejectionReason: uniqueErrors.length === 0 ? null : uniqueErrors.join(","),
+ errors: uniqueErrors
+ };
+}
+
+function signerAddressFromPublicKey(publicKey) {
+ if (!isPublicKey(publicKey)) {
+ return null;
+ }
+ return keccakUtf8(`flowchain.local-alpha.signer:${publicKey}`);
+}
+
+function isPublicKey(value) {
+ return typeof value === "string" && /^0x([0-9a-fA-F]{66}|[0-9a-fA-F]{130})$/.test(value);
+}
diff --git a/crypto/src/wallet.js b/crypto/src/wallet.js
index fab6971f..fa6a722c 100644
--- a/crypto/src/wallet.js
+++ b/crypto/src/wallet.js
@@ -12,16 +12,21 @@ import { LOCAL_ALPHA_SIGNER_ROLES } from "./constants.js";
const VAULT_SCHEMA = "flowmemory.crypto.local-test-vault.v0";
const VAULT_SECRETS_SCHEMA = "flowmemory.crypto.local-test-vault-secrets.v0";
const PUBLIC_EXPORT_SCHEMA = "flowmemory.crypto.local-test-vault-public-metadata.v0";
+export const LOCAL_WALLET_PUBLIC_METADATA_SCHEMA = "flowchain.local_wallet_public_metadata.v0";
+export const LOCAL_WALLET_KEY_SCHEME = "secp256k1";
+export const DEFAULT_LOCAL_WALLET_CHAIN_ID = "31337";
export function createEncryptedTestVault({
password,
label = "local-operator",
signerRole = "operator",
createdAtUnixMs = Date.now().toString(),
- privateKey
+ privateKey,
+ chainId = DEFAULT_LOCAL_WALLET_CHAIN_ID,
+ lastKnownNonce = "0"
} = {}) {
requirePassword(password);
- const account = createVaultAccount({ label, signerRole, createdAtUnixMs, privateKey });
+ const account = createVaultAccount({ label, signerRole, createdAtUnixMs, privateKey, chainId, lastKnownNonce });
return encryptVaultSecrets({
password,
publicAccounts: [publicAccount(account)],
@@ -59,6 +64,52 @@ export function exportVaultPublicMetadata(vaultOrSession) {
};
}
+export function exportLocalWalletPublicMetadata(vaultOrSession, {
+ updatedAtUnixMs = Date.now().toString()
+} = {}) {
+ return {
+ schema: LOCAL_WALLET_PUBLIC_METADATA_SCHEMA,
+ vaultId: vaultOrSession.vaultId,
+ createdAtUnixMs: vaultOrSession.createdAtUnixMs,
+ updatedAtUnixMs: String(updatedAtUnixMs),
+ accounts: listVaultPublicAccounts(vaultOrSession).map(localWalletPublicAccount),
+ boundary: "Public local wallet metadata only. Signing material, vault encryption payloads, credentials, and webhooks are excluded."
+ };
+}
+
+export function validateLocalWalletPublicMetadata(metadata, { expectedChainId } = {}) {
+ const errors = [];
+ if (!metadata || typeof metadata !== "object") {
+ return verificationResult({ errors: ["metadata-not-object"], metadata });
+ }
+ if (metadata.schema !== LOCAL_WALLET_PUBLIC_METADATA_SCHEMA) {
+ errors.push("wrong-schema");
+ }
+ if (containsSecretMaterial(metadata)) {
+ errors.push("secret-material");
+ }
+ if (!isHex32(metadata.vaultId)) {
+ errors.push("bad-vault-id");
+ }
+ if (!isUintString(metadata.createdAtUnixMs) || !isUintString(metadata.updatedAtUnixMs)) {
+ errors.push("bad-time");
+ }
+ if (!Array.isArray(metadata.accounts) || metadata.accounts.length === 0) {
+ errors.push("missing-accounts");
+ }
+
+ let chainIdMatch = true;
+ for (const account of metadata.accounts ?? []) {
+ const accountErrors = validateLocalWalletPublicAccount(account, { expectedChainId });
+ for (const error of accountErrors.errors) {
+ errors.push(error);
+ }
+ chainIdMatch = chainIdMatch && accountErrors.chainIdMatch;
+ }
+
+ return verificationResult({ errors, metadata, chainIdMatch });
+}
+
export function addEncryptedTestVaultAccount({
vault,
password,
@@ -66,10 +117,20 @@ export function addEncryptedTestVaultAccount({
signerRole = "agent",
createdAtUnixMs = Date.now().toString(),
privateKey,
- signerId
+ signerId,
+ chainId = vault.publicAccounts?.[0]?.chainId ?? DEFAULT_LOCAL_WALLET_CHAIN_ID,
+ lastKnownNonce = "0"
}) {
const secrets = decryptVaultSecrets({ vault, password });
- const account = createVaultAccount({ label, signerRole, createdAtUnixMs, privateKey, signerId });
+ const account = createVaultAccount({
+ label,
+ signerRole,
+ createdAtUnixMs,
+ privateKey,
+ signerId,
+ chainId,
+ lastKnownNonce
+ });
const accounts = [...secrets.accounts, account];
return encryptVaultSecrets({
password,
@@ -89,7 +150,8 @@ export function rotateEncryptedTestVaultAccount({
signerKeyId,
label,
createdAtUnixMs = Date.now().toString(),
- privateKey
+ privateKey,
+ chainId
}) {
const secrets = decryptVaultSecrets({ vault, password });
const index = secrets.accounts.findIndex((account) => account.signerKeyId === signerKeyId);
@@ -104,6 +166,8 @@ export function rotateEncryptedTestVaultAccount({
createdAtUnixMs,
privateKey,
signerId: previous.signerId,
+ chainId: chainId ?? previous.chainId ?? DEFAULT_LOCAL_WALLET_CHAIN_ID,
+ lastKnownNonce: previous.lastKnownNonce ?? "0",
rotatedFromSignerKeyId: previous.signerKeyId
});
const accounts = [...secrets.accounts.slice(0, index), previous, replacement, ...secrets.accounts.slice(index + 1)];
@@ -228,22 +292,42 @@ function createVaultAccount({
createdAtUnixMs,
privateKey = randomPrivateKey(),
signerId,
- rotatedFromSignerKeyId
+ rotatedFromSignerKeyId,
+ chainId = DEFAULT_LOCAL_WALLET_CHAIN_ID,
+ lastKnownNonce = "0"
}) {
if (LOCAL_ALPHA_SIGNER_ROLES[signerRole] === undefined) {
throw new Error(`unsupported signer role: ${signerRole}`);
}
+ if (!isUintString(String(chainId))) {
+ throw new Error(`unsupported wallet chain id: ${chainId}`);
+ }
+ if (!isUintString(String(lastKnownNonce))) {
+ throw new Error(`invalid wallet last known nonce: ${lastKnownNonce}`);
+ }
const publicKey = publicKeyFromPrivateKey(privateKey);
const publicKeyHash = keccakUtf8(publicKey);
+ const derivedSignerId = keccakUtf8(`flowchain.local-alpha.signer:${publicKey}`);
+ const effectiveSignerId = signerId ?? derivedSignerId;
+ if (!isHex32(effectiveSignerId)) {
+ throw new Error(`invalid signer id: ${effectiveSignerId}`);
+ }
const account = {
label,
+ accountId: effectiveSignerId,
+ address: effectiveSignerId,
signerRole,
signerRoleCode: LOCAL_ALPHA_SIGNER_ROLES[signerRole],
- signerId: signerId ?? keccakUtf8(`flowchain.local-alpha.signer:${publicKey}`),
+ signerId: effectiveSignerId,
signerKeyId: keccakUtf8(`flowchain.local-alpha.signer-key:${publicKey}`),
publicKey,
publicKeyHash,
+ keyScheme: LOCAL_WALLET_KEY_SCHEME,
+ chainId: String(chainId),
+ lastKnownNonce: String(lastKnownNonce),
+ nextNonce: nextNonce(lastKnownNonce),
createdAtUnixMs,
+ status: "active",
active: true,
privateKey
};
@@ -261,6 +345,28 @@ function publicAccount(account) {
return metadata;
}
+function localWalletPublicAccount(account) {
+ return {
+ accountId: account.accountId ?? account.signerId,
+ address: account.address ?? account.signerId,
+ signerId: account.signerId,
+ signerKeyId: account.signerKeyId,
+ signerRole: account.signerRole,
+ signerRoleCode: account.signerRoleCode,
+ publicKey: account.publicKey,
+ publicKeyHash: account.publicKeyHash,
+ keyScheme: account.keyScheme ?? LOCAL_WALLET_KEY_SCHEME,
+ label: account.label,
+ status: account.active === false ? "rotated" : (account.status ?? "active"),
+ active: account.active !== false,
+ createdAtUnixMs: account.createdAtUnixMs,
+ chainId: String(account.chainId ?? DEFAULT_LOCAL_WALLET_CHAIN_ID),
+ lastKnownNonce: String(account.lastKnownNonce ?? "0"),
+ nextNonce: String(account.nextNonce ?? nextNonce(account.lastKnownNonce ?? "0")),
+ rotatedFromSignerKeyId: account.rotatedFromSignerKeyId
+ };
+}
+
function randomPrivateKey() {
for (;;) {
const candidate = bytesToHex(randomBytes(32));
@@ -282,3 +388,73 @@ function requirePassword(password) {
throw new Error("local test vault password must be at least 8 characters");
}
}
+
+function validateLocalWalletPublicAccount(account, { expectedChainId } = {}) {
+ const errors = [];
+ let chainIdMatch = true;
+ if (!account || typeof account !== "object") {
+ return { errors: ["bad-account"], chainIdMatch: false };
+ }
+ if (!isHex32(account.accountId) || !isHex32(account.address) || !isHex32(account.signerId) || !isHex32(account.signerKeyId)) {
+ errors.push("bad-account-id");
+ }
+ if (account.accountId !== account.signerId || account.address !== account.signerId) {
+ errors.push("address-mismatch");
+ }
+ if (account.keyScheme !== LOCAL_WALLET_KEY_SCHEME) {
+ errors.push("bad-key-scheme");
+ }
+ if (LOCAL_ALPHA_SIGNER_ROLES[account.signerRole] !== account.signerRoleCode) {
+ errors.push("bad-signer-role");
+ }
+ if (!isPublicKey(account.publicKey)) {
+ errors.push("malformed-public-key");
+ } else {
+ const signerId = keccakUtf8(`flowchain.local-alpha.signer:${account.publicKey}`);
+ const signerKeyId = keccakUtf8(`flowchain.local-alpha.signer-key:${account.publicKey}`);
+ if (account.signerId !== signerId || account.signerKeyId !== signerKeyId) {
+ errors.push("public-key-mismatch");
+ }
+ }
+ if (!isUintString(account.createdAtUnixMs) || !isUintString(account.chainId) || !isUintString(account.lastKnownNonce) || !isUintString(account.nextNonce)) {
+ errors.push("bad-account-metadata");
+ }
+ if (expectedChainId !== undefined && String(account.chainId) !== String(expectedChainId)) {
+ chainIdMatch = false;
+ errors.push("wrong-chain-id");
+ }
+ return { errors, chainIdMatch };
+}
+
+function verificationResult({ errors, metadata, chainIdMatch = errors.length === 0 }) {
+ return {
+ schema: "flowchain.local_wallet_public_metadata_verification.v0",
+ valid: errors.length === 0,
+ secretFree: !containsSecretMaterial(metadata),
+ chainIdMatch,
+ accountCount: Array.isArray(metadata?.accounts) ? metadata.accounts.length : 0,
+ errors: [...new Set(errors)]
+ };
+}
+
+function containsSecretMaterial(value) {
+ const serialized = JSON.stringify(value);
+ return /"(privateKey|private_key|seedPhrase|mnemonic|ciphertext|authTag|password|rpcUrl|rpc_url|apiKey|api_key|webhookUrl|webhook_url)"\s*:/i.test(serialized) ||
+ /https:\/\/hooks\.slack\.com|https:\/\/discord\.com\/api\/webhooks|BEGIN RSA PRIVATE KEY|BEGIN OPENSSH PRIVATE KEY/i.test(serialized);
+}
+
+function isHex32(value) {
+ return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
+}
+
+function isPublicKey(value) {
+ return typeof value === "string" && /^0x([0-9a-fA-F]{66}|[0-9a-fA-F]{130})$/.test(value);
+}
+
+function isUintString(value) {
+ return typeof value === "string" && /^[0-9]+$/.test(value);
+}
+
+function nextNonce(value) {
+ return (BigInt(value) + 1n).toString();
+}
diff --git a/docs/CURRENT_STATE.md b/docs/CURRENT_STATE.md
index c11020e9..c6965a7e 100644
--- a/docs/CURRENT_STATE.md
+++ b/docs/CURRENT_STATE.md
@@ -1,6 +1,6 @@
# Current State
-Last updated: 2026-05-13
+Last updated: 2026-05-17
This file is the beginner-friendly source of truth for what exists in FlowMemory right now. It should stay factual, dated, and conservative. GitHub issues, pull requests, and merged files remain the final project record.
@@ -75,7 +75,61 @@ Indexer/verifier local package:
- `npm run verify:base-canary:sources` produces a dry-run source verification plan for all canary contracts and writes `fixtures/deployments/base-canary-source-verification-plan.json`; `npm run verify:base-canary:sources:submit` submits after `BASESCAN_API_KEY` is configured.
- All 10 deployed Base canary contracts are verified on BaseScan. `FlowMemoryHookAdapter` was verified against deployment-source commit `11d562c` because `main` now contains the newer v4-shaped callback path.
- `npm run deploy:base-sepolia:plan` writes a non-secret Base Sepolia rehearsal plan artifact. `npm run deploy:base-sepolia` and `npm run deploy:base-sepolia:broadcast` provide Foundry dry-run/broadcast commands for the current V0 Base Sepolia testnet contract set. They require local env values and do not commit credentials.
+- `npm run hook:base-sepolia:plan`, `npm run hook:base-sepolia:env-check`,
+ `npm run hook:base-sepolia:evidence`,
+ `npm run hook:base-sepolia:require-live-proof`,
+ `npm run hook:base-sepolia:acceptance`,
+ `npm run hook:base-sepolia:check`, `npm run hook:base-sepolia:dry-run`,
+ `npm run hook:base-sepolia:swap-proof:dry-run`,
+ `npm run hook:base-sepolia:readback-range`,
+ `npm run hook:base-sepolia:readback:auto`, and
+ `npm run hook:base-sepolia:flowmemory` now provide a Base Sepolia Uniswap v4
+ hook proof path for `FlowMemoryAfterSwapHook`.
+ `npm run hook:base-sepolia:env-check` writes a non-secret local readiness
+ artifact for RPC presence, Base Sepolia chain id, deployer key format,
+ deployer address, and test ETH balance before any public testnet broadcast.
+ `npm run hook:base-sepolia:readback-range` writes a non-secret post-broadcast
+ readback range plan from explicit operator block numbers or, after broadcast,
+ from successful receipt block numbers in the swap-proof artifact. It refuses
+ dry-run artifacts as live readback evidence. The auto-readback command runs
+ readback from that inferred broadcast receipt range. The manual readback
+ command wraps the Base Sepolia indexer for the hook address and fails by
+ default unless the selected block range contains at least one hook FlowPulse
+ observation. The current mined hook address is
+ `0xD24d7f807cb00D28DdF675E55879547d4F7B0040`, with afterSwap-only low bits
+ `0x0040`, salt
+ `0x0000000000000000000000000000000000000000000000004915000000000000`,
+ and PoolManager constructor arg
+ `0x05E73354cFDd6745C338b50BcFDfA3Aa6fA03408`. Live code checks against
+ Uniswap's Base Sepolia PoolManager, router, periphery, test helper, and
+ Permit2 addresses pass. A full PoolManager swap proof has passed dry-run
+ simulation. `npm run hook:base-sepolia:evidence` currently reports
+ `stage: dry-run-proof-ready` and `liveProofComplete: false`; the strict
+ `npm run hook:base-sepolia:require-live-proof -- --json` gate correctly fails
+ until the real funded Base Sepolia broadcast and non-empty indexer readback
+ are complete. `npm run hook:base-sepolia:flowmemory` now converts completed
+ readback/evidence artifacts into Flow Memory, Rootflow, and dashboard JSON;
+ it fails by default until live proof is complete, while
+ `npm run hook:base-sepolia:flowmemory -- --allow-incomplete` writes a
+ diagnostic, non-production dashboard artifact that shows the missing proof
+ pieces. The strict generator also rejects weak readbacks: counts must agree
+ across evidence, readback, checkpoint, and indexer state; observations must be
+ successful `IFlowPulse.FlowPulse` logs from the planned Base Sepolia hook; and
+ at least one non-duplicate, non-reorged observation must be pulse type `4`
+ (`SWAP_MEMORY_SIGNAL`). `npm run hook:base-sepolia:acceptance` writes the
+ final non-secret acceptance package and exits nonzero until env-check,
+ official live code, deployed hook code, broadcast receipts, PoolManager
+ initialize/liquidity/swap actions, successful receipts for those actions,
+ planned hook runtime-bytecode identity, readback range, range/readback
+ artifact agreement, non-empty readback, consolidated evidence, generated Flow
+ Memory objects, FlowPulse observation integrity, successful
+ `SWAP_MEMORY_SIGNAL` evidence, Flow Memory signal linkage to successful
+ broadcast receipt transactions, and count agreement all pass.
- `docs/DEPLOYMENTS/BASE_SEPOLIA_REHEARSAL.md` defines the dry-run, optional broadcast, write smoke, readback, source-verification, and rollback rehearsal path.
+- `docs/DEPLOYMENTS/BASE_SEPOLIA_V4_HOOK_PROOF.md` defines the exact live
+ proof runbook for broadcasting the mined hook, initializing a v4 pool, adding
+ tiny test liquidity, executing a real Base Sepolia swap, reading FlowPulse
+ logs, and generating Flow Memory / Rootflow evidence.
- A Base mainnet V0 canary deployment exists for testing only; deployed addresses and smoke transactions are recorded in `docs/DEPLOYMENTS/2026-05-13-base-canary-v0.md`.
Dashboard V0:
@@ -141,7 +195,8 @@ FlowChain private/local testnet snapshot:
Windows-first root wrapper commands for prerequisite checks, init, bounded
start/stop, demo, smoke, full smoke, export/import, and workbench dev mode.
- In flight: optional hardware operator signal fixtures, Base Sepolia live
- rehearsal execution against a funded testnet deployer, and advanced L1
+ rehearsal execution against a funded testnet deployer, the real Base Sepolia
+ Uniswap v4 PoolManager hook swap proof and indexer readback, and advanced L1
research gates.
- Missing: long-running multi-process node behavior, LAN peer mode, encrypted
local operator vault, and second-computer smoke evidence for the latest
@@ -157,6 +212,9 @@ FlowChain private/local testnet snapshot:
- Production ownership, upgrade, governance, fee, token, or incentive mechanics.
- Dynamic fees or tokenomics.
- Production Uniswap v4 hook deployment.
+- Live deployed Uniswap v4 pool proof using the mined Base Sepolia hook address
+ and a real Base Sepolia PoolManager swap. The tooling and dry-run proof path
+ exist; the funded public-testnet broadcast/readback is still pending.
- Production Rootflow runtime implementation.
- Production Flow Memory runtime implementation.
- Hosted launch-core services.
@@ -230,7 +288,18 @@ Before assigning agents, check for dirty worktrees and avoid overlapping folders
3. Use `npm run flowchain:full-smoke` as the private/local package acceptance gate before claiming a branch is demo-ready.
4. Keep improving the missing subsystem pieces behind the wrappers: long-running runtime behavior, LAN peer mode, encrypted local operator vault, workbench polish, and Base Sepolia hook deployment planning.
5. Keep the guarded Base canary reader and dashboard canary artifacts fresh when canary smoke actions change.
-6. Exercise the Base Sepolia deploy/read path on explicit testnet contract addresses only.
+6. Complete the Base Sepolia v4 hook proof with a funded testnet key: broadcast
+ after `npm run hook:base-sepolia:env-check -- --json` reports
+ `broadcastReady: true`, then run
+ `npm run hook:base-sepolia:swap-proof:broadcast -- --json`, run
+ `npm run hook:base-sepolia:readback-range -- --infer-readback-range --json`,
+ run `npm run hook:base-sepolia:readback:auto -- --json` or the explicit
+ `npm run hook:base-sepolia:readback -- --rpc-url $env:BASE_SEPOLIA_RPC_URL --from-block --to-block --finalized-block --json`,
+ require `npm run hook:base-sepolia:require-live-proof -- --json` to pass, and
+ run `npm run hook:base-sepolia:flowmemory` to generate Flow Memory /
+ Rootflow dashboard evidence from the live observation, then require
+ `npm run hook:base-sepolia:acceptance -- --json` to pass before calling the
+ Base Sepolia hook proof accepted.
7. Continue contracts hardening without production mainnet deployment or token mechanics.
8. Keep dashboard work fixture-backed until a production API is explicitly scoped.
9. Keep production mainnet, public validator, tokenomics, audited-cryptography, and production bridge claims out of scope.
diff --git a/docs/DASHBOARD_MVP.md b/docs/DASHBOARD_MVP.md
index 0e006731..c77cf225 100644
--- a/docs/DASHBOARD_MVP.md
+++ b/docs/DASHBOARD_MVP.md
@@ -9,7 +9,7 @@ The MVP covers local inspection of:
- Local control-plane health and state from `http://127.0.0.1:8787/health`, `/state`, and `/rpc`
- Node status, peers, mempool, accounts, local balances, faucet events, public wallet references, and setup status
- Product Testnet V1 wallet/account public state, local/test token launch records, token balances, DEX pools, liquidity positions, swaps, and unified explorer rollups
-- Real-value pilot status for capped owner testing: Base deposit observation, local credit, replay/retry, withdrawal intent, release evidence, caps, pause, emergency state, and exact next operator command
+- Real-value pilot status for capped owner testing: Base 8453 live readiness, missing env names without values, Base deposit observation, exact local credit, wallet transferability, withdrawal/release evidence, caps, pause, emergency state, and exact next operator command
- FlowPulse observations from indexer-style receipt/log data
- Rootfield registry state
- Work lanes and work receipts
@@ -72,6 +72,10 @@ When `npm run control-plane:serve` is running, the workbench probes:
GET http://127.0.0.1:8787/health
GET http://127.0.0.1:8787/state
GET http://127.0.0.1:8787/pilot/status
+GET http://127.0.0.1:8787/bridge/live-readiness
+GET http://127.0.0.1:8787/pilot/lifecycle
+GET http://127.0.0.1:8787/wallets/balances
+GET http://127.0.0.1:8787/wallets/transfers
POST http://127.0.0.1:8787/rpc
```
@@ -93,6 +97,20 @@ keys, mnemonics, seed phrases, RPC credentials, API keys, or webhooks to
localStorage/sessionStorage; it only consumes browser-safe control-plane
responses and fixture data.
+The first screen also includes a `Bridge live readiness` panel. It shows
+`BLOCKED`, `FAILED`, or `READY_FOR_OPERATOR_LIVE_PILOT`, Base chain ID `8453`,
+whether the local node is running, whether the lockbox is configured, whether
+confirmation depth is configured, and missing env names. It never renders env
+values. Mock/local artifacts remain labeled as local or mock; the live panel
+must not present mock data as real.
+
+The `Real-Value Pilot` workbench table includes lifecycle records from
+`/pilot/lifecycle`, exact value equality for deposit, observed, credited, wallet
+delta, transferable, withdrawal, and release amounts, wallet balances from
+`/wallets/balances`, and transfer history from `/wallets/transfers`. The global
+search field can filter visible records by Base tx hash, credit id, wallet
+address, and status.
+
## Non-Goals
- No backend service required for V0
diff --git a/docs/DECISIONS/2026-05-13-flowchain-private-local-protocol-contract.md b/docs/DECISIONS/2026-05-13-flowchain-private-local-protocol-contract.md
new file mode 100644
index 00000000..d01cf957
--- /dev/null
+++ b/docs/DECISIONS/2026-05-13-flowchain-private-local-protocol-contract.md
@@ -0,0 +1,45 @@
+# FlowChain Private/Local Protocol Contract
+
+Date: 2026-05-13
+
+## Status
+
+Accepted for the private/local testnet package.
+
+## Context
+
+Runtime, crypto, RPC, wallet, bridge, and dashboard agents need one source of truth for profile IDs, genesis, accounts, transaction envelopes, payloads, blocks, receipts, events, bridge evidence, state roots, finality, and export snapshots.
+
+The existing Rust devnet and control-plane surfaces already model many of these concepts, but they did not have one schema-backed fixture package that every downstream agent could validate.
+
+## Decision
+
+Add private/local FlowChain protocol schemas under `schemas/flowmemory/production-*.schema.json` and deterministic fixtures under `fixtures/production-l1/`.
+
+The canonical profile IDs are:
+
+- `flowchain-local-private`
+- `flowchain-local-multinode`
+- `flowchain-base8453-pilot`
+
+The old phrases `flowchain-local` and `flowchain-private-lan` are aliases only. They are not valid signing-domain profile IDs.
+
+The Base pilot profile is local/private on the destination side and uses Base chain ID `8453` only as the source evidence chain.
+
+## Consequences
+
+- Downstream agents can run `npm run validate:production-l1-protocol` and `npm run validate:production-l1-fixtures`.
+- The fixture package contains one valid transaction for every payload type and one invalid transaction for every payload type.
+- Bridge duplicate-source-event rejection has a stable error code.
+- State roots and genesis hashes are deterministic and recomputed by validation.
+
+## Scope Boundaries
+
+This decision does not authorize public mainnet readiness, public validators, tokenomics, value-bearing bridge readiness, audited cryptography, public RPC, or hosted services.
+
+## Follow-Ups
+
+- Runtime agent: implement typed state transition checks against the payload catalog.
+- Wallet/crypto agent: replace fixture-only signatures with the accepted local signing helper.
+- Bridge agent: preserve source event duplicate keys from bounded Base readers.
+- RPC/dashboard agents: display structured receipt failure reasons and finality states.
diff --git a/docs/DECISIONS/2026-05-13-uniswap-v4-shaped-hook-path.md b/docs/DECISIONS/2026-05-13-uniswap-v4-shaped-hook-path.md
index 75995e1c..104f81aa 100644
--- a/docs/DECISIONS/2026-05-13-uniswap-v4-shaped-hook-path.md
+++ b/docs/DECISIONS/2026-05-13-uniswap-v4-shaped-hook-path.md
@@ -67,7 +67,7 @@ to satisfy those hook address flags.
Base Sepolia is the next allowed live-network path for this hook work:
- chain id: `84532`;
-- PoolManager: `0x9a13F98Cb987694C9F086b1F5eB990EeA8264Ec3`;
+- PoolManager: `0x05E73354cFDd6745C338b50BcFDfA3Aa6fA03408`;
- CREATE2 deployer: `0x4e59b44847b379578588920cA78FbF26c0B4956C`;
- target hook address bits: exactly `0x40` under Uniswap v4's low-bit hook
mask;
diff --git a/docs/DEPLOYMENTS/BASE_SEPOLIA_V4_HOOK_PROOF.md b/docs/DEPLOYMENTS/BASE_SEPOLIA_V4_HOOK_PROOF.md
new file mode 100644
index 00000000..b8174bd8
--- /dev/null
+++ b/docs/DEPLOYMENTS/BASE_SEPOLIA_V4_HOOK_PROOF.md
@@ -0,0 +1,411 @@
+# Base Sepolia Uniswap V4 Hook Proof
+
+Status: broadcast-capable public testnet proof tooling. The hook and swap proof
+have passed local/fork dry runs, but this shell is not broadcast-ready until
+`hook:base-sepolia:env-check` reports `broadcastReady: true`. A funded Base
+Sepolia broadcast and indexer readback have not been completed.
+
+This is not a production mainnet deployment guide, production hook approval,
+token launch guide, custody guide, bridge guide, or production L1 readiness
+claim.
+
+## What This Proves
+
+This proof path is designed to answer one launch-critical question:
+
+Can a real Uniswap v4 PoolManager swap call the FlowMemory afterSwap hook and
+emit a FlowPulse that the indexer can later turn into a Flow Memory signal?
+
+The current repo can now:
+
+1. confirm the official Uniswap v4 Base Sepolia contract addresses have code;
+2. mine a CREATE2 hook address with the required afterSwap-only hook flag;
+3. compute the expected runtime bytecode hash for the compiled
+ `FlowMemoryAfterSwapHook` artifact with the Base Sepolia PoolManager
+ immutable patched in;
+4. dry-run deployment of `FlowMemoryAfterSwapHook` at the mined address;
+5. dry-run a full public-testnet proof flow with throwaway tokens, pool
+ initialization, liquidity, and a tiny swap through PoolManager test helpers;
+6. write non-secret proof-plan, env-readiness, and dry-run artifacts;
+7. check whether the local shell has a Base Sepolia RPC, deployer key, matching
+ chain id, and enough test ETH for the swap-proof estimate without writing
+ secrets;
+8. write a non-secret evidence artifact that distinguishes dry-run-ready,
+ broadcast-awaiting-readback, and live-proof-complete states;
+9. plan the post-broadcast readback range explicitly or infer it from a
+ broadcast swap-proof artifact with successful receipts;
+10. read back the deployed hook address over the selected Base Sepolia block
+ range and fail the proof if no FlowPulse observations are found;
+11. generate Flow Memory / Rootflow / dashboard evidence from the readback
+ artifacts, with a strict gate that fails until the live proof is complete;
+12. write a final acceptance package that fails unless env-check, broadcast
+ receipts, selected readback range, actual readback artifact, evidence,
+ Flow Memory objects, and counts all agree.
+
+The current repo has not yet:
+
+1. broadcast the hook deployment to Base Sepolia with a funded testnet signer;
+2. broadcast the actual PoolManager pool initialize/liquidity/swap proof;
+3. read the resulting hook FlowPulse logs back through the Base Sepolia indexer;
+4. generate complete dashboard evidence from that live testnet observation.
+
+## Official Network Inputs
+
+Uniswap's deployment page lists the current v4 Base Sepolia addresses used by
+this runbook:
+
+- PoolManager: `0x05E73354cFDd6745C338b50BcFDfA3Aa6fA03408`
+- Universal Router: `0x492e6456D9528771018DeB9E87ef7750Ef184104`
+- PositionManager: `0x4B2C77d209D3405F41a037Ec6c77F7F5b8e2ca80`
+- StateView: `0x571291b572ed32ce6751A2Cb2486EbEe8DEfB9B4`
+- Quoter: `0x4A6513c898fe1B2d0E78d3b0e0A4a151589B1cBa`
+- PoolSwapTest: `0x8B5bcC363ddE2614281aD875bad385E0A785D3B9`
+- PoolModifyLiquidityTest: `0x37429cd17cb1454c34e7f50b09725202fd533039`
+- Permit2: `0x000000000022D473030F116dDEE9F6B43aC78BA3`
+
+Source: `https://developers.uniswap.org/docs/protocols/v4/deployments`.
+
+Uniswap v4 core uses the least significant 14 bits of the hook address to
+decide which callbacks are active. The afterSwap flag is `1 << 6`, so the
+FlowMemory proof hook must have low bits `0x0040` and no extra hook flags.
+
+Source: `https://raw.githubusercontent.com/Uniswap/v4-core/main/src/libraries/Hooks.sol`.
+
+## Planned Hook Address
+
+The deterministic hook plan currently generated by the repo is:
+
+| Field | Value |
+| --- | --- |
+| Chain | Base Sepolia, `84532` |
+| Hook contract | `FlowMemoryAfterSwapHook` |
+| PoolManager constructor arg | `0x05E73354cFDd6745C338b50BcFDfA3Aa6fA03408` |
+| CREATE2 deployer | `0x4e59b44847b379578588920cA78FbF26c0B4956C` |
+| Init code hash | `0x2734b4f6b4f6932249d4d98240147f02cf2ba548fe1ade4a7d63d9dd0a8b9fef` |
+| Salt | `0x0000000000000000000000000000000000000000000000004915000000000000` |
+| Planned hook address | `0xD24d7f807cb00D28DdF675E55879547d4F7B0040` |
+| Required low bits | `0x0040` |
+| Active hook permissions | afterSwap only |
+| Custody, dynamic fee, return delta | disabled |
+
+Generated plan artifact:
+
+```text
+fixtures/deployments/base-sepolia-v4-hook-proof-plan.json
+```
+
+Latest dry-run artifact:
+
+```text
+fixtures/deployments/base-sepolia-v4-hook-proof.latest.json
+```
+
+## Environment
+
+Use shell variables or an ignored `.env` loader only. Do not paste real private
+keys, RPC URLs, API keys, seed phrases, or webhook URLs into docs, GitHub,
+screenshots, generated artifacts, or chat.
+
+Required for dry-run or broadcast:
+
+```powershell
+$env:BASE_SEPOLIA_RPC_URL=""
+$env:BASE_SEPOLIA_DEPLOYER_KEY_HEX="<0x-prefixed-32-byte-testnet-key>"
+```
+
+Optional:
+
+```powershell
+$env:BASE_SEPOLIA_BASESCAN_API_KEY=""
+$env:BASESCAN_API_KEY=""
+$env:BASE_SEPOLIA_V4_HOOK_READBACK_FROM_BLOCK=""
+$env:BASE_SEPOLIA_V4_HOOK_READBACK_TO_BLOCK=""
+$env:BASE_SEPOLIA_V4_HOOK_READBACK_FINALIZED_BLOCK=""
+$env:FLOWMEMORY_HOOK_PROOF_TOKEN_MINT="1000000000000000000000"
+$env:FLOWMEMORY_HOOK_PROOF_LIQUIDITY_DELTA="1000000000000000000"
+$env:FLOWMEMORY_HOOK_PROOF_SWAP_AMOUNT="10000000000000000"
+```
+
+`FLOWMEMORY_HOOK_PROOF_OPERATOR` is optional. If omitted, the runner derives it
+from `BASE_SEPOLIA_DEPLOYER_KEY_HEX`. If provided, it must match the deployer
+signer because the proof tokens are minted, approved, and swapped by that same
+testnet signer.
+
+## Commands
+
+Plan the hook address and write a non-secret artifact:
+
+```powershell
+npm run hook:base-sepolia:plan -- --json
+```
+
+Check current live code at Base Sepolia official Uniswap v4 addresses and the
+planned hook address:
+
+```powershell
+npm run hook:base-sepolia:check -- --json
+```
+
+Check whether the local shell is actually ready to broadcast. This writes a
+non-secret env-check artifact and should show `broadcastReady: true` before the
+public testnet proof is sent:
+
+```powershell
+npm run hook:base-sepolia:env-check -- --json
+```
+
+Default env-check output:
+
+```text
+fixtures/deployments/base-sepolia-v4-hook-env-check.latest.json
+```
+
+Dry-run only the hook deployment:
+
+```powershell
+npm run hook:base-sepolia:dry-run -- --json
+```
+
+Dry-run the full PoolManager proof path:
+
+```powershell
+npm run hook:base-sepolia:swap-proof:dry-run -- --json
+```
+
+Broadcast the full proof only after the testnet key is funded and the operator
+accepts public testnet gas spend:
+
+```powershell
+npm run hook:base-sepolia:swap-proof:broadcast -- --json
+```
+
+After broadcast, first generate the readback range plan. The explicit form is
+useful when the operator already knows the deploy/swap block window:
+
+```powershell
+npm run hook:base-sepolia:readback-range -- --from-block --to-block --finalized-block --json
+```
+
+The automatic form derives the readback window from the successful receipt block
+numbers in the latest broadcast swap-proof artifact. It intentionally refuses
+dry-run artifacts:
+
+```powershell
+npm run hook:base-sepolia:readback-range -- --infer-readback-range --json
+```
+
+To run the readback directly from the inferred broadcast receipt range:
+
+```powershell
+npm run hook:base-sepolia:readback:auto -- --json
+```
+
+Then use the proof runner readback command if you need to pass an explicit
+range. This wraps the existing Base Sepolia indexer, writes non-secret
+state/checkpoint JSON, and fails by default if the selected block range contains
+zero FlowPulse observations from the hook:
+
+```powershell
+npm run hook:base-sepolia:readback -- --rpc-url $env:BASE_SEPOLIA_RPC_URL --from-block --to-block --finalized-block --json
+```
+
+Default readback outputs:
+
+```text
+fixtures/deployments/base-sepolia-v4-hook-readback-range.latest.json
+fixtures/deployments/base-sepolia-v4-hook-readback-state.latest.json
+fixtures/deployments/base-sepolia-v4-hook-readback-checkpoint.latest.json
+fixtures/deployments/base-sepolia-v4-hook-readback.latest.json
+```
+
+For diagnostics only, an operator can allow an empty readback artifact:
+
+```powershell
+npm run hook:base-sepolia:readback -- --rpc-url $env:BASE_SEPOLIA_RPC_URL --from-block --to-block --allow-empty-readback --json
+```
+
+The underlying direct indexer command remains:
+
+```powershell
+npm run index:base-sepolia -- --rpc-url $env:BASE_SEPOLIA_RPC_URL --address 0xD24d7f807cb00D28DdF675E55879547d4F7B0040 --from-block --to-block --finalized-block
+```
+
+After env-check, dry-run or broadcast, and readback, write the consolidated
+evidence artifact:
+
+```powershell
+npm run hook:base-sepolia:evidence -- --json
+```
+
+Default evidence output:
+
+```text
+fixtures/deployments/base-sepolia-v4-hook-evidence.latest.json
+```
+
+For a strict gate that must fail until the real public-testnet proof is complete:
+
+```powershell
+npm run hook:base-sepolia:require-live-proof -- --json
+```
+
+Then regenerate launch/dashboard state from the live observation path selected
+by the indexer worktree:
+
+```powershell
+npm run hook:base-sepolia:flowmemory
+```
+
+Strict Flow Memory generation fails until the live proof is complete. For
+operator diagnostics only, the same command can write an incomplete,
+non-production dashboard artifact that makes the missing deploy/broadcast/read
+steps visible:
+
+```powershell
+npm run hook:base-sepolia:flowmemory -- --allow-incomplete
+```
+
+Default Flow Memory / dashboard outputs:
+
+```text
+fixtures/deployments/base-sepolia-v4-hook-flowmemory.latest.json
+fixtures/dashboard/flowmemory-dashboard-base-sepolia-v4-hook.json
+apps/dashboard/public/data/flowmemory-dashboard-base-sepolia-v4-hook.json
+```
+
+Then run the final acceptance package. This is the last proof gate; without
+`--allow-incomplete` it exits nonzero until every live proof check passes:
+
+```powershell
+npm run hook:base-sepolia:acceptance -- --json
+```
+
+For diagnostics only, an incomplete package can be written without passing the
+gate:
+
+```powershell
+npm run hook:base-sepolia:acceptance -- --allow-incomplete --json
+```
+
+Default acceptance output:
+
+```text
+fixtures/deployments/base-sepolia-v4-hook-acceptance.latest.json
+```
+
+`require-live-proof` must fail until all of these are true:
+
+- official Base Sepolia Uniswap v4 contract code is present;
+- the mined FlowMemory hook address has deployed code;
+- the deployed mined hook runtime bytecode hash matches the expected compiled
+ `FlowMemoryAfterSwapHook` runtime hash with the Base Sepolia PoolManager
+ immutable constructor argument patched in;
+- a broadcast swap-proof artifact with receipts exists;
+- the readback range comes from operator-reviewed block numbers or successful
+ broadcast receipt block numbers;
+- the readback artifact uses the same source, hook address, `fromBlock`,
+ `toBlock`, and `finalizedBlock` as the selected readback range artifact;
+- readback has `proofComplete: true`;
+- readback has at least one hook FlowPulse observation.
+
+Strict Flow Memory / Rootflow evidence has an additional acceptance gate:
+
+- `npm run hook:base-sepolia:flowmemory` succeeds without `--allow-incomplete`;
+- evidence, readback, checkpoint, and indexer observation counts agree;
+- every readback observation is an `IFlowPulse.FlowPulse` event from the planned
+ hook address on Base Sepolia;
+- every readback observation has `receiptStatus: "success"`;
+- at least one non-duplicate, non-reorged observation has `pulseType: "4"`,
+ which is the `SWAP_MEMORY_SIGNAL` pulse emitted by the hook after a swap.
+- generated Flow Memory signal transaction hashes are present in the successful
+ broadcast receipt transaction hashes from the swap-proof artifact.
+
+The final acceptance package must have `liveProofAccepted: true`. Its checks
+must pass for env readiness, official Uniswap code, planned hook code,
+broadcast swap-proof receipts, planned hook runtime-bytecode identity,
+PoolManager initialize/liquidity/swap actions, readback range selection,
+successful receipts for those initialize/liquidity/swap actions, readback range
+selection, readback range/readback artifact agreement, non-empty FlowPulse
+readback, consolidated evidence, generated Flow Memory objects, FlowPulse
+observation integrity, successful `SWAP_MEMORY_SIGNAL` evidence, Flow Memory
+signal linkage to successful broadcast receipt transactions, and count
+agreement.
+
+## Dry-Run Evidence
+
+The latest local/fork dry run proved the script can execute the production-shaped
+testnet sequence without broadcasting:
+
+- deploy or reuse `FlowMemoryAfterSwapHook` at
+ `0xD24d7f807cb00D28DdF675E55879547d4F7B0040`;
+- deploy two throwaway public-mint proof tokens;
+- initialize a v4 pool whose `PoolKey.hooks` value is the FlowMemory hook;
+- add tiny test liquidity through `PoolModifyLiquidityTest`;
+- execute a tiny exact-input swap through `PoolSwapTest`;
+- pass `FlowMemorySwapHookData` containing a Rootfield id, commitment,
+ parent pulse id, and URI;
+- produce a proof artifact without writing secrets.
+
+The dry-run artifact is non-secret and intentionally marks
+`productionReady: false`.
+
+The proof runner also records a sanitized Foundry run summary in the latest
+artifact. It includes transaction type, contract name, contract address, public
+sender/target addresses, tx hashes when present, receipt counts, and decoded
+proof return fields such as `token0`, `token1`, `poolId`, `rootfieldId`, and
+`commitment`. It does not write private keys, RPC credentials, raw signed
+transactions, explorer API keys, seed phrases, or webhook URLs.
+
+## Acceptance Checklist
+
+The live proof is accepted only when all of these are recorded in a dated
+deployment note:
+
+- funded testnet deployer address;
+- hook deployment transaction hash and block;
+- actual hook address equals `0xD24d7f807cb00D28DdF675E55879547d4F7B0040`;
+- actual hook runtime bytecode hash equals the plan's
+ `hook.expectedRuntime.runtimeBytecodeHash`;
+- hook source verification status;
+- proof token addresses;
+- pool initialize transaction hash and successful receipt status;
+- liquidity transaction hash and successful receipt status;
+- swap transaction hash and successful receipt status;
+- readback range artifact path and whether the range source is
+ `operator-explicit` or `broadcast-proof-artifact-receipts`;
+- actual readback artifact path and confirmation that its source and block
+ window match the selected readback range artifact;
+- emitted `FlowPulse` transaction hash and log index from the indexer, not from
+ hook execution;
+- at least one emitted `FlowPulse` has `pulseType: "4"` / `SWAP_MEMORY_SIGNAL`,
+ a successful receipt, and no duplicate or reorg lifecycle state;
+- generated Flow Memory signals link back to successful broadcast receipt
+ transaction hashes from the swap-proof artifact;
+- evidence, readback, checkpoint, and indexer observation counts agree;
+- Base Sepolia indexer checkpoint path;
+- generated Flow Memory / Rootflow output path;
+- dashboard evidence path;
+- final acceptance package path;
+- failed or skipped steps, if any.
+
+The readback artifact must have `proofComplete: true` and
+`indexer.observationCount > 0` before the live proof is considered complete.
+The evidence artifact must have `liveProofComplete: true`; otherwise launch
+language must stay at dry-run/tooling readiness only. The Flow Memory evidence
+artifact must also have `liveProofComplete: true`; artifacts generated with
+`--allow-incomplete` are diagnostics, not acceptance evidence. The final
+acceptance package must have `liveProofAccepted: true`; otherwise launch
+language must stay at dry-run/tooling readiness only.
+
+## Boundaries
+
+- This is public testnet evidence only.
+- A deployed hook alone is not enough; the proof requires a PoolManager swap
+ that emits FlowPulse.
+- The hook cannot know `txHash` or `logIndex`; the indexer derives those after
+ receipts and logs exist.
+- The proof uses throwaway testnet tokens unless a reviewed testnet currency
+ path is explicitly accepted.
+- No production mainnet hook, production L1, trustless verifier network,
+ free-storage, AI-on-chain, custody, bridge, or token-launch claim is allowed
+ from this proof.
diff --git a/docs/FLOWCHAIN_CONTROL_PLANE_API.md b/docs/FLOWCHAIN_CONTROL_PLANE_API.md
index d8f2697c..d535d48e 100644
--- a/docs/FLOWCHAIN_CONTROL_PLANE_API.md
+++ b/docs/FLOWCHAIN_CONTROL_PLANE_API.md
@@ -20,6 +20,7 @@ Commands:
npm run control-plane:test
npm run control-plane:demo
npm run control-plane:smoke
+npm run flowchain:rpc:e2e
npm run control-plane:serve
```
@@ -52,6 +53,11 @@ devnet/local/intake/transactions.ndjson
devnet/local/intake/bridge-observations.ndjson
```
+`transaction_submit` can also be asked to forward a valid local devnet transaction
+into the active Rust runtime state with `runtimeSubmit: true` or
+`runtimeSubmitMode: "direct"`. That mode is still local-only, but it proves the
+RPC can drive the same state file that block production reads.
+
All JSON-RPC responses and local intake payloads are scanned for private-key, mnemonic, seed phrase, RPC credential, API key, and webhook-shaped material.
## JSON-RPC Envelope
@@ -125,11 +131,54 @@ GET /health
Browser-safe summary endpoints are also available:
```text
+GET /rpc/discover
+GET /rpc/readiness
GET /explorer/summary
GET /product-flow/status
GET /pilot/status
```
+### `rpc_discover`
+
+Params: none.
+
+Returns the FlowChain-native JSON-RPC method inventory for wallets, explorers,
+relayers, and deployment checks. This method is intentionally not EVM JSON-RPC
+or Solana JSON-RPC compatibility. It reports the supported FlowChain methods,
+their categories, read/write mode, local-only boundary, and current production
+readiness status.
+
+HTTP mirror:
+
+```text
+GET /rpc/discover
+```
+
+### `rpc_readiness`
+
+Params: none.
+
+Returns a fail-closed machine-readable readiness object for the FlowChain RPC.
+It reports whether active runtime state is readable, whether wallet/explorer/
+bridge consumers can use the current RPC, and which public deployment inputs
+are missing. It returns environment variable names only, never values.
+
+Public RPC deployment inputs currently checked by name:
+
+```text
+FLOWCHAIN_RPC_PUBLIC_URL
+FLOWCHAIN_RPC_ALLOWED_ORIGINS
+FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE
+FLOWCHAIN_RPC_TLS_TERMINATED
+FLOWCHAIN_RPC_STATE_BACKUP_PATH
+```
+
+HTTP mirror:
+
+```text
+GET /rpc/readiness
+```
+
### `chain_status`
Params: none.
@@ -245,11 +294,16 @@ Params:
},
"signature": "0x..."
},
- "submittedBy": "local-operator"
+ "submittedBy": "local-operator",
+ "runtimeSubmit": true
}
```
-Accepts signed local test transaction envelopes only. Plain `transaction`, `tx`, or `txs` params are rejected. The method rejects secret-shaped material and appends an intake row to `devnet/local/intake/transactions.ndjson`. It does not broadcast to a public chain.
+Accepts signed local test transaction envelopes only. Plain `transaction`, `tx`,
+or `txs` params are rejected. The method rejects secret-shaped material and
+appends an intake row to `devnet/local/intake/transactions.ndjson`. With
+`runtimeSubmit` enabled, the contained devnet `tx` is also submitted directly to
+the active local Rust runtime state. It does not broadcast to a public chain.
### `mempool_list`
@@ -443,6 +497,79 @@ Returns:
The result includes lifecycle rows for Base deposit observation, local credit application, replay/retry checks, withdrawal intent, release evidence, caps, pause, and emergency state.
+`bridge_live_readiness`
+
+Params: none.
+
+Returns a fail-closed machine-readable readiness object for operator live-pilot
+inspection. It always identifies Base as chain ID `8453`, reports whether the
+lockbox, Base reader endpoint, block range, confirmation depth, cap env, and
+operator acknowledgement are configured, and lists missing env names only. It
+must not return env values.
+
+Important fields:
+
+```json
+{
+ "schema": "flowmemory.control_plane.bridge_live_readiness.v0",
+ "baseChainId": 8453,
+ "failClosedStatus": "BLOCKED",
+ "readyForOperatorLivePilot": false,
+ "missingEnvNames": ["FLOWCHAIN_BASE8453_RPC_URL"],
+ "envValuesPrinted": false,
+ "issues": [
+ { "reasonCode": "missing_env", "status": "blocked" }
+ ]
+}
+```
+
+`failClosedStatus` is one of `BLOCKED`, `FAILED`, or
+`READY_FOR_OPERATOR_LIVE_PILOT`. The dashboard must not present live mode as
+ready until this field is `READY_FOR_OPERATOR_LIVE_PILOT` and the lockbox
+address has been owner-verified outside the browser.
+
+`pilot_lifecycle_record_list`
+
+Returns deposit/credit lifecycle records keyed by Base tx hash, log index,
+credit id, recipient wallet, asset, amount in smallest units, status, and
+evidence path/id.
+
+List params:
+
+```json
+{
+ "baseTxHash": "0x...",
+ "creditId": "0x...",
+ "walletAddress": "0x...",
+ "status": "release_evidence_recorded",
+ "query": "optional free text",
+ "limit": 50
+}
+```
+
+Each record includes exact value equality fields:
+
+```json
+{
+ "depositAmount": "1000000",
+ "observedAmount": "1000000",
+ "creditedAmount": "1000000",
+ "walletDelta": "1000000",
+ "transferableAmount": "1000000",
+ "withdrawalAmount": "1000000",
+ "releaseAmount": "1000000",
+ "allEqual": true
+}
+```
+
+`wallet_balance_list` and `wallet_transfer_history`
+
+These methods expose wallet balances and transfer history needed to prove that
+a credited FlowChain wallet can transfer the credited amount and that recipient
+balances update exactly. They accept the same list filters for
+`walletAddress`, `status`, `query`, and `limit`, and they return
+`localOnly: true`, `productionReady: false`.
+
List methods:
- `pilot_deposit_observation_list`
@@ -465,6 +592,8 @@ Browser-safe HTTP mirrors are also available:
```text
GET /pilot/status
+GET /bridge/live-readiness
+GET /pilot/lifecycle?txHash=0x...&creditId=0x...&walletAddress=0x...&status=...
GET /pilot/deposits?limit=50
GET /pilot/credits?limit=50
GET /pilot/withdrawal-intents?limit=50
@@ -473,9 +602,11 @@ GET /pilot/cap-status
GET /pilot/pause-status
GET /pilot/retry-status
GET /pilot/emergency-status
+GET /wallets/balances?walletAddress=0x...&status=credited
+GET /wallets/transfers?walletAddress=0x...&status=applied
```
-The four HTTP list endpoints accept the same `limit` bound as the JSON-RPC list methods. Invalid limits return the standard JSON-RPC invalid params error envelope as JSON.
+The HTTP list endpoints accept the same `limit` bound and filters as the JSON-RPC list methods. Invalid limits return the standard JSON-RPC invalid params error envelope as JSON.
### `faucet_event_list`
diff --git a/docs/FLOWCHAIN_LIVE_L1_BRIDGE_GO_NO_GO.md b/docs/FLOWCHAIN_LIVE_L1_BRIDGE_GO_NO_GO.md
new file mode 100644
index 00000000..20e8e940
--- /dev/null
+++ b/docs/FLOWCHAIN_LIVE_L1_BRIDGE_GO_NO_GO.md
@@ -0,0 +1,83 @@
+# FlowChain Live L1 Bridge Go/No-Go
+
+Date: 2026-05-14
+
+Final status: `EXTERNAL-BLOCKED ONLY`
+
+## Standard
+
+The go/no-go standard is not softened:
+
+> A user can bridge a small amount of real ETH from Base 8453 into FlowChain,
+> wait less than one minute after confirmation eligibility, see the credit in
+> the running FlowChain L1 node state, and spend/transfer it on FlowChain.
+> Nothing required for that path is mock-only.
+
+## Decision
+
+No-go for the live-funds bridge path.
+
+The code-controlled verification gate exists and runs, but the strict live path
+cannot pass in this environment because owner/operator Base 8453 live inputs are
+not present. No live Base transaction was broadcast. No environment values were
+printed. The running FlowChain node state still has `bridgeCredits: 0`, so PASS
+is not claimed.
+
+Machine evidence:
+
+- `devnet/local/live-l1-bridge-e2e/flowchain-live-l1-bridge-e2e-report.json`
+ reports `EXTERNAL-BLOCKED`.
+- `devnet/local/live-l1-bridge-e2e/flowchain-live-l1-bridge-e2e-summary.md`
+ reports zero credits in main state and unmeasured latency.
+- `devnet/local/bridge-live-readiness/bridge-live-readiness-report.json`
+ reports `blocked` with missing owner env names only.
+- `devnet/local/production-l1-e2e/flowchain-production-l1-e2e-report.json`
+ reports aggregate `passed-with-live-blockers`.
+
+## What Changed
+
+- Added `npm run flowchain:live-l1-bridge:e2e`.
+- Added a read-only Base 8453 transaction diagnostic command:
+ `npm run flowchain:bridge:diagnose:tx`.
+- Added the live L1 bridge gate under
+ `infra/scripts/flowchain-live-l1-bridge-e2e.ps1`.
+- Hardened node startup so the live gate verifies a real running node process
+ without hanging on a long-running wrapper.
+- Updated the control-plane server to reload state per request instead of
+ holding stale startup state.
+- Extended the no-secret scan to cover `devnet/local/live-l1-bridge-e2e`.
+
+## Required Follow-Up
+
+Owner/operator inputs required before PASS can be attempted:
+
+- `FLOWCHAIN_PILOT_OPERATOR_ACK`
+- `FLOWCHAIN_BASE8453_RPC_URL`
+- `FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS`
+- `FLOWCHAIN_BASE8453_SUPPORTED_TOKEN`
+- `FLOWCHAIN_BASE8453_ASSET_DECIMALS`
+- `FLOWCHAIN_BASE8453_FROM_BLOCK`
+- `FLOWCHAIN_BASE8453_TO_BLOCK`
+- `FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI`
+- `FLOWCHAIN_PILOT_TOTAL_CAP_WEI`
+- `FLOWCHAIN_PILOT_CONFIRMATIONS=12`
+
+For the tx-specific diagnostic, also provide either
+`FLOWCHAIN_BASE8453_TX_HASH` or `FLOWCHAIN_BASE8453_OPERATOR_TX_HASH`.
+
+After those inputs exist, rerun:
+
+```powershell
+npm run flowchain:bridge:live:check
+npm run flowchain:live-l1-bridge:e2e
+```
+
+PASS requires the new gate to show all of these in the main running node state,
+not only in proof artifacts: `bridgeCredits`, `bridgeCreditReceipts`,
+`bridgeReplayKeys`, credited balance, a one-unit transfer from the credited
+account, matching export/import roots, and measured latency under 60 seconds
+from confirmation eligibility to spendable credit.
+
+Release/broadcast back to Base remains separate and absent unless explicitly
+authorized.
+
diff --git a/docs/FLOWCHAIN_PRODUCTION_L1_GO_NO_GO.md b/docs/FLOWCHAIN_PRODUCTION_L1_GO_NO_GO.md
new file mode 100644
index 00000000..344e8525
--- /dev/null
+++ b/docs/FLOWCHAIN_PRODUCTION_L1_GO_NO_GO.md
@@ -0,0 +1,79 @@
+# FlowChain Gated L1 Go/No-Go
+
+Date: 2026-05-14
+
+Status: `EXTERNAL-BLOCKED ONLY`.
+
+## Decision
+
+The branch is runnable for the private/local production L1 path. The final
+aggregate production L1 gate completed as `passed-with-live-blockers`: every
+code-controlled local/mock subsystem passed, and the only remaining live
+readiness blocker is missing owner-supplied Base 8453 pilot inputs.
+
+No Base transaction was broadcast. The live gate fails closed until the owner
+supplies the required RPC, lockbox, bounded block range, caps, confirmation
+depth, and explicit acknowledgement in a local shell.
+
+## Latest Evidence
+
+| Command | Result |
+| --- | --- |
+| `powershell -NoProfile -ExecutionPolicy Bypass -File E:\FlowMemory\agent-dispatch\generated\production-l1-complete\audit-production-l1-readiness.ps1 -RepoRoot E:\FlowMemory\flowmemory-prod-hq` | passed, exit 0; `BlockingIssueCount=0`; audit `E:\FlowMemory\agent-dispatch\generated\production-l1-complete\audit\production-l1-readiness-audit-20260514-111620.json` |
+| `npm run flowchain:production-l1:e2e` | passed, exit 0; final status `passed-with-live-blockers`; report timestamp `2026-05-14T16:23:45.5784606Z` |
+| `npm run flowchain:real-value-pilot:e2e` | passed, exit 0; status `passed`; `AllowIncomplete=false`; `SkipBaseline=false`; report generated `2026-05-14T16:18:47.3193401Z` |
+| `npm run flowchain:no-secret:scan` | passed, exit 0; report generated `2026-05-14T16:24:08.6005410Z`; scanned 288 files; findings 0 |
+| `npm run flowchain:bridge:live:check` | failed closed, exit 1; report generated `2026-05-14T16:24:12.4188670Z`; status `blocked`; missing owner env only; `broadcasts=false`; `printsEnvValues=false`; `noSecrets=true` |
+| `git diff --check` | passed, exit 0 |
+
+The final aggregate report records:
+
+- status `passed-with-live-blockers`
+- local/mock path `passed`
+- live readiness `blocked`
+- dashboard build `passed`
+- bridge mock path `passed`
+- restart recovery `passed`
+- export/import root compare `passed`
+- no-secret scan `passed`
+- local state root `0x149b8017ed2be0fb192c295383d2a198798a24e8509978c678fe78205ca8ee58`
+
+## Required Owner Inputs
+
+- `FLOWCHAIN_PILOT_OPERATOR_ACK`
+- `FLOWCHAIN_BASE8453_RPC_URL`
+- `FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS`
+- `FLOWCHAIN_BASE8453_SUPPORTED_TOKEN`
+- `FLOWCHAIN_BASE8453_ASSET_DECIMALS`
+- `FLOWCHAIN_BASE8453_FROM_BLOCK`
+- `FLOWCHAIN_BASE8453_TO_BLOCK`
+- `FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI`
+- `FLOWCHAIN_PILOT_TOTAL_CAP_WEI`
+- `FLOWCHAIN_PILOT_CONFIRMATIONS`
+
+Acknowledgement value:
+
+`I_UNDERSTAND_THIS_IS_CAPPED_BASE8453_OWNER_PILOT`
+
+## Final Reports
+
+- `devnet/local/production-l1-e2e/flowchain-production-l1-e2e-report.json`
+- `devnet/local/production-l1-e2e/flowchain-production-l1-e2e-summary.md`
+- `devnet/local/production-l1-e2e/bridge-live-readiness-report.json`
+- `devnet/local/production-l1-e2e/no-secret-scan-report.json`
+- `devnet/local/production-l1-e2e/export-import-root-compare.json`
+- `devnet/local/production-l1-e2e/real-value-pilot-coordination/flowchain-real-value-pilot-e2e-report.json`
+- `devnet/local/production-l1-e2e/evidence/flowchain-production-l1-evidence-export-report.json`
+- `devnet/local/production-l1-e2e/evidence/flowchain-production-l1-evidence.zip`
+
+The standalone real-value pilot report from the explicit gate is also present
+at `devnet/local/real-value-pilot/flowchain-real-value-pilot-e2e-report.json`.
+
+## Operator Boundary
+
+Do not send funds or broadcast transactions until the owner supplies the inputs
+above in a local shell and reruns:
+
+```powershell
+npm run flowchain:bridge:live:check
+```
diff --git a/docs/FLOWCHAIN_REAL_VALUE_PILOT.md b/docs/FLOWCHAIN_REAL_VALUE_PILOT.md
index f1edab6d..c5ff753f 100644
--- a/docs/FLOWCHAIN_REAL_VALUE_PILOT.md
+++ b/docs/FLOWCHAIN_REAL_VALUE_PILOT.md
@@ -178,6 +178,72 @@ from `main`.
| Control plane/dashboard | `flowchain:real-value-pilot:control-dashboard` merged on `main` through PR #142 and closed issue #137. | No control-dashboard blocker remains for the final pilot gate. |
| Ops/installer | `flowchain:real-value-pilot:ops` merged on `main` through PR #144 and closed issue #135. | No ops/installer blocker remains for the final pilot gate. |
+## Operator UI/API Inspection Before Any Real Funds
+
+Do not send any real Base funds until the control-plane API and dashboard both
+show `READY_FOR_OPERATOR_LIVE_PILOT` and the lockbox address has been
+owner-verified outside the browser. A configured env name is not owner
+verification by itself.
+
+From the repo root, run the local runtime and control-plane surfaces:
+
+```powershell
+npm run flowchain:start
+npm run control-plane:serve
+npm run workbench:dev
+```
+
+In a separate terminal, inspect the live-readiness check without printing env
+values:
+
+```powershell
+powershell -NoProfile -ExecutionPolicy Bypass -File infra/scripts/flowchain-bridge-live-check.ps1 -AllowBlocked
+```
+
+Before sending any real funds, inspect these API pages in the browser or with a
+local HTTP client:
+
+```text
+http://127.0.0.1:8787/chain/status
+http://127.0.0.1:8787/bridge/live-readiness
+http://127.0.0.1:8787/pilot/status
+http://127.0.0.1:8787/pilot/lifecycle
+http://127.0.0.1:8787/wallets/balances
+http://127.0.0.1:8787/wallets/transfers
+```
+
+The required pre-funds API state is:
+
+- `/chain/status` shows the node running, chain id/name, block height, latest
+ state root, and restart/export/import continuity status.
+- `/bridge/live-readiness` shows `baseChainId: 8453`,
+ `failClosedStatus: "READY_FOR_OPERATOR_LIVE_PILOT"`,
+ `envValuesPrinted: false`, no missing required env names, confirmation depth
+ inside policy, tiny cap env names configured, and lockbox configured.
+- `/pilot/lifecycle` shows no mock artifact presented as live. Existing
+ local/mock/Base Sepolia rows are acceptable as historical evidence only.
+- `/wallets/balances` and `/wallets/transfers` are reachable and return
+ machine-readable `localOnly: true`, `productionReady: false` rows or useful
+ empty states.
+
+Open the dashboard at the Vite URL printed by `npm run workbench:dev` and
+inspect the first screen:
+
+- `Node and API status` must show the local control-plane as available.
+- `Bridge live readiness` must show `READY_FOR_OPERATOR_LIVE_PILOT`.
+- `Real-value pilot` must remain labeled `capped owner testing`.
+- The `Real-Value Pilot` table must be searchable by Base tx hash, credit id,
+ wallet address, and status.
+
+After a pilot deposit is observed, do not rely on manual file inspection alone.
+Use the API and dashboard to confirm one lifecycle row keyed by Base tx hash and
+log index, with the expected credit id, recipient wallet, asset, amount in
+smallest units, evidence path/id, and exact equality across deposit amount,
+observed amount, credited amount, wallet delta, transferable amount, withdrawal
+amount, and release amount. Then confirm wallet balances and transfer history
+show the credited wallet can transfer the exact credited amount and the
+recipient balance updates exactly.
+
## Owner Go/No-Go Checklist
The owner should mark the pilot `go` only when all rows are true:
diff --git a/docs/OPERATIONS/FLOWCHAIN_BASE8453_LOCKBOX_DEPLOYMENT_RUNBOOK.md b/docs/OPERATIONS/FLOWCHAIN_BASE8453_LOCKBOX_DEPLOYMENT_RUNBOOK.md
new file mode 100644
index 00000000..d38d032f
--- /dev/null
+++ b/docs/OPERATIONS/FLOWCHAIN_BASE8453_LOCKBOX_DEPLOYMENT_RUNBOOK.md
@@ -0,0 +1,153 @@
+# FlowChain Base 8453 Lockbox Deployment Runbook
+
+Status: owner-operated capped pilot procedure. No transaction is broadcast by readiness checks.
+
+## Owner Inputs
+
+Deployment scripts require these names in the local shell only:
+
+```powershell
+$env:FLOWCHAIN_PILOT_OPERATOR_ACK="I_UNDERSTAND_THIS_IS_CAPPED_BASE8453_OWNER_PILOT"
+$env:FLOWCHAIN_BASE8453_RPC_URL=" "
+$env:FLOWCHAIN_BASE8453_DEPLOYER_PRIVATE_KEY = ""
+$env:FLOWCHAIN_BASE8453_SUPPORTED_TOKEN="<0x0000000000000000000000000000000000000000 or ERC-20 address>"
+$env:FLOWCHAIN_PILOT_MAX_DEPOSIT_WEI=""
+$env:FLOWCHAIN_PILOT_TOTAL_CAP_WEI=""
+```
+
+Optional owner-role overrides:
+
+```powershell
+$env:FLOWCHAIN_BASE8453_OWNER_ADDRESS=""
+$env:FLOWCHAIN_BASE8453_RELEASE_AUTHORITY_ADDRESS=""
+$env:FLOWCHAIN_BASE8453_SETTLEMENT_SUBMITTER_ADDRESS=""
+```
+
+Never commit these values.
+
+## Step 1: Dry-Run
+
+Run:
+
+```powershell
+npm run flowchain:bridge:deploy:base8453
+```
+
+Expected dry-run evidence:
+
+```text
+devnet/local/bridge-live-readiness/base8453-deploy-readiness.json
+```
+
+The dry-run must report `ready-no-broadcast` before broadcast is considered.
+
+## Step 2: Broadcast
+
+Broadcast requires an additional acknowledgement:
+
+```powershell
+$env:FLOWCHAIN_BASE8453_BROADCAST_ACK="I_UNDERSTAND_THIS_SENDS_A_BASE8453_BRIDGE_TRANSACTION"
+npm run flowchain:bridge:deploy:base8453 -- -Mode Broadcast -AcknowledgeBroadcast
+```
+
+Record the deployment evidence without private keys:
+
+```text
+transactionHash=
+lockboxAddress=
+blockNumber=
+deployerAddress=
+```
+
+Put those fields in a local evidence file outside committed source first. Only commit a redacted deployment note after review.
+
+## Step 3: Verify Before Any Funds
+
+Configure:
+
+```powershell
+$env:FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS=""
+$env:FLOWCHAIN_BASE8453_ASSET_DECIMALS=""
+$env:FLOWCHAIN_BASE8453_FROM_BLOCK=""
+$env:FLOWCHAIN_BASE8453_TO_BLOCK=""
+$env:FLOWCHAIN_PILOT_CONFIRMATIONS=""
+```
+
+Then run:
+
+```powershell
+npm run flowchain:bridge:infra:check
+npm run flowchain:bridge:live:check
+```
+
+Required verification:
+
+- Base RPC reports chain ID `8453`.
+- Lockbox address is a 20-byte hex address.
+- `eth_getCode` returns deployed bytecode for the lockbox.
+- Token mode is native ETH via zero address or ERC-20 with deployed token bytecode.
+- Caps, range, decimals, and confirmations are numeric and bounded.
+- Operator acknowledgement is exact.
+
+Do not send funds until both checks pass with the owner-verified lockbox.
+
+## Step 4: Observe Deposits
+
+For a bounded owner-supplied block range:
+
+```powershell
+npm run flowchain:bridge:observe:base8453
+```
+
+Evidence paths:
+
+```text
+services/bridge-relayer/out/base8453-pilot-bridge-observation.json
+services/bridge-relayer/out/base8453-pilot-bridge-credit.json
+services/bridge-relayer/out/base8453-pilot-bridge-handoff.json
+services/bridge-relayer/out/base8453-pilot-evidence.json
+devnet/local/bridge-live-readiness/bridge-observe-base8453-report.json
+```
+
+## Emergency Commands
+
+Dry-run control command discovery:
+
+```powershell
+npm run flowchain:bridge:pause
+npm run flowchain:bridge:resume
+npm run flowchain:bridge:emergency-stop
+npm run flowchain:emergency:stop-local
+```
+
+The Base control script has an `-Execute` mode for owner-authorized changes. Do not use `-Execute` unless the owner intentionally provides the deployer key and broadcast acknowledgement in the local shell.
+
+## Transaction Diagnosis
+
+For an owner-supplied Base transaction hash:
+
+```powershell
+$env:FLOWCHAIN_BASE8453_TX_HASH=" "
+npm run flowchain:bridge:diagnose:tx
+```
+
+Alternative env name:
+
+```powershell
+$env:FLOWCHAIN_BASE8453_OPERATOR_TX_HASH=" "
+npm run flowchain:bridge:diagnose:tx
+```
+
+The diagnostic path does not require private keys and must not print the Base RPC endpoint value.
+
+## Verification Handoff
+
+After dry-run, broadcast, and post-deploy verification, hand these paths to the verification owner:
+
+```text
+devnet/local/bridge-live-readiness/base8453-deploy-readiness.json
+docs/agent-runs/live-product-infra-rpc/bridge-infra-readiness-report.json
+docs/agent-runs/live-product-infra-rpc/bridge-live-readiness-report.json
+devnet/local/bridge-live-readiness/bridge-observe-base8453-report.json
+services/bridge-relayer/out/base8453-pilot-evidence.json
+```
diff --git a/docs/OPERATIONS/FLOWCHAIN_OWNER_OPERATED_PUBLIC_RPC.md b/docs/OPERATIONS/FLOWCHAIN_OWNER_OPERATED_PUBLIC_RPC.md
new file mode 100644
index 00000000..887c47e7
--- /dev/null
+++ b/docs/OPERATIONS/FLOWCHAIN_OWNER_OPERATED_PUBLIC_RPC.md
@@ -0,0 +1,132 @@
+# FlowChain Owner-Operated Public RPC Runbook
+
+Status: fail-closed operator path. This document does not claim broad public use readiness.
+
+## What Code Provides
+
+The repository now provides:
+
+- `npm run flowchain:service:start` for supervised node and control-plane processes on Windows.
+- `npm run flowchain:service:status` for safe process, bind, height, backup, and bridge status.
+- `npm run flowchain:service:stop` and `npm run flowchain:service:restart`, which preserve runtime state.
+- `npm run flowchain:public-rpc:check` for endpoint, TLS, CORS, rate-limit, health, discovery, readiness, state, and response-hygiene checks.
+- `npm run flowchain:backup:check` for writable backup path and state readback verification.
+- `npm run flowchain:bridge:infra:check` for Base 8453 deployment input checks.
+- `npm run flowchain:live-infra:check` as the aggregate gate.
+
+## What The Owner Must Provide
+
+The owner must provide, in the local shell or service environment only:
+
+```powershell
+$env:FLOWCHAIN_RPC_PUBLIC_URL=""
+$env:FLOWCHAIN_RPC_ALLOWED_ORIGINS=""
+$env:FLOWCHAIN_RPC_RATE_LIMIT_PER_MINUTE=""
+$env:FLOWCHAIN_RPC_TLS_TERMINATED="true"
+$env:FLOWCHAIN_RPC_STATE_BACKUP_PATH="