From 62c4bc5adecc7e785ab436131ec1a31eba22697c Mon Sep 17 00:00:00 2001 From: hdbg Date: Wed, 4 Mar 2026 15:27:27 +0100 Subject: [PATCH 01/13] feat(useragent): initial impl --- CLAUDE.md | 128 + mise.lock | 4 + mise.toml | 8 + .../src/actors/user_agent/session.rs | 24 + server/crates/arbiter-useragent/src/lib.rs | 124 +- .../.dart_tool/extension_discovery/README.md | 31 - .../extension_discovery/vs_code.json | 1 - useragent/.dart_tool/package_config.json | 702 +++++ useragent/.dart_tool/package_graph.json | 1246 +++++++- useragent/README.md | 2 +- useragent/android/.gitignore | 14 + useragent/android/app/build.gradle.kts | 44 + .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 45 + .../com/example/useragent/MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + useragent/android/build.gradle.kts | 24 + useragent/android/gradle.properties | 2 + .../gradle/wrapper/gradle-wrapper.properties | 5 + useragent/android/settings.gradle.kts | 26 + useragent/lib/features/adaptive_switcher.dart | 118 + useragent/lib/features/bootstrap.dart | 26 + useragent/lib/features/pk_manager.dart | 19 + useragent/lib/features/simple_ed25519.dart | 94 + useragent/lib/home.dart | 24 + useragent/lib/main.dart | 127 +- useragent/lib/proto/arbiter.pb.dart | 107 + useragent/lib/proto/arbiter.pbenum.dart | 11 + useragent/lib/proto/arbiter.pbjson.dart | 124 + useragent/lib/proto/arbiter.pbserver.dart | 56 + useragent/lib/proto/client.pb.dart | 551 ++++ useragent/lib/proto/client.pbenum.dart | 42 + useragent/lib/proto/client.pbjson.dart | 197 ++ useragent/lib/proto/evm.pb.dart | 2582 +++++++++++++++++ useragent/lib/proto/evm.pbenum.dart | 40 + useragent/lib/proto/evm.pbjson.dart | 950 ++++++ useragent/lib/proto/user_agent.pb.dart | 1166 ++++++++ useragent/lib/proto/user_agent.pbenum.dart | 71 + useragent/lib/proto/user_agent.pbjson.dart | 457 +++ useragent/lib/providers/key.dart | 60 + useragent/lib/providers/key.g.dart | 94 + useragent/lib/router.dart | 32 + useragent/lib/screens/about.dart | 9 + useragent/lib/screens/calc.dart | 74 + .../macos/Flutter/Flutter-Debug.xcconfig | 1 + .../macos/Flutter/Flutter-Release.xcconfig | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 10 + .../ephemeral/Flutter-Generated.xcconfig | 8 +- .../ephemeral/flutter_export_environment.sh | 8 +- useragent/macos/Podfile | 42 + useragent/macos/Podfile.lock | 47 + .../macos/Runner.xcodeproj/project.pbxproj | 114 +- .../xcshareddata/xcschemes/Runner.xcscheme | 8 +- .../contents.xcworkspacedata | 3 + .../macos/Runner/Configs/AppInfo.xcconfig | 4 +- useragent/pubspec.lock | 936 +++++- useragent/pubspec.yaml | 29 +- useragent/web/favicon.png | Bin 0 -> 917 bytes useragent/web/icons/Icon-192.png | Bin 0 -> 5292 bytes useragent/web/icons/Icon-512.png | Bin 0 -> 8252 bytes useragent/web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes useragent/web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes useragent/web/index.html | 38 + useragent/web/manifest.json | 35 + useragent/windows/CMakeLists.txt | 4 +- .../flutter/generated_plugin_registrant.cc | 15 + .../windows/flutter/generated_plugins.cmake | 5 + useragent/windows/runner/Runner.rc | 8 +- useragent/windows/runner/main.cpp | 2 +- 78 files changed, 10635 insertions(+), 223 deletions(-) create mode 100644 CLAUDE.md delete mode 100644 useragent/.dart_tool/extension_discovery/README.md delete mode 100644 useragent/.dart_tool/extension_discovery/vs_code.json create mode 100644 useragent/android/.gitignore create mode 100644 useragent/android/app/build.gradle.kts create mode 100644 useragent/android/app/src/debug/AndroidManifest.xml create mode 100644 useragent/android/app/src/main/AndroidManifest.xml create mode 100644 useragent/android/app/src/main/kotlin/com/example/useragent/MainActivity.kt create mode 100644 useragent/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 useragent/android/app/src/main/res/drawable/launch_background.xml create mode 100644 useragent/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 useragent/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 useragent/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 useragent/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 useragent/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 useragent/android/app/src/main/res/values-night/styles.xml create mode 100644 useragent/android/app/src/main/res/values/styles.xml create mode 100644 useragent/android/app/src/profile/AndroidManifest.xml create mode 100644 useragent/android/build.gradle.kts create mode 100644 useragent/android/gradle.properties create mode 100644 useragent/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 useragent/android/settings.gradle.kts create mode 100644 useragent/lib/features/adaptive_switcher.dart create mode 100644 useragent/lib/features/bootstrap.dart create mode 100644 useragent/lib/features/pk_manager.dart create mode 100644 useragent/lib/features/simple_ed25519.dart create mode 100644 useragent/lib/home.dart create mode 100644 useragent/lib/proto/arbiter.pb.dart create mode 100644 useragent/lib/proto/arbiter.pbenum.dart create mode 100644 useragent/lib/proto/arbiter.pbjson.dart create mode 100644 useragent/lib/proto/arbiter.pbserver.dart create mode 100644 useragent/lib/proto/client.pb.dart create mode 100644 useragent/lib/proto/client.pbenum.dart create mode 100644 useragent/lib/proto/client.pbjson.dart create mode 100644 useragent/lib/proto/evm.pb.dart create mode 100644 useragent/lib/proto/evm.pbenum.dart create mode 100644 useragent/lib/proto/evm.pbjson.dart create mode 100644 useragent/lib/proto/user_agent.pb.dart create mode 100644 useragent/lib/proto/user_agent.pbenum.dart create mode 100644 useragent/lib/proto/user_agent.pbjson.dart create mode 100644 useragent/lib/providers/key.dart create mode 100644 useragent/lib/providers/key.g.dart create mode 100644 useragent/lib/router.dart create mode 100644 useragent/lib/screens/about.dart create mode 100644 useragent/lib/screens/calc.dart create mode 100644 useragent/macos/Podfile create mode 100644 useragent/macos/Podfile.lock create mode 100644 useragent/web/favicon.png create mode 100644 useragent/web/icons/Icon-192.png create mode 100644 useragent/web/icons/Icon-512.png create mode 100644 useragent/web/icons/Icon-maskable-192.png create mode 100644 useragent/web/icons/Icon-maskable-512.png create mode 100644 useragent/web/index.html create mode 100644 useragent/web/manifest.json diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..776eccc --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,128 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Arbiter is a **permissioned signing service** for cryptocurrency wallets. It consists of: +- **`server/`** — Rust gRPC daemon that holds encrypted keys and enforces policies +- **`useragent/`** — Flutter desktop app (macOS/Windows) with a Rust backend via Rinf +- **`protobufs/`** — Protocol Buffer definitions shared between server and client + +The vault never exposes key material; it only produces signatures when requests satisfy configured policies. + +## Toolchain Setup + +Tools are managed via [mise](https://mise.jdx.dev/). Install all required tools: +```sh +mise install +``` + +Key versions: Rust 1.93.0 (with clippy), Flutter 3.38.9-stable, protoc 29.6, diesel_cli 2.3.6 (sqlite). + +## Server (Rust workspace at `server/`) + +### Crates + +| Crate | Purpose | +|---|---| +| `arbiter-proto` | Generated gRPC stubs + protobuf types; compiled from `protobufs/*.proto` via `tonic-prost-build` | +| `arbiter-server` | Main daemon — actors, DB, EVM policy engine, gRPC service implementation | +| `arbiter-useragent` | Rust client library for the user agent side of the gRPC protocol | +| `arbiter-client` | Rust client library for SDK clients | + +### Common Commands + +```sh +cd server + +# Build +cargo build + +# Run the server daemon +cargo run -p arbiter-server + +# Run all tests (preferred over cargo test) +cargo nextest run + +# Run a single test +cargo nextest run + +# Lint +cargo clippy + +# Security audit +cargo audit + +# Check unused dependencies +cargo shear + +# Run snapshot tests and update snapshots +cargo insta review +``` + +### Architecture + +The server is actor-based using the **kameo** crate. All long-lived state lives in `GlobalActors`: + +- **`Bootstrapper`** — Manages the one-time bootstrap token written to `~/.arbiter/bootstrap_token` on first run. +- **`KeyHolder`** — Holds the encrypted root key and manages the Sealed/Unsealed vault state machine. On unseal, decrypts the root key into a `memsafe` hardened memory cell. +- **`MessageRouter`** — Coordinates streaming messages between user agents and SDK clients. +- **`EvmActor`** — Handles EVM transaction policy enforcement and signing. + +Per-connection actors live under `actors/user_agent/` and `actors/client/`, each with `auth` (challenge-response authentication) and `session` (post-auth operations) sub-modules. + +**Database:** SQLite via `diesel-async` + `bb8` connection pool. Schema managed by embedded Diesel migrations in `crates/arbiter-server/migrations/`. DB file lives at `~/.arbiter/arbiter.sqlite`. Tests use a temp-file DB via `db::create_test_pool()`. + +**Cryptography:** +- Authentication: ed25519 (challenge-response, nonce-tracked per peer) +- Encryption at rest: XChaCha20-Poly1305 (versioned via `scheme` field for transparent migration on unseal) +- Password KDF: Argon2 +- Unseal transport: X25519 ephemeral key exchange +- TLS: self-signed certificate (aws-lc-rs backend), fingerprint distributed via `ArbiterUrl` + +**Protocol:** gRPC with Protocol Buffers. The `ArbiterUrl` type encodes host, port, CA cert, and bootstrap token into a single shareable string (printed to console on first run). + +### Proto Regeneration + +When `.proto` files in `protobufs/` change, rebuild to regenerate: +```sh +cd server && cargo build -p arbiter-proto +``` + +### Database Migrations + +```sh +# Create a new migration +diesel migration generate --migration-dir crates/arbiter-server/migrations + +# Run migrations manually (server also runs them on startup) +diesel migration run --migration-dir crates/arbiter-server/migrations +``` + +## User Agent (Flutter + Rinf at `useragent/`) + +The Flutter app uses [Rinf](https://rinf.cunarist.org) to call Rust code. The Rust logic lives in `useragent/native/hub/` as a separate crate that uses `arbiter-useragent` for the gRPC client. + +Communication between Dart and Rust uses typed **signals** defined in `useragent/native/hub/src/signals/`. After modifying signal structs, regenerate Dart bindings: + +```sh +cd useragent && rinf gen +``` + +### Common Commands + +```sh +cd useragent + +# Run the app (macOS or Windows) +flutter run + +# Regenerate Rust↔Dart signal bindings +rinf gen + +# Analyze Dart code +flutter analyze +``` + +The Rinf Rust entry point is `useragent/native/hub/src/lib.rs`. It spawns actors defined in `useragent/native/hub/src/actors/` which handle Dart↔server communication via signals. diff --git a/mise.lock b/mise.lock index f3fc6ba..e3ad0a5 100644 --- a/mise.lock +++ b/mise.lock @@ -42,6 +42,10 @@ backend = "cargo:diesel_cli" default-features = "false" features = "sqlite,sqlite-bundled" +[[tools."cargo:rinf_cli"]] +version = "8.9.1" +backend = "cargo:rinf_cli" + [[tools.flutter]] version = "3.38.9-stable" backend = "asdf:flutter" diff --git a/mise.toml b/mise.toml index 1860773..7110df7 100644 --- a/mise.toml +++ b/mise.toml @@ -10,3 +10,11 @@ protoc = "29.6" "cargo:cargo-shear" = "latest" "cargo:cargo-insta" = "1.46.3" python = "3.14.3" + +[tasks.codegen] +sources = ['protobufs/*.proto'] +outputs = ['useragent/lib/proto/*'] +run = ''' +dart pub global activate protoc_plugin && \ +protoc --dart_out=useragent/lib/proto --proto_path=protobufs/ protobufs/*.proto +''' \ No newline at end of file diff --git a/server/crates/arbiter-server/src/actors/user_agent/session.rs b/server/crates/arbiter-server/src/actors/user_agent/session.rs index b686796..5ef3b20 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/session.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/session.rs @@ -168,6 +168,9 @@ impl UserAgentSession { UserAgentRequestPayload::UnsealEncryptedKey(unseal_encrypted_key) => { self.handle_unseal_encrypted_key(unseal_encrypted_key).await } + UserAgentRequestPayload::QueryVaultState(_) => { + self.handle_query_vault_state().await + } UserAgentRequestPayload::EvmWalletCreate(_) => self.handle_evm_wallet_create().await, UserAgentRequestPayload::EvmWalletList(_) => self.handle_evm_wallet_list().await, _ => Err(TransportResponseError::UnexpectedRequestPayload), @@ -290,6 +293,27 @@ impl UserAgentSession { } } +impl UserAgentSession { + async fn handle_query_vault_state(&mut self) -> Output { + use arbiter_proto::proto::user_agent::VaultState; + use crate::actors::keyholder::{GetState, StateDiscriminants}; + + let vault_state = match self.props.actors.key_holder.ask(GetState {}).await { + Ok(StateDiscriminants::Unbootstrapped) => VaultState::Unbootstrapped, + Ok(StateDiscriminants::Sealed) => VaultState::Sealed, + Ok(StateDiscriminants::Unsealed) => VaultState::Unsealed, + Err(err) => { + error!(?err, actor = "useragent", "keyholder.query.failed"); + VaultState::Error + } + }; + + Ok(response(UserAgentResponsePayload::VaultState( + vault_state.into(), + ))) + } +} + impl UserAgentSession { async fn handle_evm_wallet_create(&mut self) -> Output { use evm_proto::wallet_create_response::Result as CreateResult; diff --git a/server/crates/arbiter-useragent/src/lib.rs b/server/crates/arbiter-useragent/src/lib.rs index 5da9d68..c21b4a3 100644 --- a/server/crates/arbiter-useragent/src/lib.rs +++ b/server/crates/arbiter-useragent/src/lib.rs @@ -254,4 +254,126 @@ where } mod grpc; -pub use grpc::{ConnectError, connect_grpc}; +pub use grpc::{connect_grpc, ConnectError, UserAgentGrpc}; + +use arbiter_proto::proto::user_agent::{ + UnsealEncryptedKey, UnsealStart, + user_agent_request::Payload as RequestPayload, + user_agent_response::Payload as ResponsePayload, +}; + +/// Send an `UnsealStart` request and await the server's `UnsealStartResponse`. +pub struct SendUnsealStart { + pub client_pubkey: Vec, +} + +/// Send an `UnsealEncryptedKey` request and await the server's `UnsealResult`. +pub struct SendUnsealEncryptedKey { + pub nonce: Vec, + pub ciphertext: Vec, + pub associated_data: Vec, +} + +/// Query the server for the current `VaultState`. +pub struct QueryVaultState; + +/// Errors that can occur during post-authentication session operations. +#[derive(Debug, thiserror::Error)] +pub enum SessionError { + #[error("Transport send failed")] + TransportSendFailed, + #[error("Transport closed unexpectedly")] + TransportClosed, + #[error("Server sent an unexpected response payload")] + UnexpectedResponse, +} + +impl kameo::message::Message for UserAgentActor +where + Transport: Bi, +{ + type Reply = Result; + + async fn handle( + &mut self, + msg: SendUnsealStart, + _ctx: &mut kameo::message::Context, + ) -> Self::Reply { + self.transport + .send(UserAgentRequest { + payload: Some(RequestPayload::UnsealStart(UnsealStart { + client_pubkey: msg.client_pubkey, + })), + }) + .await + .map_err(|_| SessionError::TransportSendFailed)?; + + match self.transport.recv().await { + Some(resp) => match resp.payload { + Some(ResponsePayload::UnsealStartResponse(r)) => Ok(r), + _ => Err(SessionError::UnexpectedResponse), + }, + None => Err(SessionError::TransportClosed), + } + } +} + +impl kameo::message::Message for UserAgentActor +where + Transport: Bi, +{ + type Reply = Result; + + async fn handle( + &mut self, + msg: SendUnsealEncryptedKey, + _ctx: &mut kameo::message::Context, + ) -> Self::Reply { + self.transport + .send(UserAgentRequest { + payload: Some(RequestPayload::UnsealEncryptedKey(UnsealEncryptedKey { + nonce: msg.nonce, + ciphertext: msg.ciphertext, + associated_data: msg.associated_data, + })), + }) + .await + .map_err(|_| SessionError::TransportSendFailed)?; + + match self.transport.recv().await { + Some(resp) => match resp.payload { + Some(ResponsePayload::UnsealResult(r)) => Ok(r), + _ => Err(SessionError::UnexpectedResponse), + }, + None => Err(SessionError::TransportClosed), + } + } +} + +impl kameo::message::Message for UserAgentActor +where + Transport: Bi, +{ + type Reply = Result; + + async fn handle( + &mut self, + _msg: QueryVaultState, + _ctx: &mut kameo::message::Context, + ) -> Self::Reply { + self.transport + .send(UserAgentRequest { + payload: Some(RequestPayload::QueryVaultState(())), + }) + .await + .map_err(|_| SessionError::TransportSendFailed)?; + + match self.transport.recv().await { + Some(resp) => match resp.payload { + Some(ResponsePayload::VaultState(v)) => Ok(v), + _ => Err(SessionError::UnexpectedResponse), + }, + None => Err(SessionError::TransportClosed), + } + } +} diff --git a/useragent/.dart_tool/extension_discovery/README.md b/useragent/.dart_tool/extension_discovery/README.md deleted file mode 100644 index 9dc6757..0000000 --- a/useragent/.dart_tool/extension_discovery/README.md +++ /dev/null @@ -1,31 +0,0 @@ -Extension Discovery Cache -========================= - -This folder is used by `package:extension_discovery` to cache lists of -packages that contains extensions for other packages. - -DO NOT USE THIS FOLDER ----------------------- - - * Do not read (or rely) the contents of this folder. - * Do write to this folder. - -If you're interested in the lists of extensions stored in this folder use the -API offered by package `extension_discovery` to get this information. - -If this package doesn't work for your use-case, then don't try to read the -contents of this folder. It may change, and will not remain stable. - -Use package `extension_discovery` ---------------------------------- - -If you want to access information from this folder. - -Feel free to delete this folder -------------------------------- - -Files in this folder act as a cache, and the cache is discarded if the files -are older than the modification time of `.dart_tool/package_config.json`. - -Hence, it should never be necessary to clear this cache manually, if you find a -need to do please file a bug. diff --git a/useragent/.dart_tool/extension_discovery/vs_code.json b/useragent/.dart_tool/extension_discovery/vs_code.json deleted file mode 100644 index 85db9d3..0000000 --- a/useragent/.dart_tool/extension_discovery/vs_code.json +++ /dev/null @@ -1 +0,0 @@ -{"version":2,"entries":[{"package":"arbiter","rootUri":"../","packageUri":"lib/"}]} \ No newline at end of file diff --git a/useragent/.dart_tool/package_config.json b/useragent/.dart_tool/package_config.json index 70aeb5c..ce1c5aa 100644 --- a/useragent/.dart_tool/package_config.json +++ b/useragent/.dart_tool/package_config.json @@ -1,60 +1,402 @@ { "configVersion": 2, "packages": [ + { + "name": "_fe_analyzer_shared", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/_fe_analyzer_shared-91.0.0", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "analyzer", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/analyzer-8.4.1", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "analyzer_buffer", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/analyzer_buffer-0.1.11", + "packageUri": "lib/", + "languageVersion": "3.6" + }, + { + "name": "ansicolor", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/ansicolor-2.0.3", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "args", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/args-2.7.0", + "packageUri": "lib/", + "languageVersion": "3.3" + }, { "name": "async", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/async-2.13.0", "packageUri": "lib/", "languageVersion": "3.4" }, + { + "name": "biometric_signature", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/biometric_signature-10.2.0", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "bloc", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/bloc-9.2.0", + "packageUri": "lib/", + "languageVersion": "2.14" + }, { "name": "boolean_selector", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2", "packageUri": "lib/", "languageVersion": "3.1" }, + { + "name": "build", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/build-4.0.4", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "build_config", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/build_config-1.3.0", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "build_daemon", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/build_daemon-4.1.1", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "build_runner", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/build_runner-2.12.2", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "built_collection", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/built_collection-5.1.1", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "built_value", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/built_value-8.12.4", + "packageUri": "lib/", + "languageVersion": "3.0" + }, { "name": "characters", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/characters-1.4.0", "packageUri": "lib/", "languageVersion": "3.4" }, + { + "name": "checked_yaml", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/checked_yaml-2.0.4", + "packageUri": "lib/", + "languageVersion": "3.8" + }, + { + "name": "cli_config", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cli_config-0.2.0", + "packageUri": "lib/", + "languageVersion": "3.0" + }, { "name": "clock", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/clock-1.1.2", "packageUri": "lib/", "languageVersion": "3.4" }, + { + "name": "code_assets", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/code_assets-1.0.0", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "code_builder", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/code_builder-4.11.1", + "packageUri": "lib/", + "languageVersion": "3.7" + }, { "name": "collection", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/collection-1.19.1", "packageUri": "lib/", "languageVersion": "3.4" }, + { + "name": "convert", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/convert-3.1.2", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "coverage", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/coverage-1.15.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "cross_file", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cross_file-0.3.5+2", + "packageUri": "lib/", + "languageVersion": "3.8" + }, + { + "name": "crypto", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/crypto-3.0.7", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "cryptography", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cryptography-2.9.0", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "cryptography_flutter", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cryptography_flutter-2.3.4", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "cupertino_icons", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cupertino_icons-1.0.8", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "dart_style", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/dart_style-3.1.3", + "packageUri": "lib/", + "languageVersion": "3.9" + }, { "name": "fake_async", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/fake_async-1.3.3", "packageUri": "lib/", "languageVersion": "3.3" }, + { + "name": "ffi", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/ffi-2.2.0", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "file", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/file-7.0.1", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "fixnum", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/fixnum-1.1.1", + "packageUri": "lib/", + "languageVersion": "3.1" + }, { "name": "flutter", "rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/packages/flutter", "packageUri": "lib/", "languageVersion": "3.8" }, + { + "name": "flutter_adaptive_scaffold", + "rootUri": "file:///Users/kaska/.pub-cache/git/flutter_adaptive_scaffold-4852b5041d8d9ed6cacb4dc4c6723086f0612e37/", + "packageUri": "lib/", + "languageVersion": "3.10" + }, + { + "name": "flutter_bloc", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_bloc-9.1.1", + "packageUri": "lib/", + "languageVersion": "2.14" + }, + { + "name": "flutter_hooks", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_hooks-0.21.3+1", + "packageUri": "lib/", + "languageVersion": "2.17" + }, { "name": "flutter_lints", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_lints-6.0.0", "packageUri": "lib/", "languageVersion": "3.8" }, + { + "name": "flutter_riverpod", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_riverpod-3.1.0", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "flutter_secure_storage", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage-10.0.0", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "flutter_secure_storage_darwin", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage_darwin-0.2.0", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "flutter_secure_storage_linux", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-3.0.0", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "flutter_secure_storage_platform_interface", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage_platform_interface-2.0.1", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "flutter_secure_storage_web", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-2.1.0", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "flutter_secure_storage_windows", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-4.1.0", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "flutter_spinkit", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_spinkit-5.2.2", + "packageUri": "lib/", + "languageVersion": "2.12" + }, { "name": "flutter_test", "rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/packages/flutter_test", "packageUri": "lib/", "languageVersion": "3.8" }, + { + "name": "flutter_web_plugins", + "rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/packages/flutter_web_plugins", + "packageUri": "lib/", + "languageVersion": "3.8" + }, + { + "name": "freezed_annotation", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/freezed_annotation-3.1.0", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "frontend_server_client", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/frontend_server_client-4.0.0", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "glob", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/glob-2.1.3", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "google_identity_services_web", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/google_identity_services_web-0.3.3+1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "googleapis_auth", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/googleapis_auth-2.0.0", + "packageUri": "lib/", + "languageVersion": "3.6" + }, + { + "name": "graphs", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/graphs-2.3.2", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "group_button", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/group_button-5.3.4", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "grpc", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/grpc-5.1.0", + "packageUri": "lib/", + "languageVersion": "3.8" + }, + { + "name": "hooks", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/hooks-1.0.2", + "packageUri": "lib/", + "languageVersion": "3.10" + }, + { + "name": "hooks_riverpod", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/hooks_riverpod-3.1.0", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "http", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/http-1.6.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "http2", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/http2-2.3.1", + "packageUri": "lib/", + "languageVersion": "3.2" + }, + { + "name": "http_multi_server", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/http_multi_server-3.2.2", + "packageUri": "lib/", + "languageVersion": "3.2" + }, + { + "name": "http_parser", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/http_parser-4.1.2", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "io", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/io-1.0.5", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "js", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/js-0.7.2", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "json_annotation", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/json_annotation-4.11.0", + "packageUri": "lib/", + "languageVersion": "3.9" + }, { "name": "leak_tracker", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/leak_tracker-11.0.2", @@ -79,6 +421,12 @@ "packageUri": "lib/", "languageVersion": "3.8" }, + { + "name": "logging", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/logging-1.3.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, { "name": "matcher", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/matcher-0.12.17", @@ -97,18 +445,246 @@ "packageUri": "lib/", "languageVersion": "3.5" }, + { + "name": "mime", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/mime-2.0.0", + "packageUri": "lib/", + "languageVersion": "3.2" + }, + { + "name": "mockito", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/mockito-5.6.3", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "mtcore", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/git.markettakers.org%2547api%2547packages%2547MarketTakers%2547pub%2547/mtcore-1.0.6", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "native_toolchain_c", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/native_toolchain_c-0.17.5", + "packageUri": "lib/", + "languageVersion": "3.10" + }, + { + "name": "nested", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/nested-1.0.0", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "node_preamble", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/node_preamble-2.0.2", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "objective_c", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/objective_c-9.3.0", + "packageUri": "lib/", + "languageVersion": "3.10" + }, + { + "name": "package_config", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/package_config-2.2.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, { "name": "path", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path-1.9.1", "packageUri": "lib/", "languageVersion": "3.4" }, + { + "name": "path_provider", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider-2.1.5", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "path_provider_android", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider_android-2.2.22", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "path_provider_foundation", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider_foundation-2.6.0", + "packageUri": "lib/", + "languageVersion": "3.10" + }, + { + "name": "path_provider_linux", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1", + "packageUri": "lib/", + "languageVersion": "2.19" + }, + { + "name": "path_provider_platform_interface", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider_platform_interface-2.1.2", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "path_provider_windows", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0", + "packageUri": "lib/", + "languageVersion": "3.2" + }, + { + "name": "percent_indicator", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/percent_indicator-4.2.5", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "platform", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/platform-3.1.6", + "packageUri": "lib/", + "languageVersion": "3.2" + }, + { + "name": "plugin_platform_interface", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.8", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "pool", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/pool-1.5.2", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "protobuf", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/protobuf-6.0.0", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "provider", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/provider-6.1.5+1", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "pub_semver", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/pub_semver-2.2.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "pubspec_parse", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/pubspec_parse-1.5.0", + "packageUri": "lib/", + "languageVersion": "3.6" + }, + { + "name": "rive", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/rive-0.14.4", + "packageUri": "lib/", + "languageVersion": "3.6" + }, + { + "name": "rive_native", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/rive_native-0.1.4", + "packageUri": "lib/", + "languageVersion": "3.6" + }, + { + "name": "riverpod", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/riverpod-3.1.0", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "riverpod_analyzer_utils", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/riverpod_analyzer_utils-1.0.0-dev.8", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "riverpod_annotation", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/riverpod_annotation-4.0.0", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "riverpod_generator", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/riverpod_generator-4.0.0+1", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "share_plus", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/share_plus-12.0.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "share_plus_platform_interface", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/share_plus_platform_interface-6.1.0", + "packageUri": "lib/", + "languageVersion": "2.18" + }, + { + "name": "shelf", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/shelf-1.4.2", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "shelf_packages_handler", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/shelf_packages_handler-3.0.2", + "packageUri": "lib/", + "languageVersion": "2.17" + }, + { + "name": "shelf_static", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/shelf_static-1.1.3", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "shelf_web_socket", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/shelf_web_socket-3.0.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "sizer", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/sizer-3.1.3", + "packageUri": "lib/", + "languageVersion": "2.12" + }, { "name": "sky_engine", "rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/bin/cache/pkg/sky_engine", "packageUri": "lib/", "languageVersion": "3.8" }, + { + "name": "source_gen", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/source_gen-4.2.1", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "source_map_stack_trace", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/source_map_stack_trace-2.1.2", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "source_maps", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/source_maps-0.10.13", + "packageUri": "lib/", + "languageVersion": "3.3" + }, { "name": "source_span", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/source_span-1.10.2", @@ -121,30 +697,108 @@ "packageUri": "lib/", "languageVersion": "3.4" }, + { + "name": "state_notifier", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/state_notifier-1.0.0", + "packageUri": "lib/", + "languageVersion": "2.12" + }, { "name": "stream_channel", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/stream_channel-2.1.4", "packageUri": "lib/", "languageVersion": "3.3" }, + { + "name": "stream_transform", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/stream_transform-2.1.1", + "packageUri": "lib/", + "languageVersion": "3.1" + }, { "name": "string_scanner", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/string_scanner-1.4.1", "packageUri": "lib/", "languageVersion": "3.1" }, + { + "name": "talker", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/talker-5.1.15", + "packageUri": "lib/", + "languageVersion": "2.17" + }, + { + "name": "talker_flutter", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/talker_flutter-5.1.15", + "packageUri": "lib/", + "languageVersion": "3.6" + }, + { + "name": "talker_logger", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/talker_logger-5.1.15", + "packageUri": "lib/", + "languageVersion": "2.15" + }, { "name": "term_glyph", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/term_glyph-1.2.2", "packageUri": "lib/", "languageVersion": "3.1" }, + { + "name": "test", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/test-1.26.3", + "packageUri": "lib/", + "languageVersion": "3.5" + }, { "name": "test_api", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/test_api-0.7.7", "packageUri": "lib/", "languageVersion": "3.5" }, + { + "name": "test_core", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/test_core-0.6.12", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "typed_data", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/typed_data-1.4.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "url_launcher_linux", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.2", + "packageUri": "lib/", + "languageVersion": "3.8" + }, + { + "name": "url_launcher_platform_interface", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/url_launcher_platform_interface-2.3.2", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "url_launcher_web", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.2", + "packageUri": "lib/", + "languageVersion": "3.10" + }, + { + "name": "url_launcher_windows", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.5", + "packageUri": "lib/", + "languageVersion": "3.8" + }, + { + "name": "uuid", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/uuid-4.5.3", + "packageUri": "lib/", + "languageVersion": "3.0" + }, { "name": "vector_math", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/vector_math-2.2.0", @@ -157,6 +811,54 @@ "packageUri": "lib/", "languageVersion": "3.5" }, + { + "name": "watcher", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/watcher-1.2.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "web", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/web-1.1.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "web_socket", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/web_socket-1.0.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "web_socket_channel", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/web_socket_channel-3.0.3", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "webkit_inspection_protocol", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/webkit_inspection_protocol-1.2.1", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "win32", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/win32-5.15.0", + "packageUri": "lib/", + "languageVersion": "3.8" + }, + { + "name": "xdg_directories", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/xdg_directories-1.1.0", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "yaml", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/yaml-3.1.3", + "packageUri": "lib/", + "languageVersion": "3.4" + }, { "name": "arbiter", "rootUri": "../", diff --git a/useragent/.dart_tool/package_graph.json b/useragent/.dart_tool/package_graph.json index 5affe43..c9e0bdd 100644 --- a/useragent/.dart_tool/package_graph.json +++ b/useragent/.dart_tool/package_graph.json @@ -5,13 +5,84 @@ "packages": [ { "name": "arbiter", - "version": "0.1.0", + "version": "1.0.0+1", "dependencies": [ - "flutter" + "biometric_signature", + "cryptography", + "cryptography_flutter", + "cupertino_icons", + "flutter", + "flutter_adaptive_scaffold", + "flutter_hooks", + "flutter_secure_storage", + "grpc", + "hooks_riverpod", + "mtcore", + "riverpod", + "riverpod_annotation", + "sizer", + "talker" ], "devDependencies": [ + "build_runner", "flutter_lints", - "flutter_test" + "flutter_test", + "riverpod_generator" + ] + }, + { + "name": "build_runner", + "version": "2.12.2", + "dependencies": [ + "analyzer", + "args", + "async", + "build", + "build_config", + "build_daemon", + "built_collection", + "built_value", + "code_builder", + "collection", + "convert", + "crypto", + "dart_style", + "glob", + "graphs", + "http_multi_server", + "io", + "json_annotation", + "logging", + "meta", + "mime", + "package_config", + "path", + "pool", + "pub_semver", + "shelf", + "shelf_web_socket", + "stream_transform", + "watcher", + "web_socket_channel", + "yaml" + ] + }, + { + "name": "riverpod_generator", + "version": "4.0.0+1", + "dependencies": [ + "analyzer", + "analyzer_buffer", + "build", + "build_config", + "collection", + "crypto", + "meta", + "mockito", + "path", + "riverpod_analyzer_utils", + "riverpod_annotation", + "source_gen" ] }, { @@ -39,6 +110,146 @@ "vector_math" ] }, + { + "name": "flutter_hooks", + "version": "0.21.3+1", + "dependencies": [ + "flutter" + ] + }, + { + "name": "grpc", + "version": "5.1.0", + "dependencies": [ + "async", + "clock", + "crypto", + "fixnum", + "googleapis_auth", + "http", + "http2", + "meta", + "protobuf", + "web" + ] + }, + { + "name": "riverpod_annotation", + "version": "4.0.0", + "dependencies": [ + "meta", + "riverpod" + ] + }, + { + "name": "cryptography_flutter", + "version": "2.3.4", + "dependencies": [ + "cryptography", + "flutter" + ] + }, + { + "name": "flutter_secure_storage", + "version": "10.0.0", + "dependencies": [ + "flutter", + "flutter_secure_storage_darwin", + "flutter_secure_storage_linux", + "flutter_secure_storage_platform_interface", + "flutter_secure_storage_web", + "flutter_secure_storage_windows", + "meta" + ] + }, + { + "name": "cryptography", + "version": "2.9.0", + "dependencies": [ + "collection", + "crypto", + "ffi", + "meta", + "typed_data" + ] + }, + { + "name": "mtcore", + "version": "1.0.6", + "dependencies": [ + "bloc", + "flutter", + "flutter_bloc", + "flutter_hooks", + "flutter_riverpod", + "flutter_spinkit", + "freezed_annotation", + "hooks_riverpod", + "percent_indicator", + "rive", + "riverpod_annotation", + "talker_flutter" + ] + }, + { + "name": "biometric_signature", + "version": "10.2.0", + "dependencies": [ + "flutter", + "plugin_platform_interface" + ] + }, + { + "name": "sizer", + "version": "3.1.3", + "dependencies": [ + "flutter" + ] + }, + { + "name": "hooks_riverpod", + "version": "3.1.0", + "dependencies": [ + "collection", + "flutter", + "flutter_hooks", + "flutter_riverpod", + "flutter_test", + "riverpod", + "state_notifier" + ] + }, + { + "name": "riverpod", + "version": "3.1.0", + "dependencies": [ + "async", + "clock", + "collection", + "meta", + "state_notifier", + "test" + ] + }, + { + "name": "talker", + "version": "5.1.15", + "dependencies": [ + "talker_logger" + ] + }, + { + "name": "flutter_adaptive_scaffold", + "version": "0.3.3+1", + "dependencies": [ + "flutter" + ] + }, + { + "name": "cupertino_icons", + "version": "1.0.8", + "dependencies": [] + }, { "name": "flutter", "version": "0.0.0", @@ -51,6 +262,329 @@ "vector_math" ] }, + { + "name": "yaml", + "version": "3.1.3", + "dependencies": [ + "collection", + "source_span", + "string_scanner" + ] + }, + { + "name": "web_socket_channel", + "version": "3.0.3", + "dependencies": [ + "async", + "crypto", + "stream_channel", + "web", + "web_socket" + ] + }, + { + "name": "watcher", + "version": "1.2.1", + "dependencies": [ + "async", + "path" + ] + }, + { + "name": "stream_transform", + "version": "2.1.1", + "dependencies": [] + }, + { + "name": "shelf_web_socket", + "version": "3.0.0", + "dependencies": [ + "shelf", + "stream_channel", + "web_socket_channel" + ] + }, + { + "name": "shelf", + "version": "1.4.2", + "dependencies": [ + "async", + "collection", + "http_parser", + "path", + "stack_trace", + "stream_channel" + ] + }, + { + "name": "pub_semver", + "version": "2.2.0", + "dependencies": [ + "collection" + ] + }, + { + "name": "pool", + "version": "1.5.2", + "dependencies": [ + "async", + "stack_trace" + ] + }, + { + "name": "path", + "version": "1.9.1", + "dependencies": [] + }, + { + "name": "package_config", + "version": "2.2.0", + "dependencies": [ + "path" + ] + }, + { + "name": "mime", + "version": "2.0.0", + "dependencies": [] + }, + { + "name": "meta", + "version": "1.17.0", + "dependencies": [] + }, + { + "name": "logging", + "version": "1.3.0", + "dependencies": [] + }, + { + "name": "json_annotation", + "version": "4.11.0", + "dependencies": [ + "meta" + ] + }, + { + "name": "io", + "version": "1.0.5", + "dependencies": [ + "meta", + "path", + "string_scanner" + ] + }, + { + "name": "http_multi_server", + "version": "3.2.2", + "dependencies": [ + "async" + ] + }, + { + "name": "graphs", + "version": "2.3.2", + "dependencies": [ + "collection" + ] + }, + { + "name": "glob", + "version": "2.1.3", + "dependencies": [ + "async", + "collection", + "file", + "path", + "string_scanner" + ] + }, + { + "name": "dart_style", + "version": "3.1.3", + "dependencies": [ + "analyzer", + "args", + "collection", + "package_config", + "path", + "pub_semver", + "source_span", + "yaml" + ] + }, + { + "name": "crypto", + "version": "3.0.7", + "dependencies": [ + "typed_data" + ] + }, + { + "name": "convert", + "version": "3.1.2", + "dependencies": [ + "typed_data" + ] + }, + { + "name": "collection", + "version": "1.19.1", + "dependencies": [] + }, + { + "name": "code_builder", + "version": "4.11.1", + "dependencies": [ + "built_collection", + "built_value", + "collection", + "matcher", + "meta" + ] + }, + { + "name": "built_value", + "version": "8.12.4", + "dependencies": [ + "built_collection", + "collection", + "fixnum", + "meta" + ] + }, + { + "name": "built_collection", + "version": "5.1.1", + "dependencies": [] + }, + { + "name": "build_daemon", + "version": "4.1.1", + "dependencies": [ + "built_collection", + "built_value", + "crypto", + "http_multi_server", + "logging", + "path", + "pool", + "shelf", + "shelf_web_socket", + "stream_transform", + "watcher", + "web_socket_channel" + ] + }, + { + "name": "build_config", + "version": "1.3.0", + "dependencies": [ + "checked_yaml", + "json_annotation", + "path", + "pubspec_parse" + ] + }, + { + "name": "build", + "version": "4.0.4", + "dependencies": [ + "analyzer", + "crypto", + "glob", + "logging", + "package_config", + "path" + ] + }, + { + "name": "async", + "version": "2.13.0", + "dependencies": [ + "collection", + "meta" + ] + }, + { + "name": "args", + "version": "2.7.0", + "dependencies": [] + }, + { + "name": "analyzer", + "version": "8.4.1", + "dependencies": [ + "_fe_analyzer_shared", + "collection", + "convert", + "crypto", + "glob", + "meta", + "package_config", + "path", + "pub_semver", + "source_span", + "watcher", + "yaml" + ] + }, + { + "name": "source_gen", + "version": "4.2.1", + "dependencies": [ + "analyzer", + "async", + "build", + "dart_style", + "glob", + "path", + "pub_semver", + "source_span", + "yaml" + ] + }, + { + "name": "riverpod_analyzer_utils", + "version": "1.0.0-dev.8", + "dependencies": [ + "analyzer", + "analyzer_buffer", + "collection", + "crypto", + "freezed_annotation", + "meta", + "path", + "source_span" + ] + }, + { + "name": "mockito", + "version": "5.6.3", + "dependencies": [ + "analyzer", + "build", + "code_builder", + "collection", + "dart_style", + "matcher", + "meta", + "path", + "source_gen", + "test_api" + ] + }, + { + "name": "analyzer_buffer", + "version": "0.1.11", + "dependencies": [ + "analyzer", + "collection", + "meta", + "path", + "source_gen" + ] + }, { "name": "lints", "version": "6.1.0", @@ -63,16 +597,6 @@ "async" ] }, - { - "name": "meta", - "version": "1.17.0", - "dependencies": [] - }, - { - "name": "collection", - "version": "1.19.1", - "dependencies": [] - }, { "name": "leak_tracker_flutter_testing", "version": "3.0.10", @@ -109,11 +633,6 @@ "collection" ] }, - { - "name": "path", - "version": "1.9.1", - "dependencies": [] - }, { "name": "matcher", "version": "0.12.17", @@ -140,6 +659,236 @@ "term_glyph" ] }, + { + "name": "web", + "version": "1.1.1", + "dependencies": [] + }, + { + "name": "protobuf", + "version": "6.0.0", + "dependencies": [ + "collection", + "fixnum", + "meta" + ] + }, + { + "name": "http2", + "version": "2.3.1", + "dependencies": [] + }, + { + "name": "http", + "version": "1.6.0", + "dependencies": [ + "async", + "http_parser", + "meta", + "web" + ] + }, + { + "name": "googleapis_auth", + "version": "2.0.0", + "dependencies": [ + "args", + "crypto", + "google_identity_services_web", + "http", + "http_parser" + ] + }, + { + "name": "fixnum", + "version": "1.1.1", + "dependencies": [] + }, + { + "name": "flutter_secure_storage_windows", + "version": "4.1.0", + "dependencies": [ + "ffi", + "flutter", + "flutter_secure_storage_platform_interface", + "path", + "path_provider", + "win32" + ] + }, + { + "name": "flutter_secure_storage_web", + "version": "2.1.0", + "dependencies": [ + "flutter", + "flutter_secure_storage_platform_interface", + "flutter_web_plugins", + "web" + ] + }, + { + "name": "flutter_secure_storage_platform_interface", + "version": "2.0.1", + "dependencies": [ + "flutter", + "plugin_platform_interface" + ] + }, + { + "name": "flutter_secure_storage_linux", + "version": "3.0.0", + "dependencies": [ + "flutter", + "flutter_secure_storage_platform_interface" + ] + }, + { + "name": "flutter_secure_storage_darwin", + "version": "0.2.0", + "dependencies": [ + "flutter", + "plugin_platform_interface" + ] + }, + { + "name": "typed_data", + "version": "1.4.0", + "dependencies": [ + "collection" + ] + }, + { + "name": "ffi", + "version": "2.2.0", + "dependencies": [] + }, + { + "name": "talker_flutter", + "version": "5.1.15", + "dependencies": [ + "flutter", + "group_button", + "path_provider", + "share_plus", + "talker", + "web" + ] + }, + { + "name": "rive", + "version": "0.14.4", + "dependencies": [ + "flutter", + "flutter_web_plugins", + "meta", + "rive_native" + ] + }, + { + "name": "percent_indicator", + "version": "4.2.5", + "dependencies": [ + "flutter" + ] + }, + { + "name": "freezed_annotation", + "version": "3.1.0", + "dependencies": [ + "collection", + "json_annotation", + "meta" + ] + }, + { + "name": "flutter_spinkit", + "version": "5.2.2", + "dependencies": [ + "flutter" + ] + }, + { + "name": "flutter_riverpod", + "version": "3.1.0", + "dependencies": [ + "collection", + "flutter", + "flutter_test", + "meta", + "riverpod", + "state_notifier" + ] + }, + { + "name": "flutter_bloc", + "version": "9.1.1", + "dependencies": [ + "bloc", + "flutter", + "provider" + ] + }, + { + "name": "bloc", + "version": "9.2.0", + "dependencies": [ + "meta" + ] + }, + { + "name": "plugin_platform_interface", + "version": "2.1.8", + "dependencies": [ + "meta" + ] + }, + { + "name": "state_notifier", + "version": "1.0.0", + "dependencies": [ + "meta" + ] + }, + { + "name": "test", + "version": "1.26.3", + "dependencies": [ + "analyzer", + "async", + "boolean_selector", + "collection", + "coverage", + "http_multi_server", + "io", + "js", + "matcher", + "node_preamble", + "package_config", + "path", + "pool", + "shelf", + "shelf_packages_handler", + "shelf_static", + "shelf_web_socket", + "source_span", + "stack_trace", + "stream_channel", + "test_api", + "test_core", + "typed_data", + "web_socket_channel", + "webkit_inspection_protocol", + "yaml" + ] + }, + { + "name": "talker_logger", + "version": "5.1.15", + "dependencies": [ + "ansicolor", + "web" + ] + }, { "name": "sky_engine", "version": "0.0.0", @@ -158,10 +907,70 @@ "dependencies": [] }, { - "name": "async", - "version": "2.13.0", + "name": "string_scanner", + "version": "1.4.1", + "dependencies": [ + "source_span" + ] + }, + { + "name": "source_span", + "version": "1.10.2", "dependencies": [ "collection", + "path", + "term_glyph" + ] + }, + { + "name": "web_socket", + "version": "1.0.1", + "dependencies": [ + "web" + ] + }, + { + "name": "http_parser", + "version": "4.1.2", + "dependencies": [ + "collection", + "source_span", + "string_scanner", + "typed_data" + ] + }, + { + "name": "file", + "version": "7.0.1", + "dependencies": [ + "meta", + "path" + ] + }, + { + "name": "pubspec_parse", + "version": "1.5.0", + "dependencies": [ + "checked_yaml", + "collection", + "json_annotation", + "pub_semver", + "yaml" + ] + }, + { + "name": "checked_yaml", + "version": "2.0.4", + "dependencies": [ + "json_annotation", + "source_span", + "yaml" + ] + }, + { + "name": "_fe_analyzer_shared", + "version": "91.0.0", + "dependencies": [ "meta" ] }, @@ -190,22 +999,6 @@ "version": "1.2.2", "dependencies": [] }, - { - "name": "string_scanner", - "version": "1.4.1", - "dependencies": [ - "source_span" - ] - }, - { - "name": "source_span", - "version": "1.10.2", - "dependencies": [ - "collection", - "path", - "term_glyph" - ] - }, { "name": "boolean_selector", "version": "2.1.2", @@ -214,10 +1007,391 @@ "string_scanner" ] }, + { + "name": "google_identity_services_web", + "version": "0.3.3+1", + "dependencies": [ + "meta", + "web" + ] + }, + { + "name": "win32", + "version": "5.15.0", + "dependencies": [ + "ffi" + ] + }, + { + "name": "path_provider", + "version": "2.1.5", + "dependencies": [ + "flutter", + "path_provider_android", + "path_provider_foundation", + "path_provider_linux", + "path_provider_platform_interface", + "path_provider_windows" + ] + }, + { + "name": "flutter_web_plugins", + "version": "0.0.0", + "dependencies": [ + "flutter" + ] + }, + { + "name": "share_plus", + "version": "12.0.1", + "dependencies": [ + "cross_file", + "ffi", + "file", + "flutter", + "flutter_web_plugins", + "meta", + "mime", + "share_plus_platform_interface", + "url_launcher_linux", + "url_launcher_platform_interface", + "url_launcher_web", + "url_launcher_windows", + "web", + "win32" + ] + }, + { + "name": "group_button", + "version": "5.3.4", + "dependencies": [ + "flutter" + ] + }, + { + "name": "rive_native", + "version": "0.1.4", + "dependencies": [ + "args", + "ffi", + "flutter", + "flutter_web_plugins", + "graphs", + "http", + "meta", + "path", + "plugin_platform_interface", + "vector_math", + "web" + ] + }, + { + "name": "provider", + "version": "6.1.5+1", + "dependencies": [ + "collection", + "flutter", + "nested" + ] + }, + { + "name": "webkit_inspection_protocol", + "version": "1.2.1", + "dependencies": [ + "logging" + ] + }, + { + "name": "test_core", + "version": "0.6.12", + "dependencies": [ + "analyzer", + "args", + "async", + "boolean_selector", + "collection", + "coverage", + "frontend_server_client", + "glob", + "io", + "meta", + "package_config", + "path", + "pool", + "source_map_stack_trace", + "source_maps", + "source_span", + "stack_trace", + "stream_channel", + "test_api", + "vm_service", + "yaml" + ] + }, + { + "name": "shelf_static", + "version": "1.1.3", + "dependencies": [ + "convert", + "http_parser", + "mime", + "path", + "shelf" + ] + }, + { + "name": "shelf_packages_handler", + "version": "3.0.2", + "dependencies": [ + "path", + "shelf", + "shelf_static" + ] + }, + { + "name": "node_preamble", + "version": "2.0.2", + "dependencies": [] + }, + { + "name": "js", + "version": "0.7.2", + "dependencies": [] + }, + { + "name": "coverage", + "version": "1.15.0", + "dependencies": [ + "args", + "cli_config", + "glob", + "logging", + "meta", + "package_config", + "path", + "source_maps", + "stack_trace", + "vm_service", + "yaml" + ] + }, + { + "name": "ansicolor", + "version": "2.0.3", + "dependencies": [] + }, { "name": "vm_service", "version": "15.0.2", "dependencies": [] + }, + { + "name": "path_provider_windows", + "version": "2.3.0", + "dependencies": [ + "ffi", + "flutter", + "path", + "path_provider_platform_interface" + ] + }, + { + "name": "path_provider_platform_interface", + "version": "2.1.2", + "dependencies": [ + "flutter", + "platform", + "plugin_platform_interface" + ] + }, + { + "name": "path_provider_linux", + "version": "2.2.1", + "dependencies": [ + "ffi", + "flutter", + "path", + "path_provider_platform_interface", + "xdg_directories" + ] + }, + { + "name": "path_provider_foundation", + "version": "2.6.0", + "dependencies": [ + "ffi", + "flutter", + "objective_c", + "path_provider_platform_interface" + ] + }, + { + "name": "path_provider_android", + "version": "2.2.22", + "dependencies": [ + "flutter", + "path_provider_platform_interface" + ] + }, + { + "name": "url_launcher_platform_interface", + "version": "2.3.2", + "dependencies": [ + "flutter", + "plugin_platform_interface" + ] + }, + { + "name": "url_launcher_linux", + "version": "3.2.2", + "dependencies": [ + "flutter", + "url_launcher_platform_interface" + ] + }, + { + "name": "url_launcher_windows", + "version": "3.1.5", + "dependencies": [ + "flutter", + "url_launcher_platform_interface" + ] + }, + { + "name": "url_launcher_web", + "version": "2.4.2", + "dependencies": [ + "flutter", + "flutter_web_plugins", + "url_launcher_platform_interface", + "web" + ] + }, + { + "name": "share_plus_platform_interface", + "version": "6.1.0", + "dependencies": [ + "cross_file", + "flutter", + "meta", + "mime", + "path_provider", + "plugin_platform_interface", + "uuid" + ] + }, + { + "name": "cross_file", + "version": "0.3.5+2", + "dependencies": [ + "meta", + "web" + ] + }, + { + "name": "nested", + "version": "1.0.0", + "dependencies": [ + "flutter" + ] + }, + { + "name": "source_maps", + "version": "0.10.13", + "dependencies": [ + "source_span" + ] + }, + { + "name": "source_map_stack_trace", + "version": "2.1.2", + "dependencies": [ + "path", + "source_maps", + "stack_trace" + ] + }, + { + "name": "frontend_server_client", + "version": "4.0.0", + "dependencies": [ + "async", + "path" + ] + }, + { + "name": "cli_config", + "version": "0.2.0", + "dependencies": [ + "args", + "yaml" + ] + }, + { + "name": "platform", + "version": "3.1.6", + "dependencies": [] + }, + { + "name": "xdg_directories", + "version": "1.1.0", + "dependencies": [ + "meta", + "path" + ] + }, + { + "name": "objective_c", + "version": "9.3.0", + "dependencies": [ + "code_assets", + "collection", + "ffi", + "hooks", + "logging", + "native_toolchain_c", + "pub_semver" + ] + }, + { + "name": "uuid", + "version": "4.5.3", + "dependencies": [ + "crypto", + "fixnum" + ] + }, + { + "name": "native_toolchain_c", + "version": "0.17.5", + "dependencies": [ + "code_assets", + "glob", + "hooks", + "logging", + "meta", + "pub_semver" + ] + }, + { + "name": "hooks", + "version": "1.0.2", + "dependencies": [ + "collection", + "crypto", + "logging", + "meta", + "pub_semver", + "yaml" + ] + }, + { + "name": "code_assets", + "version": "1.0.0", + "dependencies": [ + "collection", + "hooks" + ] } ], "configVersion": 1 diff --git a/useragent/README.md b/useragent/README.md index b73cfb0..cfb1511 100644 --- a/useragent/README.md +++ b/useragent/README.md @@ -1,4 +1,4 @@ -# app +# useragent A new Flutter project. diff --git a/useragent/android/.gitignore b/useragent/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/useragent/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/useragent/android/app/build.gradle.kts b/useragent/android/app/build.gradle.kts new file mode 100644 index 0000000..3199895 --- /dev/null +++ b/useragent/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.useragent" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.useragent" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/useragent/android/app/src/debug/AndroidManifest.xml b/useragent/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/useragent/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/useragent/android/app/src/main/AndroidManifest.xml b/useragent/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..435af81 --- /dev/null +++ b/useragent/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/useragent/android/app/src/main/kotlin/com/example/useragent/MainActivity.kt b/useragent/android/app/src/main/kotlin/com/example/useragent/MainActivity.kt new file mode 100644 index 0000000..abbe9cf --- /dev/null +++ b/useragent/android/app/src/main/kotlin/com/example/useragent/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.useragent + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/useragent/android/app/src/main/res/drawable-v21/launch_background.xml b/useragent/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/useragent/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/useragent/android/app/src/main/res/drawable/launch_background.xml b/useragent/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/useragent/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/useragent/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/useragent/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/useragent/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/useragent/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/useragent/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/useragent/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/useragent/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/useragent/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/useragent/android/app/src/main/res/values-night/styles.xml b/useragent/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/useragent/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/useragent/android/app/src/main/res/values/styles.xml b/useragent/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/useragent/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/useragent/android/app/src/profile/AndroidManifest.xml b/useragent/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/useragent/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/useragent/android/build.gradle.kts b/useragent/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/useragent/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/useragent/android/gradle.properties b/useragent/android/gradle.properties new file mode 100644 index 0000000..fbee1d8 --- /dev/null +++ b/useragent/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/useragent/android/gradle/wrapper/gradle-wrapper.properties b/useragent/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e4ef43f --- /dev/null +++ b/useragent/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/useragent/android/settings.gradle.kts b/useragent/android/settings.gradle.kts new file mode 100644 index 0000000..ca7fe06 --- /dev/null +++ b/useragent/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/useragent/lib/features/adaptive_switcher.dart b/useragent/lib/features/adaptive_switcher.dart new file mode 100644 index 0000000..fa8a3c4 --- /dev/null +++ b/useragent/lib/features/adaptive_switcher.dart @@ -0,0 +1,118 @@ +import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:sizer/sizer.dart'; + +const transitionDuration = Duration(milliseconds: 800); + +class AdaptiveBuilders { + WidgetBuilder? buildSmall; + WidgetBuilder? buildMediumLarge; + WidgetBuilder? buildLarge; + WidgetBuilder? buildExtraLarge; + WidgetBuilder? build; + + AdaptiveBuilders({ + this.buildSmall, + this.buildMediumLarge, + this.buildLarge, + this.buildExtraLarge, + this.build, + }); +} + +class Destination { + final String label; + final String? tooltip; + final Icon icon; + final Icon? selectedIcon; + + final AdaptiveBuilders main; + final AdaptiveBuilders? secondary; + + Destination({ + required this.label, + required this.icon, + this.selectedIcon, + required this.main, + this.secondary, + this.tooltip, + }); +} + +class Switcher extends StatelessWidget { + final Widget? child; + + const Switcher({super.key, this.child}); + @override + Widget build(BuildContext context) { + return AnimatedSwitcher( + duration: Duration( + milliseconds: transitionDuration.inMilliseconds ~/ 100, + ), + transitionBuilder: (child, animation) { + return FadeTransition(opacity: animation, child: child); + }, + child: child, + ); + } +} + +WidgetBuilder? patchAnimated(WidgetBuilder? input) { + if (input == null) return null; + return (context) => Switcher(child: input(context)); +} + +class HomeRouter extends HookWidget { + final List destinations; + + HomeRouter({super.key, required this.destinations}) + : assert(destinations.isNotEmpty); + + @override + Widget build(BuildContext context) { + final selectedIndex = useState(0); + final destination = useMemoized(() => destinations[selectedIndex.value], [ + selectedIndex.value, + ]); + final dispatcher = useMemoized(() => destination.main, [ + selectedIndex.value, + ]); + final secondaryDispatcher = useMemoized(() => destination.secondary, [ + selectedIndex.value, + ]); + + return AdaptiveScaffold( + destinations: destinations + .map( + (destination) => NavigationDestination( + label: destination.label, + icon: destination.icon, + selectedIcon: destination.selectedIcon, + tooltip: destination.tooltip, + ), + ) + .toList(), + + selectedIndex: selectedIndex.value, + onSelectedIndexChange: (index) => selectedIndex.value = index, + useDrawer: true, + + smallBody: patchAnimated(dispatcher.buildSmall), + body: patchAnimated(dispatcher.build), + mediumLargeBody: patchAnimated(dispatcher.buildMediumLarge), + largeBody: patchAnimated(dispatcher.buildLarge), + extraLargeBody: patchAnimated(dispatcher.buildExtraLarge), + + smallSecondaryBody: patchAnimated(secondaryDispatcher?.buildSmall), + secondaryBody: patchAnimated(secondaryDispatcher?.build), + mediumLargeSecondaryBody: patchAnimated( + secondaryDispatcher?.buildMediumLarge, + ), + largeSecondaryBody: patchAnimated(secondaryDispatcher?.buildLarge), + extraLargeSecondaryBody: patchAnimated( + secondaryDispatcher?.buildExtraLarge, + ), + ); + } +} diff --git a/useragent/lib/features/bootstrap.dart b/useragent/lib/features/bootstrap.dart new file mode 100644 index 0000000..c4de910 --- /dev/null +++ b/useragent/lib/features/bootstrap.dart @@ -0,0 +1,26 @@ +import 'dart:async'; + +import 'package:arbiter/providers/key.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:mtcore/markettakers.dart'; + +class Bootstrap extends HookConsumerWidget { + final Completer completer; + + const Bootstrap({required this.completer}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final container = ProviderScope.containerOf(context); + final stages = useMemoized(() { + return [KeyBootstrapper(ref: container)]; + }, []); + final bootstrapper = useMemoized( + () => Bootstrapper(stages: stages, onCompleted: completer), + [stages], + ); + return bootstrapper; + } +} diff --git a/useragent/lib/features/pk_manager.dart b/useragent/lib/features/pk_manager.dart new file mode 100644 index 0000000..a9bfb62 --- /dev/null +++ b/useragent/lib/features/pk_manager.dart @@ -0,0 +1,19 @@ + +import 'package:flutter/services.dart'; + +enum KeyAlgorithm { + rsa, ecdsa, ed25519 +} + +// The API to handle without storing the private key in memory. +//The implementation will use platform-specific secure storage and signing capabilities. +abstract class KeyHandle { + KeyAlgorithm get alg; + Future> sign(List data); + Future> getPublicKey(); +} + +abstract class KeyManager { + Future get(); + Future create(); +} \ No newline at end of file diff --git a/useragent/lib/features/simple_ed25519.dart b/useragent/lib/features/simple_ed25519.dart new file mode 100644 index 0000000..f5e2183 --- /dev/null +++ b/useragent/lib/features/simple_ed25519.dart @@ -0,0 +1,94 @@ +import 'dart:convert'; + +import 'package:cryptography/cryptography.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:arbiter/features/pk_manager.dart'; + +final storage = FlutterSecureStorage( + aOptions: AndroidOptions.biometric( + enforceBiometrics: true, + biometricPromptTitle: 'Authentication Required', + ), + mOptions: MacOsOptions( + accessibility: KeychainAccessibility.unlocked_this_device, + label: "Arbiter", + description: "Confirm your identity to access vault", + synchronizable: false, + accessControlFlags: [ + AccessControlFlag.userPresence, + AccessControlFlag.privateKeyUsage, + ], + usesDataProtectionKeychain: true, + ), +); + +final processor = Ed25519(); + +class SimpleEd25519 extends KeyHandle { + final SimpleKeyPair _keyPair; + + SimpleEd25519({required SimpleKeyPair keyPair}) : _keyPair = keyPair; + + @override + KeyAlgorithm get alg => KeyAlgorithm.ed25519; + + @override + Future> getPublicKey() async { + final publicKey = await _keyPair.extractPublicKey(); + return publicKey.bytes; + } + + @override + Future> sign(List data) async { + final signature = await processor.sign(data, keyPair: _keyPair); + return signature.bytes; + } +} + +class SimpleEd25519Manager extends KeyManager { + static const _storageKey = "ed25519_identity"; + static const _storagePublicKey = "ed25519_public_key"; + + @override + Future create() async { + final storedKey = await get(); + if (storedKey != null) { + return storedKey; + } + + final newKey = await processor.newKeyPair(); + final rawKey = await newKey.extract(); + + final keyData = base64Encode(rawKey.bytes); + await storage.write(key: _storageKey, value: keyData); + + final publicKeyData = base64Encode(rawKey.publicKey.bytes); + await storage.write(key: _storagePublicKey, value: publicKeyData); + + return SimpleEd25519(keyPair: newKey); + } + + @override + Future get() async { + final storedKeyPair = await storage.read(key: _storageKey); + if (storedKeyPair == null) { + return null; + } + + final publicKeyData = await storage.read(key: _storagePublicKey); + final publicKeyRaw = base64Decode(publicKeyData!); + final publicKey = SimplePublicKey( + publicKeyRaw, + type: processor.keyPairType, + ); + + final keyBytes = base64Decode(storedKeyPair); + final keypair = SimpleKeyPairData( + keyBytes, + publicKey: publicKey, + type: processor.keyPairType, + ); + + return SimpleEd25519(keyPair: keypair); + } +} diff --git a/useragent/lib/home.dart b/useragent/lib/home.dart new file mode 100644 index 0000000..036e609 --- /dev/null +++ b/useragent/lib/home.dart @@ -0,0 +1,24 @@ +import 'package:arbiter/features/adaptive_switcher.dart'; +import 'package:arbiter/screens/about.dart'; +import 'package:arbiter/screens/calc.dart'; +import 'package:flutter/material.dart'; + +final _destinations = [ + Destination( + label: "About", + icon: Icon(Icons.info_outline), + main: AdaptiveBuilders(build: (_) => About()), + ), + Destination( + label: "Calc", + icon: Icon(Icons.calculate), + main: AdaptiveBuilders(build: (_) => CalcScreen()), + ), +]; + +class Home extends StatelessWidget { + @override + Widget build(BuildContext context) { + return HomeRouter(destinations: _destinations); + } +} diff --git a/useragent/lib/main.dart b/useragent/lib/main.dart index 244a702..bced8fe 100644 --- a/useragent/lib/main.dart +++ b/useragent/lib/main.dart @@ -1,122 +1,11 @@ -import 'package:flutter/material.dart'; +import 'package:arbiter/router.dart'; +import 'package:flutter/material.dart' hide Router; +import 'package:hooks_riverpod/hooks_riverpod.dart'; void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: .fromSeed(seedColor: Colors.deepPurple), - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: .center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), - ); - } + runApp( + MaterialApp( + home: ProviderScope(child: Scaffold(body: Router())), + ), + ); } diff --git a/useragent/lib/proto/arbiter.pb.dart b/useragent/lib/proto/arbiter.pb.dart new file mode 100644 index 0000000..a0f96d7 --- /dev/null +++ b/useragent/lib/proto/arbiter.pb.dart @@ -0,0 +1,107 @@ +// This is a generated file - do not edit. +// +// Generated from arbiter.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:async' as $async; +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +import 'client.pb.dart' as $0; +import 'user_agent.pb.dart' as $1; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +class ServerInfo extends $pb.GeneratedMessage { + factory ServerInfo({ + $core.String? version, + $core.List<$core.int>? certPublicKey, + }) { + final result = create(); + if (version != null) result.version = version; + if (certPublicKey != null) result.certPublicKey = certPublicKey; + return result; + } + + ServerInfo._(); + + factory ServerInfo.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ServerInfo.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ServerInfo', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'version') + ..a<$core.List<$core.int>>( + 2, _omitFieldNames ? '' : 'certPublicKey', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ServerInfo clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ServerInfo copyWith(void Function(ServerInfo) updates) => + super.copyWith((message) => updates(message as ServerInfo)) as ServerInfo; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ServerInfo create() => ServerInfo._(); + @$core.override + ServerInfo createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ServerInfo getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ServerInfo? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get version => $_getSZ(0); + @$pb.TagNumber(1) + set version($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasVersion() => $_has(0); + @$pb.TagNumber(1) + void clearVersion() => $_clearField(1); + + @$pb.TagNumber(2) + $core.List<$core.int> get certPublicKey => $_getN(1); + @$pb.TagNumber(2) + set certPublicKey($core.List<$core.int> value) => $_setBytes(1, value); + @$pb.TagNumber(2) + $core.bool hasCertPublicKey() => $_has(1); + @$pb.TagNumber(2) + void clearCertPublicKey() => $_clearField(2); +} + +class ArbiterServiceApi { + final $pb.RpcClient _client; + + ArbiterServiceApi(this._client); + + $async.Future<$0.ClientResponse> client( + $pb.ClientContext? ctx, $0.ClientRequest request) => + _client.invoke<$0.ClientResponse>( + ctx, 'ArbiterService', 'Client', request, $0.ClientResponse()); + $async.Future<$1.UserAgentResponse> userAgent( + $pb.ClientContext? ctx, $1.UserAgentRequest request) => + _client.invoke<$1.UserAgentResponse>( + ctx, 'ArbiterService', 'UserAgent', request, $1.UserAgentResponse()); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/useragent/lib/proto/arbiter.pbenum.dart b/useragent/lib/proto/arbiter.pbenum.dart new file mode 100644 index 0000000..f68d099 --- /dev/null +++ b/useragent/lib/proto/arbiter.pbenum.dart @@ -0,0 +1,11 @@ +// This is a generated file - do not edit. +// +// Generated from arbiter.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports diff --git a/useragent/lib/proto/arbiter.pbjson.dart b/useragent/lib/proto/arbiter.pbjson.dart new file mode 100644 index 0000000..3300005 --- /dev/null +++ b/useragent/lib/proto/arbiter.pbjson.dart @@ -0,0 +1,124 @@ +// This is a generated file - do not edit. +// +// Generated from arbiter.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports +// ignore_for_file: unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +import 'package:protobuf/well_known_types/google/protobuf/empty.pbjson.dart' + as $3; +import 'package:protobuf/well_known_types/google/protobuf/timestamp.pbjson.dart' + as $4; + +import 'client.pbjson.dart' as $0; +import 'evm.pbjson.dart' as $2; +import 'user_agent.pbjson.dart' as $1; + +@$core.Deprecated('Use serverInfoDescriptor instead') +const ServerInfo$json = { + '1': 'ServerInfo', + '2': [ + {'1': 'version', '3': 1, '4': 1, '5': 9, '10': 'version'}, + {'1': 'cert_public_key', '3': 2, '4': 1, '5': 12, '10': 'certPublicKey'}, + ], +}; + +/// Descriptor for `ServerInfo`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List serverInfoDescriptor = $convert.base64Decode( + 'CgpTZXJ2ZXJJbmZvEhgKB3ZlcnNpb24YASABKAlSB3ZlcnNpb24SJgoPY2VydF9wdWJsaWNfa2' + 'V5GAIgASgMUg1jZXJ0UHVibGljS2V5'); + +const $core.Map<$core.String, $core.dynamic> ArbiterServiceBase$json = { + '1': 'ArbiterService', + '2': [ + { + '1': 'Client', + '2': '.arbiter.client.ClientRequest', + '3': '.arbiter.client.ClientResponse', + '5': true, + '6': true + }, + { + '1': 'UserAgent', + '2': '.arbiter.user_agent.UserAgentRequest', + '3': '.arbiter.user_agent.UserAgentResponse', + '5': true, + '6': true + }, + ], +}; + +@$core.Deprecated('Use arbiterServiceDescriptor instead') +const $core.Map<$core.String, $core.Map<$core.String, $core.dynamic>> + ArbiterServiceBase$messageJson = { + '.arbiter.client.ClientRequest': $0.ClientRequest$json, + '.arbiter.client.AuthChallengeRequest': $0.AuthChallengeRequest$json, + '.arbiter.client.AuthChallengeSolution': $0.AuthChallengeSolution$json, + '.arbiter.client.ClientResponse': $0.ClientResponse$json, + '.arbiter.client.AuthChallenge': $0.AuthChallenge$json, + '.arbiter.client.AuthOk': $0.AuthOk$json, + '.arbiter.evm.EvmSignTransactionResponse': $2.EvmSignTransactionResponse$json, + '.arbiter.evm.TransactionEvalError': $2.TransactionEvalError$json, + '.google.protobuf.Empty': $3.Empty$json, + '.arbiter.evm.NoMatchingGrantError': $2.NoMatchingGrantError$json, + '.arbiter.evm.SpecificMeaning': $2.SpecificMeaning$json, + '.arbiter.evm.EtherTransferMeaning': $2.EtherTransferMeaning$json, + '.arbiter.evm.TokenTransferMeaning': $2.TokenTransferMeaning$json, + '.arbiter.evm.TokenInfo': $2.TokenInfo$json, + '.arbiter.evm.PolicyViolationsError': $2.PolicyViolationsError$json, + '.arbiter.evm.EvalViolation': $2.EvalViolation$json, + '.arbiter.evm.GasLimitExceededViolation': $2.GasLimitExceededViolation$json, + '.arbiter.evm.EvmAnalyzeTransactionResponse': + $2.EvmAnalyzeTransactionResponse$json, + '.arbiter.client.ClientConnectError': $0.ClientConnectError$json, + '.arbiter.user_agent.UserAgentRequest': $1.UserAgentRequest$json, + '.arbiter.user_agent.AuthChallengeRequest': $1.AuthChallengeRequest$json, + '.arbiter.user_agent.AuthChallengeSolution': $1.AuthChallengeSolution$json, + '.arbiter.user_agent.UnsealStart': $1.UnsealStart$json, + '.arbiter.user_agent.UnsealEncryptedKey': $1.UnsealEncryptedKey$json, + '.arbiter.evm.EvmGrantCreateRequest': $2.EvmGrantCreateRequest$json, + '.arbiter.evm.SharedSettings': $2.SharedSettings$json, + '.google.protobuf.Timestamp': $4.Timestamp$json, + '.arbiter.evm.TransactionRateLimit': $2.TransactionRateLimit$json, + '.arbiter.evm.SpecificGrant': $2.SpecificGrant$json, + '.arbiter.evm.EtherTransferSettings': $2.EtherTransferSettings$json, + '.arbiter.evm.VolumeRateLimit': $2.VolumeRateLimit$json, + '.arbiter.evm.TokenTransferSettings': $2.TokenTransferSettings$json, + '.arbiter.evm.EvmGrantDeleteRequest': $2.EvmGrantDeleteRequest$json, + '.arbiter.evm.EvmGrantListRequest': $2.EvmGrantListRequest$json, + '.arbiter.user_agent.ClientConnectionResponse': + $1.ClientConnectionResponse$json, + '.arbiter.user_agent.UserAgentResponse': $1.UserAgentResponse$json, + '.arbiter.user_agent.AuthChallenge': $1.AuthChallenge$json, + '.arbiter.user_agent.AuthOk': $1.AuthOk$json, + '.arbiter.user_agent.UnsealStartResponse': $1.UnsealStartResponse$json, + '.arbiter.evm.WalletCreateResponse': $2.WalletCreateResponse$json, + '.arbiter.evm.WalletEntry': $2.WalletEntry$json, + '.arbiter.evm.WalletListResponse': $2.WalletListResponse$json, + '.arbiter.evm.WalletList': $2.WalletList$json, + '.arbiter.evm.EvmGrantCreateResponse': $2.EvmGrantCreateResponse$json, + '.arbiter.evm.EvmGrantDeleteResponse': $2.EvmGrantDeleteResponse$json, + '.arbiter.evm.EvmGrantListResponse': $2.EvmGrantListResponse$json, + '.arbiter.evm.EvmGrantList': $2.EvmGrantList$json, + '.arbiter.evm.GrantEntry': $2.GrantEntry$json, + '.arbiter.user_agent.ClientConnectionRequest': + $1.ClientConnectionRequest$json, + '.arbiter.user_agent.ClientConnectionCancel': $1.ClientConnectionCancel$json, +}; + +/// Descriptor for `ArbiterService`. Decode as a `google.protobuf.ServiceDescriptorProto`. +final $typed_data.Uint8List arbiterServiceDescriptor = $convert.base64Decode( + 'Cg5BcmJpdGVyU2VydmljZRJLCgZDbGllbnQSHS5hcmJpdGVyLmNsaWVudC5DbGllbnRSZXF1ZX' + 'N0Gh4uYXJiaXRlci5jbGllbnQuQ2xpZW50UmVzcG9uc2UoATABElwKCVVzZXJBZ2VudBIkLmFy' + 'Yml0ZXIudXNlcl9hZ2VudC5Vc2VyQWdlbnRSZXF1ZXN0GiUuYXJiaXRlci51c2VyX2FnZW50Ll' + 'VzZXJBZ2VudFJlc3BvbnNlKAEwAQ=='); diff --git a/useragent/lib/proto/arbiter.pbserver.dart b/useragent/lib/proto/arbiter.pbserver.dart new file mode 100644 index 0000000..59ed28f --- /dev/null +++ b/useragent/lib/proto/arbiter.pbserver.dart @@ -0,0 +1,56 @@ +// This is a generated file - do not edit. +// +// Generated from arbiter.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:async' as $async; +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +import 'arbiter.pbjson.dart'; +import 'client.pb.dart' as $0; +import 'user_agent.pb.dart' as $1; + +export 'arbiter.pb.dart'; + +abstract class ArbiterServiceBase extends $pb.GeneratedService { + $async.Future<$0.ClientResponse> client( + $pb.ServerContext ctx, $0.ClientRequest request); + $async.Future<$1.UserAgentResponse> userAgent( + $pb.ServerContext ctx, $1.UserAgentRequest request); + + $pb.GeneratedMessage createRequest($core.String methodName) { + switch (methodName) { + case 'Client': + return $0.ClientRequest(); + case 'UserAgent': + return $1.UserAgentRequest(); + default: + throw $core.ArgumentError('Unknown method: $methodName'); + } + } + + $async.Future<$pb.GeneratedMessage> handleCall($pb.ServerContext ctx, + $core.String methodName, $pb.GeneratedMessage request) { + switch (methodName) { + case 'Client': + return client(ctx, request as $0.ClientRequest); + case 'UserAgent': + return userAgent(ctx, request as $1.UserAgentRequest); + default: + throw $core.ArgumentError('Unknown method: $methodName'); + } + } + + $core.Map<$core.String, $core.dynamic> get $json => ArbiterServiceBase$json; + $core.Map<$core.String, $core.Map<$core.String, $core.dynamic>> + get $messageJson => ArbiterServiceBase$messageJson; +} diff --git a/useragent/lib/proto/client.pb.dart b/useragent/lib/proto/client.pb.dart new file mode 100644 index 0000000..4a5e8f5 --- /dev/null +++ b/useragent/lib/proto/client.pb.dart @@ -0,0 +1,551 @@ +// This is a generated file - do not edit. +// +// Generated from client.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +import 'client.pbenum.dart'; +import 'evm.pb.dart' as $0; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +export 'client.pbenum.dart'; + +class AuthChallengeRequest extends $pb.GeneratedMessage { + factory AuthChallengeRequest({ + $core.List<$core.int>? pubkey, + }) { + final result = create(); + if (pubkey != null) result.pubkey = pubkey; + return result; + } + + AuthChallengeRequest._(); + + factory AuthChallengeRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory AuthChallengeRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'AuthChallengeRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthChallengeRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthChallengeRequest copyWith(void Function(AuthChallengeRequest) updates) => + super.copyWith((message) => updates(message as AuthChallengeRequest)) + as AuthChallengeRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static AuthChallengeRequest create() => AuthChallengeRequest._(); + @$core.override + AuthChallengeRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static AuthChallengeRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static AuthChallengeRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get pubkey => $_getN(0); + @$pb.TagNumber(1) + set pubkey($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasPubkey() => $_has(0); + @$pb.TagNumber(1) + void clearPubkey() => $_clearField(1); +} + +class AuthChallenge extends $pb.GeneratedMessage { + factory AuthChallenge({ + $core.List<$core.int>? pubkey, + $core.int? nonce, + }) { + final result = create(); + if (pubkey != null) result.pubkey = pubkey; + if (nonce != null) result.nonce = nonce; + return result; + } + + AuthChallenge._(); + + factory AuthChallenge.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory AuthChallenge.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'AuthChallenge', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY) + ..aI(2, _omitFieldNames ? '' : 'nonce') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthChallenge clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthChallenge copyWith(void Function(AuthChallenge) updates) => + super.copyWith((message) => updates(message as AuthChallenge)) + as AuthChallenge; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static AuthChallenge create() => AuthChallenge._(); + @$core.override + AuthChallenge createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static AuthChallenge getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static AuthChallenge? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get pubkey => $_getN(0); + @$pb.TagNumber(1) + set pubkey($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasPubkey() => $_has(0); + @$pb.TagNumber(1) + void clearPubkey() => $_clearField(1); + + @$pb.TagNumber(2) + $core.int get nonce => $_getIZ(1); + @$pb.TagNumber(2) + set nonce($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasNonce() => $_has(1); + @$pb.TagNumber(2) + void clearNonce() => $_clearField(2); +} + +class AuthChallengeSolution extends $pb.GeneratedMessage { + factory AuthChallengeSolution({ + $core.List<$core.int>? signature, + }) { + final result = create(); + if (signature != null) result.signature = signature; + return result; + } + + AuthChallengeSolution._(); + + factory AuthChallengeSolution.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory AuthChallengeSolution.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'AuthChallengeSolution', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'signature', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthChallengeSolution clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthChallengeSolution copyWith( + void Function(AuthChallengeSolution) updates) => + super.copyWith((message) => updates(message as AuthChallengeSolution)) + as AuthChallengeSolution; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static AuthChallengeSolution create() => AuthChallengeSolution._(); + @$core.override + AuthChallengeSolution createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static AuthChallengeSolution getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static AuthChallengeSolution? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get signature => $_getN(0); + @$pb.TagNumber(1) + set signature($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasSignature() => $_has(0); + @$pb.TagNumber(1) + void clearSignature() => $_clearField(1); +} + +class AuthOk extends $pb.GeneratedMessage { + factory AuthOk() => create(); + + AuthOk._(); + + factory AuthOk.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory AuthOk.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'AuthOk', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'), + createEmptyInstance: create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthOk clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthOk copyWith(void Function(AuthOk) updates) => + super.copyWith((message) => updates(message as AuthOk)) as AuthOk; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static AuthOk create() => AuthOk._(); + @$core.override + AuthOk createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static AuthOk getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static AuthOk? _defaultInstance; +} + +enum ClientRequest_Payload { + authChallengeRequest, + authChallengeSolution, + notSet +} + +class ClientRequest extends $pb.GeneratedMessage { + factory ClientRequest({ + AuthChallengeRequest? authChallengeRequest, + AuthChallengeSolution? authChallengeSolution, + }) { + final result = create(); + if (authChallengeRequest != null) + result.authChallengeRequest = authChallengeRequest; + if (authChallengeSolution != null) + result.authChallengeSolution = authChallengeSolution; + return result; + } + + ClientRequest._(); + + factory ClientRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ClientRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, ClientRequest_Payload> + _ClientRequest_PayloadByTag = { + 1: ClientRequest_Payload.authChallengeRequest, + 2: ClientRequest_Payload.authChallengeSolution, + 0: ClientRequest_Payload.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ClientRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'), + createEmptyInstance: create) + ..oo(0, [1, 2]) + ..aOM( + 1, _omitFieldNames ? '' : 'authChallengeRequest', + subBuilder: AuthChallengeRequest.create) + ..aOM( + 2, _omitFieldNames ? '' : 'authChallengeSolution', + subBuilder: AuthChallengeSolution.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientRequest copyWith(void Function(ClientRequest) updates) => + super.copyWith((message) => updates(message as ClientRequest)) + as ClientRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ClientRequest create() => ClientRequest._(); + @$core.override + ClientRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ClientRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ClientRequest? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + ClientRequest_Payload whichPayload() => + _ClientRequest_PayloadByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + void clearPayload() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + AuthChallengeRequest get authChallengeRequest => $_getN(0); + @$pb.TagNumber(1) + set authChallengeRequest(AuthChallengeRequest value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasAuthChallengeRequest() => $_has(0); + @$pb.TagNumber(1) + void clearAuthChallengeRequest() => $_clearField(1); + @$pb.TagNumber(1) + AuthChallengeRequest ensureAuthChallengeRequest() => $_ensure(0); + + @$pb.TagNumber(2) + AuthChallengeSolution get authChallengeSolution => $_getN(1); + @$pb.TagNumber(2) + set authChallengeSolution(AuthChallengeSolution value) => + $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasAuthChallengeSolution() => $_has(1); + @$pb.TagNumber(2) + void clearAuthChallengeSolution() => $_clearField(2); + @$pb.TagNumber(2) + AuthChallengeSolution ensureAuthChallengeSolution() => $_ensure(1); +} + +class ClientConnectError extends $pb.GeneratedMessage { + factory ClientConnectError({ + ClientConnectError_Code? code, + }) { + final result = create(); + if (code != null) result.code = code; + return result; + } + + ClientConnectError._(); + + factory ClientConnectError.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ClientConnectError.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ClientConnectError', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'), + createEmptyInstance: create) + ..aE(1, _omitFieldNames ? '' : 'code', + enumValues: ClientConnectError_Code.values) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientConnectError clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientConnectError copyWith(void Function(ClientConnectError) updates) => + super.copyWith((message) => updates(message as ClientConnectError)) + as ClientConnectError; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ClientConnectError create() => ClientConnectError._(); + @$core.override + ClientConnectError createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ClientConnectError getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ClientConnectError? _defaultInstance; + + @$pb.TagNumber(1) + ClientConnectError_Code get code => $_getN(0); + @$pb.TagNumber(1) + set code(ClientConnectError_Code value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasCode() => $_has(0); + @$pb.TagNumber(1) + void clearCode() => $_clearField(1); +} + +enum ClientResponse_Payload { + authChallenge, + authOk, + evmSignTransaction, + evmAnalyzeTransaction, + clientConnectError, + notSet +} + +class ClientResponse extends $pb.GeneratedMessage { + factory ClientResponse({ + AuthChallenge? authChallenge, + AuthOk? authOk, + $0.EvmSignTransactionResponse? evmSignTransaction, + $0.EvmAnalyzeTransactionResponse? evmAnalyzeTransaction, + ClientConnectError? clientConnectError, + }) { + final result = create(); + if (authChallenge != null) result.authChallenge = authChallenge; + if (authOk != null) result.authOk = authOk; + if (evmSignTransaction != null) + result.evmSignTransaction = evmSignTransaction; + if (evmAnalyzeTransaction != null) + result.evmAnalyzeTransaction = evmAnalyzeTransaction; + if (clientConnectError != null) + result.clientConnectError = clientConnectError; + return result; + } + + ClientResponse._(); + + factory ClientResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ClientResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, ClientResponse_Payload> + _ClientResponse_PayloadByTag = { + 1: ClientResponse_Payload.authChallenge, + 2: ClientResponse_Payload.authOk, + 3: ClientResponse_Payload.evmSignTransaction, + 4: ClientResponse_Payload.evmAnalyzeTransaction, + 5: ClientResponse_Payload.clientConnectError, + 0: ClientResponse_Payload.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ClientResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'), + createEmptyInstance: create) + ..oo(0, [1, 2, 3, 4, 5]) + ..aOM(1, _omitFieldNames ? '' : 'authChallenge', + subBuilder: AuthChallenge.create) + ..aOM(2, _omitFieldNames ? '' : 'authOk', subBuilder: AuthOk.create) + ..aOM<$0.EvmSignTransactionResponse>( + 3, _omitFieldNames ? '' : 'evmSignTransaction', + subBuilder: $0.EvmSignTransactionResponse.create) + ..aOM<$0.EvmAnalyzeTransactionResponse>( + 4, _omitFieldNames ? '' : 'evmAnalyzeTransaction', + subBuilder: $0.EvmAnalyzeTransactionResponse.create) + ..aOM(5, _omitFieldNames ? '' : 'clientConnectError', + subBuilder: ClientConnectError.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientResponse copyWith(void Function(ClientResponse) updates) => + super.copyWith((message) => updates(message as ClientResponse)) + as ClientResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ClientResponse create() => ClientResponse._(); + @$core.override + ClientResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ClientResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ClientResponse? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + @$pb.TagNumber(4) + @$pb.TagNumber(5) + ClientResponse_Payload whichPayload() => + _ClientResponse_PayloadByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + @$pb.TagNumber(4) + @$pb.TagNumber(5) + void clearPayload() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + AuthChallenge get authChallenge => $_getN(0); + @$pb.TagNumber(1) + set authChallenge(AuthChallenge value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasAuthChallenge() => $_has(0); + @$pb.TagNumber(1) + void clearAuthChallenge() => $_clearField(1); + @$pb.TagNumber(1) + AuthChallenge ensureAuthChallenge() => $_ensure(0); + + @$pb.TagNumber(2) + AuthOk get authOk => $_getN(1); + @$pb.TagNumber(2) + set authOk(AuthOk value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasAuthOk() => $_has(1); + @$pb.TagNumber(2) + void clearAuthOk() => $_clearField(2); + @$pb.TagNumber(2) + AuthOk ensureAuthOk() => $_ensure(1); + + @$pb.TagNumber(3) + $0.EvmSignTransactionResponse get evmSignTransaction => $_getN(2); + @$pb.TagNumber(3) + set evmSignTransaction($0.EvmSignTransactionResponse value) => + $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasEvmSignTransaction() => $_has(2); + @$pb.TagNumber(3) + void clearEvmSignTransaction() => $_clearField(3); + @$pb.TagNumber(3) + $0.EvmSignTransactionResponse ensureEvmSignTransaction() => $_ensure(2); + + @$pb.TagNumber(4) + $0.EvmAnalyzeTransactionResponse get evmAnalyzeTransaction => $_getN(3); + @$pb.TagNumber(4) + set evmAnalyzeTransaction($0.EvmAnalyzeTransactionResponse value) => + $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasEvmAnalyzeTransaction() => $_has(3); + @$pb.TagNumber(4) + void clearEvmAnalyzeTransaction() => $_clearField(4); + @$pb.TagNumber(4) + $0.EvmAnalyzeTransactionResponse ensureEvmAnalyzeTransaction() => $_ensure(3); + + @$pb.TagNumber(5) + ClientConnectError get clientConnectError => $_getN(4); + @$pb.TagNumber(5) + set clientConnectError(ClientConnectError value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasClientConnectError() => $_has(4); + @$pb.TagNumber(5) + void clearClientConnectError() => $_clearField(5); + @$pb.TagNumber(5) + ClientConnectError ensureClientConnectError() => $_ensure(4); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/useragent/lib/proto/client.pbenum.dart b/useragent/lib/proto/client.pbenum.dart new file mode 100644 index 0000000..f5d4b5e --- /dev/null +++ b/useragent/lib/proto/client.pbenum.dart @@ -0,0 +1,42 @@ +// This is a generated file - do not edit. +// +// Generated from client.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +class ClientConnectError_Code extends $pb.ProtobufEnum { + static const ClientConnectError_Code UNKNOWN = + ClientConnectError_Code._(0, _omitEnumNames ? '' : 'UNKNOWN'); + static const ClientConnectError_Code APPROVAL_DENIED = + ClientConnectError_Code._(1, _omitEnumNames ? '' : 'APPROVAL_DENIED'); + static const ClientConnectError_Code NO_USER_AGENTS_ONLINE = + ClientConnectError_Code._( + 2, _omitEnumNames ? '' : 'NO_USER_AGENTS_ONLINE'); + + static const $core.List values = + [ + UNKNOWN, + APPROVAL_DENIED, + NO_USER_AGENTS_ONLINE, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 2); + static ClientConnectError_Code? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const ClientConnectError_Code._(super.value, super.name); +} + +const $core.bool _omitEnumNames = + $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/useragent/lib/proto/client.pbjson.dart b/useragent/lib/proto/client.pbjson.dart new file mode 100644 index 0000000..d005846 --- /dev/null +++ b/useragent/lib/proto/client.pbjson.dart @@ -0,0 +1,197 @@ +// This is a generated file - do not edit. +// +// Generated from client.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports +// ignore_for_file: unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use authChallengeRequestDescriptor instead') +const AuthChallengeRequest$json = { + '1': 'AuthChallengeRequest', + '2': [ + {'1': 'pubkey', '3': 1, '4': 1, '5': 12, '10': 'pubkey'}, + ], +}; + +/// Descriptor for `AuthChallengeRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List authChallengeRequestDescriptor = + $convert.base64Decode( + 'ChRBdXRoQ2hhbGxlbmdlUmVxdWVzdBIWCgZwdWJrZXkYASABKAxSBnB1YmtleQ=='); + +@$core.Deprecated('Use authChallengeDescriptor instead') +const AuthChallenge$json = { + '1': 'AuthChallenge', + '2': [ + {'1': 'pubkey', '3': 1, '4': 1, '5': 12, '10': 'pubkey'}, + {'1': 'nonce', '3': 2, '4': 1, '5': 5, '10': 'nonce'}, + ], +}; + +/// Descriptor for `AuthChallenge`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List authChallengeDescriptor = $convert.base64Decode( + 'Cg1BdXRoQ2hhbGxlbmdlEhYKBnB1YmtleRgBIAEoDFIGcHVia2V5EhQKBW5vbmNlGAIgASgFUg' + 'Vub25jZQ=='); + +@$core.Deprecated('Use authChallengeSolutionDescriptor instead') +const AuthChallengeSolution$json = { + '1': 'AuthChallengeSolution', + '2': [ + {'1': 'signature', '3': 1, '4': 1, '5': 12, '10': 'signature'}, + ], +}; + +/// Descriptor for `AuthChallengeSolution`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List authChallengeSolutionDescriptor = $convert.base64Decode( + 'ChVBdXRoQ2hhbGxlbmdlU29sdXRpb24SHAoJc2lnbmF0dXJlGAEgASgMUglzaWduYXR1cmU='); + +@$core.Deprecated('Use authOkDescriptor instead') +const AuthOk$json = { + '1': 'AuthOk', +}; + +/// Descriptor for `AuthOk`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List authOkDescriptor = + $convert.base64Decode('CgZBdXRoT2s='); + +@$core.Deprecated('Use clientRequestDescriptor instead') +const ClientRequest$json = { + '1': 'ClientRequest', + '2': [ + { + '1': 'auth_challenge_request', + '3': 1, + '4': 1, + '5': 11, + '6': '.arbiter.client.AuthChallengeRequest', + '9': 0, + '10': 'authChallengeRequest' + }, + { + '1': 'auth_challenge_solution', + '3': 2, + '4': 1, + '5': 11, + '6': '.arbiter.client.AuthChallengeSolution', + '9': 0, + '10': 'authChallengeSolution' + }, + ], + '8': [ + {'1': 'payload'}, + ], +}; + +/// Descriptor for `ClientRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List clientRequestDescriptor = $convert.base64Decode( + 'Cg1DbGllbnRSZXF1ZXN0ElwKFmF1dGhfY2hhbGxlbmdlX3JlcXVlc3QYASABKAsyJC5hcmJpdG' + 'VyLmNsaWVudC5BdXRoQ2hhbGxlbmdlUmVxdWVzdEgAUhRhdXRoQ2hhbGxlbmdlUmVxdWVzdBJf' + 'ChdhdXRoX2NoYWxsZW5nZV9zb2x1dGlvbhgCIAEoCzIlLmFyYml0ZXIuY2xpZW50LkF1dGhDaG' + 'FsbGVuZ2VTb2x1dGlvbkgAUhVhdXRoQ2hhbGxlbmdlU29sdXRpb25CCQoHcGF5bG9hZA=='); + +@$core.Deprecated('Use clientConnectErrorDescriptor instead') +const ClientConnectError$json = { + '1': 'ClientConnectError', + '2': [ + { + '1': 'code', + '3': 1, + '4': 1, + '5': 14, + '6': '.arbiter.client.ClientConnectError.Code', + '10': 'code' + }, + ], + '4': [ClientConnectError_Code$json], +}; + +@$core.Deprecated('Use clientConnectErrorDescriptor instead') +const ClientConnectError_Code$json = { + '1': 'Code', + '2': [ + {'1': 'UNKNOWN', '2': 0}, + {'1': 'APPROVAL_DENIED', '2': 1}, + {'1': 'NO_USER_AGENTS_ONLINE', '2': 2}, + ], +}; + +/// Descriptor for `ClientConnectError`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List clientConnectErrorDescriptor = $convert.base64Decode( + 'ChJDbGllbnRDb25uZWN0RXJyb3ISOwoEY29kZRgBIAEoDjInLmFyYml0ZXIuY2xpZW50LkNsaW' + 'VudENvbm5lY3RFcnJvci5Db2RlUgRjb2RlIkMKBENvZGUSCwoHVU5LTk9XThAAEhMKD0FQUFJP' + 'VkFMX0RFTklFRBABEhkKFU5PX1VTRVJfQUdFTlRTX09OTElORRAC'); + +@$core.Deprecated('Use clientResponseDescriptor instead') +const ClientResponse$json = { + '1': 'ClientResponse', + '2': [ + { + '1': 'auth_challenge', + '3': 1, + '4': 1, + '5': 11, + '6': '.arbiter.client.AuthChallenge', + '9': 0, + '10': 'authChallenge' + }, + { + '1': 'auth_ok', + '3': 2, + '4': 1, + '5': 11, + '6': '.arbiter.client.AuthOk', + '9': 0, + '10': 'authOk' + }, + { + '1': 'client_connect_error', + '3': 5, + '4': 1, + '5': 11, + '6': '.arbiter.client.ClientConnectError', + '9': 0, + '10': 'clientConnectError' + }, + { + '1': 'evm_sign_transaction', + '3': 3, + '4': 1, + '5': 11, + '6': '.arbiter.evm.EvmSignTransactionResponse', + '9': 0, + '10': 'evmSignTransaction' + }, + { + '1': 'evm_analyze_transaction', + '3': 4, + '4': 1, + '5': 11, + '6': '.arbiter.evm.EvmAnalyzeTransactionResponse', + '9': 0, + '10': 'evmAnalyzeTransaction' + }, + ], + '8': [ + {'1': 'payload'}, + ], +}; + +/// Descriptor for `ClientResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List clientResponseDescriptor = $convert.base64Decode( + 'Cg5DbGllbnRSZXNwb25zZRJGCg5hdXRoX2NoYWxsZW5nZRgBIAEoCzIdLmFyYml0ZXIuY2xpZW' + '50LkF1dGhDaGFsbGVuZ2VIAFINYXV0aENoYWxsZW5nZRIxCgdhdXRoX29rGAIgASgLMhYuYXJi' + 'aXRlci5jbGllbnQuQXV0aE9rSABSBmF1dGhPaxJWChRjbGllbnRfY29ubmVjdF9lcnJvchgFIA' + 'EoCzIiLmFyYml0ZXIuY2xpZW50LkNsaWVudENvbm5lY3RFcnJvckgAUhJjbGllbnRDb25uZWN0' + 'RXJyb3ISWwoUZXZtX3NpZ25fdHJhbnNhY3Rpb24YAyABKAsyJy5hcmJpdGVyLmV2bS5Fdm1TaW' + 'duVHJhbnNhY3Rpb25SZXNwb25zZUgAUhJldm1TaWduVHJhbnNhY3Rpb24SZAoXZXZtX2FuYWx5' + 'emVfdHJhbnNhY3Rpb24YBCABKAsyKi5hcmJpdGVyLmV2bS5Fdm1BbmFseXplVHJhbnNhY3Rpb2' + '5SZXNwb25zZUgAUhVldm1BbmFseXplVHJhbnNhY3Rpb25CCQoHcGF5bG9hZA=='); diff --git a/useragent/lib/proto/evm.pb.dart b/useragent/lib/proto/evm.pb.dart new file mode 100644 index 0000000..d748202 --- /dev/null +++ b/useragent/lib/proto/evm.pb.dart @@ -0,0 +1,2582 @@ +// This is a generated file - do not edit. +// +// Generated from evm.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:fixnum/fixnum.dart' as $fixnum; +import 'package:protobuf/protobuf.dart' as $pb; +import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart' as $1; +import 'package:protobuf/well_known_types/google/protobuf/timestamp.pb.dart' + as $0; + +import 'evm.pbenum.dart'; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +export 'evm.pbenum.dart'; + +class WalletEntry extends $pb.GeneratedMessage { + factory WalletEntry({ + $core.List<$core.int>? address, + }) { + final result = create(); + if (address != null) result.address = address; + return result; + } + + WalletEntry._(); + + factory WalletEntry.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory WalletEntry.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'WalletEntry', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'address', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WalletEntry clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WalletEntry copyWith(void Function(WalletEntry) updates) => + super.copyWith((message) => updates(message as WalletEntry)) + as WalletEntry; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static WalletEntry create() => WalletEntry._(); + @$core.override + WalletEntry createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static WalletEntry getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static WalletEntry? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get address => $_getN(0); + @$pb.TagNumber(1) + set address($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasAddress() => $_has(0); + @$pb.TagNumber(1) + void clearAddress() => $_clearField(1); +} + +class WalletList extends $pb.GeneratedMessage { + factory WalletList({ + $core.Iterable? wallets, + }) { + final result = create(); + if (wallets != null) result.wallets.addAll(wallets); + return result; + } + + WalletList._(); + + factory WalletList.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory WalletList.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'WalletList', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..pPM(1, _omitFieldNames ? '' : 'wallets', + subBuilder: WalletEntry.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WalletList clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WalletList copyWith(void Function(WalletList) updates) => + super.copyWith((message) => updates(message as WalletList)) as WalletList; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static WalletList create() => WalletList._(); + @$core.override + WalletList createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static WalletList getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static WalletList? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbList get wallets => $_getList(0); +} + +enum WalletCreateResponse_Result { wallet, error, notSet } + +class WalletCreateResponse extends $pb.GeneratedMessage { + factory WalletCreateResponse({ + WalletEntry? wallet, + EvmError? error, + }) { + final result = create(); + if (wallet != null) result.wallet = wallet; + if (error != null) result.error = error; + return result; + } + + WalletCreateResponse._(); + + factory WalletCreateResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory WalletCreateResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, WalletCreateResponse_Result> + _WalletCreateResponse_ResultByTag = { + 1: WalletCreateResponse_Result.wallet, + 2: WalletCreateResponse_Result.error, + 0: WalletCreateResponse_Result.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'WalletCreateResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..oo(0, [1, 2]) + ..aOM(1, _omitFieldNames ? '' : 'wallet', + subBuilder: WalletEntry.create) + ..aE(2, _omitFieldNames ? '' : 'error', + enumValues: EvmError.values) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WalletCreateResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WalletCreateResponse copyWith(void Function(WalletCreateResponse) updates) => + super.copyWith((message) => updates(message as WalletCreateResponse)) + as WalletCreateResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static WalletCreateResponse create() => WalletCreateResponse._(); + @$core.override + WalletCreateResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static WalletCreateResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static WalletCreateResponse? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + WalletCreateResponse_Result whichResult() => + _WalletCreateResponse_ResultByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + void clearResult() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + WalletEntry get wallet => $_getN(0); + @$pb.TagNumber(1) + set wallet(WalletEntry value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasWallet() => $_has(0); + @$pb.TagNumber(1) + void clearWallet() => $_clearField(1); + @$pb.TagNumber(1) + WalletEntry ensureWallet() => $_ensure(0); + + @$pb.TagNumber(2) + EvmError get error => $_getN(1); + @$pb.TagNumber(2) + set error(EvmError value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasError() => $_has(1); + @$pb.TagNumber(2) + void clearError() => $_clearField(2); +} + +enum WalletListResponse_Result { wallets, error, notSet } + +class WalletListResponse extends $pb.GeneratedMessage { + factory WalletListResponse({ + WalletList? wallets, + EvmError? error, + }) { + final result = create(); + if (wallets != null) result.wallets = wallets; + if (error != null) result.error = error; + return result; + } + + WalletListResponse._(); + + factory WalletListResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory WalletListResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, WalletListResponse_Result> + _WalletListResponse_ResultByTag = { + 1: WalletListResponse_Result.wallets, + 2: WalletListResponse_Result.error, + 0: WalletListResponse_Result.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'WalletListResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..oo(0, [1, 2]) + ..aOM(1, _omitFieldNames ? '' : 'wallets', + subBuilder: WalletList.create) + ..aE(2, _omitFieldNames ? '' : 'error', + enumValues: EvmError.values) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WalletListResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WalletListResponse copyWith(void Function(WalletListResponse) updates) => + super.copyWith((message) => updates(message as WalletListResponse)) + as WalletListResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static WalletListResponse create() => WalletListResponse._(); + @$core.override + WalletListResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static WalletListResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static WalletListResponse? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + WalletListResponse_Result whichResult() => + _WalletListResponse_ResultByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + void clearResult() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + WalletList get wallets => $_getN(0); + @$pb.TagNumber(1) + set wallets(WalletList value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasWallets() => $_has(0); + @$pb.TagNumber(1) + void clearWallets() => $_clearField(1); + @$pb.TagNumber(1) + WalletList ensureWallets() => $_ensure(0); + + @$pb.TagNumber(2) + EvmError get error => $_getN(1); + @$pb.TagNumber(2) + set error(EvmError value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasError() => $_has(1); + @$pb.TagNumber(2) + void clearError() => $_clearField(2); +} + +class TransactionRateLimit extends $pb.GeneratedMessage { + factory TransactionRateLimit({ + $core.int? count, + $fixnum.Int64? windowSecs, + }) { + final result = create(); + if (count != null) result.count = count; + if (windowSecs != null) result.windowSecs = windowSecs; + return result; + } + + TransactionRateLimit._(); + + factory TransactionRateLimit.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory TransactionRateLimit.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'TransactionRateLimit', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..aI(1, _omitFieldNames ? '' : 'count', fieldType: $pb.PbFieldType.OU3) + ..aInt64(2, _omitFieldNames ? '' : 'windowSecs') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TransactionRateLimit clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TransactionRateLimit copyWith(void Function(TransactionRateLimit) updates) => + super.copyWith((message) => updates(message as TransactionRateLimit)) + as TransactionRateLimit; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static TransactionRateLimit create() => TransactionRateLimit._(); + @$core.override + TransactionRateLimit createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static TransactionRateLimit getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static TransactionRateLimit? _defaultInstance; + + @$pb.TagNumber(1) + $core.int get count => $_getIZ(0); + @$pb.TagNumber(1) + set count($core.int value) => $_setUnsignedInt32(0, value); + @$pb.TagNumber(1) + $core.bool hasCount() => $_has(0); + @$pb.TagNumber(1) + void clearCount() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get windowSecs => $_getI64(1); + @$pb.TagNumber(2) + set windowSecs($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasWindowSecs() => $_has(1); + @$pb.TagNumber(2) + void clearWindowSecs() => $_clearField(2); +} + +class VolumeRateLimit extends $pb.GeneratedMessage { + factory VolumeRateLimit({ + $core.List<$core.int>? maxVolume, + $fixnum.Int64? windowSecs, + }) { + final result = create(); + if (maxVolume != null) result.maxVolume = maxVolume; + if (windowSecs != null) result.windowSecs = windowSecs; + return result; + } + + VolumeRateLimit._(); + + factory VolumeRateLimit.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory VolumeRateLimit.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'VolumeRateLimit', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'maxVolume', $pb.PbFieldType.OY) + ..aInt64(2, _omitFieldNames ? '' : 'windowSecs') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + VolumeRateLimit clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + VolumeRateLimit copyWith(void Function(VolumeRateLimit) updates) => + super.copyWith((message) => updates(message as VolumeRateLimit)) + as VolumeRateLimit; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static VolumeRateLimit create() => VolumeRateLimit._(); + @$core.override + VolumeRateLimit createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static VolumeRateLimit getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static VolumeRateLimit? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get maxVolume => $_getN(0); + @$pb.TagNumber(1) + set maxVolume($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasMaxVolume() => $_has(0); + @$pb.TagNumber(1) + void clearMaxVolume() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get windowSecs => $_getI64(1); + @$pb.TagNumber(2) + set windowSecs($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasWindowSecs() => $_has(1); + @$pb.TagNumber(2) + void clearWindowSecs() => $_clearField(2); +} + +class SharedSettings extends $pb.GeneratedMessage { + factory SharedSettings({ + $core.int? walletId, + $fixnum.Int64? chainId, + $0.Timestamp? validFrom, + $0.Timestamp? validUntil, + $core.List<$core.int>? maxGasFeePerGas, + $core.List<$core.int>? maxPriorityFeePerGas, + TransactionRateLimit? rateLimit, + }) { + final result = create(); + if (walletId != null) result.walletId = walletId; + if (chainId != null) result.chainId = chainId; + if (validFrom != null) result.validFrom = validFrom; + if (validUntil != null) result.validUntil = validUntil; + if (maxGasFeePerGas != null) result.maxGasFeePerGas = maxGasFeePerGas; + if (maxPriorityFeePerGas != null) + result.maxPriorityFeePerGas = maxPriorityFeePerGas; + if (rateLimit != null) result.rateLimit = rateLimit; + return result; + } + + SharedSettings._(); + + factory SharedSettings.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SharedSettings.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SharedSettings', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..aI(1, _omitFieldNames ? '' : 'walletId') + ..a<$fixnum.Int64>(2, _omitFieldNames ? '' : 'chainId', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..aOM<$0.Timestamp>(3, _omitFieldNames ? '' : 'validFrom', + subBuilder: $0.Timestamp.create) + ..aOM<$0.Timestamp>(4, _omitFieldNames ? '' : 'validUntil', + subBuilder: $0.Timestamp.create) + ..a<$core.List<$core.int>>( + 5, _omitFieldNames ? '' : 'maxGasFeePerGas', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>( + 6, _omitFieldNames ? '' : 'maxPriorityFeePerGas', $pb.PbFieldType.OY) + ..aOM(7, _omitFieldNames ? '' : 'rateLimit', + subBuilder: TransactionRateLimit.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SharedSettings clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SharedSettings copyWith(void Function(SharedSettings) updates) => + super.copyWith((message) => updates(message as SharedSettings)) + as SharedSettings; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SharedSettings create() => SharedSettings._(); + @$core.override + SharedSettings createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SharedSettings getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SharedSettings? _defaultInstance; + + @$pb.TagNumber(1) + $core.int get walletId => $_getIZ(0); + @$pb.TagNumber(1) + set walletId($core.int value) => $_setSignedInt32(0, value); + @$pb.TagNumber(1) + $core.bool hasWalletId() => $_has(0); + @$pb.TagNumber(1) + void clearWalletId() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get chainId => $_getI64(1); + @$pb.TagNumber(2) + set chainId($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasChainId() => $_has(1); + @$pb.TagNumber(2) + void clearChainId() => $_clearField(2); + + @$pb.TagNumber(3) + $0.Timestamp get validFrom => $_getN(2); + @$pb.TagNumber(3) + set validFrom($0.Timestamp value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasValidFrom() => $_has(2); + @$pb.TagNumber(3) + void clearValidFrom() => $_clearField(3); + @$pb.TagNumber(3) + $0.Timestamp ensureValidFrom() => $_ensure(2); + + @$pb.TagNumber(4) + $0.Timestamp get validUntil => $_getN(3); + @$pb.TagNumber(4) + set validUntil($0.Timestamp value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasValidUntil() => $_has(3); + @$pb.TagNumber(4) + void clearValidUntil() => $_clearField(4); + @$pb.TagNumber(4) + $0.Timestamp ensureValidUntil() => $_ensure(3); + + @$pb.TagNumber(5) + $core.List<$core.int> get maxGasFeePerGas => $_getN(4); + @$pb.TagNumber(5) + set maxGasFeePerGas($core.List<$core.int> value) => $_setBytes(4, value); + @$pb.TagNumber(5) + $core.bool hasMaxGasFeePerGas() => $_has(4); + @$pb.TagNumber(5) + void clearMaxGasFeePerGas() => $_clearField(5); + + @$pb.TagNumber(6) + $core.List<$core.int> get maxPriorityFeePerGas => $_getN(5); + @$pb.TagNumber(6) + set maxPriorityFeePerGas($core.List<$core.int> value) => $_setBytes(5, value); + @$pb.TagNumber(6) + $core.bool hasMaxPriorityFeePerGas() => $_has(5); + @$pb.TagNumber(6) + void clearMaxPriorityFeePerGas() => $_clearField(6); + + @$pb.TagNumber(7) + TransactionRateLimit get rateLimit => $_getN(6); + @$pb.TagNumber(7) + set rateLimit(TransactionRateLimit value) => $_setField(7, value); + @$pb.TagNumber(7) + $core.bool hasRateLimit() => $_has(6); + @$pb.TagNumber(7) + void clearRateLimit() => $_clearField(7); + @$pb.TagNumber(7) + TransactionRateLimit ensureRateLimit() => $_ensure(6); +} + +class EtherTransferSettings extends $pb.GeneratedMessage { + factory EtherTransferSettings({ + $core.Iterable<$core.List<$core.int>>? targets, + VolumeRateLimit? limit, + }) { + final result = create(); + if (targets != null) result.targets.addAll(targets); + if (limit != null) result.limit = limit; + return result; + } + + EtherTransferSettings._(); + + factory EtherTransferSettings.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EtherTransferSettings.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EtherTransferSettings', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..p<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'targets', $pb.PbFieldType.PY) + ..aOM(2, _omitFieldNames ? '' : 'limit', + subBuilder: VolumeRateLimit.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EtherTransferSettings clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EtherTransferSettings copyWith( + void Function(EtherTransferSettings) updates) => + super.copyWith((message) => updates(message as EtherTransferSettings)) + as EtherTransferSettings; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EtherTransferSettings create() => EtherTransferSettings._(); + @$core.override + EtherTransferSettings createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EtherTransferSettings getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EtherTransferSettings? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbList<$core.List<$core.int>> get targets => $_getList(0); + + @$pb.TagNumber(2) + VolumeRateLimit get limit => $_getN(1); + @$pb.TagNumber(2) + set limit(VolumeRateLimit value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasLimit() => $_has(1); + @$pb.TagNumber(2) + void clearLimit() => $_clearField(2); + @$pb.TagNumber(2) + VolumeRateLimit ensureLimit() => $_ensure(1); +} + +class TokenTransferSettings extends $pb.GeneratedMessage { + factory TokenTransferSettings({ + $core.List<$core.int>? tokenContract, + $core.List<$core.int>? target, + $core.Iterable? volumeLimits, + }) { + final result = create(); + if (tokenContract != null) result.tokenContract = tokenContract; + if (target != null) result.target = target; + if (volumeLimits != null) result.volumeLimits.addAll(volumeLimits); + return result; + } + + TokenTransferSettings._(); + + factory TokenTransferSettings.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory TokenTransferSettings.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'TokenTransferSettings', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'tokenContract', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>( + 2, _omitFieldNames ? '' : 'target', $pb.PbFieldType.OY) + ..pPM(3, _omitFieldNames ? '' : 'volumeLimits', + subBuilder: VolumeRateLimit.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TokenTransferSettings clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TokenTransferSettings copyWith( + void Function(TokenTransferSettings) updates) => + super.copyWith((message) => updates(message as TokenTransferSettings)) + as TokenTransferSettings; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static TokenTransferSettings create() => TokenTransferSettings._(); + @$core.override + TokenTransferSettings createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static TokenTransferSettings getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static TokenTransferSettings? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get tokenContract => $_getN(0); + @$pb.TagNumber(1) + set tokenContract($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasTokenContract() => $_has(0); + @$pb.TagNumber(1) + void clearTokenContract() => $_clearField(1); + + @$pb.TagNumber(2) + $core.List<$core.int> get target => $_getN(1); + @$pb.TagNumber(2) + set target($core.List<$core.int> value) => $_setBytes(1, value); + @$pb.TagNumber(2) + $core.bool hasTarget() => $_has(1); + @$pb.TagNumber(2) + void clearTarget() => $_clearField(2); + + @$pb.TagNumber(3) + $pb.PbList get volumeLimits => $_getList(2); +} + +enum SpecificGrant_Grant { etherTransfer, tokenTransfer, notSet } + +class SpecificGrant extends $pb.GeneratedMessage { + factory SpecificGrant({ + EtherTransferSettings? etherTransfer, + TokenTransferSettings? tokenTransfer, + }) { + final result = create(); + if (etherTransfer != null) result.etherTransfer = etherTransfer; + if (tokenTransfer != null) result.tokenTransfer = tokenTransfer; + return result; + } + + SpecificGrant._(); + + factory SpecificGrant.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SpecificGrant.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, SpecificGrant_Grant> + _SpecificGrant_GrantByTag = { + 1: SpecificGrant_Grant.etherTransfer, + 2: SpecificGrant_Grant.tokenTransfer, + 0: SpecificGrant_Grant.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SpecificGrant', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..oo(0, [1, 2]) + ..aOM(1, _omitFieldNames ? '' : 'etherTransfer', + subBuilder: EtherTransferSettings.create) + ..aOM(2, _omitFieldNames ? '' : 'tokenTransfer', + subBuilder: TokenTransferSettings.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SpecificGrant clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SpecificGrant copyWith(void Function(SpecificGrant) updates) => + super.copyWith((message) => updates(message as SpecificGrant)) + as SpecificGrant; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SpecificGrant create() => SpecificGrant._(); + @$core.override + SpecificGrant createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SpecificGrant getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SpecificGrant? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + SpecificGrant_Grant whichGrant() => + _SpecificGrant_GrantByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + void clearGrant() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + EtherTransferSettings get etherTransfer => $_getN(0); + @$pb.TagNumber(1) + set etherTransfer(EtherTransferSettings value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasEtherTransfer() => $_has(0); + @$pb.TagNumber(1) + void clearEtherTransfer() => $_clearField(1); + @$pb.TagNumber(1) + EtherTransferSettings ensureEtherTransfer() => $_ensure(0); + + @$pb.TagNumber(2) + TokenTransferSettings get tokenTransfer => $_getN(1); + @$pb.TagNumber(2) + set tokenTransfer(TokenTransferSettings value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasTokenTransfer() => $_has(1); + @$pb.TagNumber(2) + void clearTokenTransfer() => $_clearField(2); + @$pb.TagNumber(2) + TokenTransferSettings ensureTokenTransfer() => $_ensure(1); +} + +class EtherTransferMeaning extends $pb.GeneratedMessage { + factory EtherTransferMeaning({ + $core.List<$core.int>? to, + $core.List<$core.int>? value, + }) { + final result = create(); + if (to != null) result.to = to; + if (value != null) result.value = value; + return result; + } + + EtherTransferMeaning._(); + + factory EtherTransferMeaning.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EtherTransferMeaning.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EtherTransferMeaning', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'to', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>( + 2, _omitFieldNames ? '' : 'value', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EtherTransferMeaning clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EtherTransferMeaning copyWith(void Function(EtherTransferMeaning) updates) => + super.copyWith((message) => updates(message as EtherTransferMeaning)) + as EtherTransferMeaning; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EtherTransferMeaning create() => EtherTransferMeaning._(); + @$core.override + EtherTransferMeaning createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EtherTransferMeaning getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EtherTransferMeaning? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get to => $_getN(0); + @$pb.TagNumber(1) + set to($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasTo() => $_has(0); + @$pb.TagNumber(1) + void clearTo() => $_clearField(1); + + @$pb.TagNumber(2) + $core.List<$core.int> get value => $_getN(1); + @$pb.TagNumber(2) + set value($core.List<$core.int> value) => $_setBytes(1, value); + @$pb.TagNumber(2) + $core.bool hasValue() => $_has(1); + @$pb.TagNumber(2) + void clearValue() => $_clearField(2); +} + +class TokenInfo extends $pb.GeneratedMessage { + factory TokenInfo({ + $core.String? symbol, + $core.List<$core.int>? address, + $fixnum.Int64? chainId, + }) { + final result = create(); + if (symbol != null) result.symbol = symbol; + if (address != null) result.address = address; + if (chainId != null) result.chainId = chainId; + return result; + } + + TokenInfo._(); + + factory TokenInfo.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory TokenInfo.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'TokenInfo', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'symbol') + ..a<$core.List<$core.int>>( + 2, _omitFieldNames ? '' : 'address', $pb.PbFieldType.OY) + ..a<$fixnum.Int64>(3, _omitFieldNames ? '' : 'chainId', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TokenInfo clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TokenInfo copyWith(void Function(TokenInfo) updates) => + super.copyWith((message) => updates(message as TokenInfo)) as TokenInfo; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static TokenInfo create() => TokenInfo._(); + @$core.override + TokenInfo createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static TokenInfo getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static TokenInfo? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get symbol => $_getSZ(0); + @$pb.TagNumber(1) + set symbol($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasSymbol() => $_has(0); + @$pb.TagNumber(1) + void clearSymbol() => $_clearField(1); + + @$pb.TagNumber(2) + $core.List<$core.int> get address => $_getN(1); + @$pb.TagNumber(2) + set address($core.List<$core.int> value) => $_setBytes(1, value); + @$pb.TagNumber(2) + $core.bool hasAddress() => $_has(1); + @$pb.TagNumber(2) + void clearAddress() => $_clearField(2); + + @$pb.TagNumber(3) + $fixnum.Int64 get chainId => $_getI64(2); + @$pb.TagNumber(3) + set chainId($fixnum.Int64 value) => $_setInt64(2, value); + @$pb.TagNumber(3) + $core.bool hasChainId() => $_has(2); + @$pb.TagNumber(3) + void clearChainId() => $_clearField(3); +} + +/// Mirror of token_transfers::Meaning +class TokenTransferMeaning extends $pb.GeneratedMessage { + factory TokenTransferMeaning({ + TokenInfo? token, + $core.List<$core.int>? to, + $core.List<$core.int>? value, + }) { + final result = create(); + if (token != null) result.token = token; + if (to != null) result.to = to; + if (value != null) result.value = value; + return result; + } + + TokenTransferMeaning._(); + + factory TokenTransferMeaning.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory TokenTransferMeaning.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'TokenTransferMeaning', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..aOM(1, _omitFieldNames ? '' : 'token', + subBuilder: TokenInfo.create) + ..a<$core.List<$core.int>>( + 2, _omitFieldNames ? '' : 'to', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>( + 3, _omitFieldNames ? '' : 'value', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TokenTransferMeaning clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TokenTransferMeaning copyWith(void Function(TokenTransferMeaning) updates) => + super.copyWith((message) => updates(message as TokenTransferMeaning)) + as TokenTransferMeaning; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static TokenTransferMeaning create() => TokenTransferMeaning._(); + @$core.override + TokenTransferMeaning createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static TokenTransferMeaning getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static TokenTransferMeaning? _defaultInstance; + + @$pb.TagNumber(1) + TokenInfo get token => $_getN(0); + @$pb.TagNumber(1) + set token(TokenInfo value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasToken() => $_has(0); + @$pb.TagNumber(1) + void clearToken() => $_clearField(1); + @$pb.TagNumber(1) + TokenInfo ensureToken() => $_ensure(0); + + @$pb.TagNumber(2) + $core.List<$core.int> get to => $_getN(1); + @$pb.TagNumber(2) + set to($core.List<$core.int> value) => $_setBytes(1, value); + @$pb.TagNumber(2) + $core.bool hasTo() => $_has(1); + @$pb.TagNumber(2) + void clearTo() => $_clearField(2); + + @$pb.TagNumber(3) + $core.List<$core.int> get value => $_getN(2); + @$pb.TagNumber(3) + set value($core.List<$core.int> value) => $_setBytes(2, value); + @$pb.TagNumber(3) + $core.bool hasValue() => $_has(2); + @$pb.TagNumber(3) + void clearValue() => $_clearField(3); +} + +enum SpecificMeaning_Meaning { etherTransfer, tokenTransfer, notSet } + +/// Mirror of policies::SpecificMeaning +class SpecificMeaning extends $pb.GeneratedMessage { + factory SpecificMeaning({ + EtherTransferMeaning? etherTransfer, + TokenTransferMeaning? tokenTransfer, + }) { + final result = create(); + if (etherTransfer != null) result.etherTransfer = etherTransfer; + if (tokenTransfer != null) result.tokenTransfer = tokenTransfer; + return result; + } + + SpecificMeaning._(); + + factory SpecificMeaning.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SpecificMeaning.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, SpecificMeaning_Meaning> + _SpecificMeaning_MeaningByTag = { + 1: SpecificMeaning_Meaning.etherTransfer, + 2: SpecificMeaning_Meaning.tokenTransfer, + 0: SpecificMeaning_Meaning.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SpecificMeaning', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..oo(0, [1, 2]) + ..aOM(1, _omitFieldNames ? '' : 'etherTransfer', + subBuilder: EtherTransferMeaning.create) + ..aOM(2, _omitFieldNames ? '' : 'tokenTransfer', + subBuilder: TokenTransferMeaning.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SpecificMeaning clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SpecificMeaning copyWith(void Function(SpecificMeaning) updates) => + super.copyWith((message) => updates(message as SpecificMeaning)) + as SpecificMeaning; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SpecificMeaning create() => SpecificMeaning._(); + @$core.override + SpecificMeaning createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SpecificMeaning getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SpecificMeaning? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + SpecificMeaning_Meaning whichMeaning() => + _SpecificMeaning_MeaningByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + void clearMeaning() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + EtherTransferMeaning get etherTransfer => $_getN(0); + @$pb.TagNumber(1) + set etherTransfer(EtherTransferMeaning value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasEtherTransfer() => $_has(0); + @$pb.TagNumber(1) + void clearEtherTransfer() => $_clearField(1); + @$pb.TagNumber(1) + EtherTransferMeaning ensureEtherTransfer() => $_ensure(0); + + @$pb.TagNumber(2) + TokenTransferMeaning get tokenTransfer => $_getN(1); + @$pb.TagNumber(2) + set tokenTransfer(TokenTransferMeaning value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasTokenTransfer() => $_has(1); + @$pb.TagNumber(2) + void clearTokenTransfer() => $_clearField(2); + @$pb.TagNumber(2) + TokenTransferMeaning ensureTokenTransfer() => $_ensure(1); +} + +/// --- Eval error types --- +class GasLimitExceededViolation extends $pb.GeneratedMessage { + factory GasLimitExceededViolation({ + $core.List<$core.int>? maxGasFeePerGas, + $core.List<$core.int>? maxPriorityFeePerGas, + }) { + final result = create(); + if (maxGasFeePerGas != null) result.maxGasFeePerGas = maxGasFeePerGas; + if (maxPriorityFeePerGas != null) + result.maxPriorityFeePerGas = maxPriorityFeePerGas; + return result; + } + + GasLimitExceededViolation._(); + + factory GasLimitExceededViolation.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GasLimitExceededViolation.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GasLimitExceededViolation', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'maxGasFeePerGas', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>( + 2, _omitFieldNames ? '' : 'maxPriorityFeePerGas', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GasLimitExceededViolation clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GasLimitExceededViolation copyWith( + void Function(GasLimitExceededViolation) updates) => + super.copyWith((message) => updates(message as GasLimitExceededViolation)) + as GasLimitExceededViolation; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GasLimitExceededViolation create() => GasLimitExceededViolation._(); + @$core.override + GasLimitExceededViolation createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GasLimitExceededViolation getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GasLimitExceededViolation? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get maxGasFeePerGas => $_getN(0); + @$pb.TagNumber(1) + set maxGasFeePerGas($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasMaxGasFeePerGas() => $_has(0); + @$pb.TagNumber(1) + void clearMaxGasFeePerGas() => $_clearField(1); + + @$pb.TagNumber(2) + $core.List<$core.int> get maxPriorityFeePerGas => $_getN(1); + @$pb.TagNumber(2) + set maxPriorityFeePerGas($core.List<$core.int> value) => $_setBytes(1, value); + @$pb.TagNumber(2) + $core.bool hasMaxPriorityFeePerGas() => $_has(1); + @$pb.TagNumber(2) + void clearMaxPriorityFeePerGas() => $_clearField(2); +} + +enum EvalViolation_Kind { + invalidTarget, + gasLimitExceeded, + rateLimitExceeded, + volumetricLimitExceeded, + invalidTime, + invalidTransactionType, + notSet +} + +class EvalViolation extends $pb.GeneratedMessage { + factory EvalViolation({ + $core.List<$core.int>? invalidTarget, + GasLimitExceededViolation? gasLimitExceeded, + $1.Empty? rateLimitExceeded, + $1.Empty? volumetricLimitExceeded, + $1.Empty? invalidTime, + $1.Empty? invalidTransactionType, + }) { + final result = create(); + if (invalidTarget != null) result.invalidTarget = invalidTarget; + if (gasLimitExceeded != null) result.gasLimitExceeded = gasLimitExceeded; + if (rateLimitExceeded != null) result.rateLimitExceeded = rateLimitExceeded; + if (volumetricLimitExceeded != null) + result.volumetricLimitExceeded = volumetricLimitExceeded; + if (invalidTime != null) result.invalidTime = invalidTime; + if (invalidTransactionType != null) + result.invalidTransactionType = invalidTransactionType; + return result; + } + + EvalViolation._(); + + factory EvalViolation.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EvalViolation.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, EvalViolation_Kind> + _EvalViolation_KindByTag = { + 1: EvalViolation_Kind.invalidTarget, + 2: EvalViolation_Kind.gasLimitExceeded, + 3: EvalViolation_Kind.rateLimitExceeded, + 4: EvalViolation_Kind.volumetricLimitExceeded, + 5: EvalViolation_Kind.invalidTime, + 6: EvalViolation_Kind.invalidTransactionType, + 0: EvalViolation_Kind.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EvalViolation', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..oo(0, [1, 2, 3, 4, 5, 6]) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'invalidTarget', $pb.PbFieldType.OY) + ..aOM( + 2, _omitFieldNames ? '' : 'gasLimitExceeded', + subBuilder: GasLimitExceededViolation.create) + ..aOM<$1.Empty>(3, _omitFieldNames ? '' : 'rateLimitExceeded', + subBuilder: $1.Empty.create) + ..aOM<$1.Empty>(4, _omitFieldNames ? '' : 'volumetricLimitExceeded', + subBuilder: $1.Empty.create) + ..aOM<$1.Empty>(5, _omitFieldNames ? '' : 'invalidTime', + subBuilder: $1.Empty.create) + ..aOM<$1.Empty>(6, _omitFieldNames ? '' : 'invalidTransactionType', + subBuilder: $1.Empty.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvalViolation clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvalViolation copyWith(void Function(EvalViolation) updates) => + super.copyWith((message) => updates(message as EvalViolation)) + as EvalViolation; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EvalViolation create() => EvalViolation._(); + @$core.override + EvalViolation createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EvalViolation getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EvalViolation? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + @$pb.TagNumber(4) + @$pb.TagNumber(5) + @$pb.TagNumber(6) + EvalViolation_Kind whichKind() => _EvalViolation_KindByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + @$pb.TagNumber(4) + @$pb.TagNumber(5) + @$pb.TagNumber(6) + void clearKind() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + $core.List<$core.int> get invalidTarget => $_getN(0); + @$pb.TagNumber(1) + set invalidTarget($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasInvalidTarget() => $_has(0); + @$pb.TagNumber(1) + void clearInvalidTarget() => $_clearField(1); + + @$pb.TagNumber(2) + GasLimitExceededViolation get gasLimitExceeded => $_getN(1); + @$pb.TagNumber(2) + set gasLimitExceeded(GasLimitExceededViolation value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasGasLimitExceeded() => $_has(1); + @$pb.TagNumber(2) + void clearGasLimitExceeded() => $_clearField(2); + @$pb.TagNumber(2) + GasLimitExceededViolation ensureGasLimitExceeded() => $_ensure(1); + + @$pb.TagNumber(3) + $1.Empty get rateLimitExceeded => $_getN(2); + @$pb.TagNumber(3) + set rateLimitExceeded($1.Empty value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasRateLimitExceeded() => $_has(2); + @$pb.TagNumber(3) + void clearRateLimitExceeded() => $_clearField(3); + @$pb.TagNumber(3) + $1.Empty ensureRateLimitExceeded() => $_ensure(2); + + @$pb.TagNumber(4) + $1.Empty get volumetricLimitExceeded => $_getN(3); + @$pb.TagNumber(4) + set volumetricLimitExceeded($1.Empty value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasVolumetricLimitExceeded() => $_has(3); + @$pb.TagNumber(4) + void clearVolumetricLimitExceeded() => $_clearField(4); + @$pb.TagNumber(4) + $1.Empty ensureVolumetricLimitExceeded() => $_ensure(3); + + @$pb.TagNumber(5) + $1.Empty get invalidTime => $_getN(4); + @$pb.TagNumber(5) + set invalidTime($1.Empty value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasInvalidTime() => $_has(4); + @$pb.TagNumber(5) + void clearInvalidTime() => $_clearField(5); + @$pb.TagNumber(5) + $1.Empty ensureInvalidTime() => $_ensure(4); + + @$pb.TagNumber(6) + $1.Empty get invalidTransactionType => $_getN(5); + @$pb.TagNumber(6) + set invalidTransactionType($1.Empty value) => $_setField(6, value); + @$pb.TagNumber(6) + $core.bool hasInvalidTransactionType() => $_has(5); + @$pb.TagNumber(6) + void clearInvalidTransactionType() => $_clearField(6); + @$pb.TagNumber(6) + $1.Empty ensureInvalidTransactionType() => $_ensure(5); +} + +/// Transaction was classified but no grant covers it +class NoMatchingGrantError extends $pb.GeneratedMessage { + factory NoMatchingGrantError({ + SpecificMeaning? meaning, + }) { + final result = create(); + if (meaning != null) result.meaning = meaning; + return result; + } + + NoMatchingGrantError._(); + + factory NoMatchingGrantError.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory NoMatchingGrantError.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'NoMatchingGrantError', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..aOM(1, _omitFieldNames ? '' : 'meaning', + subBuilder: SpecificMeaning.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + NoMatchingGrantError clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + NoMatchingGrantError copyWith(void Function(NoMatchingGrantError) updates) => + super.copyWith((message) => updates(message as NoMatchingGrantError)) + as NoMatchingGrantError; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static NoMatchingGrantError create() => NoMatchingGrantError._(); + @$core.override + NoMatchingGrantError createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static NoMatchingGrantError getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static NoMatchingGrantError? _defaultInstance; + + @$pb.TagNumber(1) + SpecificMeaning get meaning => $_getN(0); + @$pb.TagNumber(1) + set meaning(SpecificMeaning value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasMeaning() => $_has(0); + @$pb.TagNumber(1) + void clearMeaning() => $_clearField(1); + @$pb.TagNumber(1) + SpecificMeaning ensureMeaning() => $_ensure(0); +} + +/// Transaction was classified and a grant was found, but constraints were violated +class PolicyViolationsError extends $pb.GeneratedMessage { + factory PolicyViolationsError({ + SpecificMeaning? meaning, + $core.Iterable? violations, + }) { + final result = create(); + if (meaning != null) result.meaning = meaning; + if (violations != null) result.violations.addAll(violations); + return result; + } + + PolicyViolationsError._(); + + factory PolicyViolationsError.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory PolicyViolationsError.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'PolicyViolationsError', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..aOM(1, _omitFieldNames ? '' : 'meaning', + subBuilder: SpecificMeaning.create) + ..pPM(2, _omitFieldNames ? '' : 'violations', + subBuilder: EvalViolation.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PolicyViolationsError clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PolicyViolationsError copyWith( + void Function(PolicyViolationsError) updates) => + super.copyWith((message) => updates(message as PolicyViolationsError)) + as PolicyViolationsError; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static PolicyViolationsError create() => PolicyViolationsError._(); + @$core.override + PolicyViolationsError createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static PolicyViolationsError getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static PolicyViolationsError? _defaultInstance; + + @$pb.TagNumber(1) + SpecificMeaning get meaning => $_getN(0); + @$pb.TagNumber(1) + set meaning(SpecificMeaning value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasMeaning() => $_has(0); + @$pb.TagNumber(1) + void clearMeaning() => $_clearField(1); + @$pb.TagNumber(1) + SpecificMeaning ensureMeaning() => $_ensure(0); + + @$pb.TagNumber(2) + $pb.PbList get violations => $_getList(1); +} + +enum TransactionEvalError_Kind { + contractCreationNotSupported, + unsupportedTransactionType, + noMatchingGrant, + policyViolations, + notSet +} + +/// top-level error returned when transaction evaluation fails +class TransactionEvalError extends $pb.GeneratedMessage { + factory TransactionEvalError({ + $1.Empty? contractCreationNotSupported, + $1.Empty? unsupportedTransactionType, + NoMatchingGrantError? noMatchingGrant, + PolicyViolationsError? policyViolations, + }) { + final result = create(); + if (contractCreationNotSupported != null) + result.contractCreationNotSupported = contractCreationNotSupported; + if (unsupportedTransactionType != null) + result.unsupportedTransactionType = unsupportedTransactionType; + if (noMatchingGrant != null) result.noMatchingGrant = noMatchingGrant; + if (policyViolations != null) result.policyViolations = policyViolations; + return result; + } + + TransactionEvalError._(); + + factory TransactionEvalError.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory TransactionEvalError.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, TransactionEvalError_Kind> + _TransactionEvalError_KindByTag = { + 1: TransactionEvalError_Kind.contractCreationNotSupported, + 2: TransactionEvalError_Kind.unsupportedTransactionType, + 3: TransactionEvalError_Kind.noMatchingGrant, + 4: TransactionEvalError_Kind.policyViolations, + 0: TransactionEvalError_Kind.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'TransactionEvalError', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..oo(0, [1, 2, 3, 4]) + ..aOM<$1.Empty>(1, _omitFieldNames ? '' : 'contractCreationNotSupported', + subBuilder: $1.Empty.create) + ..aOM<$1.Empty>(2, _omitFieldNames ? '' : 'unsupportedTransactionType', + subBuilder: $1.Empty.create) + ..aOM(3, _omitFieldNames ? '' : 'noMatchingGrant', + subBuilder: NoMatchingGrantError.create) + ..aOM(4, _omitFieldNames ? '' : 'policyViolations', + subBuilder: PolicyViolationsError.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TransactionEvalError clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TransactionEvalError copyWith(void Function(TransactionEvalError) updates) => + super.copyWith((message) => updates(message as TransactionEvalError)) + as TransactionEvalError; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static TransactionEvalError create() => TransactionEvalError._(); + @$core.override + TransactionEvalError createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static TransactionEvalError getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static TransactionEvalError? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + @$pb.TagNumber(4) + TransactionEvalError_Kind whichKind() => + _TransactionEvalError_KindByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + @$pb.TagNumber(4) + void clearKind() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + $1.Empty get contractCreationNotSupported => $_getN(0); + @$pb.TagNumber(1) + set contractCreationNotSupported($1.Empty value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasContractCreationNotSupported() => $_has(0); + @$pb.TagNumber(1) + void clearContractCreationNotSupported() => $_clearField(1); + @$pb.TagNumber(1) + $1.Empty ensureContractCreationNotSupported() => $_ensure(0); + + @$pb.TagNumber(2) + $1.Empty get unsupportedTransactionType => $_getN(1); + @$pb.TagNumber(2) + set unsupportedTransactionType($1.Empty value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasUnsupportedTransactionType() => $_has(1); + @$pb.TagNumber(2) + void clearUnsupportedTransactionType() => $_clearField(2); + @$pb.TagNumber(2) + $1.Empty ensureUnsupportedTransactionType() => $_ensure(1); + + @$pb.TagNumber(3) + NoMatchingGrantError get noMatchingGrant => $_getN(2); + @$pb.TagNumber(3) + set noMatchingGrant(NoMatchingGrantError value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasNoMatchingGrant() => $_has(2); + @$pb.TagNumber(3) + void clearNoMatchingGrant() => $_clearField(3); + @$pb.TagNumber(3) + NoMatchingGrantError ensureNoMatchingGrant() => $_ensure(2); + + @$pb.TagNumber(4) + PolicyViolationsError get policyViolations => $_getN(3); + @$pb.TagNumber(4) + set policyViolations(PolicyViolationsError value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasPolicyViolations() => $_has(3); + @$pb.TagNumber(4) + void clearPolicyViolations() => $_clearField(4); + @$pb.TagNumber(4) + PolicyViolationsError ensurePolicyViolations() => $_ensure(3); +} + +/// --- UserAgent grant management --- +class EvmGrantCreateRequest extends $pb.GeneratedMessage { + factory EvmGrantCreateRequest({ + $core.int? clientId, + SharedSettings? shared, + SpecificGrant? specific, + }) { + final result = create(); + if (clientId != null) result.clientId = clientId; + if (shared != null) result.shared = shared; + if (specific != null) result.specific = specific; + return result; + } + + EvmGrantCreateRequest._(); + + factory EvmGrantCreateRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EvmGrantCreateRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EvmGrantCreateRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..aI(1, _omitFieldNames ? '' : 'clientId') + ..aOM(2, _omitFieldNames ? '' : 'shared', + subBuilder: SharedSettings.create) + ..aOM(3, _omitFieldNames ? '' : 'specific', + subBuilder: SpecificGrant.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmGrantCreateRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmGrantCreateRequest copyWith( + void Function(EvmGrantCreateRequest) updates) => + super.copyWith((message) => updates(message as EvmGrantCreateRequest)) + as EvmGrantCreateRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EvmGrantCreateRequest create() => EvmGrantCreateRequest._(); + @$core.override + EvmGrantCreateRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EvmGrantCreateRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EvmGrantCreateRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.int get clientId => $_getIZ(0); + @$pb.TagNumber(1) + set clientId($core.int value) => $_setSignedInt32(0, value); + @$pb.TagNumber(1) + $core.bool hasClientId() => $_has(0); + @$pb.TagNumber(1) + void clearClientId() => $_clearField(1); + + @$pb.TagNumber(2) + SharedSettings get shared => $_getN(1); + @$pb.TagNumber(2) + set shared(SharedSettings value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasShared() => $_has(1); + @$pb.TagNumber(2) + void clearShared() => $_clearField(2); + @$pb.TagNumber(2) + SharedSettings ensureShared() => $_ensure(1); + + @$pb.TagNumber(3) + SpecificGrant get specific => $_getN(2); + @$pb.TagNumber(3) + set specific(SpecificGrant value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasSpecific() => $_has(2); + @$pb.TagNumber(3) + void clearSpecific() => $_clearField(3); + @$pb.TagNumber(3) + SpecificGrant ensureSpecific() => $_ensure(2); +} + +enum EvmGrantCreateResponse_Result { grantId, error, notSet } + +class EvmGrantCreateResponse extends $pb.GeneratedMessage { + factory EvmGrantCreateResponse({ + $core.int? grantId, + EvmError? error, + }) { + final result = create(); + if (grantId != null) result.grantId = grantId; + if (error != null) result.error = error; + return result; + } + + EvmGrantCreateResponse._(); + + factory EvmGrantCreateResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EvmGrantCreateResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, EvmGrantCreateResponse_Result> + _EvmGrantCreateResponse_ResultByTag = { + 1: EvmGrantCreateResponse_Result.grantId, + 2: EvmGrantCreateResponse_Result.error, + 0: EvmGrantCreateResponse_Result.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EvmGrantCreateResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..oo(0, [1, 2]) + ..aI(1, _omitFieldNames ? '' : 'grantId') + ..aE(2, _omitFieldNames ? '' : 'error', + enumValues: EvmError.values) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmGrantCreateResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmGrantCreateResponse copyWith( + void Function(EvmGrantCreateResponse) updates) => + super.copyWith((message) => updates(message as EvmGrantCreateResponse)) + as EvmGrantCreateResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EvmGrantCreateResponse create() => EvmGrantCreateResponse._(); + @$core.override + EvmGrantCreateResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EvmGrantCreateResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EvmGrantCreateResponse? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + EvmGrantCreateResponse_Result whichResult() => + _EvmGrantCreateResponse_ResultByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + void clearResult() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + $core.int get grantId => $_getIZ(0); + @$pb.TagNumber(1) + set grantId($core.int value) => $_setSignedInt32(0, value); + @$pb.TagNumber(1) + $core.bool hasGrantId() => $_has(0); + @$pb.TagNumber(1) + void clearGrantId() => $_clearField(1); + + @$pb.TagNumber(2) + EvmError get error => $_getN(1); + @$pb.TagNumber(2) + set error(EvmError value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasError() => $_has(1); + @$pb.TagNumber(2) + void clearError() => $_clearField(2); +} + +class EvmGrantDeleteRequest extends $pb.GeneratedMessage { + factory EvmGrantDeleteRequest({ + $core.int? grantId, + }) { + final result = create(); + if (grantId != null) result.grantId = grantId; + return result; + } + + EvmGrantDeleteRequest._(); + + factory EvmGrantDeleteRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EvmGrantDeleteRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EvmGrantDeleteRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..aI(1, _omitFieldNames ? '' : 'grantId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmGrantDeleteRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmGrantDeleteRequest copyWith( + void Function(EvmGrantDeleteRequest) updates) => + super.copyWith((message) => updates(message as EvmGrantDeleteRequest)) + as EvmGrantDeleteRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EvmGrantDeleteRequest create() => EvmGrantDeleteRequest._(); + @$core.override + EvmGrantDeleteRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EvmGrantDeleteRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EvmGrantDeleteRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.int get grantId => $_getIZ(0); + @$pb.TagNumber(1) + set grantId($core.int value) => $_setSignedInt32(0, value); + @$pb.TagNumber(1) + $core.bool hasGrantId() => $_has(0); + @$pb.TagNumber(1) + void clearGrantId() => $_clearField(1); +} + +enum EvmGrantDeleteResponse_Result { ok, error, notSet } + +class EvmGrantDeleteResponse extends $pb.GeneratedMessage { + factory EvmGrantDeleteResponse({ + $1.Empty? ok, + EvmError? error, + }) { + final result = create(); + if (ok != null) result.ok = ok; + if (error != null) result.error = error; + return result; + } + + EvmGrantDeleteResponse._(); + + factory EvmGrantDeleteResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EvmGrantDeleteResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, EvmGrantDeleteResponse_Result> + _EvmGrantDeleteResponse_ResultByTag = { + 1: EvmGrantDeleteResponse_Result.ok, + 2: EvmGrantDeleteResponse_Result.error, + 0: EvmGrantDeleteResponse_Result.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EvmGrantDeleteResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..oo(0, [1, 2]) + ..aOM<$1.Empty>(1, _omitFieldNames ? '' : 'ok', subBuilder: $1.Empty.create) + ..aE(2, _omitFieldNames ? '' : 'error', + enumValues: EvmError.values) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmGrantDeleteResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmGrantDeleteResponse copyWith( + void Function(EvmGrantDeleteResponse) updates) => + super.copyWith((message) => updates(message as EvmGrantDeleteResponse)) + as EvmGrantDeleteResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EvmGrantDeleteResponse create() => EvmGrantDeleteResponse._(); + @$core.override + EvmGrantDeleteResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EvmGrantDeleteResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EvmGrantDeleteResponse? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + EvmGrantDeleteResponse_Result whichResult() => + _EvmGrantDeleteResponse_ResultByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + void clearResult() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + $1.Empty get ok => $_getN(0); + @$pb.TagNumber(1) + set ok($1.Empty value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasOk() => $_has(0); + @$pb.TagNumber(1) + void clearOk() => $_clearField(1); + @$pb.TagNumber(1) + $1.Empty ensureOk() => $_ensure(0); + + @$pb.TagNumber(2) + EvmError get error => $_getN(1); + @$pb.TagNumber(2) + set error(EvmError value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasError() => $_has(1); + @$pb.TagNumber(2) + void clearError() => $_clearField(2); +} + +/// Basic grant info returned in grant listings +class GrantEntry extends $pb.GeneratedMessage { + factory GrantEntry({ + $core.int? id, + $core.int? clientId, + SharedSettings? shared, + SpecificGrant? specific, + }) { + final result = create(); + if (id != null) result.id = id; + if (clientId != null) result.clientId = clientId; + if (shared != null) result.shared = shared; + if (specific != null) result.specific = specific; + return result; + } + + GrantEntry._(); + + factory GrantEntry.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GrantEntry.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GrantEntry', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..aI(1, _omitFieldNames ? '' : 'id') + ..aI(2, _omitFieldNames ? '' : 'clientId') + ..aOM(3, _omitFieldNames ? '' : 'shared', + subBuilder: SharedSettings.create) + ..aOM(4, _omitFieldNames ? '' : 'specific', + subBuilder: SpecificGrant.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GrantEntry clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GrantEntry copyWith(void Function(GrantEntry) updates) => + super.copyWith((message) => updates(message as GrantEntry)) as GrantEntry; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GrantEntry create() => GrantEntry._(); + @$core.override + GrantEntry createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GrantEntry getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GrantEntry? _defaultInstance; + + @$pb.TagNumber(1) + $core.int get id => $_getIZ(0); + @$pb.TagNumber(1) + set id($core.int value) => $_setSignedInt32(0, value); + @$pb.TagNumber(1) + $core.bool hasId() => $_has(0); + @$pb.TagNumber(1) + void clearId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.int get clientId => $_getIZ(1); + @$pb.TagNumber(2) + set clientId($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasClientId() => $_has(1); + @$pb.TagNumber(2) + void clearClientId() => $_clearField(2); + + @$pb.TagNumber(3) + SharedSettings get shared => $_getN(2); + @$pb.TagNumber(3) + set shared(SharedSettings value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasShared() => $_has(2); + @$pb.TagNumber(3) + void clearShared() => $_clearField(3); + @$pb.TagNumber(3) + SharedSettings ensureShared() => $_ensure(2); + + @$pb.TagNumber(4) + SpecificGrant get specific => $_getN(3); + @$pb.TagNumber(4) + set specific(SpecificGrant value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasSpecific() => $_has(3); + @$pb.TagNumber(4) + void clearSpecific() => $_clearField(4); + @$pb.TagNumber(4) + SpecificGrant ensureSpecific() => $_ensure(3); +} + +class EvmGrantListRequest extends $pb.GeneratedMessage { + factory EvmGrantListRequest({ + $core.int? walletId, + }) { + final result = create(); + if (walletId != null) result.walletId = walletId; + return result; + } + + EvmGrantListRequest._(); + + factory EvmGrantListRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EvmGrantListRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EvmGrantListRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..aI(1, _omitFieldNames ? '' : 'walletId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmGrantListRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmGrantListRequest copyWith(void Function(EvmGrantListRequest) updates) => + super.copyWith((message) => updates(message as EvmGrantListRequest)) + as EvmGrantListRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EvmGrantListRequest create() => EvmGrantListRequest._(); + @$core.override + EvmGrantListRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EvmGrantListRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EvmGrantListRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.int get walletId => $_getIZ(0); + @$pb.TagNumber(1) + set walletId($core.int value) => $_setSignedInt32(0, value); + @$pb.TagNumber(1) + $core.bool hasWalletId() => $_has(0); + @$pb.TagNumber(1) + void clearWalletId() => $_clearField(1); +} + +enum EvmGrantListResponse_Result { grants, error, notSet } + +class EvmGrantListResponse extends $pb.GeneratedMessage { + factory EvmGrantListResponse({ + EvmGrantList? grants, + EvmError? error, + }) { + final result = create(); + if (grants != null) result.grants = grants; + if (error != null) result.error = error; + return result; + } + + EvmGrantListResponse._(); + + factory EvmGrantListResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EvmGrantListResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, EvmGrantListResponse_Result> + _EvmGrantListResponse_ResultByTag = { + 1: EvmGrantListResponse_Result.grants, + 2: EvmGrantListResponse_Result.error, + 0: EvmGrantListResponse_Result.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EvmGrantListResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..oo(0, [1, 2]) + ..aOM(1, _omitFieldNames ? '' : 'grants', + subBuilder: EvmGrantList.create) + ..aE(2, _omitFieldNames ? '' : 'error', + enumValues: EvmError.values) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmGrantListResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmGrantListResponse copyWith(void Function(EvmGrantListResponse) updates) => + super.copyWith((message) => updates(message as EvmGrantListResponse)) + as EvmGrantListResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EvmGrantListResponse create() => EvmGrantListResponse._(); + @$core.override + EvmGrantListResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EvmGrantListResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EvmGrantListResponse? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + EvmGrantListResponse_Result whichResult() => + _EvmGrantListResponse_ResultByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + void clearResult() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + EvmGrantList get grants => $_getN(0); + @$pb.TagNumber(1) + set grants(EvmGrantList value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasGrants() => $_has(0); + @$pb.TagNumber(1) + void clearGrants() => $_clearField(1); + @$pb.TagNumber(1) + EvmGrantList ensureGrants() => $_ensure(0); + + @$pb.TagNumber(2) + EvmError get error => $_getN(1); + @$pb.TagNumber(2) + set error(EvmError value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasError() => $_has(1); + @$pb.TagNumber(2) + void clearError() => $_clearField(2); +} + +class EvmGrantList extends $pb.GeneratedMessage { + factory EvmGrantList({ + $core.Iterable? grants, + }) { + final result = create(); + if (grants != null) result.grants.addAll(grants); + return result; + } + + EvmGrantList._(); + + factory EvmGrantList.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EvmGrantList.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EvmGrantList', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..pPM(1, _omitFieldNames ? '' : 'grants', + subBuilder: GrantEntry.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmGrantList clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmGrantList copyWith(void Function(EvmGrantList) updates) => + super.copyWith((message) => updates(message as EvmGrantList)) + as EvmGrantList; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EvmGrantList create() => EvmGrantList._(); + @$core.override + EvmGrantList createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EvmGrantList getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EvmGrantList? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbList get grants => $_getList(0); +} + +class EvmSignTransactionRequest extends $pb.GeneratedMessage { + factory EvmSignTransactionRequest({ + $core.List<$core.int>? walletAddress, + $core.List<$core.int>? rlpTransaction, + }) { + final result = create(); + if (walletAddress != null) result.walletAddress = walletAddress; + if (rlpTransaction != null) result.rlpTransaction = rlpTransaction; + return result; + } + + EvmSignTransactionRequest._(); + + factory EvmSignTransactionRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EvmSignTransactionRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EvmSignTransactionRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'walletAddress', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>( + 2, _omitFieldNames ? '' : 'rlpTransaction', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmSignTransactionRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmSignTransactionRequest copyWith( + void Function(EvmSignTransactionRequest) updates) => + super.copyWith((message) => updates(message as EvmSignTransactionRequest)) + as EvmSignTransactionRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EvmSignTransactionRequest create() => EvmSignTransactionRequest._(); + @$core.override + EvmSignTransactionRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EvmSignTransactionRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EvmSignTransactionRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get walletAddress => $_getN(0); + @$pb.TagNumber(1) + set walletAddress($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasWalletAddress() => $_has(0); + @$pb.TagNumber(1) + void clearWalletAddress() => $_clearField(1); + + @$pb.TagNumber(2) + $core.List<$core.int> get rlpTransaction => $_getN(1); + @$pb.TagNumber(2) + set rlpTransaction($core.List<$core.int> value) => $_setBytes(1, value); + @$pb.TagNumber(2) + $core.bool hasRlpTransaction() => $_has(1); + @$pb.TagNumber(2) + void clearRlpTransaction() => $_clearField(2); +} + +enum EvmSignTransactionResponse_Result { signature, evalError, error, notSet } + +/// oneof because signing and evaluation happen atomically — a signing failure +/// is always either an eval error or an internal error, never a partial success +class EvmSignTransactionResponse extends $pb.GeneratedMessage { + factory EvmSignTransactionResponse({ + $core.List<$core.int>? signature, + TransactionEvalError? evalError, + EvmError? error, + }) { + final result = create(); + if (signature != null) result.signature = signature; + if (evalError != null) result.evalError = evalError; + if (error != null) result.error = error; + return result; + } + + EvmSignTransactionResponse._(); + + factory EvmSignTransactionResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EvmSignTransactionResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, EvmSignTransactionResponse_Result> + _EvmSignTransactionResponse_ResultByTag = { + 1: EvmSignTransactionResponse_Result.signature, + 2: EvmSignTransactionResponse_Result.evalError, + 3: EvmSignTransactionResponse_Result.error, + 0: EvmSignTransactionResponse_Result.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EvmSignTransactionResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..oo(0, [1, 2, 3]) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'signature', $pb.PbFieldType.OY) + ..aOM(2, _omitFieldNames ? '' : 'evalError', + subBuilder: TransactionEvalError.create) + ..aE(3, _omitFieldNames ? '' : 'error', + enumValues: EvmError.values) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmSignTransactionResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmSignTransactionResponse copyWith( + void Function(EvmSignTransactionResponse) updates) => + super.copyWith( + (message) => updates(message as EvmSignTransactionResponse)) + as EvmSignTransactionResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EvmSignTransactionResponse create() => EvmSignTransactionResponse._(); + @$core.override + EvmSignTransactionResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EvmSignTransactionResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EvmSignTransactionResponse? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + EvmSignTransactionResponse_Result whichResult() => + _EvmSignTransactionResponse_ResultByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + void clearResult() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + $core.List<$core.int> get signature => $_getN(0); + @$pb.TagNumber(1) + set signature($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasSignature() => $_has(0); + @$pb.TagNumber(1) + void clearSignature() => $_clearField(1); + + @$pb.TagNumber(2) + TransactionEvalError get evalError => $_getN(1); + @$pb.TagNumber(2) + set evalError(TransactionEvalError value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasEvalError() => $_has(1); + @$pb.TagNumber(2) + void clearEvalError() => $_clearField(2); + @$pb.TagNumber(2) + TransactionEvalError ensureEvalError() => $_ensure(1); + + @$pb.TagNumber(3) + EvmError get error => $_getN(2); + @$pb.TagNumber(3) + set error(EvmError value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasError() => $_has(2); + @$pb.TagNumber(3) + void clearError() => $_clearField(3); +} + +class EvmAnalyzeTransactionRequest extends $pb.GeneratedMessage { + factory EvmAnalyzeTransactionRequest({ + $core.List<$core.int>? walletAddress, + $core.List<$core.int>? rlpTransaction, + }) { + final result = create(); + if (walletAddress != null) result.walletAddress = walletAddress; + if (rlpTransaction != null) result.rlpTransaction = rlpTransaction; + return result; + } + + EvmAnalyzeTransactionRequest._(); + + factory EvmAnalyzeTransactionRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EvmAnalyzeTransactionRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EvmAnalyzeTransactionRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'walletAddress', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>( + 2, _omitFieldNames ? '' : 'rlpTransaction', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmAnalyzeTransactionRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmAnalyzeTransactionRequest copyWith( + void Function(EvmAnalyzeTransactionRequest) updates) => + super.copyWith( + (message) => updates(message as EvmAnalyzeTransactionRequest)) + as EvmAnalyzeTransactionRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EvmAnalyzeTransactionRequest create() => + EvmAnalyzeTransactionRequest._(); + @$core.override + EvmAnalyzeTransactionRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EvmAnalyzeTransactionRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EvmAnalyzeTransactionRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get walletAddress => $_getN(0); + @$pb.TagNumber(1) + set walletAddress($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasWalletAddress() => $_has(0); + @$pb.TagNumber(1) + void clearWalletAddress() => $_clearField(1); + + @$pb.TagNumber(2) + $core.List<$core.int> get rlpTransaction => $_getN(1); + @$pb.TagNumber(2) + set rlpTransaction($core.List<$core.int> value) => $_setBytes(1, value); + @$pb.TagNumber(2) + $core.bool hasRlpTransaction() => $_has(1); + @$pb.TagNumber(2) + void clearRlpTransaction() => $_clearField(2); +} + +enum EvmAnalyzeTransactionResponse_Result { meaning, evalError, error, notSet } + +class EvmAnalyzeTransactionResponse extends $pb.GeneratedMessage { + factory EvmAnalyzeTransactionResponse({ + SpecificMeaning? meaning, + TransactionEvalError? evalError, + EvmError? error, + }) { + final result = create(); + if (meaning != null) result.meaning = meaning; + if (evalError != null) result.evalError = evalError; + if (error != null) result.error = error; + return result; + } + + EvmAnalyzeTransactionResponse._(); + + factory EvmAnalyzeTransactionResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EvmAnalyzeTransactionResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, EvmAnalyzeTransactionResponse_Result> + _EvmAnalyzeTransactionResponse_ResultByTag = { + 1: EvmAnalyzeTransactionResponse_Result.meaning, + 2: EvmAnalyzeTransactionResponse_Result.evalError, + 3: EvmAnalyzeTransactionResponse_Result.error, + 0: EvmAnalyzeTransactionResponse_Result.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EvmAnalyzeTransactionResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'), + createEmptyInstance: create) + ..oo(0, [1, 2, 3]) + ..aOM(1, _omitFieldNames ? '' : 'meaning', + subBuilder: SpecificMeaning.create) + ..aOM(2, _omitFieldNames ? '' : 'evalError', + subBuilder: TransactionEvalError.create) + ..aE(3, _omitFieldNames ? '' : 'error', + enumValues: EvmError.values) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmAnalyzeTransactionResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvmAnalyzeTransactionResponse copyWith( + void Function(EvmAnalyzeTransactionResponse) updates) => + super.copyWith( + (message) => updates(message as EvmAnalyzeTransactionResponse)) + as EvmAnalyzeTransactionResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EvmAnalyzeTransactionResponse create() => + EvmAnalyzeTransactionResponse._(); + @$core.override + EvmAnalyzeTransactionResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EvmAnalyzeTransactionResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EvmAnalyzeTransactionResponse? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + EvmAnalyzeTransactionResponse_Result whichResult() => + _EvmAnalyzeTransactionResponse_ResultByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + void clearResult() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + SpecificMeaning get meaning => $_getN(0); + @$pb.TagNumber(1) + set meaning(SpecificMeaning value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasMeaning() => $_has(0); + @$pb.TagNumber(1) + void clearMeaning() => $_clearField(1); + @$pb.TagNumber(1) + SpecificMeaning ensureMeaning() => $_ensure(0); + + @$pb.TagNumber(2) + TransactionEvalError get evalError => $_getN(1); + @$pb.TagNumber(2) + set evalError(TransactionEvalError value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasEvalError() => $_has(1); + @$pb.TagNumber(2) + void clearEvalError() => $_clearField(2); + @$pb.TagNumber(2) + TransactionEvalError ensureEvalError() => $_ensure(1); + + @$pb.TagNumber(3) + EvmError get error => $_getN(2); + @$pb.TagNumber(3) + set error(EvmError value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasError() => $_has(2); + @$pb.TagNumber(3) + void clearError() => $_clearField(3); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/useragent/lib/proto/evm.pbenum.dart b/useragent/lib/proto/evm.pbenum.dart new file mode 100644 index 0000000..3146b4d --- /dev/null +++ b/useragent/lib/proto/evm.pbenum.dart @@ -0,0 +1,40 @@ +// This is a generated file - do not edit. +// +// Generated from evm.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +class EvmError extends $pb.ProtobufEnum { + static const EvmError EVM_ERROR_UNSPECIFIED = + EvmError._(0, _omitEnumNames ? '' : 'EVM_ERROR_UNSPECIFIED'); + static const EvmError EVM_ERROR_VAULT_SEALED = + EvmError._(1, _omitEnumNames ? '' : 'EVM_ERROR_VAULT_SEALED'); + static const EvmError EVM_ERROR_INTERNAL = + EvmError._(2, _omitEnumNames ? '' : 'EVM_ERROR_INTERNAL'); + + static const $core.List values = [ + EVM_ERROR_UNSPECIFIED, + EVM_ERROR_VAULT_SEALED, + EVM_ERROR_INTERNAL, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 2); + static EvmError? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const EvmError._(super.value, super.name); +} + +const $core.bool _omitEnumNames = + $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/useragent/lib/proto/evm.pbjson.dart b/useragent/lib/proto/evm.pbjson.dart new file mode 100644 index 0000000..a4a9d09 --- /dev/null +++ b/useragent/lib/proto/evm.pbjson.dart @@ -0,0 +1,950 @@ +// This is a generated file - do not edit. +// +// Generated from evm.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports +// ignore_for_file: unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use evmErrorDescriptor instead') +const EvmError$json = { + '1': 'EvmError', + '2': [ + {'1': 'EVM_ERROR_UNSPECIFIED', '2': 0}, + {'1': 'EVM_ERROR_VAULT_SEALED', '2': 1}, + {'1': 'EVM_ERROR_INTERNAL', '2': 2}, + ], +}; + +/// Descriptor for `EvmError`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List evmErrorDescriptor = $convert.base64Decode( + 'CghFdm1FcnJvchIZChVFVk1fRVJST1JfVU5TUEVDSUZJRUQQABIaChZFVk1fRVJST1JfVkFVTF' + 'RfU0VBTEVEEAESFgoSRVZNX0VSUk9SX0lOVEVSTkFMEAI='); + +@$core.Deprecated('Use walletEntryDescriptor instead') +const WalletEntry$json = { + '1': 'WalletEntry', + '2': [ + {'1': 'address', '3': 1, '4': 1, '5': 12, '10': 'address'}, + ], +}; + +/// Descriptor for `WalletEntry`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List walletEntryDescriptor = $convert + .base64Decode('CgtXYWxsZXRFbnRyeRIYCgdhZGRyZXNzGAEgASgMUgdhZGRyZXNz'); + +@$core.Deprecated('Use walletListDescriptor instead') +const WalletList$json = { + '1': 'WalletList', + '2': [ + { + '1': 'wallets', + '3': 1, + '4': 3, + '5': 11, + '6': '.arbiter.evm.WalletEntry', + '10': 'wallets' + }, + ], +}; + +/// Descriptor for `WalletList`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List walletListDescriptor = $convert.base64Decode( + 'CgpXYWxsZXRMaXN0EjIKB3dhbGxldHMYASADKAsyGC5hcmJpdGVyLmV2bS5XYWxsZXRFbnRyeV' + 'IHd2FsbGV0cw=='); + +@$core.Deprecated('Use walletCreateResponseDescriptor instead') +const WalletCreateResponse$json = { + '1': 'WalletCreateResponse', + '2': [ + { + '1': 'wallet', + '3': 1, + '4': 1, + '5': 11, + '6': '.arbiter.evm.WalletEntry', + '9': 0, + '10': 'wallet' + }, + { + '1': 'error', + '3': 2, + '4': 1, + '5': 14, + '6': '.arbiter.evm.EvmError', + '9': 0, + '10': 'error' + }, + ], + '8': [ + {'1': 'result'}, + ], +}; + +/// Descriptor for `WalletCreateResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List walletCreateResponseDescriptor = $convert.base64Decode( + 'ChRXYWxsZXRDcmVhdGVSZXNwb25zZRIyCgZ3YWxsZXQYASABKAsyGC5hcmJpdGVyLmV2bS5XYW' + 'xsZXRFbnRyeUgAUgZ3YWxsZXQSLQoFZXJyb3IYAiABKA4yFS5hcmJpdGVyLmV2bS5Fdm1FcnJv' + 'ckgAUgVlcnJvckIICgZyZXN1bHQ='); + +@$core.Deprecated('Use walletListResponseDescriptor instead') +const WalletListResponse$json = { + '1': 'WalletListResponse', + '2': [ + { + '1': 'wallets', + '3': 1, + '4': 1, + '5': 11, + '6': '.arbiter.evm.WalletList', + '9': 0, + '10': 'wallets' + }, + { + '1': 'error', + '3': 2, + '4': 1, + '5': 14, + '6': '.arbiter.evm.EvmError', + '9': 0, + '10': 'error' + }, + ], + '8': [ + {'1': 'result'}, + ], +}; + +/// Descriptor for `WalletListResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List walletListResponseDescriptor = $convert.base64Decode( + 'ChJXYWxsZXRMaXN0UmVzcG9uc2USMwoHd2FsbGV0cxgBIAEoCzIXLmFyYml0ZXIuZXZtLldhbG' + 'xldExpc3RIAFIHd2FsbGV0cxItCgVlcnJvchgCIAEoDjIVLmFyYml0ZXIuZXZtLkV2bUVycm9y' + 'SABSBWVycm9yQggKBnJlc3VsdA=='); + +@$core.Deprecated('Use transactionRateLimitDescriptor instead') +const TransactionRateLimit$json = { + '1': 'TransactionRateLimit', + '2': [ + {'1': 'count', '3': 1, '4': 1, '5': 13, '10': 'count'}, + {'1': 'window_secs', '3': 2, '4': 1, '5': 3, '10': 'windowSecs'}, + ], +}; + +/// Descriptor for `TransactionRateLimit`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List transactionRateLimitDescriptor = $convert.base64Decode( + 'ChRUcmFuc2FjdGlvblJhdGVMaW1pdBIUCgVjb3VudBgBIAEoDVIFY291bnQSHwoLd2luZG93X3' + 'NlY3MYAiABKANSCndpbmRvd1NlY3M='); + +@$core.Deprecated('Use volumeRateLimitDescriptor instead') +const VolumeRateLimit$json = { + '1': 'VolumeRateLimit', + '2': [ + {'1': 'max_volume', '3': 1, '4': 1, '5': 12, '10': 'maxVolume'}, + {'1': 'window_secs', '3': 2, '4': 1, '5': 3, '10': 'windowSecs'}, + ], +}; + +/// Descriptor for `VolumeRateLimit`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List volumeRateLimitDescriptor = $convert.base64Decode( + 'Cg9Wb2x1bWVSYXRlTGltaXQSHQoKbWF4X3ZvbHVtZRgBIAEoDFIJbWF4Vm9sdW1lEh8KC3dpbm' + 'Rvd19zZWNzGAIgASgDUgp3aW5kb3dTZWNz'); + +@$core.Deprecated('Use sharedSettingsDescriptor instead') +const SharedSettings$json = { + '1': 'SharedSettings', + '2': [ + {'1': 'wallet_id', '3': 1, '4': 1, '5': 5, '10': 'walletId'}, + {'1': 'chain_id', '3': 2, '4': 1, '5': 4, '10': 'chainId'}, + { + '1': 'valid_from', + '3': 3, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '9': 0, + '10': 'validFrom', + '17': true + }, + { + '1': 'valid_until', + '3': 4, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '9': 1, + '10': 'validUntil', + '17': true + }, + { + '1': 'max_gas_fee_per_gas', + '3': 5, + '4': 1, + '5': 12, + '9': 2, + '10': 'maxGasFeePerGas', + '17': true + }, + { + '1': 'max_priority_fee_per_gas', + '3': 6, + '4': 1, + '5': 12, + '9': 3, + '10': 'maxPriorityFeePerGas', + '17': true + }, + { + '1': 'rate_limit', + '3': 7, + '4': 1, + '5': 11, + '6': '.arbiter.evm.TransactionRateLimit', + '9': 4, + '10': 'rateLimit', + '17': true + }, + ], + '8': [ + {'1': '_valid_from'}, + {'1': '_valid_until'}, + {'1': '_max_gas_fee_per_gas'}, + {'1': '_max_priority_fee_per_gas'}, + {'1': '_rate_limit'}, + ], +}; + +/// Descriptor for `SharedSettings`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List sharedSettingsDescriptor = $convert.base64Decode( + 'Cg5TaGFyZWRTZXR0aW5ncxIbCgl3YWxsZXRfaWQYASABKAVSCHdhbGxldElkEhkKCGNoYWluX2' + 'lkGAIgASgEUgdjaGFpbklkEj4KCnZhbGlkX2Zyb20YAyABKAsyGi5nb29nbGUucHJvdG9idWYu' + 'VGltZXN0YW1wSABSCXZhbGlkRnJvbYgBARJACgt2YWxpZF91bnRpbBgEIAEoCzIaLmdvb2dsZS' + '5wcm90b2J1Zi5UaW1lc3RhbXBIAVIKdmFsaWRVbnRpbIgBARIxChNtYXhfZ2FzX2ZlZV9wZXJf' + 'Z2FzGAUgASgMSAJSD21heEdhc0ZlZVBlckdhc4gBARI7ChhtYXhfcHJpb3JpdHlfZmVlX3Blcl' + '9nYXMYBiABKAxIA1IUbWF4UHJpb3JpdHlGZWVQZXJHYXOIAQESRQoKcmF0ZV9saW1pdBgHIAEo' + 'CzIhLmFyYml0ZXIuZXZtLlRyYW5zYWN0aW9uUmF0ZUxpbWl0SARSCXJhdGVMaW1pdIgBAUINCg' + 'tfdmFsaWRfZnJvbUIOCgxfdmFsaWRfdW50aWxCFgoUX21heF9nYXNfZmVlX3Blcl9nYXNCGwoZ' + 'X21heF9wcmlvcml0eV9mZWVfcGVyX2dhc0INCgtfcmF0ZV9saW1pdA=='); + +@$core.Deprecated('Use etherTransferSettingsDescriptor instead') +const EtherTransferSettings$json = { + '1': 'EtherTransferSettings', + '2': [ + {'1': 'targets', '3': 1, '4': 3, '5': 12, '10': 'targets'}, + { + '1': 'limit', + '3': 2, + '4': 1, + '5': 11, + '6': '.arbiter.evm.VolumeRateLimit', + '10': 'limit' + }, + ], +}; + +/// Descriptor for `EtherTransferSettings`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List etherTransferSettingsDescriptor = $convert.base64Decode( + 'ChVFdGhlclRyYW5zZmVyU2V0dGluZ3MSGAoHdGFyZ2V0cxgBIAMoDFIHdGFyZ2V0cxIyCgVsaW' + '1pdBgCIAEoCzIcLmFyYml0ZXIuZXZtLlZvbHVtZVJhdGVMaW1pdFIFbGltaXQ='); + +@$core.Deprecated('Use tokenTransferSettingsDescriptor instead') +const TokenTransferSettings$json = { + '1': 'TokenTransferSettings', + '2': [ + {'1': 'token_contract', '3': 1, '4': 1, '5': 12, '10': 'tokenContract'}, + { + '1': 'target', + '3': 2, + '4': 1, + '5': 12, + '9': 0, + '10': 'target', + '17': true + }, + { + '1': 'volume_limits', + '3': 3, + '4': 3, + '5': 11, + '6': '.arbiter.evm.VolumeRateLimit', + '10': 'volumeLimits' + }, + ], + '8': [ + {'1': '_target'}, + ], +}; + +/// Descriptor for `TokenTransferSettings`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List tokenTransferSettingsDescriptor = $convert.base64Decode( + 'ChVUb2tlblRyYW5zZmVyU2V0dGluZ3MSJQoOdG9rZW5fY29udHJhY3QYASABKAxSDXRva2VuQ2' + '9udHJhY3QSGwoGdGFyZ2V0GAIgASgMSABSBnRhcmdldIgBARJBCg12b2x1bWVfbGltaXRzGAMg' + 'AygLMhwuYXJiaXRlci5ldm0uVm9sdW1lUmF0ZUxpbWl0Ugx2b2x1bWVMaW1pdHNCCQoHX3Rhcm' + 'dldA=='); + +@$core.Deprecated('Use specificGrantDescriptor instead') +const SpecificGrant$json = { + '1': 'SpecificGrant', + '2': [ + { + '1': 'ether_transfer', + '3': 1, + '4': 1, + '5': 11, + '6': '.arbiter.evm.EtherTransferSettings', + '9': 0, + '10': 'etherTransfer' + }, + { + '1': 'token_transfer', + '3': 2, + '4': 1, + '5': 11, + '6': '.arbiter.evm.TokenTransferSettings', + '9': 0, + '10': 'tokenTransfer' + }, + ], + '8': [ + {'1': 'grant'}, + ], +}; + +/// Descriptor for `SpecificGrant`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List specificGrantDescriptor = $convert.base64Decode( + 'Cg1TcGVjaWZpY0dyYW50EksKDmV0aGVyX3RyYW5zZmVyGAEgASgLMiIuYXJiaXRlci5ldm0uRX' + 'RoZXJUcmFuc2ZlclNldHRpbmdzSABSDWV0aGVyVHJhbnNmZXISSwoOdG9rZW5fdHJhbnNmZXIY' + 'AiABKAsyIi5hcmJpdGVyLmV2bS5Ub2tlblRyYW5zZmVyU2V0dGluZ3NIAFINdG9rZW5UcmFuc2' + 'ZlckIHCgVncmFudA=='); + +@$core.Deprecated('Use etherTransferMeaningDescriptor instead') +const EtherTransferMeaning$json = { + '1': 'EtherTransferMeaning', + '2': [ + {'1': 'to', '3': 1, '4': 1, '5': 12, '10': 'to'}, + {'1': 'value', '3': 2, '4': 1, '5': 12, '10': 'value'}, + ], +}; + +/// Descriptor for `EtherTransferMeaning`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List etherTransferMeaningDescriptor = $convert.base64Decode( + 'ChRFdGhlclRyYW5zZmVyTWVhbmluZxIOCgJ0bxgBIAEoDFICdG8SFAoFdmFsdWUYAiABKAxSBX' + 'ZhbHVl'); + +@$core.Deprecated('Use tokenInfoDescriptor instead') +const TokenInfo$json = { + '1': 'TokenInfo', + '2': [ + {'1': 'symbol', '3': 1, '4': 1, '5': 9, '10': 'symbol'}, + {'1': 'address', '3': 2, '4': 1, '5': 12, '10': 'address'}, + {'1': 'chain_id', '3': 3, '4': 1, '5': 4, '10': 'chainId'}, + ], +}; + +/// Descriptor for `TokenInfo`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List tokenInfoDescriptor = $convert.base64Decode( + 'CglUb2tlbkluZm8SFgoGc3ltYm9sGAEgASgJUgZzeW1ib2wSGAoHYWRkcmVzcxgCIAEoDFIHYW' + 'RkcmVzcxIZCghjaGFpbl9pZBgDIAEoBFIHY2hhaW5JZA=='); + +@$core.Deprecated('Use tokenTransferMeaningDescriptor instead') +const TokenTransferMeaning$json = { + '1': 'TokenTransferMeaning', + '2': [ + { + '1': 'token', + '3': 1, + '4': 1, + '5': 11, + '6': '.arbiter.evm.TokenInfo', + '10': 'token' + }, + {'1': 'to', '3': 2, '4': 1, '5': 12, '10': 'to'}, + {'1': 'value', '3': 3, '4': 1, '5': 12, '10': 'value'}, + ], +}; + +/// Descriptor for `TokenTransferMeaning`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List tokenTransferMeaningDescriptor = $convert.base64Decode( + 'ChRUb2tlblRyYW5zZmVyTWVhbmluZxIsCgV0b2tlbhgBIAEoCzIWLmFyYml0ZXIuZXZtLlRva2' + 'VuSW5mb1IFdG9rZW4SDgoCdG8YAiABKAxSAnRvEhQKBXZhbHVlGAMgASgMUgV2YWx1ZQ=='); + +@$core.Deprecated('Use specificMeaningDescriptor instead') +const SpecificMeaning$json = { + '1': 'SpecificMeaning', + '2': [ + { + '1': 'ether_transfer', + '3': 1, + '4': 1, + '5': 11, + '6': '.arbiter.evm.EtherTransferMeaning', + '9': 0, + '10': 'etherTransfer' + }, + { + '1': 'token_transfer', + '3': 2, + '4': 1, + '5': 11, + '6': '.arbiter.evm.TokenTransferMeaning', + '9': 0, + '10': 'tokenTransfer' + }, + ], + '8': [ + {'1': 'meaning'}, + ], +}; + +/// Descriptor for `SpecificMeaning`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List specificMeaningDescriptor = $convert.base64Decode( + 'Cg9TcGVjaWZpY01lYW5pbmcSSgoOZXRoZXJfdHJhbnNmZXIYASABKAsyIS5hcmJpdGVyLmV2bS' + '5FdGhlclRyYW5zZmVyTWVhbmluZ0gAUg1ldGhlclRyYW5zZmVyEkoKDnRva2VuX3RyYW5zZmVy' + 'GAIgASgLMiEuYXJiaXRlci5ldm0uVG9rZW5UcmFuc2Zlck1lYW5pbmdIAFINdG9rZW5UcmFuc2' + 'ZlckIJCgdtZWFuaW5n'); + +@$core.Deprecated('Use gasLimitExceededViolationDescriptor instead') +const GasLimitExceededViolation$json = { + '1': 'GasLimitExceededViolation', + '2': [ + { + '1': 'max_gas_fee_per_gas', + '3': 1, + '4': 1, + '5': 12, + '9': 0, + '10': 'maxGasFeePerGas', + '17': true + }, + { + '1': 'max_priority_fee_per_gas', + '3': 2, + '4': 1, + '5': 12, + '9': 1, + '10': 'maxPriorityFeePerGas', + '17': true + }, + ], + '8': [ + {'1': '_max_gas_fee_per_gas'}, + {'1': '_max_priority_fee_per_gas'}, + ], +}; + +/// Descriptor for `GasLimitExceededViolation`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List gasLimitExceededViolationDescriptor = $convert.base64Decode( + 'ChlHYXNMaW1pdEV4Y2VlZGVkVmlvbGF0aW9uEjEKE21heF9nYXNfZmVlX3Blcl9nYXMYASABKA' + 'xIAFIPbWF4R2FzRmVlUGVyR2FziAEBEjsKGG1heF9wcmlvcml0eV9mZWVfcGVyX2dhcxgCIAEo' + 'DEgBUhRtYXhQcmlvcml0eUZlZVBlckdhc4gBAUIWChRfbWF4X2dhc19mZWVfcGVyX2dhc0IbCh' + 'lfbWF4X3ByaW9yaXR5X2ZlZV9wZXJfZ2Fz'); + +@$core.Deprecated('Use evalViolationDescriptor instead') +const EvalViolation$json = { + '1': 'EvalViolation', + '2': [ + { + '1': 'invalid_target', + '3': 1, + '4': 1, + '5': 12, + '9': 0, + '10': 'invalidTarget' + }, + { + '1': 'gas_limit_exceeded', + '3': 2, + '4': 1, + '5': 11, + '6': '.arbiter.evm.GasLimitExceededViolation', + '9': 0, + '10': 'gasLimitExceeded' + }, + { + '1': 'rate_limit_exceeded', + '3': 3, + '4': 1, + '5': 11, + '6': '.google.protobuf.Empty', + '9': 0, + '10': 'rateLimitExceeded' + }, + { + '1': 'volumetric_limit_exceeded', + '3': 4, + '4': 1, + '5': 11, + '6': '.google.protobuf.Empty', + '9': 0, + '10': 'volumetricLimitExceeded' + }, + { + '1': 'invalid_time', + '3': 5, + '4': 1, + '5': 11, + '6': '.google.protobuf.Empty', + '9': 0, + '10': 'invalidTime' + }, + { + '1': 'invalid_transaction_type', + '3': 6, + '4': 1, + '5': 11, + '6': '.google.protobuf.Empty', + '9': 0, + '10': 'invalidTransactionType' + }, + ], + '8': [ + {'1': 'kind'}, + ], +}; + +/// Descriptor for `EvalViolation`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List evalViolationDescriptor = $convert.base64Decode( + 'Cg1FdmFsVmlvbGF0aW9uEicKDmludmFsaWRfdGFyZ2V0GAEgASgMSABSDWludmFsaWRUYXJnZX' + 'QSVgoSZ2FzX2xpbWl0X2V4Y2VlZGVkGAIgASgLMiYuYXJiaXRlci5ldm0uR2FzTGltaXRFeGNl' + 'ZWRlZFZpb2xhdGlvbkgAUhBnYXNMaW1pdEV4Y2VlZGVkEkgKE3JhdGVfbGltaXRfZXhjZWVkZW' + 'QYAyABKAsyFi5nb29nbGUucHJvdG9idWYuRW1wdHlIAFIRcmF0ZUxpbWl0RXhjZWVkZWQSVAoZ' + 'dm9sdW1ldHJpY19saW1pdF9leGNlZWRlZBgEIAEoCzIWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eU' + 'gAUhd2b2x1bWV0cmljTGltaXRFeGNlZWRlZBI7CgxpbnZhbGlkX3RpbWUYBSABKAsyFi5nb29n' + 'bGUucHJvdG9idWYuRW1wdHlIAFILaW52YWxpZFRpbWUSUgoYaW52YWxpZF90cmFuc2FjdGlvbl' + '90eXBlGAYgASgLMhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5SABSFmludmFsaWRUcmFuc2FjdGlv' + 'blR5cGVCBgoEa2luZA=='); + +@$core.Deprecated('Use noMatchingGrantErrorDescriptor instead') +const NoMatchingGrantError$json = { + '1': 'NoMatchingGrantError', + '2': [ + { + '1': 'meaning', + '3': 1, + '4': 1, + '5': 11, + '6': '.arbiter.evm.SpecificMeaning', + '10': 'meaning' + }, + ], +}; + +/// Descriptor for `NoMatchingGrantError`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List noMatchingGrantErrorDescriptor = $convert.base64Decode( + 'ChROb01hdGNoaW5nR3JhbnRFcnJvchI2CgdtZWFuaW5nGAEgASgLMhwuYXJiaXRlci5ldm0uU3' + 'BlY2lmaWNNZWFuaW5nUgdtZWFuaW5n'); + +@$core.Deprecated('Use policyViolationsErrorDescriptor instead') +const PolicyViolationsError$json = { + '1': 'PolicyViolationsError', + '2': [ + { + '1': 'meaning', + '3': 1, + '4': 1, + '5': 11, + '6': '.arbiter.evm.SpecificMeaning', + '10': 'meaning' + }, + { + '1': 'violations', + '3': 2, + '4': 3, + '5': 11, + '6': '.arbiter.evm.EvalViolation', + '10': 'violations' + }, + ], +}; + +/// Descriptor for `PolicyViolationsError`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List policyViolationsErrorDescriptor = $convert.base64Decode( + 'ChVQb2xpY3lWaW9sYXRpb25zRXJyb3ISNgoHbWVhbmluZxgBIAEoCzIcLmFyYml0ZXIuZXZtLl' + 'NwZWNpZmljTWVhbmluZ1IHbWVhbmluZxI6Cgp2aW9sYXRpb25zGAIgAygLMhouYXJiaXRlci5l' + 'dm0uRXZhbFZpb2xhdGlvblIKdmlvbGF0aW9ucw=='); + +@$core.Deprecated('Use transactionEvalErrorDescriptor instead') +const TransactionEvalError$json = { + '1': 'TransactionEvalError', + '2': [ + { + '1': 'contract_creation_not_supported', + '3': 1, + '4': 1, + '5': 11, + '6': '.google.protobuf.Empty', + '9': 0, + '10': 'contractCreationNotSupported' + }, + { + '1': 'unsupported_transaction_type', + '3': 2, + '4': 1, + '5': 11, + '6': '.google.protobuf.Empty', + '9': 0, + '10': 'unsupportedTransactionType' + }, + { + '1': 'no_matching_grant', + '3': 3, + '4': 1, + '5': 11, + '6': '.arbiter.evm.NoMatchingGrantError', + '9': 0, + '10': 'noMatchingGrant' + }, + { + '1': 'policy_violations', + '3': 4, + '4': 1, + '5': 11, + '6': '.arbiter.evm.PolicyViolationsError', + '9': 0, + '10': 'policyViolations' + }, + ], + '8': [ + {'1': 'kind'}, + ], +}; + +/// Descriptor for `TransactionEvalError`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List transactionEvalErrorDescriptor = $convert.base64Decode( + 'ChRUcmFuc2FjdGlvbkV2YWxFcnJvchJfCh9jb250cmFjdF9jcmVhdGlvbl9ub3Rfc3VwcG9ydG' + 'VkGAEgASgLMhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5SABSHGNvbnRyYWN0Q3JlYXRpb25Ob3RT' + 'dXBwb3J0ZWQSWgocdW5zdXBwb3J0ZWRfdHJhbnNhY3Rpb25fdHlwZRgCIAEoCzIWLmdvb2dsZS' + '5wcm90b2J1Zi5FbXB0eUgAUhp1bnN1cHBvcnRlZFRyYW5zYWN0aW9uVHlwZRJPChFub19tYXRj' + 'aGluZ19ncmFudBgDIAEoCzIhLmFyYml0ZXIuZXZtLk5vTWF0Y2hpbmdHcmFudEVycm9ySABSD2' + '5vTWF0Y2hpbmdHcmFudBJRChFwb2xpY3lfdmlvbGF0aW9ucxgEIAEoCzIiLmFyYml0ZXIuZXZt' + 'LlBvbGljeVZpb2xhdGlvbnNFcnJvckgAUhBwb2xpY3lWaW9sYXRpb25zQgYKBGtpbmQ='); + +@$core.Deprecated('Use evmGrantCreateRequestDescriptor instead') +const EvmGrantCreateRequest$json = { + '1': 'EvmGrantCreateRequest', + '2': [ + {'1': 'client_id', '3': 1, '4': 1, '5': 5, '10': 'clientId'}, + { + '1': 'shared', + '3': 2, + '4': 1, + '5': 11, + '6': '.arbiter.evm.SharedSettings', + '10': 'shared' + }, + { + '1': 'specific', + '3': 3, + '4': 1, + '5': 11, + '6': '.arbiter.evm.SpecificGrant', + '10': 'specific' + }, + ], +}; + +/// Descriptor for `EvmGrantCreateRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List evmGrantCreateRequestDescriptor = $convert.base64Decode( + 'ChVFdm1HcmFudENyZWF0ZVJlcXVlc3QSGwoJY2xpZW50X2lkGAEgASgFUghjbGllbnRJZBIzCg' + 'ZzaGFyZWQYAiABKAsyGy5hcmJpdGVyLmV2bS5TaGFyZWRTZXR0aW5nc1IGc2hhcmVkEjYKCHNw' + 'ZWNpZmljGAMgASgLMhouYXJiaXRlci5ldm0uU3BlY2lmaWNHcmFudFIIc3BlY2lmaWM='); + +@$core.Deprecated('Use evmGrantCreateResponseDescriptor instead') +const EvmGrantCreateResponse$json = { + '1': 'EvmGrantCreateResponse', + '2': [ + {'1': 'grant_id', '3': 1, '4': 1, '5': 5, '9': 0, '10': 'grantId'}, + { + '1': 'error', + '3': 2, + '4': 1, + '5': 14, + '6': '.arbiter.evm.EvmError', + '9': 0, + '10': 'error' + }, + ], + '8': [ + {'1': 'result'}, + ], +}; + +/// Descriptor for `EvmGrantCreateResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List evmGrantCreateResponseDescriptor = $convert.base64Decode( + 'ChZFdm1HcmFudENyZWF0ZVJlc3BvbnNlEhsKCGdyYW50X2lkGAEgASgFSABSB2dyYW50SWQSLQ' + 'oFZXJyb3IYAiABKA4yFS5hcmJpdGVyLmV2bS5Fdm1FcnJvckgAUgVlcnJvckIICgZyZXN1bHQ='); + +@$core.Deprecated('Use evmGrantDeleteRequestDescriptor instead') +const EvmGrantDeleteRequest$json = { + '1': 'EvmGrantDeleteRequest', + '2': [ + {'1': 'grant_id', '3': 1, '4': 1, '5': 5, '10': 'grantId'}, + ], +}; + +/// Descriptor for `EvmGrantDeleteRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List evmGrantDeleteRequestDescriptor = + $convert.base64Decode( + 'ChVFdm1HcmFudERlbGV0ZVJlcXVlc3QSGQoIZ3JhbnRfaWQYASABKAVSB2dyYW50SWQ='); + +@$core.Deprecated('Use evmGrantDeleteResponseDescriptor instead') +const EvmGrantDeleteResponse$json = { + '1': 'EvmGrantDeleteResponse', + '2': [ + { + '1': 'ok', + '3': 1, + '4': 1, + '5': 11, + '6': '.google.protobuf.Empty', + '9': 0, + '10': 'ok' + }, + { + '1': 'error', + '3': 2, + '4': 1, + '5': 14, + '6': '.arbiter.evm.EvmError', + '9': 0, + '10': 'error' + }, + ], + '8': [ + {'1': 'result'}, + ], +}; + +/// Descriptor for `EvmGrantDeleteResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List evmGrantDeleteResponseDescriptor = $convert.base64Decode( + 'ChZFdm1HcmFudERlbGV0ZVJlc3BvbnNlEigKAm9rGAEgASgLMhYuZ29vZ2xlLnByb3RvYnVmLk' + 'VtcHR5SABSAm9rEi0KBWVycm9yGAIgASgOMhUuYXJiaXRlci5ldm0uRXZtRXJyb3JIAFIFZXJy' + 'b3JCCAoGcmVzdWx0'); + +@$core.Deprecated('Use grantEntryDescriptor instead') +const GrantEntry$json = { + '1': 'GrantEntry', + '2': [ + {'1': 'id', '3': 1, '4': 1, '5': 5, '10': 'id'}, + {'1': 'client_id', '3': 2, '4': 1, '5': 5, '10': 'clientId'}, + { + '1': 'shared', + '3': 3, + '4': 1, + '5': 11, + '6': '.arbiter.evm.SharedSettings', + '10': 'shared' + }, + { + '1': 'specific', + '3': 4, + '4': 1, + '5': 11, + '6': '.arbiter.evm.SpecificGrant', + '10': 'specific' + }, + ], +}; + +/// Descriptor for `GrantEntry`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List grantEntryDescriptor = $convert.base64Decode( + 'CgpHcmFudEVudHJ5Eg4KAmlkGAEgASgFUgJpZBIbCgljbGllbnRfaWQYAiABKAVSCGNsaWVudE' + 'lkEjMKBnNoYXJlZBgDIAEoCzIbLmFyYml0ZXIuZXZtLlNoYXJlZFNldHRpbmdzUgZzaGFyZWQS' + 'NgoIc3BlY2lmaWMYBCABKAsyGi5hcmJpdGVyLmV2bS5TcGVjaWZpY0dyYW50UghzcGVjaWZpYw' + '=='); + +@$core.Deprecated('Use evmGrantListRequestDescriptor instead') +const EvmGrantListRequest$json = { + '1': 'EvmGrantListRequest', + '2': [ + { + '1': 'wallet_id', + '3': 1, + '4': 1, + '5': 5, + '9': 0, + '10': 'walletId', + '17': true + }, + ], + '8': [ + {'1': '_wallet_id'}, + ], +}; + +/// Descriptor for `EvmGrantListRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List evmGrantListRequestDescriptor = $convert.base64Decode( + 'ChNFdm1HcmFudExpc3RSZXF1ZXN0EiAKCXdhbGxldF9pZBgBIAEoBUgAUgh3YWxsZXRJZIgBAU' + 'IMCgpfd2FsbGV0X2lk'); + +@$core.Deprecated('Use evmGrantListResponseDescriptor instead') +const EvmGrantListResponse$json = { + '1': 'EvmGrantListResponse', + '2': [ + { + '1': 'grants', + '3': 1, + '4': 1, + '5': 11, + '6': '.arbiter.evm.EvmGrantList', + '9': 0, + '10': 'grants' + }, + { + '1': 'error', + '3': 2, + '4': 1, + '5': 14, + '6': '.arbiter.evm.EvmError', + '9': 0, + '10': 'error' + }, + ], + '8': [ + {'1': 'result'}, + ], +}; + +/// Descriptor for `EvmGrantListResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List evmGrantListResponseDescriptor = $convert.base64Decode( + 'ChRFdm1HcmFudExpc3RSZXNwb25zZRIzCgZncmFudHMYASABKAsyGS5hcmJpdGVyLmV2bS5Fdm' + '1HcmFudExpc3RIAFIGZ3JhbnRzEi0KBWVycm9yGAIgASgOMhUuYXJiaXRlci5ldm0uRXZtRXJy' + 'b3JIAFIFZXJyb3JCCAoGcmVzdWx0'); + +@$core.Deprecated('Use evmGrantListDescriptor instead') +const EvmGrantList$json = { + '1': 'EvmGrantList', + '2': [ + { + '1': 'grants', + '3': 1, + '4': 3, + '5': 11, + '6': '.arbiter.evm.GrantEntry', + '10': 'grants' + }, + ], +}; + +/// Descriptor for `EvmGrantList`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List evmGrantListDescriptor = $convert.base64Decode( + 'CgxFdm1HcmFudExpc3QSLwoGZ3JhbnRzGAEgAygLMhcuYXJiaXRlci5ldm0uR3JhbnRFbnRyeV' + 'IGZ3JhbnRz'); + +@$core.Deprecated('Use evmSignTransactionRequestDescriptor instead') +const EvmSignTransactionRequest$json = { + '1': 'EvmSignTransactionRequest', + '2': [ + {'1': 'wallet_address', '3': 1, '4': 1, '5': 12, '10': 'walletAddress'}, + {'1': 'rlp_transaction', '3': 2, '4': 1, '5': 12, '10': 'rlpTransaction'}, + ], +}; + +/// Descriptor for `EvmSignTransactionRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List evmSignTransactionRequestDescriptor = + $convert.base64Decode( + 'ChlFdm1TaWduVHJhbnNhY3Rpb25SZXF1ZXN0EiUKDndhbGxldF9hZGRyZXNzGAEgASgMUg13YW' + 'xsZXRBZGRyZXNzEicKD3JscF90cmFuc2FjdGlvbhgCIAEoDFIOcmxwVHJhbnNhY3Rpb24='); + +@$core.Deprecated('Use evmSignTransactionResponseDescriptor instead') +const EvmSignTransactionResponse$json = { + '1': 'EvmSignTransactionResponse', + '2': [ + {'1': 'signature', '3': 1, '4': 1, '5': 12, '9': 0, '10': 'signature'}, + { + '1': 'eval_error', + '3': 2, + '4': 1, + '5': 11, + '6': '.arbiter.evm.TransactionEvalError', + '9': 0, + '10': 'evalError' + }, + { + '1': 'error', + '3': 3, + '4': 1, + '5': 14, + '6': '.arbiter.evm.EvmError', + '9': 0, + '10': 'error' + }, + ], + '8': [ + {'1': 'result'}, + ], +}; + +/// Descriptor for `EvmSignTransactionResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List evmSignTransactionResponseDescriptor = $convert.base64Decode( + 'ChpFdm1TaWduVHJhbnNhY3Rpb25SZXNwb25zZRIeCglzaWduYXR1cmUYASABKAxIAFIJc2lnbm' + 'F0dXJlEkIKCmV2YWxfZXJyb3IYAiABKAsyIS5hcmJpdGVyLmV2bS5UcmFuc2FjdGlvbkV2YWxF' + 'cnJvckgAUglldmFsRXJyb3ISLQoFZXJyb3IYAyABKA4yFS5hcmJpdGVyLmV2bS5Fdm1FcnJvck' + 'gAUgVlcnJvckIICgZyZXN1bHQ='); + +@$core.Deprecated('Use evmAnalyzeTransactionRequestDescriptor instead') +const EvmAnalyzeTransactionRequest$json = { + '1': 'EvmAnalyzeTransactionRequest', + '2': [ + {'1': 'wallet_address', '3': 1, '4': 1, '5': 12, '10': 'walletAddress'}, + {'1': 'rlp_transaction', '3': 2, '4': 1, '5': 12, '10': 'rlpTransaction'}, + ], +}; + +/// Descriptor for `EvmAnalyzeTransactionRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List evmAnalyzeTransactionRequestDescriptor = + $convert.base64Decode( + 'ChxFdm1BbmFseXplVHJhbnNhY3Rpb25SZXF1ZXN0EiUKDndhbGxldF9hZGRyZXNzGAEgASgMUg' + '13YWxsZXRBZGRyZXNzEicKD3JscF90cmFuc2FjdGlvbhgCIAEoDFIOcmxwVHJhbnNhY3Rpb24='); + +@$core.Deprecated('Use evmAnalyzeTransactionResponseDescriptor instead') +const EvmAnalyzeTransactionResponse$json = { + '1': 'EvmAnalyzeTransactionResponse', + '2': [ + { + '1': 'meaning', + '3': 1, + '4': 1, + '5': 11, + '6': '.arbiter.evm.SpecificMeaning', + '9': 0, + '10': 'meaning' + }, + { + '1': 'eval_error', + '3': 2, + '4': 1, + '5': 11, + '6': '.arbiter.evm.TransactionEvalError', + '9': 0, + '10': 'evalError' + }, + { + '1': 'error', + '3': 3, + '4': 1, + '5': 14, + '6': '.arbiter.evm.EvmError', + '9': 0, + '10': 'error' + }, + ], + '8': [ + {'1': 'result'}, + ], +}; + +/// Descriptor for `EvmAnalyzeTransactionResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List evmAnalyzeTransactionResponseDescriptor = $convert.base64Decode( + 'Ch1Fdm1BbmFseXplVHJhbnNhY3Rpb25SZXNwb25zZRI4CgdtZWFuaW5nGAEgASgLMhwuYXJiaX' + 'Rlci5ldm0uU3BlY2lmaWNNZWFuaW5nSABSB21lYW5pbmcSQgoKZXZhbF9lcnJvchgCIAEoCzIh' + 'LmFyYml0ZXIuZXZtLlRyYW5zYWN0aW9uRXZhbEVycm9ySABSCWV2YWxFcnJvchItCgVlcnJvch' + 'gDIAEoDjIVLmFyYml0ZXIuZXZtLkV2bUVycm9ySABSBWVycm9yQggKBnJlc3VsdA=='); diff --git a/useragent/lib/proto/user_agent.pb.dart b/useragent/lib/proto/user_agent.pb.dart new file mode 100644 index 0000000..8ea3f4f --- /dev/null +++ b/useragent/lib/proto/user_agent.pb.dart @@ -0,0 +1,1166 @@ +// This is a generated file - do not edit. +// +// Generated from user_agent.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; +import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart' as $0; + +import 'evm.pb.dart' as $1; +import 'user_agent.pbenum.dart'; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +export 'user_agent.pbenum.dart'; + +class AuthChallengeRequest extends $pb.GeneratedMessage { + factory AuthChallengeRequest({ + $core.List<$core.int>? pubkey, + $core.String? bootstrapToken, + }) { + final result = create(); + if (pubkey != null) result.pubkey = pubkey; + if (bootstrapToken != null) result.bootstrapToken = bootstrapToken; + return result; + } + + AuthChallengeRequest._(); + + factory AuthChallengeRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory AuthChallengeRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'AuthChallengeRequest', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY) + ..aOS(2, _omitFieldNames ? '' : 'bootstrapToken') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthChallengeRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthChallengeRequest copyWith(void Function(AuthChallengeRequest) updates) => + super.copyWith((message) => updates(message as AuthChallengeRequest)) + as AuthChallengeRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static AuthChallengeRequest create() => AuthChallengeRequest._(); + @$core.override + AuthChallengeRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static AuthChallengeRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static AuthChallengeRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get pubkey => $_getN(0); + @$pb.TagNumber(1) + set pubkey($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasPubkey() => $_has(0); + @$pb.TagNumber(1) + void clearPubkey() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get bootstrapToken => $_getSZ(1); + @$pb.TagNumber(2) + set bootstrapToken($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasBootstrapToken() => $_has(1); + @$pb.TagNumber(2) + void clearBootstrapToken() => $_clearField(2); +} + +class AuthChallenge extends $pb.GeneratedMessage { + factory AuthChallenge({ + $core.List<$core.int>? pubkey, + $core.int? nonce, + }) { + final result = create(); + if (pubkey != null) result.pubkey = pubkey; + if (nonce != null) result.nonce = nonce; + return result; + } + + AuthChallenge._(); + + factory AuthChallenge.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory AuthChallenge.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'AuthChallenge', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY) + ..aI(2, _omitFieldNames ? '' : 'nonce') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthChallenge clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthChallenge copyWith(void Function(AuthChallenge) updates) => + super.copyWith((message) => updates(message as AuthChallenge)) + as AuthChallenge; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static AuthChallenge create() => AuthChallenge._(); + @$core.override + AuthChallenge createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static AuthChallenge getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static AuthChallenge? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get pubkey => $_getN(0); + @$pb.TagNumber(1) + set pubkey($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasPubkey() => $_has(0); + @$pb.TagNumber(1) + void clearPubkey() => $_clearField(1); + + @$pb.TagNumber(2) + $core.int get nonce => $_getIZ(1); + @$pb.TagNumber(2) + set nonce($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasNonce() => $_has(1); + @$pb.TagNumber(2) + void clearNonce() => $_clearField(2); +} + +class AuthChallengeSolution extends $pb.GeneratedMessage { + factory AuthChallengeSolution({ + $core.List<$core.int>? signature, + }) { + final result = create(); + if (signature != null) result.signature = signature; + return result; + } + + AuthChallengeSolution._(); + + factory AuthChallengeSolution.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory AuthChallengeSolution.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'AuthChallengeSolution', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'signature', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthChallengeSolution clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthChallengeSolution copyWith( + void Function(AuthChallengeSolution) updates) => + super.copyWith((message) => updates(message as AuthChallengeSolution)) + as AuthChallengeSolution; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static AuthChallengeSolution create() => AuthChallengeSolution._(); + @$core.override + AuthChallengeSolution createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static AuthChallengeSolution getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static AuthChallengeSolution? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get signature => $_getN(0); + @$pb.TagNumber(1) + set signature($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasSignature() => $_has(0); + @$pb.TagNumber(1) + void clearSignature() => $_clearField(1); +} + +class AuthOk extends $pb.GeneratedMessage { + factory AuthOk() => create(); + + AuthOk._(); + + factory AuthOk.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory AuthOk.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'AuthOk', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'), + createEmptyInstance: create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthOk clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AuthOk copyWith(void Function(AuthOk) updates) => + super.copyWith((message) => updates(message as AuthOk)) as AuthOk; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static AuthOk create() => AuthOk._(); + @$core.override + AuthOk createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static AuthOk getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static AuthOk? _defaultInstance; +} + +class UnsealStart extends $pb.GeneratedMessage { + factory UnsealStart({ + $core.List<$core.int>? clientPubkey, + }) { + final result = create(); + if (clientPubkey != null) result.clientPubkey = clientPubkey; + return result; + } + + UnsealStart._(); + + factory UnsealStart.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory UnsealStart.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'UnsealStart', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'clientPubkey', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UnsealStart clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UnsealStart copyWith(void Function(UnsealStart) updates) => + super.copyWith((message) => updates(message as UnsealStart)) + as UnsealStart; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static UnsealStart create() => UnsealStart._(); + @$core.override + UnsealStart createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static UnsealStart getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static UnsealStart? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get clientPubkey => $_getN(0); + @$pb.TagNumber(1) + set clientPubkey($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasClientPubkey() => $_has(0); + @$pb.TagNumber(1) + void clearClientPubkey() => $_clearField(1); +} + +class UnsealStartResponse extends $pb.GeneratedMessage { + factory UnsealStartResponse({ + $core.List<$core.int>? serverPubkey, + }) { + final result = create(); + if (serverPubkey != null) result.serverPubkey = serverPubkey; + return result; + } + + UnsealStartResponse._(); + + factory UnsealStartResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory UnsealStartResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'UnsealStartResponse', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'serverPubkey', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UnsealStartResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UnsealStartResponse copyWith(void Function(UnsealStartResponse) updates) => + super.copyWith((message) => updates(message as UnsealStartResponse)) + as UnsealStartResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static UnsealStartResponse create() => UnsealStartResponse._(); + @$core.override + UnsealStartResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static UnsealStartResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static UnsealStartResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get serverPubkey => $_getN(0); + @$pb.TagNumber(1) + set serverPubkey($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasServerPubkey() => $_has(0); + @$pb.TagNumber(1) + void clearServerPubkey() => $_clearField(1); +} + +class UnsealEncryptedKey extends $pb.GeneratedMessage { + factory UnsealEncryptedKey({ + $core.List<$core.int>? nonce, + $core.List<$core.int>? ciphertext, + $core.List<$core.int>? associatedData, + }) { + final result = create(); + if (nonce != null) result.nonce = nonce; + if (ciphertext != null) result.ciphertext = ciphertext; + if (associatedData != null) result.associatedData = associatedData; + return result; + } + + UnsealEncryptedKey._(); + + factory UnsealEncryptedKey.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory UnsealEncryptedKey.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'UnsealEncryptedKey', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'nonce', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>( + 2, _omitFieldNames ? '' : 'ciphertext', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>( + 3, _omitFieldNames ? '' : 'associatedData', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UnsealEncryptedKey clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UnsealEncryptedKey copyWith(void Function(UnsealEncryptedKey) updates) => + super.copyWith((message) => updates(message as UnsealEncryptedKey)) + as UnsealEncryptedKey; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static UnsealEncryptedKey create() => UnsealEncryptedKey._(); + @$core.override + UnsealEncryptedKey createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static UnsealEncryptedKey getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static UnsealEncryptedKey? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get nonce => $_getN(0); + @$pb.TagNumber(1) + set nonce($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasNonce() => $_has(0); + @$pb.TagNumber(1) + void clearNonce() => $_clearField(1); + + @$pb.TagNumber(2) + $core.List<$core.int> get ciphertext => $_getN(1); + @$pb.TagNumber(2) + set ciphertext($core.List<$core.int> value) => $_setBytes(1, value); + @$pb.TagNumber(2) + $core.bool hasCiphertext() => $_has(1); + @$pb.TagNumber(2) + void clearCiphertext() => $_clearField(2); + + @$pb.TagNumber(3) + $core.List<$core.int> get associatedData => $_getN(2); + @$pb.TagNumber(3) + set associatedData($core.List<$core.int> value) => $_setBytes(2, value); + @$pb.TagNumber(3) + $core.bool hasAssociatedData() => $_has(2); + @$pb.TagNumber(3) + void clearAssociatedData() => $_clearField(3); +} + +class ClientConnectionRequest extends $pb.GeneratedMessage { + factory ClientConnectionRequest({ + $core.List<$core.int>? pubkey, + }) { + final result = create(); + if (pubkey != null) result.pubkey = pubkey; + return result; + } + + ClientConnectionRequest._(); + + factory ClientConnectionRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ClientConnectionRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ClientConnectionRequest', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientConnectionRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientConnectionRequest copyWith( + void Function(ClientConnectionRequest) updates) => + super.copyWith((message) => updates(message as ClientConnectionRequest)) + as ClientConnectionRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ClientConnectionRequest create() => ClientConnectionRequest._(); + @$core.override + ClientConnectionRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ClientConnectionRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ClientConnectionRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get pubkey => $_getN(0); + @$pb.TagNumber(1) + set pubkey($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasPubkey() => $_has(0); + @$pb.TagNumber(1) + void clearPubkey() => $_clearField(1); +} + +class ClientConnectionResponse extends $pb.GeneratedMessage { + factory ClientConnectionResponse({ + $core.bool? approved, + }) { + final result = create(); + if (approved != null) result.approved = approved; + return result; + } + + ClientConnectionResponse._(); + + factory ClientConnectionResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ClientConnectionResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ClientConnectionResponse', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'approved') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientConnectionResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientConnectionResponse copyWith( + void Function(ClientConnectionResponse) updates) => + super.copyWith((message) => updates(message as ClientConnectionResponse)) + as ClientConnectionResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ClientConnectionResponse create() => ClientConnectionResponse._(); + @$core.override + ClientConnectionResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ClientConnectionResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ClientConnectionResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get approved => $_getBF(0); + @$pb.TagNumber(1) + set approved($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasApproved() => $_has(0); + @$pb.TagNumber(1) + void clearApproved() => $_clearField(1); +} + +class ClientConnectionCancel extends $pb.GeneratedMessage { + factory ClientConnectionCancel() => create(); + + ClientConnectionCancel._(); + + factory ClientConnectionCancel.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ClientConnectionCancel.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ClientConnectionCancel', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'), + createEmptyInstance: create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientConnectionCancel clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientConnectionCancel copyWith( + void Function(ClientConnectionCancel) updates) => + super.copyWith((message) => updates(message as ClientConnectionCancel)) + as ClientConnectionCancel; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ClientConnectionCancel create() => ClientConnectionCancel._(); + @$core.override + ClientConnectionCancel createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ClientConnectionCancel getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ClientConnectionCancel? _defaultInstance; +} + +enum UserAgentRequest_Payload { + authChallengeRequest, + authChallengeSolution, + unsealStart, + unsealEncryptedKey, + queryVaultState, + evmWalletCreate, + evmWalletList, + evmGrantCreate, + evmGrantDelete, + evmGrantList, + clientConnectionResponse, + notSet +} + +class UserAgentRequest extends $pb.GeneratedMessage { + factory UserAgentRequest({ + AuthChallengeRequest? authChallengeRequest, + AuthChallengeSolution? authChallengeSolution, + UnsealStart? unsealStart, + UnsealEncryptedKey? unsealEncryptedKey, + $0.Empty? queryVaultState, + $0.Empty? evmWalletCreate, + $0.Empty? evmWalletList, + $1.EvmGrantCreateRequest? evmGrantCreate, + $1.EvmGrantDeleteRequest? evmGrantDelete, + $1.EvmGrantListRequest? evmGrantList, + ClientConnectionResponse? clientConnectionResponse, + }) { + final result = create(); + if (authChallengeRequest != null) + result.authChallengeRequest = authChallengeRequest; + if (authChallengeSolution != null) + result.authChallengeSolution = authChallengeSolution; + if (unsealStart != null) result.unsealStart = unsealStart; + if (unsealEncryptedKey != null) + result.unsealEncryptedKey = unsealEncryptedKey; + if (queryVaultState != null) result.queryVaultState = queryVaultState; + if (evmWalletCreate != null) result.evmWalletCreate = evmWalletCreate; + if (evmWalletList != null) result.evmWalletList = evmWalletList; + if (evmGrantCreate != null) result.evmGrantCreate = evmGrantCreate; + if (evmGrantDelete != null) result.evmGrantDelete = evmGrantDelete; + if (evmGrantList != null) result.evmGrantList = evmGrantList; + if (clientConnectionResponse != null) + result.clientConnectionResponse = clientConnectionResponse; + return result; + } + + UserAgentRequest._(); + + factory UserAgentRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory UserAgentRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, UserAgentRequest_Payload> + _UserAgentRequest_PayloadByTag = { + 1: UserAgentRequest_Payload.authChallengeRequest, + 2: UserAgentRequest_Payload.authChallengeSolution, + 3: UserAgentRequest_Payload.unsealStart, + 4: UserAgentRequest_Payload.unsealEncryptedKey, + 5: UserAgentRequest_Payload.queryVaultState, + 6: UserAgentRequest_Payload.evmWalletCreate, + 7: UserAgentRequest_Payload.evmWalletList, + 8: UserAgentRequest_Payload.evmGrantCreate, + 9: UserAgentRequest_Payload.evmGrantDelete, + 10: UserAgentRequest_Payload.evmGrantList, + 11: UserAgentRequest_Payload.clientConnectionResponse, + 0: UserAgentRequest_Payload.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'UserAgentRequest', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'), + createEmptyInstance: create) + ..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + ..aOM( + 1, _omitFieldNames ? '' : 'authChallengeRequest', + subBuilder: AuthChallengeRequest.create) + ..aOM( + 2, _omitFieldNames ? '' : 'authChallengeSolution', + subBuilder: AuthChallengeSolution.create) + ..aOM(3, _omitFieldNames ? '' : 'unsealStart', + subBuilder: UnsealStart.create) + ..aOM(4, _omitFieldNames ? '' : 'unsealEncryptedKey', + subBuilder: UnsealEncryptedKey.create) + ..aOM<$0.Empty>(5, _omitFieldNames ? '' : 'queryVaultState', + subBuilder: $0.Empty.create) + ..aOM<$0.Empty>(6, _omitFieldNames ? '' : 'evmWalletCreate', + subBuilder: $0.Empty.create) + ..aOM<$0.Empty>(7, _omitFieldNames ? '' : 'evmWalletList', + subBuilder: $0.Empty.create) + ..aOM<$1.EvmGrantCreateRequest>(8, _omitFieldNames ? '' : 'evmGrantCreate', + subBuilder: $1.EvmGrantCreateRequest.create) + ..aOM<$1.EvmGrantDeleteRequest>(9, _omitFieldNames ? '' : 'evmGrantDelete', + subBuilder: $1.EvmGrantDeleteRequest.create) + ..aOM<$1.EvmGrantListRequest>(10, _omitFieldNames ? '' : 'evmGrantList', + subBuilder: $1.EvmGrantListRequest.create) + ..aOM( + 11, _omitFieldNames ? '' : 'clientConnectionResponse', + subBuilder: ClientConnectionResponse.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UserAgentRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UserAgentRequest copyWith(void Function(UserAgentRequest) updates) => + super.copyWith((message) => updates(message as UserAgentRequest)) + as UserAgentRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static UserAgentRequest create() => UserAgentRequest._(); + @$core.override + UserAgentRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static UserAgentRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static UserAgentRequest? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + @$pb.TagNumber(4) + @$pb.TagNumber(5) + @$pb.TagNumber(6) + @$pb.TagNumber(7) + @$pb.TagNumber(8) + @$pb.TagNumber(9) + @$pb.TagNumber(10) + @$pb.TagNumber(11) + UserAgentRequest_Payload whichPayload() => + _UserAgentRequest_PayloadByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + @$pb.TagNumber(4) + @$pb.TagNumber(5) + @$pb.TagNumber(6) + @$pb.TagNumber(7) + @$pb.TagNumber(8) + @$pb.TagNumber(9) + @$pb.TagNumber(10) + @$pb.TagNumber(11) + void clearPayload() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + AuthChallengeRequest get authChallengeRequest => $_getN(0); + @$pb.TagNumber(1) + set authChallengeRequest(AuthChallengeRequest value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasAuthChallengeRequest() => $_has(0); + @$pb.TagNumber(1) + void clearAuthChallengeRequest() => $_clearField(1); + @$pb.TagNumber(1) + AuthChallengeRequest ensureAuthChallengeRequest() => $_ensure(0); + + @$pb.TagNumber(2) + AuthChallengeSolution get authChallengeSolution => $_getN(1); + @$pb.TagNumber(2) + set authChallengeSolution(AuthChallengeSolution value) => + $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasAuthChallengeSolution() => $_has(1); + @$pb.TagNumber(2) + void clearAuthChallengeSolution() => $_clearField(2); + @$pb.TagNumber(2) + AuthChallengeSolution ensureAuthChallengeSolution() => $_ensure(1); + + @$pb.TagNumber(3) + UnsealStart get unsealStart => $_getN(2); + @$pb.TagNumber(3) + set unsealStart(UnsealStart value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasUnsealStart() => $_has(2); + @$pb.TagNumber(3) + void clearUnsealStart() => $_clearField(3); + @$pb.TagNumber(3) + UnsealStart ensureUnsealStart() => $_ensure(2); + + @$pb.TagNumber(4) + UnsealEncryptedKey get unsealEncryptedKey => $_getN(3); + @$pb.TagNumber(4) + set unsealEncryptedKey(UnsealEncryptedKey value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasUnsealEncryptedKey() => $_has(3); + @$pb.TagNumber(4) + void clearUnsealEncryptedKey() => $_clearField(4); + @$pb.TagNumber(4) + UnsealEncryptedKey ensureUnsealEncryptedKey() => $_ensure(3); + + @$pb.TagNumber(5) + $0.Empty get queryVaultState => $_getN(4); + @$pb.TagNumber(5) + set queryVaultState($0.Empty value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasQueryVaultState() => $_has(4); + @$pb.TagNumber(5) + void clearQueryVaultState() => $_clearField(5); + @$pb.TagNumber(5) + $0.Empty ensureQueryVaultState() => $_ensure(4); + + @$pb.TagNumber(6) + $0.Empty get evmWalletCreate => $_getN(5); + @$pb.TagNumber(6) + set evmWalletCreate($0.Empty value) => $_setField(6, value); + @$pb.TagNumber(6) + $core.bool hasEvmWalletCreate() => $_has(5); + @$pb.TagNumber(6) + void clearEvmWalletCreate() => $_clearField(6); + @$pb.TagNumber(6) + $0.Empty ensureEvmWalletCreate() => $_ensure(5); + + @$pb.TagNumber(7) + $0.Empty get evmWalletList => $_getN(6); + @$pb.TagNumber(7) + set evmWalletList($0.Empty value) => $_setField(7, value); + @$pb.TagNumber(7) + $core.bool hasEvmWalletList() => $_has(6); + @$pb.TagNumber(7) + void clearEvmWalletList() => $_clearField(7); + @$pb.TagNumber(7) + $0.Empty ensureEvmWalletList() => $_ensure(6); + + @$pb.TagNumber(8) + $1.EvmGrantCreateRequest get evmGrantCreate => $_getN(7); + @$pb.TagNumber(8) + set evmGrantCreate($1.EvmGrantCreateRequest value) => $_setField(8, value); + @$pb.TagNumber(8) + $core.bool hasEvmGrantCreate() => $_has(7); + @$pb.TagNumber(8) + void clearEvmGrantCreate() => $_clearField(8); + @$pb.TagNumber(8) + $1.EvmGrantCreateRequest ensureEvmGrantCreate() => $_ensure(7); + + @$pb.TagNumber(9) + $1.EvmGrantDeleteRequest get evmGrantDelete => $_getN(8); + @$pb.TagNumber(9) + set evmGrantDelete($1.EvmGrantDeleteRequest value) => $_setField(9, value); + @$pb.TagNumber(9) + $core.bool hasEvmGrantDelete() => $_has(8); + @$pb.TagNumber(9) + void clearEvmGrantDelete() => $_clearField(9); + @$pb.TagNumber(9) + $1.EvmGrantDeleteRequest ensureEvmGrantDelete() => $_ensure(8); + + @$pb.TagNumber(10) + $1.EvmGrantListRequest get evmGrantList => $_getN(9); + @$pb.TagNumber(10) + set evmGrantList($1.EvmGrantListRequest value) => $_setField(10, value); + @$pb.TagNumber(10) + $core.bool hasEvmGrantList() => $_has(9); + @$pb.TagNumber(10) + void clearEvmGrantList() => $_clearField(10); + @$pb.TagNumber(10) + $1.EvmGrantListRequest ensureEvmGrantList() => $_ensure(9); + + @$pb.TagNumber(11) + ClientConnectionResponse get clientConnectionResponse => $_getN(10); + @$pb.TagNumber(11) + set clientConnectionResponse(ClientConnectionResponse value) => + $_setField(11, value); + @$pb.TagNumber(11) + $core.bool hasClientConnectionResponse() => $_has(10); + @$pb.TagNumber(11) + void clearClientConnectionResponse() => $_clearField(11); + @$pb.TagNumber(11) + ClientConnectionResponse ensureClientConnectionResponse() => $_ensure(10); +} + +enum UserAgentResponse_Payload { + authChallenge, + authOk, + unsealStartResponse, + unsealResult, + vaultState, + evmWalletCreate, + evmWalletList, + evmGrantCreate, + evmGrantDelete, + evmGrantList, + clientConnectionRequest, + clientConnectionCancel, + notSet +} + +class UserAgentResponse extends $pb.GeneratedMessage { + factory UserAgentResponse({ + AuthChallenge? authChallenge, + AuthOk? authOk, + UnsealStartResponse? unsealStartResponse, + UnsealResult? unsealResult, + VaultState? vaultState, + $1.WalletCreateResponse? evmWalletCreate, + $1.WalletListResponse? evmWalletList, + $1.EvmGrantCreateResponse? evmGrantCreate, + $1.EvmGrantDeleteResponse? evmGrantDelete, + $1.EvmGrantListResponse? evmGrantList, + ClientConnectionRequest? clientConnectionRequest, + ClientConnectionCancel? clientConnectionCancel, + }) { + final result = create(); + if (authChallenge != null) result.authChallenge = authChallenge; + if (authOk != null) result.authOk = authOk; + if (unsealStartResponse != null) + result.unsealStartResponse = unsealStartResponse; + if (unsealResult != null) result.unsealResult = unsealResult; + if (vaultState != null) result.vaultState = vaultState; + if (evmWalletCreate != null) result.evmWalletCreate = evmWalletCreate; + if (evmWalletList != null) result.evmWalletList = evmWalletList; + if (evmGrantCreate != null) result.evmGrantCreate = evmGrantCreate; + if (evmGrantDelete != null) result.evmGrantDelete = evmGrantDelete; + if (evmGrantList != null) result.evmGrantList = evmGrantList; + if (clientConnectionRequest != null) + result.clientConnectionRequest = clientConnectionRequest; + if (clientConnectionCancel != null) + result.clientConnectionCancel = clientConnectionCancel; + return result; + } + + UserAgentResponse._(); + + factory UserAgentResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory UserAgentResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, UserAgentResponse_Payload> + _UserAgentResponse_PayloadByTag = { + 1: UserAgentResponse_Payload.authChallenge, + 2: UserAgentResponse_Payload.authOk, + 3: UserAgentResponse_Payload.unsealStartResponse, + 4: UserAgentResponse_Payload.unsealResult, + 5: UserAgentResponse_Payload.vaultState, + 6: UserAgentResponse_Payload.evmWalletCreate, + 7: UserAgentResponse_Payload.evmWalletList, + 8: UserAgentResponse_Payload.evmGrantCreate, + 9: UserAgentResponse_Payload.evmGrantDelete, + 10: UserAgentResponse_Payload.evmGrantList, + 11: UserAgentResponse_Payload.clientConnectionRequest, + 12: UserAgentResponse_Payload.clientConnectionCancel, + 0: UserAgentResponse_Payload.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'UserAgentResponse', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'), + createEmptyInstance: create) + ..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + ..aOM(1, _omitFieldNames ? '' : 'authChallenge', + subBuilder: AuthChallenge.create) + ..aOM(2, _omitFieldNames ? '' : 'authOk', subBuilder: AuthOk.create) + ..aOM(3, _omitFieldNames ? '' : 'unsealStartResponse', + subBuilder: UnsealStartResponse.create) + ..aE(4, _omitFieldNames ? '' : 'unsealResult', + enumValues: UnsealResult.values) + ..aE(5, _omitFieldNames ? '' : 'vaultState', + enumValues: VaultState.values) + ..aOM<$1.WalletCreateResponse>(6, _omitFieldNames ? '' : 'evmWalletCreate', + subBuilder: $1.WalletCreateResponse.create) + ..aOM<$1.WalletListResponse>(7, _omitFieldNames ? '' : 'evmWalletList', + subBuilder: $1.WalletListResponse.create) + ..aOM<$1.EvmGrantCreateResponse>(8, _omitFieldNames ? '' : 'evmGrantCreate', + subBuilder: $1.EvmGrantCreateResponse.create) + ..aOM<$1.EvmGrantDeleteResponse>(9, _omitFieldNames ? '' : 'evmGrantDelete', + subBuilder: $1.EvmGrantDeleteResponse.create) + ..aOM<$1.EvmGrantListResponse>(10, _omitFieldNames ? '' : 'evmGrantList', + subBuilder: $1.EvmGrantListResponse.create) + ..aOM( + 11, _omitFieldNames ? '' : 'clientConnectionRequest', + subBuilder: ClientConnectionRequest.create) + ..aOM( + 12, _omitFieldNames ? '' : 'clientConnectionCancel', + subBuilder: ClientConnectionCancel.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UserAgentResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UserAgentResponse copyWith(void Function(UserAgentResponse) updates) => + super.copyWith((message) => updates(message as UserAgentResponse)) + as UserAgentResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static UserAgentResponse create() => UserAgentResponse._(); + @$core.override + UserAgentResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static UserAgentResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static UserAgentResponse? _defaultInstance; + + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + @$pb.TagNumber(4) + @$pb.TagNumber(5) + @$pb.TagNumber(6) + @$pb.TagNumber(7) + @$pb.TagNumber(8) + @$pb.TagNumber(9) + @$pb.TagNumber(10) + @$pb.TagNumber(11) + @$pb.TagNumber(12) + UserAgentResponse_Payload whichPayload() => + _UserAgentResponse_PayloadByTag[$_whichOneof(0)]!; + @$pb.TagNumber(1) + @$pb.TagNumber(2) + @$pb.TagNumber(3) + @$pb.TagNumber(4) + @$pb.TagNumber(5) + @$pb.TagNumber(6) + @$pb.TagNumber(7) + @$pb.TagNumber(8) + @$pb.TagNumber(9) + @$pb.TagNumber(10) + @$pb.TagNumber(11) + @$pb.TagNumber(12) + void clearPayload() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + AuthChallenge get authChallenge => $_getN(0); + @$pb.TagNumber(1) + set authChallenge(AuthChallenge value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasAuthChallenge() => $_has(0); + @$pb.TagNumber(1) + void clearAuthChallenge() => $_clearField(1); + @$pb.TagNumber(1) + AuthChallenge ensureAuthChallenge() => $_ensure(0); + + @$pb.TagNumber(2) + AuthOk get authOk => $_getN(1); + @$pb.TagNumber(2) + set authOk(AuthOk value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasAuthOk() => $_has(1); + @$pb.TagNumber(2) + void clearAuthOk() => $_clearField(2); + @$pb.TagNumber(2) + AuthOk ensureAuthOk() => $_ensure(1); + + @$pb.TagNumber(3) + UnsealStartResponse get unsealStartResponse => $_getN(2); + @$pb.TagNumber(3) + set unsealStartResponse(UnsealStartResponse value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasUnsealStartResponse() => $_has(2); + @$pb.TagNumber(3) + void clearUnsealStartResponse() => $_clearField(3); + @$pb.TagNumber(3) + UnsealStartResponse ensureUnsealStartResponse() => $_ensure(2); + + @$pb.TagNumber(4) + UnsealResult get unsealResult => $_getN(3); + @$pb.TagNumber(4) + set unsealResult(UnsealResult value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasUnsealResult() => $_has(3); + @$pb.TagNumber(4) + void clearUnsealResult() => $_clearField(4); + + @$pb.TagNumber(5) + VaultState get vaultState => $_getN(4); + @$pb.TagNumber(5) + set vaultState(VaultState value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasVaultState() => $_has(4); + @$pb.TagNumber(5) + void clearVaultState() => $_clearField(5); + + @$pb.TagNumber(6) + $1.WalletCreateResponse get evmWalletCreate => $_getN(5); + @$pb.TagNumber(6) + set evmWalletCreate($1.WalletCreateResponse value) => $_setField(6, value); + @$pb.TagNumber(6) + $core.bool hasEvmWalletCreate() => $_has(5); + @$pb.TagNumber(6) + void clearEvmWalletCreate() => $_clearField(6); + @$pb.TagNumber(6) + $1.WalletCreateResponse ensureEvmWalletCreate() => $_ensure(5); + + @$pb.TagNumber(7) + $1.WalletListResponse get evmWalletList => $_getN(6); + @$pb.TagNumber(7) + set evmWalletList($1.WalletListResponse value) => $_setField(7, value); + @$pb.TagNumber(7) + $core.bool hasEvmWalletList() => $_has(6); + @$pb.TagNumber(7) + void clearEvmWalletList() => $_clearField(7); + @$pb.TagNumber(7) + $1.WalletListResponse ensureEvmWalletList() => $_ensure(6); + + @$pb.TagNumber(8) + $1.EvmGrantCreateResponse get evmGrantCreate => $_getN(7); + @$pb.TagNumber(8) + set evmGrantCreate($1.EvmGrantCreateResponse value) => $_setField(8, value); + @$pb.TagNumber(8) + $core.bool hasEvmGrantCreate() => $_has(7); + @$pb.TagNumber(8) + void clearEvmGrantCreate() => $_clearField(8); + @$pb.TagNumber(8) + $1.EvmGrantCreateResponse ensureEvmGrantCreate() => $_ensure(7); + + @$pb.TagNumber(9) + $1.EvmGrantDeleteResponse get evmGrantDelete => $_getN(8); + @$pb.TagNumber(9) + set evmGrantDelete($1.EvmGrantDeleteResponse value) => $_setField(9, value); + @$pb.TagNumber(9) + $core.bool hasEvmGrantDelete() => $_has(8); + @$pb.TagNumber(9) + void clearEvmGrantDelete() => $_clearField(9); + @$pb.TagNumber(9) + $1.EvmGrantDeleteResponse ensureEvmGrantDelete() => $_ensure(8); + + @$pb.TagNumber(10) + $1.EvmGrantListResponse get evmGrantList => $_getN(9); + @$pb.TagNumber(10) + set evmGrantList($1.EvmGrantListResponse value) => $_setField(10, value); + @$pb.TagNumber(10) + $core.bool hasEvmGrantList() => $_has(9); + @$pb.TagNumber(10) + void clearEvmGrantList() => $_clearField(10); + @$pb.TagNumber(10) + $1.EvmGrantListResponse ensureEvmGrantList() => $_ensure(9); + + @$pb.TagNumber(11) + ClientConnectionRequest get clientConnectionRequest => $_getN(10); + @$pb.TagNumber(11) + set clientConnectionRequest(ClientConnectionRequest value) => + $_setField(11, value); + @$pb.TagNumber(11) + $core.bool hasClientConnectionRequest() => $_has(10); + @$pb.TagNumber(11) + void clearClientConnectionRequest() => $_clearField(11); + @$pb.TagNumber(11) + ClientConnectionRequest ensureClientConnectionRequest() => $_ensure(10); + + @$pb.TagNumber(12) + ClientConnectionCancel get clientConnectionCancel => $_getN(11); + @$pb.TagNumber(12) + set clientConnectionCancel(ClientConnectionCancel value) => + $_setField(12, value); + @$pb.TagNumber(12) + $core.bool hasClientConnectionCancel() => $_has(11); + @$pb.TagNumber(12) + void clearClientConnectionCancel() => $_clearField(12); + @$pb.TagNumber(12) + ClientConnectionCancel ensureClientConnectionCancel() => $_ensure(11); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/useragent/lib/proto/user_agent.pbenum.dart b/useragent/lib/proto/user_agent.pbenum.dart new file mode 100644 index 0000000..b2b6484 --- /dev/null +++ b/useragent/lib/proto/user_agent.pbenum.dart @@ -0,0 +1,71 @@ +// This is a generated file - do not edit. +// +// Generated from user_agent.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +class UnsealResult extends $pb.ProtobufEnum { + static const UnsealResult UNSEAL_RESULT_UNSPECIFIED = + UnsealResult._(0, _omitEnumNames ? '' : 'UNSEAL_RESULT_UNSPECIFIED'); + static const UnsealResult UNSEAL_RESULT_SUCCESS = + UnsealResult._(1, _omitEnumNames ? '' : 'UNSEAL_RESULT_SUCCESS'); + static const UnsealResult UNSEAL_RESULT_INVALID_KEY = + UnsealResult._(2, _omitEnumNames ? '' : 'UNSEAL_RESULT_INVALID_KEY'); + static const UnsealResult UNSEAL_RESULT_UNBOOTSTRAPPED = + UnsealResult._(3, _omitEnumNames ? '' : 'UNSEAL_RESULT_UNBOOTSTRAPPED'); + + static const $core.List values = [ + UNSEAL_RESULT_UNSPECIFIED, + UNSEAL_RESULT_SUCCESS, + UNSEAL_RESULT_INVALID_KEY, + UNSEAL_RESULT_UNBOOTSTRAPPED, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 3); + static UnsealResult? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const UnsealResult._(super.value, super.name); +} + +class VaultState extends $pb.ProtobufEnum { + static const VaultState VAULT_STATE_UNSPECIFIED = + VaultState._(0, _omitEnumNames ? '' : 'VAULT_STATE_UNSPECIFIED'); + static const VaultState VAULT_STATE_UNBOOTSTRAPPED = + VaultState._(1, _omitEnumNames ? '' : 'VAULT_STATE_UNBOOTSTRAPPED'); + static const VaultState VAULT_STATE_SEALED = + VaultState._(2, _omitEnumNames ? '' : 'VAULT_STATE_SEALED'); + static const VaultState VAULT_STATE_UNSEALED = + VaultState._(3, _omitEnumNames ? '' : 'VAULT_STATE_UNSEALED'); + static const VaultState VAULT_STATE_ERROR = + VaultState._(4, _omitEnumNames ? '' : 'VAULT_STATE_ERROR'); + + static const $core.List values = [ + VAULT_STATE_UNSPECIFIED, + VAULT_STATE_UNBOOTSTRAPPED, + VAULT_STATE_SEALED, + VAULT_STATE_UNSEALED, + VAULT_STATE_ERROR, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 4); + static VaultState? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const VaultState._(super.value, super.name); +} + +const $core.bool _omitEnumNames = + $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/useragent/lib/proto/user_agent.pbjson.dart b/useragent/lib/proto/user_agent.pbjson.dart new file mode 100644 index 0000000..843ab59 --- /dev/null +++ b/useragent/lib/proto/user_agent.pbjson.dart @@ -0,0 +1,457 @@ +// This is a generated file - do not edit. +// +// Generated from user_agent.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports +// ignore_for_file: unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use unsealResultDescriptor instead') +const UnsealResult$json = { + '1': 'UnsealResult', + '2': [ + {'1': 'UNSEAL_RESULT_UNSPECIFIED', '2': 0}, + {'1': 'UNSEAL_RESULT_SUCCESS', '2': 1}, + {'1': 'UNSEAL_RESULT_INVALID_KEY', '2': 2}, + {'1': 'UNSEAL_RESULT_UNBOOTSTRAPPED', '2': 3}, + ], +}; + +/// Descriptor for `UnsealResult`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List unsealResultDescriptor = $convert.base64Decode( + 'CgxVbnNlYWxSZXN1bHQSHQoZVU5TRUFMX1JFU1VMVF9VTlNQRUNJRklFRBAAEhkKFVVOU0VBTF' + '9SRVNVTFRfU1VDQ0VTUxABEh0KGVVOU0VBTF9SRVNVTFRfSU5WQUxJRF9LRVkQAhIgChxVTlNF' + 'QUxfUkVTVUxUX1VOQk9PVFNUUkFQUEVEEAM='); + +@$core.Deprecated('Use vaultStateDescriptor instead') +const VaultState$json = { + '1': 'VaultState', + '2': [ + {'1': 'VAULT_STATE_UNSPECIFIED', '2': 0}, + {'1': 'VAULT_STATE_UNBOOTSTRAPPED', '2': 1}, + {'1': 'VAULT_STATE_SEALED', '2': 2}, + {'1': 'VAULT_STATE_UNSEALED', '2': 3}, + {'1': 'VAULT_STATE_ERROR', '2': 4}, + ], +}; + +/// Descriptor for `VaultState`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List vaultStateDescriptor = $convert.base64Decode( + 'CgpWYXVsdFN0YXRlEhsKF1ZBVUxUX1NUQVRFX1VOU1BFQ0lGSUVEEAASHgoaVkFVTFRfU1RBVE' + 'VfVU5CT09UU1RSQVBQRUQQARIWChJWQVVMVF9TVEFURV9TRUFMRUQQAhIYChRWQVVMVF9TVEFU' + 'RV9VTlNFQUxFRBADEhUKEVZBVUxUX1NUQVRFX0VSUk9SEAQ='); + +@$core.Deprecated('Use authChallengeRequestDescriptor instead') +const AuthChallengeRequest$json = { + '1': 'AuthChallengeRequest', + '2': [ + {'1': 'pubkey', '3': 1, '4': 1, '5': 12, '10': 'pubkey'}, + { + '1': 'bootstrap_token', + '3': 2, + '4': 1, + '5': 9, + '9': 0, + '10': 'bootstrapToken', + '17': true + }, + ], + '8': [ + {'1': '_bootstrap_token'}, + ], +}; + +/// Descriptor for `AuthChallengeRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List authChallengeRequestDescriptor = $convert.base64Decode( + 'ChRBdXRoQ2hhbGxlbmdlUmVxdWVzdBIWCgZwdWJrZXkYASABKAxSBnB1YmtleRIsCg9ib290c3' + 'RyYXBfdG9rZW4YAiABKAlIAFIOYm9vdHN0cmFwVG9rZW6IAQFCEgoQX2Jvb3RzdHJhcF90b2tl' + 'bg=='); + +@$core.Deprecated('Use authChallengeDescriptor instead') +const AuthChallenge$json = { + '1': 'AuthChallenge', + '2': [ + {'1': 'pubkey', '3': 1, '4': 1, '5': 12, '10': 'pubkey'}, + {'1': 'nonce', '3': 2, '4': 1, '5': 5, '10': 'nonce'}, + ], +}; + +/// Descriptor for `AuthChallenge`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List authChallengeDescriptor = $convert.base64Decode( + 'Cg1BdXRoQ2hhbGxlbmdlEhYKBnB1YmtleRgBIAEoDFIGcHVia2V5EhQKBW5vbmNlGAIgASgFUg' + 'Vub25jZQ=='); + +@$core.Deprecated('Use authChallengeSolutionDescriptor instead') +const AuthChallengeSolution$json = { + '1': 'AuthChallengeSolution', + '2': [ + {'1': 'signature', '3': 1, '4': 1, '5': 12, '10': 'signature'}, + ], +}; + +/// Descriptor for `AuthChallengeSolution`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List authChallengeSolutionDescriptor = $convert.base64Decode( + 'ChVBdXRoQ2hhbGxlbmdlU29sdXRpb24SHAoJc2lnbmF0dXJlGAEgASgMUglzaWduYXR1cmU='); + +@$core.Deprecated('Use authOkDescriptor instead') +const AuthOk$json = { + '1': 'AuthOk', +}; + +/// Descriptor for `AuthOk`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List authOkDescriptor = + $convert.base64Decode('CgZBdXRoT2s='); + +@$core.Deprecated('Use unsealStartDescriptor instead') +const UnsealStart$json = { + '1': 'UnsealStart', + '2': [ + {'1': 'client_pubkey', '3': 1, '4': 1, '5': 12, '10': 'clientPubkey'}, + ], +}; + +/// Descriptor for `UnsealStart`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List unsealStartDescriptor = $convert.base64Decode( + 'CgtVbnNlYWxTdGFydBIjCg1jbGllbnRfcHVia2V5GAEgASgMUgxjbGllbnRQdWJrZXk='); + +@$core.Deprecated('Use unsealStartResponseDescriptor instead') +const UnsealStartResponse$json = { + '1': 'UnsealStartResponse', + '2': [ + {'1': 'server_pubkey', '3': 1, '4': 1, '5': 12, '10': 'serverPubkey'}, + ], +}; + +/// Descriptor for `UnsealStartResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List unsealStartResponseDescriptor = $convert.base64Decode( + 'ChNVbnNlYWxTdGFydFJlc3BvbnNlEiMKDXNlcnZlcl9wdWJrZXkYASABKAxSDHNlcnZlclB1Ym' + 'tleQ=='); + +@$core.Deprecated('Use unsealEncryptedKeyDescriptor instead') +const UnsealEncryptedKey$json = { + '1': 'UnsealEncryptedKey', + '2': [ + {'1': 'nonce', '3': 1, '4': 1, '5': 12, '10': 'nonce'}, + {'1': 'ciphertext', '3': 2, '4': 1, '5': 12, '10': 'ciphertext'}, + {'1': 'associated_data', '3': 3, '4': 1, '5': 12, '10': 'associatedData'}, + ], +}; + +/// Descriptor for `UnsealEncryptedKey`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List unsealEncryptedKeyDescriptor = $convert.base64Decode( + 'ChJVbnNlYWxFbmNyeXB0ZWRLZXkSFAoFbm9uY2UYASABKAxSBW5vbmNlEh4KCmNpcGhlcnRleH' + 'QYAiABKAxSCmNpcGhlcnRleHQSJwoPYXNzb2NpYXRlZF9kYXRhGAMgASgMUg5hc3NvY2lhdGVk' + 'RGF0YQ=='); + +@$core.Deprecated('Use clientConnectionRequestDescriptor instead') +const ClientConnectionRequest$json = { + '1': 'ClientConnectionRequest', + '2': [ + {'1': 'pubkey', '3': 1, '4': 1, '5': 12, '10': 'pubkey'}, + ], +}; + +/// Descriptor for `ClientConnectionRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List clientConnectionRequestDescriptor = + $convert.base64Decode( + 'ChdDbGllbnRDb25uZWN0aW9uUmVxdWVzdBIWCgZwdWJrZXkYASABKAxSBnB1YmtleQ=='); + +@$core.Deprecated('Use clientConnectionResponseDescriptor instead') +const ClientConnectionResponse$json = { + '1': 'ClientConnectionResponse', + '2': [ + {'1': 'approved', '3': 1, '4': 1, '5': 8, '10': 'approved'}, + ], +}; + +/// Descriptor for `ClientConnectionResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List clientConnectionResponseDescriptor = + $convert.base64Decode( + 'ChhDbGllbnRDb25uZWN0aW9uUmVzcG9uc2USGgoIYXBwcm92ZWQYASABKAhSCGFwcHJvdmVk'); + +@$core.Deprecated('Use clientConnectionCancelDescriptor instead') +const ClientConnectionCancel$json = { + '1': 'ClientConnectionCancel', +}; + +/// Descriptor for `ClientConnectionCancel`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List clientConnectionCancelDescriptor = + $convert.base64Decode('ChZDbGllbnRDb25uZWN0aW9uQ2FuY2Vs'); + +@$core.Deprecated('Use userAgentRequestDescriptor instead') +const UserAgentRequest$json = { + '1': 'UserAgentRequest', + '2': [ + { + '1': 'auth_challenge_request', + '3': 1, + '4': 1, + '5': 11, + '6': '.arbiter.user_agent.AuthChallengeRequest', + '9': 0, + '10': 'authChallengeRequest' + }, + { + '1': 'auth_challenge_solution', + '3': 2, + '4': 1, + '5': 11, + '6': '.arbiter.user_agent.AuthChallengeSolution', + '9': 0, + '10': 'authChallengeSolution' + }, + { + '1': 'unseal_start', + '3': 3, + '4': 1, + '5': 11, + '6': '.arbiter.user_agent.UnsealStart', + '9': 0, + '10': 'unsealStart' + }, + { + '1': 'unseal_encrypted_key', + '3': 4, + '4': 1, + '5': 11, + '6': '.arbiter.user_agent.UnsealEncryptedKey', + '9': 0, + '10': 'unsealEncryptedKey' + }, + { + '1': 'query_vault_state', + '3': 5, + '4': 1, + '5': 11, + '6': '.google.protobuf.Empty', + '9': 0, + '10': 'queryVaultState' + }, + { + '1': 'evm_wallet_create', + '3': 6, + '4': 1, + '5': 11, + '6': '.google.protobuf.Empty', + '9': 0, + '10': 'evmWalletCreate' + }, + { + '1': 'evm_wallet_list', + '3': 7, + '4': 1, + '5': 11, + '6': '.google.protobuf.Empty', + '9': 0, + '10': 'evmWalletList' + }, + { + '1': 'evm_grant_create', + '3': 8, + '4': 1, + '5': 11, + '6': '.arbiter.evm.EvmGrantCreateRequest', + '9': 0, + '10': 'evmGrantCreate' + }, + { + '1': 'evm_grant_delete', + '3': 9, + '4': 1, + '5': 11, + '6': '.arbiter.evm.EvmGrantDeleteRequest', + '9': 0, + '10': 'evmGrantDelete' + }, + { + '1': 'evm_grant_list', + '3': 10, + '4': 1, + '5': 11, + '6': '.arbiter.evm.EvmGrantListRequest', + '9': 0, + '10': 'evmGrantList' + }, + { + '1': 'client_connection_response', + '3': 11, + '4': 1, + '5': 11, + '6': '.arbiter.user_agent.ClientConnectionResponse', + '9': 0, + '10': 'clientConnectionResponse' + }, + ], + '8': [ + {'1': 'payload'}, + ], +}; + +/// Descriptor for `UserAgentRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List userAgentRequestDescriptor = $convert.base64Decode( + 'ChBVc2VyQWdlbnRSZXF1ZXN0EmAKFmF1dGhfY2hhbGxlbmdlX3JlcXVlc3QYASABKAsyKC5hcm' + 'JpdGVyLnVzZXJfYWdlbnQuQXV0aENoYWxsZW5nZVJlcXVlc3RIAFIUYXV0aENoYWxsZW5nZVJl' + 'cXVlc3QSYwoXYXV0aF9jaGFsbGVuZ2Vfc29sdXRpb24YAiABKAsyKS5hcmJpdGVyLnVzZXJfYW' + 'dlbnQuQXV0aENoYWxsZW5nZVNvbHV0aW9uSABSFWF1dGhDaGFsbGVuZ2VTb2x1dGlvbhJECgx1' + 'bnNlYWxfc3RhcnQYAyABKAsyHy5hcmJpdGVyLnVzZXJfYWdlbnQuVW5zZWFsU3RhcnRIAFILdW' + '5zZWFsU3RhcnQSWgoUdW5zZWFsX2VuY3J5cHRlZF9rZXkYBCABKAsyJi5hcmJpdGVyLnVzZXJf' + 'YWdlbnQuVW5zZWFsRW5jcnlwdGVkS2V5SABSEnVuc2VhbEVuY3J5cHRlZEtleRJEChFxdWVyeV' + '92YXVsdF9zdGF0ZRgFIAEoCzIWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eUgAUg9xdWVyeVZhdWx0' + 'U3RhdGUSRAoRZXZtX3dhbGxldF9jcmVhdGUYBiABKAsyFi5nb29nbGUucHJvdG9idWYuRW1wdH' + 'lIAFIPZXZtV2FsbGV0Q3JlYXRlEkAKD2V2bV93YWxsZXRfbGlzdBgHIAEoCzIWLmdvb2dsZS5w' + 'cm90b2J1Zi5FbXB0eUgAUg1ldm1XYWxsZXRMaXN0Ek4KEGV2bV9ncmFudF9jcmVhdGUYCCABKA' + 'syIi5hcmJpdGVyLmV2bS5Fdm1HcmFudENyZWF0ZVJlcXVlc3RIAFIOZXZtR3JhbnRDcmVhdGUS' + 'TgoQZXZtX2dyYW50X2RlbGV0ZRgJIAEoCzIiLmFyYml0ZXIuZXZtLkV2bUdyYW50RGVsZXRlUm' + 'VxdWVzdEgAUg5ldm1HcmFudERlbGV0ZRJICg5ldm1fZ3JhbnRfbGlzdBgKIAEoCzIgLmFyYml0' + 'ZXIuZXZtLkV2bUdyYW50TGlzdFJlcXVlc3RIAFIMZXZtR3JhbnRMaXN0EmwKGmNsaWVudF9jb2' + '5uZWN0aW9uX3Jlc3BvbnNlGAsgASgLMiwuYXJiaXRlci51c2VyX2FnZW50LkNsaWVudENvbm5l' + 'Y3Rpb25SZXNwb25zZUgAUhhjbGllbnRDb25uZWN0aW9uUmVzcG9uc2VCCQoHcGF5bG9hZA=='); + +@$core.Deprecated('Use userAgentResponseDescriptor instead') +const UserAgentResponse$json = { + '1': 'UserAgentResponse', + '2': [ + { + '1': 'auth_challenge', + '3': 1, + '4': 1, + '5': 11, + '6': '.arbiter.user_agent.AuthChallenge', + '9': 0, + '10': 'authChallenge' + }, + { + '1': 'auth_ok', + '3': 2, + '4': 1, + '5': 11, + '6': '.arbiter.user_agent.AuthOk', + '9': 0, + '10': 'authOk' + }, + { + '1': 'unseal_start_response', + '3': 3, + '4': 1, + '5': 11, + '6': '.arbiter.user_agent.UnsealStartResponse', + '9': 0, + '10': 'unsealStartResponse' + }, + { + '1': 'unseal_result', + '3': 4, + '4': 1, + '5': 14, + '6': '.arbiter.user_agent.UnsealResult', + '9': 0, + '10': 'unsealResult' + }, + { + '1': 'vault_state', + '3': 5, + '4': 1, + '5': 14, + '6': '.arbiter.user_agent.VaultState', + '9': 0, + '10': 'vaultState' + }, + { + '1': 'evm_wallet_create', + '3': 6, + '4': 1, + '5': 11, + '6': '.arbiter.evm.WalletCreateResponse', + '9': 0, + '10': 'evmWalletCreate' + }, + { + '1': 'evm_wallet_list', + '3': 7, + '4': 1, + '5': 11, + '6': '.arbiter.evm.WalletListResponse', + '9': 0, + '10': 'evmWalletList' + }, + { + '1': 'evm_grant_create', + '3': 8, + '4': 1, + '5': 11, + '6': '.arbiter.evm.EvmGrantCreateResponse', + '9': 0, + '10': 'evmGrantCreate' + }, + { + '1': 'evm_grant_delete', + '3': 9, + '4': 1, + '5': 11, + '6': '.arbiter.evm.EvmGrantDeleteResponse', + '9': 0, + '10': 'evmGrantDelete' + }, + { + '1': 'evm_grant_list', + '3': 10, + '4': 1, + '5': 11, + '6': '.arbiter.evm.EvmGrantListResponse', + '9': 0, + '10': 'evmGrantList' + }, + { + '1': 'client_connection_request', + '3': 11, + '4': 1, + '5': 11, + '6': '.arbiter.user_agent.ClientConnectionRequest', + '9': 0, + '10': 'clientConnectionRequest' + }, + { + '1': 'client_connection_cancel', + '3': 12, + '4': 1, + '5': 11, + '6': '.arbiter.user_agent.ClientConnectionCancel', + '9': 0, + '10': 'clientConnectionCancel' + }, + ], + '8': [ + {'1': 'payload'}, + ], +}; + +/// Descriptor for `UserAgentResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List userAgentResponseDescriptor = $convert.base64Decode( + 'ChFVc2VyQWdlbnRSZXNwb25zZRJKCg5hdXRoX2NoYWxsZW5nZRgBIAEoCzIhLmFyYml0ZXIudX' + 'Nlcl9hZ2VudC5BdXRoQ2hhbGxlbmdlSABSDWF1dGhDaGFsbGVuZ2USNQoHYXV0aF9vaxgCIAEo' + 'CzIaLmFyYml0ZXIudXNlcl9hZ2VudC5BdXRoT2tIAFIGYXV0aE9rEl0KFXVuc2VhbF9zdGFydF' + '9yZXNwb25zZRgDIAEoCzInLmFyYml0ZXIudXNlcl9hZ2VudC5VbnNlYWxTdGFydFJlc3BvbnNl' + 'SABSE3Vuc2VhbFN0YXJ0UmVzcG9uc2USRwoNdW5zZWFsX3Jlc3VsdBgEIAEoDjIgLmFyYml0ZX' + 'IudXNlcl9hZ2VudC5VbnNlYWxSZXN1bHRIAFIMdW5zZWFsUmVzdWx0EkEKC3ZhdWx0X3N0YXRl' + 'GAUgASgOMh4uYXJiaXRlci51c2VyX2FnZW50LlZhdWx0U3RhdGVIAFIKdmF1bHRTdGF0ZRJPCh' + 'Fldm1fd2FsbGV0X2NyZWF0ZRgGIAEoCzIhLmFyYml0ZXIuZXZtLldhbGxldENyZWF0ZVJlc3Bv' + 'bnNlSABSD2V2bVdhbGxldENyZWF0ZRJJCg9ldm1fd2FsbGV0X2xpc3QYByABKAsyHy5hcmJpdG' + 'VyLmV2bS5XYWxsZXRMaXN0UmVzcG9uc2VIAFINZXZtV2FsbGV0TGlzdBJPChBldm1fZ3JhbnRf' + 'Y3JlYXRlGAggASgLMiMuYXJiaXRlci5ldm0uRXZtR3JhbnRDcmVhdGVSZXNwb25zZUgAUg5ldm' + '1HcmFudENyZWF0ZRJPChBldm1fZ3JhbnRfZGVsZXRlGAkgASgLMiMuYXJiaXRlci5ldm0uRXZt' + 'R3JhbnREZWxldGVSZXNwb25zZUgAUg5ldm1HcmFudERlbGV0ZRJJCg5ldm1fZ3JhbnRfbGlzdB' + 'gKIAEoCzIhLmFyYml0ZXIuZXZtLkV2bUdyYW50TGlzdFJlc3BvbnNlSABSDGV2bUdyYW50TGlz' + 'dBJpChljbGllbnRfY29ubmVjdGlvbl9yZXF1ZXN0GAsgASgLMisuYXJiaXRlci51c2VyX2FnZW' + '50LkNsaWVudENvbm5lY3Rpb25SZXF1ZXN0SABSF2NsaWVudENvbm5lY3Rpb25SZXF1ZXN0EmYK' + 'GGNsaWVudF9jb25uZWN0aW9uX2NhbmNlbBgMIAEoCzIqLmFyYml0ZXIudXNlcl9hZ2VudC5DbG' + 'llbnRDb25uZWN0aW9uQ2FuY2VsSABSFmNsaWVudENvbm5lY3Rpb25DYW5jZWxCCQoHcGF5bG9h' + 'ZA=='); diff --git a/useragent/lib/providers/key.dart b/useragent/lib/providers/key.dart new file mode 100644 index 0000000..4636817 --- /dev/null +++ b/useragent/lib/providers/key.dart @@ -0,0 +1,60 @@ +import 'package:hooks_riverpod/experimental/mutation.dart'; +import 'package:mtcore/markettakers.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:arbiter/features/pk_manager.dart'; +import 'package:arbiter/features/simple_ed25519.dart'; + +part 'key.g.dart'; + +@riverpod +KeyManager keyManager(Ref ref) { + return SimpleEd25519Manager(); +} + +@riverpod +class Key extends _$Key { + @override + Future build() async { + final manager = SimpleEd25519Manager(); + final keyHandle = await manager.get(); + return keyHandle; + } + + Future create() async { + if (state.value != null) { + return; + } + + state = await AsyncValue.guard(() async { + final manager = SimpleEd25519Manager(); + final newKeyHandle = await manager.create(); + return newKeyHandle; + }); + } +} + +class KeyBootstrapper implements StageFactory { + final MutationTarget ref; + + KeyBootstrapper({required this.ref}); + + @override + String get title => "Setting up your identity"; + + @override + Future get isAlreadyCompleted async { + final key = await ref.container.read(keyProvider.future); + return key != null; + } + + @override + Future start(StageController controller) async { + controller.setIndefiniteProgress(); + final key = ref.container.read(keyProvider.notifier); + + await key.create(); + } + + @override + void dispose() {} +} diff --git a/useragent/lib/providers/key.g.dart b/useragent/lib/providers/key.g.dart new file mode 100644 index 0000000..37cfe01 --- /dev/null +++ b/useragent/lib/providers/key.g.dart @@ -0,0 +1,94 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'key.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(keyManager) +final keyManagerProvider = KeyManagerProvider._(); + +final class KeyManagerProvider + extends $FunctionalProvider + with $Provider { + KeyManagerProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'keyManagerProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$keyManagerHash(); + + @$internal + @override + $ProviderElement $createElement($ProviderPointer pointer) => + $ProviderElement(pointer); + + @override + KeyManager create(Ref ref) { + return keyManager(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(KeyManager value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$keyManagerHash() => r'aa37bca34c01a39c11e29d57e320172b37c0b116'; + +@ProviderFor(Key) +final keyProvider = KeyProvider._(); + +final class KeyProvider extends $AsyncNotifierProvider { + KeyProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'keyProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$keyHash(); + + @$internal + @override + Key create() => Key(); +} + +String _$keyHash() => r'6d66204174c4d2d5c76e27f3a8de8f9a9c88a3e0'; + +abstract class _$Key extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, KeyHandle?>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, KeyHandle?>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/useragent/lib/router.dart b/useragent/lib/router.dart new file mode 100644 index 0000000..aef8e74 --- /dev/null +++ b/useragent/lib/router.dart @@ -0,0 +1,32 @@ +import 'dart:async'; + +import 'package:arbiter/features/bootstrap.dart'; +import 'package:arbiter/home.dart'; +import 'package:flutter/src/widgets/async.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final _bootstapCompleter = Completer(); + +class Router extends HookConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final bootstrapper = useMemoized( + () => Bootstrap(completer: _bootstapCompleter), + ); + final bootstrapFuture = useFuture(_bootstapCompleter.future); + + switch (bootstrapFuture.connectionState) { + case ConnectionState.none || + ConnectionState.waiting || + ConnectionState.active: + return bootstrapper; + + case ConnectionState.done: + break; + } + + return Home(); + } +} diff --git a/useragent/lib/screens/about.dart b/useragent/lib/screens/about.dart new file mode 100644 index 0000000..51291c5 --- /dev/null +++ b/useragent/lib/screens/about.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; +import 'package:mtcore/markettakers.dart'; + +class About extends StatelessWidget { + @override + Widget build(BuildContext context) { + return AboutScreen(decription: "Arbiter is bla bla bla"); + } +} diff --git a/useragent/lib/screens/calc.dart b/useragent/lib/screens/calc.dart new file mode 100644 index 0000000..3d090cf --- /dev/null +++ b/useragent/lib/screens/calc.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +class CalcScreen extends StatefulWidget { + const CalcScreen({super.key}); + + @override + State createState() => _CalcScreenState(); +} + +class _CalcScreenState extends State { + int _count = 0; + + void _increment() { + setState(() { + _count++; + }); + } + + void _decrement() { + setState(() { + _count--; + }); + } + + void _reset() { + setState(() { + _count = 0; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Counter'), + ), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Current count'), + const SizedBox(height: 8), + Text( + '$_count', + style: Theme.of(context).textTheme.displaySmall, + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + onPressed: _decrement, + icon: const Icon(Icons.remove_circle_outline), + tooltip: 'Decrement', + ), + const SizedBox(width: 12), + ElevatedButton( + onPressed: _reset, + child: const Text('Reset'), + ), + const SizedBox(width: 12), + IconButton( + onPressed: _increment, + icon: const Icon(Icons.add_circle_outline), + tooltip: 'Increment', + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/useragent/macos/Flutter/Flutter-Debug.xcconfig b/useragent/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b..4b81f9b 100644 --- a/useragent/macos/Flutter/Flutter-Debug.xcconfig +++ b/useragent/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/useragent/macos/Flutter/Flutter-Release.xcconfig b/useragent/macos/Flutter/Flutter-Release.xcconfig index c2efd0b..5caa9d1 100644 --- a/useragent/macos/Flutter/Flutter-Release.xcconfig +++ b/useragent/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/useragent/macos/Flutter/GeneratedPluginRegistrant.swift b/useragent/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..476bca2 100644 --- a/useragent/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/useragent/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,16 @@ import FlutterMacOS import Foundation +import biometric_signature +import cryptography_flutter +import flutter_secure_storage_darwin +import rive_native +import share_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + BiometricSignaturePlugin.register(with: registry.registrar(forPlugin: "BiometricSignaturePlugin")) + CryptographyFlutterPlugin.register(with: registry.registrar(forPlugin: "CryptographyFlutterPlugin")) + FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) + RiveNativePlugin.register(with: registry.registrar(forPlugin: "RiveNativePlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) } diff --git a/useragent/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/useragent/macos/Flutter/ephemeral/Flutter-Generated.xcconfig index ae3789d..1e123d0 100644 --- a/useragent/macos/Flutter/ephemeral/Flutter-Generated.xcconfig +++ b/useragent/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -2,10 +2,12 @@ FLUTTER_ROOT=/Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable FLUTTER_APPLICATION_PATH=/Users/kaska/Documents/Projects/Major/arbiter/useragent COCOAPODS_PARALLEL_CODE_SIGN=true +FLUTTER_TARGET=/Users/kaska/Documents/Projects/Major/arbiter/useragent/lib/main.dart FLUTTER_BUILD_DIR=build -FLUTTER_BUILD_NAME=0.1.0 -FLUTTER_BUILD_NUMBER=0.1.0 +FLUTTER_BUILD_NAME=1.0.0 +FLUTTER_BUILD_NUMBER=1 +DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuMzguOQ==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049NjczMjNkZTI4NQ==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NTg3YzE4Zjg3Mw==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMC44 DART_OBFUSCATION=false TRACK_WIDGET_CREATION=true TREE_SHAKE_ICONS=false -PACKAGE_CONFIG=.dart_tool/package_config.json +PACKAGE_CONFIG=/Users/kaska/Documents/Projects/Major/arbiter/useragent/.dart_tool/package_config.json diff --git a/useragent/macos/Flutter/ephemeral/flutter_export_environment.sh b/useragent/macos/Flutter/ephemeral/flutter_export_environment.sh index bae557f..301f1f6 100755 --- a/useragent/macos/Flutter/ephemeral/flutter_export_environment.sh +++ b/useragent/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -3,10 +3,12 @@ export "FLUTTER_ROOT=/Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable" export "FLUTTER_APPLICATION_PATH=/Users/kaska/Documents/Projects/Major/arbiter/useragent" export "COCOAPODS_PARALLEL_CODE_SIGN=true" +export "FLUTTER_TARGET=/Users/kaska/Documents/Projects/Major/arbiter/useragent/lib/main.dart" export "FLUTTER_BUILD_DIR=build" -export "FLUTTER_BUILD_NAME=0.1.0" -export "FLUTTER_BUILD_NUMBER=0.1.0" +export "FLUTTER_BUILD_NAME=1.0.0" +export "FLUTTER_BUILD_NUMBER=1" +export "DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuMzguOQ==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049NjczMjNkZTI4NQ==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NTg3YzE4Zjg3Mw==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMC44" export "DART_OBFUSCATION=false" export "TRACK_WIDGET_CREATION=true" export "TREE_SHAKE_ICONS=false" -export "PACKAGE_CONFIG=.dart_tool/package_config.json" +export "PACKAGE_CONFIG=/Users/kaska/Documents/Projects/Major/arbiter/useragent/.dart_tool/package_config.json" diff --git a/useragent/macos/Podfile b/useragent/macos/Podfile new file mode 100644 index 0000000..ff5ddb3 --- /dev/null +++ b/useragent/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/useragent/macos/Podfile.lock b/useragent/macos/Podfile.lock new file mode 100644 index 0000000..32a275d --- /dev/null +++ b/useragent/macos/Podfile.lock @@ -0,0 +1,47 @@ +PODS: + - biometric_signature (10.2.0): + - FlutterMacOS + - cryptography_flutter (0.0.1): + - FlutterMacOS + - flutter_secure_storage_darwin (10.0.0): + - Flutter + - FlutterMacOS + - FlutterMacOS (1.0.0) + - rive_native (0.0.1): + - FlutterMacOS + - share_plus (0.0.1): + - FlutterMacOS + +DEPENDENCIES: + - biometric_signature (from `Flutter/ephemeral/.symlinks/plugins/biometric_signature/macos`) + - cryptography_flutter (from `Flutter/ephemeral/.symlinks/plugins/cryptography_flutter/macos`) + - flutter_secure_storage_darwin (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_darwin/darwin`) + - FlutterMacOS (from `Flutter/ephemeral`) + - rive_native (from `Flutter/ephemeral/.symlinks/plugins/rive_native/macos`) + - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) + +EXTERNAL SOURCES: + biometric_signature: + :path: Flutter/ephemeral/.symlinks/plugins/biometric_signature/macos + cryptography_flutter: + :path: Flutter/ephemeral/.symlinks/plugins/cryptography_flutter/macos + flutter_secure_storage_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_darwin/darwin + FlutterMacOS: + :path: Flutter/ephemeral + rive_native: + :path: Flutter/ephemeral/.symlinks/plugins/rive_native/macos + share_plus: + :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos + +SPEC CHECKSUMS: + biometric_signature: 7ef6a703fcc2c3b0e3d937a8560507b1ba9d3414 + cryptography_flutter: be2b3e0e31603521b6a1c2bea232a88a2488a91c + flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23 + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + rive_native: 1c53d33e44c2b54424810effea4590671dd220c7 + share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc + +PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 + +COCOAPODS: 1.16.2 diff --git a/useragent/macos/Runner.xcodeproj/project.pbxproj b/useragent/macos/Runner.xcodeproj/project.pbxproj index 062b369..28b98d2 100644 --- a/useragent/macos/Runner.xcodeproj/project.pbxproj +++ b/useragent/macos/Runner.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 89374438F7FC24C1E409AC55 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84BA779FA182F4B90ADDD656 /* Pods_RunnerTests.framework */; }; + 9812E904D4443BD8157BA2CD /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFE2551831D6F491FC95F4C0 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -64,7 +66,7 @@ 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "app.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* useragent.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = useragent.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -76,8 +78,16 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 5385F9987FF8E7FD3BA4E87E /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 84BA779FA182F4B90ADDD656 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 989E0AE288EA0AECFF244CAB /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + ADF8B0EB51CA38AE67931C44 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + DC537F8D4AE9B12FB802E110 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + F06E58390BD453D6E42AD67C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + F9C83BBEBB84A7F9ACC93B93 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + FFE2551831D6F491FC95F4C0 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,6 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 89374438F7FC24C1E409AC55 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -92,6 +103,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 9812E904D4443BD8157BA2CD /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -125,13 +137,14 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + C5764DB16A5CAE65539863D1 /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* app.app */, + 33CC10ED2044A3C60003C045 /* useragent.app */, 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; @@ -172,9 +185,25 @@ path = Runner; sourceTree = ""; }; + C5764DB16A5CAE65539863D1 /* Pods */ = { + isa = PBXGroup; + children = ( + 989E0AE288EA0AECFF244CAB /* Pods-Runner.debug.xcconfig */, + F06E58390BD453D6E42AD67C /* Pods-Runner.release.xcconfig */, + F9C83BBEBB84A7F9ACC93B93 /* Pods-Runner.profile.xcconfig */, + DC537F8D4AE9B12FB802E110 /* Pods-RunnerTests.debug.xcconfig */, + 5385F9987FF8E7FD3BA4E87E /* Pods-RunnerTests.release.xcconfig */, + ADF8B0EB51CA38AE67931C44 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + FFE2551831D6F491FC95F4C0 /* Pods_Runner.framework */, + 84BA779FA182F4B90ADDD656 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -186,6 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 72217D999FDDB4A0040A839E /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -204,11 +234,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 4909404BD2C11CE7688B3B73 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 2AF2BC4AB258588AFC797EA4 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -217,7 +249,7 @@ ); name = Runner; productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* app.app */; + productReference = 33CC10ED2044A3C60003C045 /* useragent.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -291,6 +323,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 2AF2BC4AB258588AFC797EA4 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -329,6 +378,50 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 4909404BD2C11CE7688B3B73 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 72217D999FDDB4A0040A839E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -380,43 +473,46 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = DC537F8D4AE9B12FB802E110 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.app.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.example.useragent.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/app"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/useragent.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/useragent"; }; name = Debug; }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 5385F9987FF8E7FD3BA4E87E /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.app.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.example.useragent.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/app"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/useragent.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/useragent"; }; name = Release; }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = ADF8B0EB51CA38AE67931C44 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.app.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.example.useragent.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/app"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/useragent.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/useragent"; }; name = Profile; }; diff --git a/useragent/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/useragent/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e8559da..23178e3 100644 --- a/useragent/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/useragent/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -15,7 +15,7 @@ @@ -31,7 +31,7 @@ @@ -66,7 +66,7 @@ @@ -83,7 +83,7 @@ diff --git a/useragent/macos/Runner.xcworkspace/contents.xcworkspacedata b/useragent/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/useragent/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/useragent/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/useragent/macos/Runner/Configs/AppInfo.xcconfig b/useragent/macos/Runner/Configs/AppInfo.xcconfig index 8530c6f..e7d9966 100644 --- a/useragent/macos/Runner/Configs/AppInfo.xcconfig +++ b/useragent/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = app +PRODUCT_NAME = useragent // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.app +PRODUCT_BUNDLE_IDENTIFIER = com.example.useragent // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved. diff --git a/useragent/pubspec.lock b/useragent/pubspec.lock index 134f6f2..790c4b9 100644 --- a/useragent/pubspec.lock +++ b/useragent/pubspec.lock @@ -1,6 +1,46 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d + url: "https://pub.dev" + source: hosted + version: "91.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 + url: "https://pub.dev" + source: hosted + version: "8.4.1" + analyzer_buffer: + dependency: transitive + description: + name: analyzer_buffer + sha256: aba2f75e63b3135fd1efaa8b6abefe1aa6e41b6bd9806221620fa48f98156033 + url: "https://pub.dev" + source: hosted + version: "0.1.11" + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" + url: "https://pub.dev" + source: hosted + version: "2.0.3" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -9,6 +49,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" + biometric_signature: + dependency: "direct main" + description: + name: biometric_signature + sha256: "0d22580388e5cc037f6f829b29ecda74e343fd61d26c55e33cbcf48a48e5c1dc" + url: "https://pub.dev" + source: hosted + version: "10.2.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: a48653a82055a900b88cd35f92429f068c5a8057ae9b136d197b3d56c57efb81 + url: "https://pub.dev" + source: hosted + version: "9.2.0" boolean_selector: dependency: transitive description: @@ -17,6 +73,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: "275bf6bb2a00a9852c28d4e0b410da1d833a734d57d39d44f94bfc895a484ec3" + url: "https://pub.dev" + source: hosted + version: "4.0.4" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.dev" + source: hosted + version: "4.1.1" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "7981eb922842c77033026eb4341d5af651562008cdb116bdfa31fc46516b6462" + url: "https://pub.dev" + source: hosted + version: "2.12.2" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "6ae8a6435a8c6520c7077b107e77f1fb4ba7009633259a4d49a8afd8e7efc5e9" + url: "https://pub.dev" + source: hosted + version: "8.12.4" characters: dependency: transitive description: @@ -25,6 +129,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" clock: dependency: transitive description: @@ -33,6 +153,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + url: "https://pub.dev" + source: hosted + version: "4.11.1" collection: dependency: transitive description: @@ -41,6 +177,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + url: "https://pub.dev" + source: hosted + version: "1.15.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + url: "https://pub.dev" + source: hosted + version: "0.3.5+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + cryptography: + dependency: "direct main" + description: + name: cryptography + sha256: "3eda3029d34ec9095a27a198ac9785630fe525c0eb6a49f3d575272f8e792ef0" + url: "https://pub.dev" + source: hosted + version: "2.9.0" + cryptography_flutter: + dependency: "direct main" + description: + name: cryptography_flutter + sha256: d1c7e7a31a072d63b27ce0537b89868f9bda9188f2b1651ae728a295762921d4 + url: "https://pub.dev" + source: hosted + version: "2.3.4" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b + url: "https://pub.dev" + source: hosted + version: "3.1.3" fake_async: dependency: transitive description: @@ -49,11 +249,60 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_adaptive_scaffold: + dependency: "direct main" + description: + path: "." + ref: HEAD + resolved-ref: "4852b5041d8d9ed6cacb4dc4c6723086f0612e37" + url: "https://github.com/hdbg/flutter_adaptive_scaffold.git" + source: git + version: "0.3.3+1" + flutter_bloc: + dependency: transitive + description: + name: flutter_bloc + sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38 + url: "https://pub.dev" + source: hosted + version: "9.1.1" + flutter_hooks: + dependency: "direct main" + description: + name: flutter_hooks + sha256: "8ae1f090e5f4ef5cfa6670ce1ab5dddadd33f3533a7f9ba19d9f958aa2a89f42" + url: "https://pub.dev" + source: hosted + version: "0.21.3+1" flutter_lints: dependency: "direct dev" description: @@ -62,11 +311,216 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_riverpod: + dependency: transitive + description: + name: flutter_riverpod + sha256: "38ec6c303e2c83ee84512f5fc2a82ae311531021938e63d7137eccc107bf3c02" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40 + url: "https://pub.dev" + source: hosted + version: "10.0.0" + flutter_secure_storage_darwin: + dependency: transitive + description: + name: flutter_secure_storage_darwin + sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + flutter_spinkit: + dependency: transitive + description: + name: flutter_spinkit + sha256: "77850df57c00dc218bfe96071d576a8babec24cf58b2ed121c83cca4a2fdce7f" + url: "https://pub.dev" + source: hosted + version: "5.2.2" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" + url: "https://pub.dev" + source: hosted + version: "0.3.3+1" + googleapis_auth: + dependency: transitive + description: + name: googleapis_auth + sha256: b81fe352cc4a330b3710d2b7ad258d9bcef6f909bb759b306bf42973a7d046db + url: "https://pub.dev" + source: hosted + version: "2.0.0" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + group_button: + dependency: transitive + description: + name: group_button + sha256: "0610fcf28ed122bfb4b410fce161a390f7f2531d55d1d65c5375982001415940" + url: "https://pub.dev" + source: hosted + version: "5.3.4" + grpc: + dependency: "direct main" + description: + name: grpc + sha256: "86be3a7d39ad865b214a7370021ac80e68939238b507730de6d97fc662cb2723" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + hooks: + dependency: transitive + description: + name: hooks + sha256: e79ed1e8e1929bc6ecb6ec85f0cb519c887aa5b423705ded0d0f2d9226def388 + url: "https://pub.dev" + source: hosted + version: "1.0.2" + hooks_riverpod: + dependency: "direct main" + description: + name: hooks_riverpod + sha256: b880efcd17757af0aa242e5dceac2fb781a014c22a32435a5daa8f17e9d5d8a9 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http2: + dependency: transitive + description: + name: http2 + sha256: "382d3aefc5bd6dc68c6b892d7664f29b5beb3251611ae946a98d35158a82bbfa" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + url: "https://pub.dev" + source: hosted + version: "4.11.0" leak_tracker: dependency: transitive description: @@ -99,6 +553,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -123,6 +585,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + mockito: + dependency: transitive + description: + name: mockito + sha256: a45d1aa065b796922db7b9e7e7e45f921aed17adf3a8318a1f47097e7e695566 + url: "https://pub.dev" + source: hosted + version: "5.6.3" + mtcore: + dependency: "direct main" + description: + name: mtcore + sha256: "3e7ee6e0685ddc1615cece3cdce242204030aeeb4ca4bcf4586e3f1ba7d88954" + url: "https://git.markettakers.org/api/packages/MarketTakers/pub/" + source: hosted + version: "1.0.6" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "92b2ca62c8bd2b8d2f267cdfccf9bfbdb7322f778f8f91b3ce5b5cda23a3899f" + url: "https://pub.dev" + source: hosted + version: "0.17.5" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + url: "https://pub.dev" + source: hosted + version: "9.3.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" path: dependency: transitive description: @@ -131,11 +657,251 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.dev" + source: hosted + version: "2.2.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + percent_indicator: + dependency: transitive + description: + name: percent_indicator + sha256: "157d29133bbc6ecb11f923d36e7960a96a3f28837549a20b65e5135729f0f9fd" + url: "https://pub.dev" + source: hosted + version: "4.2.5" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + protobuf: + dependency: transitive + description: + name: protobuf + sha256: "75ec242d22e950bdcc79ee38dd520ce4ee0bc491d7fadc4ea47694604d22bf06" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + provider: + dependency: transitive + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + rive: + dependency: transitive + description: + name: rive + sha256: "2d7f0b0dd6c8368c3e5a47d0a38ee68306896920aa665b5bf59e7c589a6d35a2" + url: "https://pub.dev" + source: hosted + version: "0.14.4" + rive_native: + dependency: transitive + description: + name: rive_native + sha256: fad6aac822340fa14a2cbcdc8cd064da4b4c74d22a5f6c827a59538d67f200e4 + url: "https://pub.dev" + source: hosted + version: "0.1.4" + riverpod: + dependency: "direct main" + description: + name: riverpod + sha256: "16ff608d21e8ea64364f2b7c049c94a02ab81668f78845862b6e88b71dd4935a" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + riverpod_analyzer_utils: + dependency: transitive + description: + name: riverpod_analyzer_utils + sha256: "947b05d04c52a546a2ac6b19ef2a54b08520ff6bdf9f23d67957a4c8df1c3bc0" + url: "https://pub.dev" + source: hosted + version: "1.0.0-dev.8" + riverpod_annotation: + dependency: "direct main" + description: + name: riverpod_annotation + sha256: cc1474bc2df55ec3c1da1989d139dcef22cd5e2bd78da382e867a69a8eca2e46 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + riverpod_generator: + dependency: "direct dev" + description: + name: riverpod_generator + sha256: e43b1537229cc8f487f09b0c20d15dba840acbadcf5fc6dad7ad5e8ab75950dc + url: "https://pub.dev" + source: hosted + version: "4.0.0+1" + share_plus: + dependency: transitive + description: + name: share_plus + sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840" + url: "https://pub.dev" + source: hosted + version: "12.0.1" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sizer: + dependency: "direct main" + description: + name: sizer + sha256: "9963c89e4d30d7c2108de3eafc0a7e6a4a8009799376ea6be5ef0a9ad87cfbad" + url: "https://pub.dev" + source: hosted + version: "3.1.3" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: adc962c96fffb2de1728ef396a995aaedcafbe635abdca13d2a987ce17e57751 + url: "https://pub.dev" + source: hosted + version: "4.2.1" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" source_span: dependency: transitive description: @@ -152,6 +918,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" stream_channel: dependency: transitive description: @@ -160,6 +934,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -168,6 +950,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + talker: + dependency: "direct main" + description: + name: talker + sha256: "46a60c6014a870a71ab8e3fa22f9580a8d36faf9b39431dcf124940601c0c266" + url: "https://pub.dev" + source: hosted + version: "5.1.15" + talker_flutter: + dependency: transitive + description: + name: talker_flutter + sha256: "7e1819c505cdcbf2cf6497410b8fa3b33d170e6f137716bd278940c0e509f414" + url: "https://pub.dev" + source: hosted + version: "5.1.15" + talker_logger: + dependency: transitive + description: + name: talker_logger + sha256: "70112d016d7b978ccb7ef0b0f4941e0f0b0de88d80589db43143cea1d744eae0" + url: "https://pub.dev" + source: hosted + version: "5.1.15" term_glyph: dependency: transitive description: @@ -176,6 +982,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + url: "https://pub.dev" + source: hosted + version: "1.26.3" test_api: dependency: transitive description: @@ -184,6 +998,62 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.7" + test_core: + dependency: transitive + description: + name: test_core + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + url: "https://pub.dev" + source: hosted + version: "0.6.12" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f + url: "https://pub.dev" + source: hosted + version: "2.4.2" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + uuid: + dependency: transitive + description: + name: uuid + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + url: "https://pub.dev" + source: hosted + version: "4.5.3" vector_math: dependency: transitive description: @@ -200,6 +1070,70 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: dart: ">=3.10.8 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.38.4" diff --git a/useragent/pubspec.yaml b/useragent/pubspec.yaml index 5050191..cd83ae3 100644 --- a/useragent/pubspec.yaml +++ b/useragent/pubspec.yaml @@ -1,8 +1,7 @@ name: arbiter -description: "User agent for Arbiter" -publish_to: 'none' - -version: 0.1.0 +description: "Useragent for Arbiter project" +publish_to: 'none' +version: 1.0.0+1 environment: sdk: ^3.10.8 @@ -11,11 +10,31 @@ dependencies: flutter: sdk: flutter + cupertino_icons: ^1.0.8 + flutter_adaptive_scaffold: + git: https://github.com/hdbg/flutter_adaptive_scaffold.git + talker: ^5.1.15 + riverpod: ^3.1.0 + hooks_riverpod: ^3.1.0 + sizer: ^3.1.3 + biometric_signature: ^10.2.0 + mtcore: + hosted: https://git.markettakers.org/api/packages/MarketTakers/pub/ + version: ^1.0.6 + cryptography: ^2.9.0 + flutter_secure_storage: ^10.0.0 + cryptography_flutter: ^2.3.4 + riverpod_annotation: ^4.0.0 + grpc: ^5.1.0 + flutter_hooks: ^0.21.3+1 + dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^6.0.0 + riverpod_generator: ^4.0.0+1 + build_runner: ^2.12.2 flutter: - uses-material-design: true + uses-material-design: true \ No newline at end of file diff --git a/useragent/web/favicon.png b/useragent/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/useragent/web/icons/Icon-192.png b/useragent/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 zcmZ`-2T+sGz6~)*FVZ`aW+(v>MIm&M-g^@e2u-B-DoB?qO+b1Tq<5uCCv>ESfRum& zp%X;f!~1{tzL__3=gjVJ=j=J>+nMj%ncXj1Q(b|Ckbw{Y0FWpt%4y%$uD=Z*c-x~o zE;IoE;xa#7Ll5nj-e4CuXB&G*IM~D21rCP$*xLXAK8rIMCSHuSu%bL&S3)8YI~vyp@KBu9Ph7R_pvKQ@xv>NQ`dZp(u{Z8K3yOB zn7-AR+d2JkW)KiGx0hosml;+eCXp6+w%@STjFY*CJ?udJ64&{BCbuebcuH;}(($@@ znNlgBA@ZXB)mcl9nbX#F!f_5Z=W>0kh|UVWnf!At4V*LQP%*gPdCXd6P@J4Td;!Ur z<2ZLmwr(NG`u#gDEMP19UcSzRTL@HsK+PnIXbVBT@oHm53DZr?~V(0{rsalAfwgo zEh=GviaqkF;}F_5-yA!1u3!gxaR&Mj)hLuj5Q-N-@Lra{%<4ONja8pycD90&>yMB` zchhd>0CsH`^|&TstH-8+R`CfoWqmTTF_0?zDOY`E`b)cVi!$4xA@oO;SyOjJyP^_j zx^@Gdf+w|FW@DMdOi8=4+LJl$#@R&&=UM`)G!y%6ZzQLoSL%*KE8IO0~&5XYR9 z&N)?goEiWA(YoRfT{06&D6Yuu@Qt&XVbuW@COb;>SP9~aRc+z`m`80pB2o%`#{xD@ zI3RAlukL5L>px6b?QW1Ac_0>ew%NM!XB2(H+1Y3AJC?C?O`GGs`331Nd4ZvG~bMo{lh~GeL zSL|tT*fF-HXxXYtfu5z+T5Mx9OdP7J4g%@oeC2FaWO1D{=NvL|DNZ}GO?O3`+H*SI z=grGv=7dL{+oY0eJFGO!Qe(e2F?CHW(i!!XkGo2tUvsQ)I9ev`H&=;`N%Z{L zO?vV%rDv$y(@1Yj@xfr7Kzr<~0{^T8wM80xf7IGQF_S-2c0)0D6b0~yD7BsCy+(zL z#N~%&e4iAwi4F$&dI7x6cE|B{f@lY5epaDh=2-(4N05VO~A zQT3hanGy_&p+7Fb^I#ewGsjyCEUmSCaP6JDB*=_()FgQ(-pZ28-{qx~2foO4%pM9e z*_63RT8XjgiaWY|*xydf;8MKLd{HnfZ2kM%iq}fstImB-K6A79B~YoPVa@tYN@T_$ zea+9)<%?=Fl!kd(Y!G(-o}ko28hg2!MR-o5BEa_72uj7Mrc&{lRh3u2%Y=Xk9^-qa zBPWaD=2qcuJ&@Tf6ue&)4_V*45=zWk@Z}Q?f5)*z)-+E|-yC4fs5CE6L_PH3=zI8p z*Z3!it{1e5_^(sF*v=0{`U9C741&lub89gdhKp|Y8CeC{_{wYK-LSbp{h)b~9^j!s z7e?Y{Z3pZv0J)(VL=g>l;<}xk=T*O5YR|hg0eg4u98f2IrA-MY+StQIuK-(*J6TRR z|IM(%uI~?`wsfyO6Tgmsy1b3a)j6M&-jgUjVg+mP*oTKdHg?5E`!r`7AE_#?Fc)&a z08KCq>Gc=ne{PCbRvs6gVW|tKdcE1#7C4e`M|j$C5EYZ~Y=jUtc zj`+?p4ba3uy7><7wIokM79jPza``{Lx0)zGWg;FW1^NKY+GpEi=rHJ+fVRGfXO zPHV52k?jxei_!YYAw1HIz}y8ZMwdZqU%ESwMn7~t zdI5%B;U7RF=jzRz^NuY9nM)&<%M>x>0(e$GpU9th%rHiZsIT>_qp%V~ILlyt^V`=d z!1+DX@ah?RnB$X!0xpTA0}lN@9V-ePx>wQ?-xrJr^qDlw?#O(RsXeAvM%}rg0NT#t z!CsT;-vB=B87ShG`GwO;OEbeL;a}LIu=&@9cb~Rsx(ZPNQ!NT7H{@j0e(DiLea>QD zPmpe90gEKHEZ8oQ@6%E7k-Ptn#z)b9NbD@_GTxEhbS+}Bb74WUaRy{w;E|MgDAvHw zL)ycgM7mB?XVh^OzbC?LKFMotw3r@i&VdUV%^Efdib)3@soX%vWCbnOyt@Y4swW925@bt45y0HY3YI~BnnzZYrinFy;L?2D3BAL`UQ zEj))+f>H7~g8*VuWQ83EtGcx`hun$QvuurSMg3l4IP8Fe`#C|N6mbYJ=n;+}EQm;< z!!N=5j1aAr_uEnnzrEV%_E|JpTb#1p1*}5!Ce!R@d$EtMR~%9# zd;h8=QGT)KMW2IKu_fA_>p_und#-;Q)p%%l0XZOXQicfX8M~7?8}@U^ihu;mizj)t zgV7wk%n-UOb z#!P5q?Ex+*Kx@*p`o$q8FWL*E^$&1*!gpv?Za$YO~{BHeGY*5%4HXUKa_A~~^d z=E*gf6&+LFF^`j4$T~dR)%{I)T?>@Ma?D!gi9I^HqvjPc3-v~=qpX1Mne@*rzT&Xw zQ9DXsSV@PqpEJO-g4A&L{F&;K6W60D!_vs?Vx!?w27XbEuJJP&);)^+VF1nHqHBWu z^>kI$M9yfOY8~|hZ9WB!q-9u&mKhEcRjlf2nm_@s;0D#c|@ED7NZE% zzR;>P5B{o4fzlfsn3CkBK&`OSb-YNrqx@N#4CK!>bQ(V(D#9|l!e9(%sz~PYk@8zt zPN9oK78&-IL_F zhsk1$6p;GqFbtB^ZHHP+cjMvA0(LqlskbdYE_rda>gvQLTiqOQ1~*7lg%z*&p`Ry& zRcG^DbbPj_jOKHTr8uk^15Boj6>hA2S-QY(W-6!FIq8h$<>MI>PYYRenQDBamO#Fv zAH5&ImqKBDn0v5kb|8i0wFhUBJTpT!rB-`zK)^SNnRmLraZcPYK7b{I@+}wXVdW-{Ps17qdRA3JatEd?rPV z4@}(DAMf5EqXCr4-B+~H1P#;t@O}B)tIJ(W6$LrK&0plTmnPpb1TKn3?f?Kk``?D+ zQ!MFqOX7JbsXfQrz`-M@hq7xlfNz;_B{^wbpG8des56x(Q)H)5eLeDwCrVR}hzr~= zM{yXR6IM?kXxauLza#@#u?Y|o;904HCqF<8yT~~c-xyRc0-vxofnxG^(x%>bj5r}N zyFT+xnn-?B`ohA>{+ZZQem=*Xpqz{=j8i2TAC#x-m;;mo{{sLB_z(UoAqD=A#*juZ zCv=J~i*O8;F}A^Wf#+zx;~3B{57xtoxC&j^ie^?**T`WT2OPRtC`xj~+3Kprn=rVM zVJ|h5ux%S{dO}!mq93}P+h36mZ5aZg1-?vhL$ke1d52qIiXSE(llCr5i=QUS?LIjc zV$4q=-)aaR4wsrQv}^shL5u%6;`uiSEs<1nG^?$kl$^6DL z43CjY`M*p}ew}}3rXc7Xck@k41jx}c;NgEIhKZ*jsBRZUP-x2cm;F1<5$jefl|ppO zmZd%%?gMJ^g9=RZ^#8Mf5aWNVhjAS^|DQO+q$)oeob_&ZLFL(zur$)); zU19yRm)z<4&4-M}7!9+^Wl}Uk?`S$#V2%pQ*SIH5KI-mn%i;Z7-)m$mN9CnI$G7?# zo`zVrUwoSL&_dJ92YhX5TKqaRkfPgC4=Q&=K+;_aDs&OU0&{WFH}kKX6uNQC6%oUH z2DZa1s3%Vtk|bglbxep-w)PbFG!J17`<$g8lVhqD2w;Z0zGsh-r zxZ13G$G<48leNqR!DCVt9)@}(zMI5w6Wo=N zpP1*3DI;~h2WDWgcKn*f!+ORD)f$DZFwgKBafEZmeXQMAsq9sxP9A)7zOYnkHT9JU zRA`umgmP9d6=PHmFIgx=0$(sjb>+0CHG)K@cPG{IxaJ&Ueo8)0RWgV9+gO7+Bl1(F z7!BslJ2MP*PWJ;x)QXbR$6jEr5q3 z(3}F@YO_P1NyTdEXRLU6fp?9V2-S=E+YaeLL{Y)W%6`k7$(EW8EZSA*(+;e5@jgD^I zaJQ2|oCM1n!A&-8`;#RDcZyk*+RPkn_r8?Ak@agHiSp*qFNX)&i21HE?yuZ;-C<3C zwJGd1lx5UzViP7sZJ&|LqH*mryb}y|%AOw+v)yc`qM)03qyyrqhX?ub`Cjwx2PrR! z)_z>5*!*$x1=Qa-0uE7jy0z`>|Ni#X+uV|%_81F7)b+nf%iz=`fF4g5UfHS_?PHbr zB;0$bK@=di?f`dS(j{l3-tSCfp~zUuva+=EWxJcRfp(<$@vd(GigM&~vaYZ0c#BTs z3ijkxMl=vw5AS&DcXQ%eeKt!uKvh2l3W?&3=dBHU=Gz?O!40S&&~ei2vg**c$o;i89~6DVns zG>9a*`k5)NI9|?W!@9>rzJ;9EJ=YlJTx1r1BA?H`LWijk(rTax9(OAu;q4_wTj-yj z1%W4GW&K4T=uEGb+E!>W0SD_C0RR91 literal 0 HcmV?d00001 diff --git a/useragent/web/icons/Icon-512.png b/useragent/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 zcmd5=2T+s!lYZ%-(h(2@5fr2dC?F^$C=i-}R6$UX8af(!je;W5yC_|HmujSgN*6?W z3knF*TL1$|?oD*=zPbBVex*RUIKsL<(&Rj9%^UD2IK3W?2j>D?eWQgvS-HLymHo9%~|N2Q{~j za?*X-{b9JRowv_*Mh|;*-kPFn>PI;r<#kFaxFqbn?aq|PduQg=2Q;~Qc}#z)_T%x9 zE|0!a70`58wjREmAH38H1)#gof)U3g9FZ^ zF7&-0^Hy{4XHWLoC*hOG(dg~2g6&?-wqcpf{ z&3=o8vw7lMi22jCG9RQbv8H}`+}9^zSk`nlR8?Z&G2dlDy$4#+WOlg;VHqzuE=fM@ z?OI6HEJH4&tA?FVG}9>jAnq_^tlw8NbjNhfqk2rQr?h(F&WiKy03Sn=-;ZJRh~JrD zbt)zLbnabttEZ>zUiu`N*u4sfQaLE8-WDn@tHp50uD(^r-}UsUUu)`!Rl1PozAc!a z?uj|2QDQ%oV-jxUJmJycySBINSKdX{kDYRS=+`HgR2GO19fg&lZKyBFbbXhQV~v~L za^U944F1_GtuFXtvDdDNDvp<`fqy);>Vw=ncy!NB85Tw{&sT5&Ox%-p%8fTS;OzlRBwErvO+ROe?{%q-Zge=%Up|D4L#>4K@Ke=x%?*^_^P*KD zgXueMiS63!sEw@fNLB-i^F|@Oib+S4bcy{eu&e}Xvb^(mA!=U=Xr3||IpV~3K zQWzEsUeX_qBe6fky#M zzOJm5b+l;~>=sdp%i}}0h zO?B?i*W;Ndn02Y0GUUPxERG`3Bjtj!NroLoYtyVdLtl?SE*CYpf4|_${ku2s`*_)k zN=a}V8_2R5QANlxsq!1BkT6$4>9=-Ix4As@FSS;1q^#TXPrBsw>hJ}$jZ{kUHoP+H zvoYiR39gX}2OHIBYCa~6ERRPJ#V}RIIZakUmuIoLF*{sO8rAUEB9|+A#C|@kw5>u0 zBd=F!4I)Be8ycH*)X1-VPiZ+Ts8_GB;YW&ZFFUo|Sw|x~ZajLsp+_3gv((Q#N>?Jz zFBf`~p_#^${zhPIIJY~yo!7$-xi2LK%3&RkFg}Ax)3+dFCjGgKv^1;lUzQlPo^E{K zmCnrwJ)NuSaJEmueEPO@(_6h3f5mFffhkU9r8A8(JC5eOkux{gPmx_$Uv&|hyj)gN zd>JP8l2U&81@1Hc>#*su2xd{)T`Yw< zN$dSLUN}dfx)Fu`NcY}TuZ)SdviT{JHaiYgP4~@`x{&h*Hd>c3K_To9BnQi@;tuoL z%PYQo&{|IsM)_>BrF1oB~+`2_uZQ48z9!)mtUR zdfKE+b*w8cPu;F6RYJiYyV;PRBbThqHBEu_(U{(gGtjM}Zi$pL8Whx}<JwE3RM0F8x7%!!s)UJVq|TVd#hf1zVLya$;mYp(^oZQ2>=ZXU1c$}f zm|7kfk>=4KoQoQ!2&SOW5|JP1)%#55C$M(u4%SP~tHa&M+=;YsW=v(Old9L3(j)`u z2?#fK&1vtS?G6aOt@E`gZ9*qCmyvc>Ma@Q8^I4y~f3gs7*d=ATlP>1S zyF=k&6p2;7dn^8?+!wZO5r~B+;@KXFEn^&C=6ma1J7Au6y29iMIxd7#iW%=iUzq&C=$aPLa^Q zncia$@TIy6UT@69=nbty5epP>*fVW@5qbUcb2~Gg75dNd{COFLdiz3}kODn^U*=@E z0*$7u7Rl2u)=%fk4m8EK1ctR!6%Ve`e!O20L$0LkM#f+)n9h^dn{n`T*^~d+l*Qlx z$;JC0P9+en2Wlxjwq#z^a6pdnD6fJM!GV7_%8%c)kc5LZs_G^qvw)&J#6WSp< zmsd~1-(GrgjC56Pdf6#!dt^y8Rg}!#UXf)W%~PeU+kU`FeSZHk)%sFv++#Dujk-~m zFHvVJC}UBn2jN& zs!@nZ?e(iyZPNo`p1i#~wsv9l@#Z|ag3JR>0#u1iW9M1RK1iF6-RbJ4KYg?B`dET9 zyR~DjZ>%_vWYm*Z9_+^~hJ_|SNTzBKx=U0l9 z9x(J96b{`R)UVQ$I`wTJ@$_}`)_DyUNOso6=WOmQKI1e`oyYy1C&%AQU<0-`(ow)1 zT}gYdwWdm4wW6|K)LcfMe&psE0XGhMy&xS`@vLi|1#Za{D6l@#D!?nW87wcscUZgELT{Cz**^;Zb~7 z(~WFRO`~!WvyZAW-8v!6n&j*PLm9NlN}BuUN}@E^TX*4Or#dMMF?V9KBeLSiLO4?B zcE3WNIa-H{ThrlCoN=XjOGk1dT=xwwrmt<1a)mrRzg{35`@C!T?&_;Q4Ce=5=>z^*zE_c(0*vWo2_#TD<2)pLXV$FlwP}Ik74IdDQU@yhkCr5h zn5aa>B7PWy5NQ!vf7@p_qtC*{dZ8zLS;JetPkHi>IvPjtJ#ThGQD|Lq#@vE2xdl%`x4A8xOln}BiQ92Po zW;0%A?I5CQ_O`@Ad=`2BLPPbBuPUp@Hb%a_OOI}y{Rwa<#h z5^6M}s7VzE)2&I*33pA>e71d78QpF>sNK;?lj^Kl#wU7G++`N_oL4QPd-iPqBhhs| z(uVM}$ItF-onXuuXO}o$t)emBO3Hjfyil@*+GF;9j?`&67GBM;TGkLHi>@)rkS4Nj zAEk;u)`jc4C$qN6WV2dVd#q}2X6nKt&X*}I@jP%Srs%%DS92lpDY^K*Sx4`l;aql$ zt*-V{U&$DM>pdO?%jt$t=vg5|p+Rw?SPaLW zB6nvZ69$ne4Z(s$3=Rf&RX8L9PWMV*S0@R zuIk&ba#s6sxVZ51^4Kon46X^9`?DC9mEhWB3f+o4#2EXFqy0(UTc>GU| zGCJmI|Dn-dX#7|_6(fT)>&YQ0H&&JX3cTvAq(a@ydM4>5Njnuere{J8p;3?1az60* z$1E7Yyxt^ytULeokgDnRVKQw9vzHg1>X@@jM$n$HBlveIrKP5-GJq%iWH#odVwV6cF^kKX(@#%%uQVb>#T6L^mC@)%SMd4DF? zVky!~ge27>cpUP1Vi}Z32lbLV+CQy+T5Wdmva6Fg^lKb!zrg|HPU=5Qu}k;4GVH+x z%;&pN1LOce0w@9i1Mo-Y|7|z}fbch@BPp2{&R-5{GLoeu8@limQmFF zaJRR|^;kW_nw~0V^ zfTnR!Ni*;-%oSHG1yItARs~uxra|O?YJxBzLjpeE-=~TO3Dn`JL5Gz;F~O1u3|FE- zvK2Vve`ylc`a}G`gpHg58Cqc9fMoy1L}7x7T>%~b&irrNMo?np3`q;d3d;zTK>nrK zOjPS{@&74-fA7j)8uT9~*g23uGnxwIVj9HorzUX#s0pcp2?GH6i}~+kv9fWChtPa_ z@T3m+$0pbjdQw7jcnHn;Pi85hk_u2-1^}c)LNvjdam8K-XJ+KgKQ%!?2n_!#{$H|| zLO=%;hRo6EDmnOBKCL9Cg~ETU##@u^W_5joZ%Et%X_n##%JDOcsO=0VL|Lkk!VdRJ z^|~2pB@PUspT?NOeO?=0Vb+fAGc!j%Ufn-cB`s2A~W{Zj{`wqWq_-w0wr@6VrM zbzni@8c>WS!7c&|ZR$cQ;`niRw{4kG#e z70e!uX8VmP23SuJ*)#(&R=;SxGAvq|&>geL&!5Z7@0Z(No*W561n#u$Uc`f9pD70# z=sKOSK|bF~#khTTn)B28h^a1{;>EaRnHj~>i=Fnr3+Fa4 z`^+O5_itS#7kPd20rq66_wH`%?HNzWk@XFK0n;Z@Cx{kx==2L22zWH$Yg?7 zvDj|u{{+NR3JvUH({;b*$b(U5U z7(lF!1bz2%06+|-v(D?2KgwNw7( zJB#Tz+ZRi&U$i?f34m7>uTzO#+E5cbaiQ&L}UxyOQq~afbNB4EI{E04ZWg53w0A{O%qo=lF8d zf~ktGvIgf-a~zQoWf>loF7pOodrd0a2|BzwwPDV}ShauTK8*fmF6NRbO>Iw9zZU}u zw8Ya}?seBnEGQDmH#XpUUkj}N49tP<2jYwTFp!P+&Fd(%Z#yo80|5@zN(D{_pNow*&4%ql zW~&yp@scb-+Qj-EmErY+Tu=dUmf@*BoXY2&oKT8U?8?s1d}4a`Aq>7SV800m$FE~? zjmz(LY+Xx9sDX$;vU`xgw*jLw7dWOnWWCO8o|;}f>cu0Q&`0I{YudMn;P;L3R-uz# zfns_mZED_IakFBPP2r_S8XM$X)@O-xVKi4`7373Jkd5{2$M#%cRhWer3M(vr{S6>h zj{givZJ3(`yFL@``(afn&~iNx@B1|-qfYiZu?-_&Z8+R~v`d6R-}EX9IVXWO-!hL5 z*k6T#^2zAXdardU3Ao~I)4DGdAv2bx{4nOK`20rJo>rmk3S2ZDu}))8Z1m}CKigf0 z3L`3Y`{huj`xj9@`$xTZzZc3je?n^yG<8sw$`Y%}9mUsjUR%T!?k^(q)6FH6Af^b6 zlPg~IEwg0y;`t9y;#D+uz!oE4VP&Je!<#q*F?m5L5?J3i@!0J6q#eu z!RRU`-)HeqGi_UJZ(n~|PSNsv+Wgl{P-TvaUQ9j?ZCtvb^37U$sFpBrkT{7Jpd?HpIvj2!}RIq zH{9~+gErN2+}J`>Jvng2hwM`=PLNkc7pkjblKW|+Fk9rc)G1R>Ww>RC=r-|!m-u7( zc(a$9NG}w#PjWNMS~)o=i~WA&4L(YIW25@AL9+H9!?3Y}sv#MOdY{bb9j>p`{?O(P zIvb`n?_(gP2w3P#&91JX*md+bBEr%xUHMVqfB;(f?OPtMnAZ#rm5q5mh;a2f_si2_ z3oXWB?{NF(JtkAn6F(O{z@b76OIqMC$&oJ_&S|YbFJ*)3qVX_uNf5b8(!vGX19hsG z(OP>RmZp29KH9Ge2kKjKigUmOe^K_!UXP`von)PR8Qz$%=EmOB9xS(ZxE_tnyzo}7 z=6~$~9k0M~v}`w={AeqF?_)9q{m8K#6M{a&(;u;O41j)I$^T?lx5(zlebpY@NT&#N zR+1bB)-1-xj}R8uwqwf=iP1GbxBjneCC%UrSdSxK1vM^i9;bUkS#iRZw2H>rS<2<$ zNT3|sDH>{tXb=zq7XZi*K?#Zsa1h1{h5!Tq_YbKFm_*=A5-<~j63he;4`77!|LBlo zR^~tR3yxcU=gDFbshyF6>o0bdp$qmHS7D}m3;^QZq9kBBU|9$N-~oU?G5;jyFR7>z hN`IR97YZXIo@y!QgFWddJ3|0`sjFx!m))><{BI=FK%f8s literal 0 HcmV?d00001 diff --git a/useragent/web/icons/Icon-maskable-192.png b/useragent/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 zcmdT|`#%%j|KDb2V@0DPm$^(Lx5}lO%Yv(=e*7hl@QqKS50#~#^IQPxBmuh|i9sXnt4ch@VT0F7% zMtrs@KWIOo+QV@lSs66A>2pz6-`9Jk=0vv&u?)^F@HZ)-6HT=B7LF;rdj zskUyBfbojcX#CS>WrIWo9D=DIwcXM8=I5D{SGf$~=gh-$LwY?*)cD%38%sCc?5OsX z-XfkyL-1`VavZ?>(pI-xp-kYq=1hsnyP^TLb%0vKRSo^~r{x?ISLY1i7KjSp z*0h&jG(Rkkq2+G_6eS>n&6>&Xk+ngOMcYrk<8KrukQHzfx675^^s$~<@d$9X{VBbg z2Fd4Z%g`!-P}d#`?B4#S-9x*eNlOVRnDrn#jY@~$jfQ-~3Od;A;x-BI1BEDdvr`pI z#D)d)!2_`GiZOUu1crb!hqH=ezs0qk<_xDm_Kkw?r*?0C3|Io6>$!kyDl;eH=aqg$B zsH_|ZD?jP2dc=)|L>DZmGyYKa06~5?C2Lc0#D%62p(YS;%_DRCB1k(+eLGXVMe+=4 zkKiJ%!N6^mxqM=wq`0+yoE#VHF%R<{mMamR9o_1JH8jfnJ?NPLs$9U!9!dq8 z0B{dI2!M|sYGH&9TAY34OlpIsQ4i5bnbG>?cWwat1I13|r|_inLE?FS@Hxdxn_YZN z3jfUO*X9Q@?HZ>Q{W0z60!bbGh557XIKu1?)u|cf%go`pwo}CD=0tau-}t@R2OrSH zQzZr%JfYa`>2!g??76=GJ$%ECbQh7Q2wLRp9QoyiRHP7VE^>JHm>9EqR3<$Y=Z1K^SHuwxCy-5@z3 zVM{XNNm}yM*pRdLKp??+_2&!bp#`=(Lh1vR{~j%n;cJv~9lXeMv)@}Odta)RnK|6* zC+IVSWumLo%{6bLDpn)Gz>6r&;Qs0^+Sz_yx_KNz9Dlt^ax`4>;EWrIT#(lJ_40<= z750fHZ7hI{}%%5`;lwkI4<_FJw@!U^vW;igL0k+mK)-j zYuCK#mCDK3F|SC}tC2>m$ZCqNB7ac-0UFBJ|8RxmG@4a4qdjvMzzS&h9pQmu^x&*= zGvapd1#K%Da&)8f?<9WN`2H^qpd@{7In6DNM&916TRqtF4;3`R|Nhwbw=(4|^Io@T zIjoR?tB8d*sO>PX4vaIHF|W;WVl6L1JvSmStgnRQq zTX4(>1f^5QOAH{=18Q2Vc1JI{V=yOr7yZJf4Vpfo zeHXdhBe{PyY;)yF;=ycMW@Kb>t;yE>;f79~AlJ8k`xWucCxJfsXf2P72bAavWL1G#W z;o%kdH(mYCM{$~yw4({KatNGim49O2HY6O07$B`*K7}MvgI=4x=SKdKVb8C$eJseA$tmSFOztFd*3W`J`yIB_~}k%Sd_bPBK8LxH)?8#jM{^%J_0|L z!gFI|68)G}ex5`Xh{5pB%GtlJ{Z5em*e0sH+sU1UVl7<5%Bq+YrHWL7?X?3LBi1R@_)F-_OqI1Zv`L zb6^Lq#H^2@d_(Z4E6xA9Z4o3kvf78ZDz!5W1#Mp|E;rvJz&4qj2pXVxKB8Vg0}ek%4erou@QM&2t7Cn5GwYqy%{>jI z)4;3SAgqVi#b{kqX#$Mt6L8NhZYgonb7>+r#BHje)bvaZ2c0nAvrN3gez+dNXaV;A zmyR0z@9h4@6~rJik-=2M-T+d`t&@YWhsoP_XP-NsVO}wmo!nR~QVWU?nVlQjNfgcTzE-PkfIX5G z1?&MwaeuzhF=u)X%Vpg_e@>d2yZwxl6-r3OMqDn8_6m^4z3zG##cK0Fsgq8fcvmhu z{73jseR%X%$85H^jRAcrhd&k!i^xL9FrS7qw2$&gwAS8AfAk#g_E_tP;x66fS`Mn@SNVrcn_N;EQm z`Mt3Z%rw%hDqTH-s~6SrIL$hIPKL5^7ejkLTBr46;pHTQDdoErS(B>``t;+1+M zvU&Se9@T_BeK;A^p|n^krIR+6rH~BjvRIugf`&EuX9u69`9C?9ANVL8l(rY6#mu^i z=*5Q)-%o*tWl`#b8p*ZH0I}hn#gV%|jt6V_JanDGuekR*-wF`u;amTCpGG|1;4A5$ zYbHF{?G1vv5;8Ph5%kEW)t|am2_4ik!`7q{ymfHoe^Z99c|$;FAL+NbxE-_zheYbV z3hb0`uZGTsgA5TG(X|GVDSJyJxsyR7V5PS_WSnYgwc_D60m7u*x4b2D79r5UgtL18 zcCHWk+K6N1Pg2c;0#r-)XpwGX?|Iv)^CLWqwF=a}fXUSM?n6E;cCeW5ER^om#{)Jr zJR81pkK?VoFm@N-s%hd7@hBS0xuCD0-UDVLDDkl7Ck=BAj*^ps`393}AJ+Ruq@fl9 z%R(&?5Nc3lnEKGaYMLmRzKXow1+Gh|O-LG7XiNxkG^uyv zpAtLINwMK}IWK65hOw&O>~EJ}x@lDBtB`yKeV1%GtY4PzT%@~wa1VgZn7QRwc7C)_ zpEF~upeDRg_<#w=dLQ)E?AzXUQpbKXYxkp>;c@aOr6A|dHA?KaZkL0svwB^U#zmx0 zzW4^&G!w7YeRxt<9;d@8H=u(j{6+Uj5AuTluvZZD4b+#+6Rp?(yJ`BC9EW9!b&KdPvzJYe5l7 zMJ9aC@S;sA0{F0XyVY{}FzW0Vh)0mPf_BX82E+CD&)wf2!x@{RO~XBYu80TONl3e+ zA7W$ra6LcDW_j4s-`3tI^VhG*sa5lLc+V6ONf=hO@q4|p`CinYqk1Ko*MbZ6_M05k zSwSwkvu;`|I*_Vl=zPd|dVD0lh&Ha)CSJJvV{AEdF{^Kn_Yfsd!{Pc1GNgw}(^~%)jk5~0L~ms|Rez1fiK~s5t(p1ci5Gq$JC#^JrXf?8 z-Y-Zi_Hvi>oBzV8DSRG!7dm|%IlZg3^0{5~;>)8-+Nk&EhAd(}s^7%MuU}lphNW9Q zT)DPo(ob{tB7_?u;4-qGDo!sh&7gHaJfkh43QwL|bbFVi@+oy;i;M zM&CP^v~lx1U`pi9PmSr&Mc<%HAq0DGH?Ft95)WY`P?~7O z`O^Nr{Py9M#Ls4Y7OM?e%Y*Mvrme%=DwQaye^Qut_1pOMrg^!5u(f9p(D%MR%1K>% zRGw%=dYvw@)o}Fw@tOtPjz`45mfpn;OT&V(;z75J*<$52{sB65$gDjwX3Xa!x_wE- z!#RpwHM#WrO*|~f7z}(}o7US(+0FYLM}6de>gQdtPazXz?OcNv4R^oYLJ_BQOd_l172oSK$6!1r@g+B@0ofJ4*{>_AIxfe-#xp>(1 z@Y3Nfd>fmqvjL;?+DmZk*KsfXJf<%~(gcLwEez%>1c6XSboURUh&k=B)MS>6kw9bY z{7vdev7;A}5fy*ZE23DS{J?8at~xwVk`pEwP5^k?XMQ7u64;KmFJ#POzdG#np~F&H ze-BUh@g54)dsS%nkBb}+GuUEKU~pHcYIg4vSo$J(J|U36bs0Use+3A&IMcR%6@jv$ z=+QI+@wW@?iu}Hpyzlvj-EYeop{f65GX0O%>w#0t|V z1-svWk`hU~m`|O$kw5?Yn5UhI%9P-<45A(v0ld1n+%Ziq&TVpBcV9n}L9Tus-TI)f zd_(g+nYCDR@+wYNQm1GwxhUN4tGMLCzDzPqY$~`l<47{+l<{FZ$L6(>J)|}!bi<)| zE35dl{a2)&leQ@LlDxLQOfUDS`;+ZQ4ozrleQwaR-K|@9T{#hB5Z^t#8 zC-d_G;B4;F#8A2EBL58s$zF-=SCr`P#z zNCTnHF&|X@q>SkAoYu>&s9v@zCpv9lLSH-UZzfhJh`EZA{X#%nqw@@aW^vPcfQrlPs(qQxmC|4tp^&sHy!H!2FH5eC{M@g;ElWNzlb-+ zxpfc0m4<}L){4|RZ>KReag2j%Ot_UKkgpJN!7Y_y3;Ssz{9 z!K3isRtaFtQII5^6}cm9RZd5nTp9psk&u1C(BY`(_tolBwzV_@0F*m%3G%Y?2utyS zY`xM0iDRT)yTyYukFeGQ&W@ReM+ADG1xu@ruq&^GK35`+2r}b^V!m1(VgH|QhIPDE X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/useragent/web/icons/Icon-maskable-512.png b/useragent/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 zcmeFZ_gj-)&^4Nb2tlbLMU<{!p(#yjqEe+=0IA_oih%ScH9@5#MNp&}Y#;;(h=A0@ zh7{>lT2MkSQ344eAvrhici!td|HJuyvJm#Y_w1Q9Yu3!26dNlO-oxUDK_C#XnW^Co z5C{VN6#{~B0)K2j7}*1Xq(Nqemv23A-6&=ZpEijkVnSwVGqLv40?n0=p;k3-U5e5+ z+z3>aS`u9DS=!wg8ROu?X4TFoW6CFLL&{GzoVT)ldhLekLM|+j3tIxRd|*5=c{=s&*vfPdBr(Fyj(v@%eQj1Soy7m4^@VRl1~@-PV7y+c!xz$8436WBn$t{=}mEdK#k`aystimGgI{(IBx$!pAwFoE9Y`^t^;> zKAD)C(Dl^s%`?q5$P|fZf8Xymrtu^Pv(7D`rn>Z-w$Ahs!z9!94WNVxrJuXfHAaxg zC6s@|Z1$7R$(!#t%Jb{{s6(Y?NoQXDYq)!}X@jKPhe`{9KQ@sAU8y-5`xt?S9$jKH zoi}6m5PcG*^{kjvt+kwPpyQzVg4o)a>;LK`aaN2x4@itBD3Aq?yWTM20VRn1rrd+2 zKO=P0rMjEGq_UqpMa`~7B|p?xAN1SCoCp}QxAv8O`jLJ5CVh@umR%c%i^)6!o+~`F zaalSTQcl5iwOLC&H)efzd{8(88mo`GI(56T<(&p7>Qd^;R1hn1Y~jN~tApaL8>##U zd65bo8)79CplWxr#z4!6HvLz&N7_5AN#x;kLG?zQ(#p|lj<8VUlKY=Aw!ATqeL-VG z42gA!^cMNPj>(`ZMEbCrnkg*QTsn*u(nQPWI9pA{MQ=IsPTzd7q5E#7+z>Ch=fx$~ z;J|?(5jTo5UWGvsJa(Sx0?S#56+8SD!I^tftyeh_{5_31l6&Hywtn`bbqYDqGZXI( zCG7hBgvksX2ak8+)hB4jnxlO@A32C_RM&g&qDSb~3kM&)@A_j1*oTO@nicGUyv+%^ z=vB)4(q!ykzT==Z)3*3{atJ5}2PV*?Uw+HhN&+RvKvZL3p9E?gHjv{6zM!A|z|UHK z-r6jeLxbGn0D@q5aBzlco|nG2tr}N@m;CJX(4#Cn&p&sLKwzLFx1A5izu?X_X4x8r@K*d~7>t1~ zDW1Mv5O&WOxbzFC`DQ6yNJ(^u9vJdj$fl2dq`!Yba_0^vQHXV)vqv1gssZYzBct!j zHr9>ydtM8wIs}HI4=E}qAkv|BPWzh3^_yLH(|kdb?x56^BlDC)diWyPd*|f!`^12_U>TD^^94OCN0lVv~Sgvs94ecpE^}VY$w`qr_>Ue zTfH~;C<3H<0dS5Rkf_f@1x$Gms}gK#&k()IC0zb^QbR!YLoll)c$Agfi6MKI0dP_L z=Uou&u~~^2onea2%XZ@>`0x^L8CK6=I{ge;|HXMj)-@o~h&O{CuuwBX8pVqjJ*o}5 z#8&oF_p=uSo~8vn?R0!AMWvcbZmsrj{ZswRt(aEdbi~;HeVqIe)-6*1L%5u$Gbs}| zjFh?KL&U(rC2izSGtwP5FnsR@6$-1toz?RvLD^k~h9NfZgzHE7m!!7s6(;)RKo2z} zB$Ci@h({l?arO+vF;s35h=|WpefaOtKVx>l399}EsX@Oe3>>4MPy%h&^3N_`UTAHJ zI$u(|TYC~E4)|JwkWW3F!Tib=NzjHs5ii2uj0^m|Qlh-2VnB#+X~RZ|`SA*}}&8j9IDv?F;(Y^1=Z0?wWz;ikB zewU>MAXDi~O7a~?jx1x=&8GcR-fTp>{2Q`7#BE#N6D@FCp`?ht-<1|y(NArxE_WIu zP+GuG=Qq>SHWtS2M>34xwEw^uvo4|9)4s|Ac=ud?nHQ>ax@LvBqusFcjH0}{T3ZPQ zLO1l<@B_d-(IS682}5KA&qT1+{3jxKolW+1zL4inqBS-D>BohA!K5++41tM@ z@xe<-qz27}LnV#5lk&iC40M||JRmZ*A##K3+!j93eouU8@q-`W0r%7N`V$cR&JV;iX(@cS{#*5Q>~4BEDA)EikLSP@>Oo&Bt1Z~&0d5)COI%3$cLB_M?dK# z{yv2OqW!al-#AEs&QFd;WL5zCcp)JmCKJEdNsJlL9K@MnPegK23?G|O%v`@N{rIRa zi^7a}WBCD77@VQ-z_v{ZdRsWYrYgC$<^gRQwMCi6);%R~uIi31OMS}=gUTE(GKmCI z$zM>mytL{uNN+a&S38^ez(UT=iSw=l2f+a4)DyCA1Cs_N-r?Q@$3KTYosY!;pzQ0k zzh1G|kWCJjc(oZVBji@kN%)UBw(s{KaYGy=i{g3{)Z+&H8t2`^IuLLKWT6lL<-C(! zSF9K4xd-|VO;4}$s?Z7J_dYqD#Mt)WCDnsR{Kpjq275uUq6`v0y*!PHyS(}Zmv)_{>Vose9-$h8P0|y;YG)Bo}$(3Z%+Gs0RBmFiW!^5tBmDK-g zfe5%B*27ib+7|A*Fx5e)2%kIxh7xWoc3pZcXS2zik!63lAG1;sC1ja>BqH7D zODdi5lKW$$AFvxgC-l-)!c+9@YMC7a`w?G(P#MeEQ5xID#<}W$3bSmJ`8V*x2^3qz zVe<^^_8GHqYGF$nIQm0Xq2kAgYtm#UC1A(=&85w;rmg#v906 zT;RyMgbMpYOmS&S9c38^40oUp?!}#_84`aEVw;T;r%gTZkWeU;;FwM@0y0adt{-OK z(vGnPSlR=Nv2OUN!2=xazlnHPM9EWxXg2EKf0kI{iQb#FoP>xCB<)QY>OAM$Dcdbm zU6dU|%Mo(~avBYSjRc13@|s>axhrPl@Sr81{RSZUdz4(=|82XEbV*JAX6Lfbgqgz584lYgi0 z2-E{0XCVON$wHfvaLs;=dqhQJ&6aLn$D#0i(FkAVrXG9LGm3pSTf&f~RQb6|1_;W> z?n-;&hrq*~L=(;u#jS`*Yvh@3hU-33y_Kv1nxqrsf>pHVF&|OKkoC)4DWK%I!yq?P z=vXo8*_1iEWo8xCa{HJ4tzxOmqS0&$q+>LroMKI*V-rxhOc%3Y!)Y|N6p4PLE>Yek>Y(^KRECg8<|%g*nQib_Yc#A5q8Io z6Ig&V>k|~>B6KE%h4reAo*DfOH)_01tE0nWOxX0*YTJgyw7moaI^7gW*WBAeiLbD?FV9GSB zPv3`SX*^GRBM;zledO`!EbdBO_J@fEy)B{-XUTVQv}Qf~PSDpK9+@I`7G7|>Dgbbu z_7sX9%spVo$%qwRwgzq7!_N;#Td08m5HV#?^dF-EV1o)Q=Oa+rs2xH#g;ykLbwtCh znUnA^dW!XjspJ;otq$yV@I^s9Up(5k7rqhQd@OLMyyxVLj_+$#Vc*}Usevp^I(^vH zmDgHc0VMme|K&X?9&lkN{yq_(If)O`oUPW8X}1R5pSVBpfJe0t{sPA(F#`eONTh_) zxeLqHMfJX#?P(@6w4CqRE@Eiza; z;^5)Kk=^5)KDvd9Q<`=sJU8rjjxPmtWMTmzcH={o$U)j=QBuHarp?=}c??!`3d=H$nrJMyr3L-& zA#m?t(NqLM?I3mGgWA_C+0}BWy3-Gj7bR+d+U?n*mN$%5P`ugrB{PeV>jDUn;eVc- zzeMB1mI4?fVJatrNyq|+zn=!AiN~<}eoM#4uSx^K?Iw>P2*r=k`$<3kT00BE_1c(02MRz4(Hq`L^M&xt!pV2 zn+#U3@j~PUR>xIy+P>51iPayk-mqIK_5rlQMSe5&tDkKJk_$i(X&;K(11YGpEc-K= zq4Ln%^j>Zi_+Ae9eYEq_<`D+ddb8_aY!N;)(&EHFAk@Ekg&41ABmOXfWTo)Z&KotA zh*jgDGFYQ^y=m)<_LCWB+v48DTJw*5dwMm_YP0*_{@HANValf?kV-Ic3xsC}#x2h8 z`q5}d8IRmqWk%gR)s~M}(Qas5+`np^jW^oEd-pzERRPMXj$kS17g?H#4^trtKtq;C?;c ztd|%|WP2w2Nzg@)^V}!Gv++QF2!@FP9~DFVISRW6S?eP{H;;8EH;{>X_}NGj^0cg@ z!2@A>-CTcoN02^r6@c~^QUa={0xwK0v4i-tQ9wQq^=q*-{;zJ{Qe%7Qd!&X2>rV@4 z&wznCz*63_vw4>ZF8~%QCM?=vfzW0r_4O^>UA@otm_!N%mH)!ERy&b!n3*E*@?9d^ zu}s^By@FAhG(%?xgJMuMzuJw2&@$-oK>n z=UF}rt%vuaP9fzIFCYN-1&b#r^Cl6RDFIWsEsM|ROf`E?O(cy{BPO2Ie~kT+^kI^i zp>Kbc@C?}3vy-$ZFVX#-cx)Xj&G^ibX{pWggtr(%^?HeQL@Z( zM-430g<{>vT*)jK4aY9(a{lSy{8vxLbP~n1MXwM527ne#SHCC^F_2@o`>c>>KCq9c(4c$VSyMl*y3Nq1s+!DF| z^?d9PipQN(mw^j~{wJ^VOXDCaL$UtwwTpyv8IAwGOg<|NSghkAR1GSNLZ1JwdGJYm zP}t<=5=sNNUEjc=g(y)1n5)ynX(_$1-uGuDR*6Y^Wgg(LT)Jp><5X|}bt z_qMa&QP?l_n+iVS>v%s2Li_;AIeC=Ca^v1jX4*gvB$?H?2%ndnqOaK5-J%7a} zIF{qYa&NfVY}(fmS0OmXA70{znljBOiv5Yod!vFU{D~*3B3Ka{P8?^ zfhlF6o7aNT$qi8(w<}OPw5fqA7HUje*r*Oa(YV%*l0|9FP9KW@U&{VSW{&b0?@y)M zs%4k1Ax;TGYuZ9l;vP5@?3oQsp3)rjBeBvQQ>^B;z5pc=(yHhHtq6|0m(h4envn_j787fizY@V`o(!SSyE7vlMT zbo=Z1c=atz*G!kwzGB;*uPL$Ei|EbZLh8o+1BUMOpnU(uX&OG1MV@|!&HOOeU#t^x zr9=w2ow!SsTuJWT7%Wmt14U_M*3XiWBWHxqCVZI0_g0`}*^&yEG9RK9fHK8e+S^m? zfCNn$JTswUVbiC#>|=wS{t>-MI1aYPLtzO5y|LJ9nm>L6*wpr_m!)A2Fb1RceX&*|5|MwrvOk4+!0p99B9AgP*9D{Yt|x=X}O% zgIG$MrTB=n-!q%ROT|SzH#A$Xm;|ym)0>1KR}Yl0hr-KO&qMrV+0Ej3d@?FcgZ+B3 ztEk16g#2)@x=(ko8k7^Tq$*5pfZHC@O@}`SmzT1(V@x&NkZNM2F#Q-Go7-uf_zKC( zB(lHZ=3@dHaCOf6C!6i8rDL%~XM@rVTJbZL09?ht@r^Z_6x}}atLjvH^4Vk#Ibf(^LiBJFqorm?A=lE zzFmwvp4bT@Nv2V>YQT92X;t9<2s|Ru5#w?wCvlhcHLcsq0TaFLKy(?nzezJ>CECqj zggrI~Hd4LudM(m{L@ezfnpELsRFVFw>fx;CqZtie`$BXRn#Ns%AdoE$-Pf~{9A8rV zf7FbgpKmVzmvn-z(g+&+-ID=v`;6=)itq8oM*+Uz**SMm_{%eP_c0{<%1JGiZS19o z@Gj7$Se~0lsu}w!%;L%~mIAO;AY-2i`9A*ZfFs=X!LTd6nWOZ7BZH2M{l2*I>Xu)0 z`<=;ObglnXcVk!T>e$H?El}ra0WmPZ$YAN0#$?|1v26^(quQre8;k20*dpd4N{i=b zuN=y}_ew9SlE~R{2+Rh^7%PA1H5X(p8%0TpJ=cqa$65XL)$#ign-y!qij3;2>j}I; ziO@O|aYfn&up5F`YtjGw68rD3{OSGNYmBnl?zdwY$=RFsegTZ=kkzRQ`r7ZjQP!H( zp4>)&zf<*N!tI00xzm-ME_a{_I!TbDCr;8E;kCH4LlL-tqLxDuBn-+xgPk37S&S2^ z2QZumkIimwz!c@!r0)j3*(jPIs*V!iLTRl0Cpt_UVNUgGZzdvs0(-yUghJfKr7;=h zD~y?OJ-bWJg;VdZ^r@vlDoeGV&8^--!t1AsIMZ5S440HCVr%uk- z2wV>!W1WCvFB~p$P$$_}|H5>uBeAe>`N1FI8AxM|pq%oNs;ED8x+tb44E) zTj{^fbh@eLi%5AqT?;d>Es5D*Fi{Bpk)q$^iF!!U`r2hHAO_?#!aYmf>G+jHsES4W zgpTKY59d?hsb~F0WE&dUp6lPt;Pm zcbTUqRryw^%{ViNW%Z(o8}dd00H(H-MmQmOiTq{}_rnwOr*Ybo7*}3W-qBT!#s0Ie z-s<1rvvJx_W;ViUD`04%1pra*Yw0BcGe)fDKUK8aF#BwBwMPU;9`!6E(~!043?SZx z13K%z@$$#2%2ovVlgFIPp7Q6(vO)ud)=*%ZSucL2Dh~K4B|%q4KnSpj#n@(0B})!9 z8p*hY@5)NDn^&Pmo;|!>erSYg`LkO?0FB@PLqRvc>4IsUM5O&>rRv|IBRxi(RX(gJ ztQ2;??L~&Mv;aVr5Q@(?y^DGo%pO^~zijld41aA0KKsy_6FeHIn?fNHP-z>$OoWer zjZ5hFQTy*-f7KENRiCE$ZOp4|+Wah|2=n@|W=o}bFM}Y@0e62+_|#fND5cwa3;P{^pEzlJbF1Yq^}>=wy8^^^$I2M_MH(4Dw{F6hm+vrWV5!q;oX z;tTNhz5`-V={ew|bD$?qcF^WPR{L(E%~XG8eJx(DoGzt2G{l8r!QPJ>kpHeOvCv#w zr=SSwMDaUX^*~v%6K%O~i)<^6`{go>a3IdfZ8hFmz&;Y@P%ZygShQZ2DSHd`m5AR= zx$wWU06;GYwXOf(%MFyj{8rPFXD};JCe85Bdp4$YJ2$TzZ7Gr#+SwCvBI1o$QP0(c zy`P51FEBV2HTisM3bHqpmECT@H!Y2-bv2*SoSPoO?wLe{M#zDTy@ujAZ!Izzky~3k zRA1RQIIoC*Mej1PH!sUgtkR0VCNMX(_!b65mo66iM*KQ7xT8t2eev$v#&YdUXKwGm z7okYAqYF&bveHeu6M5p9xheRCTiU8PFeb1_Rht0VVSbm%|1cOVobc8mvqcw!RjrMRM#~=7xibH&Fa5Imc|lZ{eC|R__)OrFg4@X_ ze+kk*_sDNG5^ELmHnZ7Ue?)#6!O)#Nv*Dl2mr#2)w{#i-;}0*_h4A%HidnmclH#;Q zmQbq+P4DS%3}PpPm7K_K3d2s#k~x+PlTul7+kIKol0@`YN1NG=+&PYTS->AdzPv!> zQvzT=)9se*Jr1Yq+C{wbK82gAX`NkbXFZ)4==j4t51{|-v!!$H8@WKA={d>CWRW+g z*`L>9rRucS`vbXu0rzA1#AQ(W?6)}1+oJSF=80Kf_2r~Qm-EJ6bbB3k`80rCv(0d` zvCf3;L2ovYG_TES%6vSuoKfIHC6w;V31!oqHM8-I8AFzcd^+_86!EcCOX|Ta9k1!s z_Vh(EGIIsI3fb&dF$9V8v(sTBC%!#<&KIGF;R+;MyC0~}$gC}}= zR`DbUVc&Bx`lYykFZ4{R{xRaUQkWCGCQlEc;!mf=+nOk$RUg*7 z;kP7CVLEc$CA7@6VFpsp3_t~m)W0aPxjsA3e5U%SfY{tp5BV5jH-5n?YX7*+U+Zs%LGR>U- z!x4Y_|4{gx?ZPJobISy991O znrmrC3otC;#4^&Rg_iK}XH(XX+eUHN0@Oe06hJk}F?`$)KmH^eWz@@N%wEc)%>?Ft z#9QAroDeyfztQ5Qe{m*#R#T%-h*&XvSEn@N$hYRTCMXS|EPwzF3IIysD2waj`vQD{ zv_#^Pgr?s~I*NE=acf@dWVRNWTr(GN0wrL)Z2=`Dr>}&ZDNX|+^Anl{Di%v1Id$_p zK5_H5`RDjJx`BW7hc85|> zHMMsWJ4KTMRHGu+vy*kBEMjz*^K8VtU=bXJYdhdZ-?jTXa$&n)C?QQIZ7ln$qbGlr zS*TYE+ppOrI@AoPP=VI-OXm}FzgXRL)OPvR$a_=SsC<3Jb+>5makX|U!}3lx4tX&L z^C<{9TggZNoeX!P1jX_K5HkEVnQ#s2&c#umzV6s2U-Q;({l+j^?hi7JnQ7&&*oOy9 z(|0asVTWUCiCnjcOnB2pN0DpuTglKq;&SFOQ3pUdye*eT<2()7WKbXp1qq9=bhMWlF-7BHT|i3TEIT77AcjD(v=I207wi-=vyiw5mxgPdTVUC z&h^FEUrXwWs9en2C{ywZp;nvS(Mb$8sBEh-*_d-OEm%~p1b2EpcwUdf<~zmJmaSTO zSX&&GGCEz-M^)G$fBvLC2q@wM$;n4jp+mt0MJFLuJ%c`tSp8$xuP|G81GEd2ci$|M z4XmH{5$j?rqDWoL4vs!}W&!?!rtj=6WKJcE>)?NVske(p;|#>vL|M_$as=mi-n-()a*OU3Okmk0wC<9y7t^D(er-&jEEak2!NnDiOQ99Wx8{S8}=Ng!e0tzj*#T)+%7;aM$ z&H}|o|J1p{IK0Q7JggAwipvHvko6>Epmh4RFRUr}$*2K4dz85o7|3#Bec9SQ4Y*;> zXWjT~f+d)dp_J`sV*!w>B%)#GI_;USp7?0810&3S=WntGZ)+tzhZ+!|=XlQ&@G@~3 z-dw@I1>9n1{+!x^Hz|xC+P#Ab`E@=vY?3%Bc!Po~e&&&)Qp85!I|U<-fCXy*wMa&t zgDk!l;gk;$taOCV$&60z+}_$ykz=Ea*)wJQ3-M|p*EK(cvtIre0Pta~(95J7zoxBN zS(yE^3?>88AL0Wfuou$BM{lR1hkrRibz=+I9ccwd`ZC*{NNqL)3pCcw^ygMmrG^Yp zn5f}Xf>%gncC=Yq96;rnfp4FQL#{!Y*->e82rHgY4Zwy{`JH}b9*qr^VA{%~Z}jtp z_t$PlS6}5{NtTqXHN?uI8ut8rOaD#F1C^ls73S=b_yI#iZDOGz3#^L@YheGd>L;<( z)U=iYj;`{>VDNzIxcjbTk-X3keXR8Xbc`A$o5# zKGSk-7YcoBYuAFFSCjGi;7b<;n-*`USs)IX z=0q6WZ=L!)PkYtZE-6)azhXV|+?IVGTOmMCHjhkBjfy@k1>?yFO3u!)@cl{fFAXnRYsWk)kpT?X{_$J=|?g@Q}+kFw|%n!;Zo}|HE@j=SFMvT8v`6Y zNO;tXN^036nOB2%=KzxB?n~NQ1K8IO*UE{;Xy;N^ZNI#P+hRZOaHATz9(=)w=QwV# z`z3+P>9b?l-@$@P3<;w@O1BdKh+H;jo#_%rr!ute{|YX4g5}n?O7Mq^01S5;+lABE+7`&_?mR_z7k|Ja#8h{!~j)| zbBX;*fsbUak_!kXU%HfJ2J+G7;inu#uRjMb|8a){=^))y236LDZ$$q3LRlat1D)%7K0!q5hT5V1j3qHc7MG9 z_)Q=yQ>rs>3%l=vu$#VVd$&IgO}Za#?aN!xY>-<3PhzS&q!N<=1Q7VJBfHjug^4|) z*fW^;%3}P7X#W3d;tUs3;`O&>;NKZBMR8au6>7?QriJ@gBaorz-+`pUWOP73DJL=M z(33uT6Gz@Sv40F6bN|H=lpcO z^AJl}&=TIjdevuDQ!w0K*6oZ2JBOhb31q!XDArFyKpz!I$p4|;c}@^bX{>AXdt7Bm zaLTk?c%h@%xq02reu~;t@$bv`b3i(P=g}~ywgSFpM;}b$zAD+=I!7`V~}ARB(Wx0C(EAq@?GuxOL9X+ffbkn3+Op0*80TqmpAq~EXmv%cq36celXmRz z%0(!oMp&2?`W)ALA&#|fu)MFp{V~~zIIixOxY^YtO5^FSox8v$#d0*{qk0Z)pNTt0QVZ^$`4vImEB>;Lo2!7K05TpY-sl#sWBz_W-aDIV`Ksabi zvpa#93Svo!70W*Ydh)Qzm{0?CU`y;T^ITg-J9nfWeZ-sbw)G@W?$Eomf%Bg2frfh5 zRm1{|E0+(4zXy){$}uC3%Y-mSA2-^I>Tw|gQx|7TDli_hB>``)Q^aZ`LJC2V3U$SABP}T)%}9g2pF9dT}aC~!rFFgkl1J$ z`^z{Arn3On-m%}r}TGF8KQe*OjSJ=T|caa_E;v89A{t@$yT^(G9=N9F?^kT*#s3qhJq!IH5|AhnqFd z0B&^gm3w;YbMNUKU>naBAO@fbz zqw=n!@--}o5;k6DvTW9pw)IJVz;X}ncbPVrmH>4x);8cx;q3UyiML1PWp%bxSiS|^ zC5!kc4qw%NSOGQ*Kcd#&$30=lDvs#*4W4q0u8E02U)7d=!W7+NouEyuF1dyH$D@G& zaFaxo9Ex|ZXA5y{eZT*i*dP~INSMAi@mvEX@q5i<&o&#sM}Df?Og8n8Ku4vOux=T% zeuw~z1hR}ZNwTn8KsQHKLwe2>p^K`YWUJEdVEl|mO21Bov!D0D$qPoOv=vJJ`)|%_ z>l%`eexY7t{BlVKP!`a^U@nM?#9OC*t76My_E_<16vCz1x_#82qj2PkWiMWgF8bM9 z(1t4VdHcJ;B~;Q%x01k_gQ0>u2*OjuEWNOGX#4}+N?Gb5;+NQMqp}Puqw2HnkYuKA zzKFWGHc&K>gwVgI1Sc9OT1s6fq=>$gZU!!xsilA$fF`kLdGoX*^t}ao@+^WBpk>`8 z4v_~gK|c2rCq#DZ+H)$3v~Hoi=)=1D==e3P zpKrRQ+>O^cyTuWJ%2}__0Z9SM_z9rptd*;-9uC1tDw4+A!=+K%8~M&+Zk#13hY$Y$ zo-8$*8dD5@}XDi19RjK6T^J~DIXbF5w&l?JLHMrf0 zLv0{7*G!==o|B%$V!a=EtVHdMwXLtmO~vl}P6;S(R2Q>*kTJK~!}gloxj)m|_LYK{ zl(f1cB=EON&wVFwK?MGn^nWuh@f95SHatPs(jcwSY#Dnl1@_gkOJ5=f`%s$ZHljRH0 z+c%lrb=Gi&N&1>^L_}#m>=U=(oT^vTA&3!xXNyqi$pdW1BDJ#^{h|2tZc{t^vag3& zAD7*8C`chNF|27itjBUo^CCDyEpJLX3&u+(L;YeeMwnXEoyN(ytoEabcl$lSgx~Ltatn}b$@j_yyMrBb03)shJE*$;Mw=;mZd&8e>IzE+4WIoH zCSZE7WthNUL$|Y#m!Hn?x7V1CK}V`KwW2D$-7&ODy5Cj;!_tTOOo1Mm%(RUt)#$@3 zhurA)t<7qik%%1Et+N1?R#hdBB#LdQ7{%-C zn$(`5e0eFh(#c*hvF>WT*07fk$N_631?W>kfjySN8^XC9diiOd#s?4tybICF;wBjp zIPzilX3{j%4u7blhq)tnaOBZ_`h_JqHXuI7SuIlNTgBk9{HIS&3|SEPfrvcE<@}E` zKk$y*nzsqZ{J{uWW9;#n=de&&h>m#A#q)#zRonr(?mDOYU&h&aQWD;?Z(22wY?t$U3qo`?{+amA$^TkxL+Ex2dh`q7iR&TPd0Ymwzo#b? zP$#t=elB5?k$#uE$K>C$YZbYUX_JgnXA`oF_Ifz4H7LEOW~{Gww&3s=wH4+j8*TU| zSX%LtJWqhr-xGNSe{;(16kxnak6RnZ{0qZ^kJI5X*It_YuynSpi(^-}Lolr{)#z_~ zw!(J-8%7Ybo^c3(mED`Xz8xecP35a6M8HarxRn%+NJBE;dw>>Y2T&;jzRd4FSDO3T zt*y+zXCtZQ0bP0yf6HRpD|WmzP;DR^-g^}{z~0x~z4j8m zucTe%k&S9Nt-?Jb^gYW1w6!Y3AUZ0Jcq;pJ)Exz%7k+mUOm6%ApjjSmflfKwBo6`B zhNb@$NHTJ>guaj9S{@DX)!6)b-Shav=DNKWy(V00k(D!v?PAR0f0vDNq*#mYmUp6> z76KxbFDw5U{{qx{BRj(>?|C`82ICKbfLxoldov-M?4Xl+3;I4GzLHyPOzYw7{WQST zPNYcx5onA%MAO9??41Po*1zW(Y%Zzn06-lUp{s<3!_9vv9HBjT02On0Hf$}NP;wF) zP<`2p3}A^~1YbvOh{ePMx$!JGUPX-tbBzp3mDZMY;}h;sQ->!p97GA)9a|tF(Gh{1$xk7 zUw?ELkT({Xw!KIr);kTRb1b|UL`r2_`a+&UFVCdJ)1T#fdh;71EQl9790Br0m_`$x z9|ZANuchFci8GNZ{XbP=+uXSJRe(;V5laQz$u18#?X*9}x7cIEbnr%<=1cX3EIu7$ zhHW6pe5M(&qEtsqRa>?)*{O;OJT+YUhG5{km|YI7I@JL_3Hwao9aXneiSA~a* z|Lp@c-oMNyeAEuUz{F?kuou3x#C*gU?lon!RC1s37gW^0Frc`lqQWH&(J4NoZg3m8 z;Lin#8Q+cFPD7MCzj}#|ws7b@?D9Q4dVjS4dpco=4yX5SSH=A@U@yqPdp@?g?qeia zH=Tt_9)G=6C2QIPsi-QipnK(mc0xXIN;j$WLf@n8eYvMk;*H-Q4tK%(3$CN}NGgO8n}fD~+>?<3UzvsrMf*J~%i;VKQHbF%TPalFi=#sgj)(P#SM^0Q=Tr>4kJVw8X3iWsP|e8tj}NjlMdWp z@2+M4HQu~3!=bZpjh;;DIDk&X}=c8~kn)FWWH z2KL1w^rA5&1@@^X%MjZ7;u(kH=YhH2pJPFQe=hn>tZd5RC5cfGYis8s9PKaxi*}-s6*W zRA^PwR=y^5Z){!(4D9-KC;0~;b*ploznFOaU`bJ_7U?qAi#mTo!&rIECRL$_y@yI27x2?W+zqDBD5~KCVYKFZLK+>ABC(Kj zeAll)KMgIlAG`r^rS{loBrGLtzhHY8$)<_S<(Dpkr(Ym@@vnQ&rS@FC*>2@XCH}M+an74WcRDcoQ+a3@A z9tYhl5$z7bMdTvD2r&jztBuo37?*k~wcU9GK2-)MTFS-lux-mIRYUuGUCI~V$?s#< z?1qAWb(?ZLm(N>%S%y10COdaq_Tm5c^%ooIxpR=`3e4C|@O5wY+eLik&XVi5oT7oe zmxH)Jd*5eo@!7t`x8!K=-+zJ-Sz)B_V$)s1pW~CDU$=q^&ABvf6S|?TOMB-RIm@CoFg>mjIQE)?+A1_3s6zmFU_oW&BqyMz1mY*IcP_2knjq5 zqw~JK(cVsmzc7*EvTT2rvpeqhg)W=%TOZ^>f`rD4|7Z5fq*2D^lpCttIg#ictgqZ$P@ru6P#f$x#KfnfTZj~LG6U_d-kE~`;kU_X)`H5so@?C zWmb!7x|xk@0L~0JFall*@ltyiL^)@3m4MqC7(7H0sH!WidId1#f#6R{Q&A!XzO1IAcIx;$k66dumt6lpUw@nL2MvqJ5^kbOVZ<^2jt5-njy|2@`07}0w z;M%I1$FCoLy`8xp8Tk)bFr;7aJeQ9KK6p=O$U0-&JYYy8woV*>b+FB?xLX`=pirYM z5K$BA(u)+jR{?O2r$c_Qvl?M{=Ar{yQ!UVsVn4k@0!b?_lA;dVz9uaQUgBH8Oz(Sb zrEs;&Ey>_ex8&!N{PmQjp+-Hlh|OA&wvDai#GpU=^-B70V0*LF=^bi+Nhe_o|azZ%~ZZ1$}LTmWt4aoB1 zPgccm$EwYU+jrdBaQFxQfn5gd(gM`Y*Ro1n&Zi?j=(>T3kmf94vdhf?AuS8>$Va#P zGL5F+VHpxdsCUa}+RqavXCobI-@B;WJbMphpK2%6t=XvKWWE|ruvREgM+|V=i6;;O zx$g=7^`$XWn0fu!gF=Xe9cMB8Z_SelD>&o&{1XFS`|nInK3BXlaeD*rc;R-#osyIS zWv&>~^TLIyBB6oDX+#>3<_0+2C4u2zK^wmHXXDD9_)kmLYJ!0SzM|%G9{pi)`X$uf zW}|%%#LgyK7m(4{V&?x_0KEDq56tk|0YNY~B(Sr|>WVz-pO3A##}$JCT}5P7DY+@W z#gJv>pA5>$|E3WO2tV7G^SuymB?tY`ooKcN3!vaQMnBNk-WATF{-$#}FyzgtJ8M^; zUK6KWSG)}6**+rZ&?o@PK3??uN{Q)#+bDP9i1W&j)oaU5d0bIWJ_9T5ac!qc?x66Q z$KUSZ`nYY94qfN_dpTFr8OW~A?}LD;Yty-BA)-be5Z3S#t2Io%q+cAbnGj1t$|qFR z9o?8B7OA^KjCYL=-!p}w(dkC^G6Nd%_I=1))PC0w5}ZZGJxfK)jP4Fwa@b-SYBw?% zdz9B-<`*B2dOn(N;mcTm%Do)rIvfXRNFX&1h`?>Rzuj~Wx)$p13nrDlS8-jwq@e@n zNIj_|8or==8~1h*Ih?w*8K7rYkGlwlTWAwLKc5}~dfz3y`kM&^Q|@C%1VAp_$wnw6zG~W4O+^ z>i?NY?oXf^Puc~+fDM$VgRNBpOZj{2cMP~gCqWAX4 z7>%$ux8@a&_B(pt``KSt;r+sR-$N;jdpY>|pyvPiN)9ohd*>mVST3wMo)){`B(&eX z1?zZJ-4u9NZ|~j1rdZYq4R$?swf}<6(#ex%7r{kh%U@kT)&kWuAszS%oJts=*OcL9 zaZwK<5DZw%1IFHXgFplP6JiL^dk8+SgM$D?8X+gE4172hXh!WeqIO>}$I9?Nry$*S zQ#f)RuH{P7RwA3v9f<-w>{PSzom;>(i&^l{E0(&Xp4A-*q-@{W1oE3K;1zb{&n28dSC2$N+6auXe0}e4b z)KLJ?5c*>@9K#I^)W;uU_Z`enquTUxr>mNq z1{0_puF-M7j${rs!dxxo3EelGodF1TvjV;Zpo;s{5f1pyCuRp=HDZ?s#IA4f?h|-p zGd|Mq^4hDa@Bh!c4ZE?O&x&XZ_ptZGYK4$9F4~{%R!}G1leCBx`dtNUS|K zL-7J5s4W@%mhXg1!}a4PD%!t&Qn%f_oquRajn3@C*)`o&K9o7V6DwzVMEhjVdDJ1fjhr#@=lp#@4EBqi=CCQ>73>R(>QKPNM&_Jpe5G`n4wegeC`FYEPJ{|vwS>$-`fuRSp3927qOv|NC3T3G-0 zA{K`|+tQy1yqE$ShWt8ny&5~)%ITb@^+x$w0)f&om;P8B)@}=Wzy59BwUfZ1vqw87 za2lB8J(&*l#(V}Id8SyQ0C(2amzkz3EqG&Ed0Jq1)$|&>4_|NIe=5|n=3?siFV0fI z{As5DLW^gs|B-b4C;Hd(SM-S~GQhzb>HgF2|2Usww0nL^;x@1eaB)=+Clj+$fF@H( z-fqP??~QMT$KI-#m;QC*&6vkp&8699G3)Bq0*kFZXINw=b9OVaed(3(3kS|IZ)CM? zJdnW&%t8MveBuK21uiYj)_a{Fnw0OErMzMN?d$QoPwkhOwcP&p+t>P)4tHlYw-pPN z^oJ=uc$Sl>pv@fZH~ZqxSvdhF@F1s=oZawpr^-#l{IIOGG=T%QXjtwPhIg-F@k@uIlr?J->Ia zpEUQ*=4g|XYn4Gez&aHr*;t$u3oODPmc2Ku)2Og|xjc%w;q!Zz+zY)*3{7V8bK4;& zYV82FZ+8?v)`J|G1w4I0fWdKg|2b#iaazCv;|?(W-q}$o&Y}Q5d@BRk^jL7#{kbCK zSgkyu;=DV+or2)AxCBgq-nj5=@n^`%T#V+xBGEkW4lCqrE)LMv#f;AvD__cQ@Eg3`~x| zW+h9mofSXCq5|M)9|ez(#X?-sxB%Go8};sJ?2abp(Y!lyi>k)|{M*Z$c{e1-K4ky` MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/useragent/web/index.html b/useragent/web/index.html new file mode 100644 index 0000000..1f09871 --- /dev/null +++ b/useragent/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + useragent + + + + + + diff --git a/useragent/web/manifest.json b/useragent/web/manifest.json new file mode 100644 index 0000000..a50a793 --- /dev/null +++ b/useragent/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "useragent", + "short_name": "useragent", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/useragent/windows/CMakeLists.txt b/useragent/windows/CMakeLists.txt index 19c9efd..f41a1a4 100644 --- a/useragent/windows/CMakeLists.txt +++ b/useragent/windows/CMakeLists.txt @@ -1,10 +1,10 @@ # Project-level configuration. cmake_minimum_required(VERSION 3.14) -project(app LANGUAGES CXX) +project(useragent LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. -set(BINARY_NAME "app") +set(BINARY_NAME "useragent") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. diff --git a/useragent/windows/flutter/generated_plugin_registrant.cc b/useragent/windows/flutter/generated_plugin_registrant.cc index 8b6d468..5664626 100644 --- a/useragent/windows/flutter/generated_plugin_registrant.cc +++ b/useragent/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,21 @@ #include "generated_plugin_registrant.h" +#include +#include +#include +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + BiometricSignaturePluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("BiometricSignaturePlugin")); + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + RiveNativePluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("RiveNativePlugin")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/useragent/windows/flutter/generated_plugins.cmake b/useragent/windows/flutter/generated_plugins.cmake index b93c4c3..d3c6423 100644 --- a/useragent/windows/flutter/generated_plugins.cmake +++ b/useragent/windows/flutter/generated_plugins.cmake @@ -3,6 +3,11 @@ # list(APPEND FLUTTER_PLUGIN_LIST + biometric_signature + flutter_secure_storage_windows + rive_native + share_plus + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/useragent/windows/runner/Runner.rc b/useragent/windows/runner/Runner.rc index a5f562b..bdaac96 100644 --- a/useragent/windows/runner/Runner.rc +++ b/useragent/windows/runner/Runner.rc @@ -90,12 +90,12 @@ BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.example" "\0" - VALUE "FileDescription", "app" "\0" + VALUE "FileDescription", "useragent" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "app" "\0" + VALUE "InternalName", "useragent" "\0" VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" - VALUE "OriginalFilename", "app.exe" "\0" - VALUE "ProductName", "app" "\0" + VALUE "OriginalFilename", "useragent.exe" "\0" + VALUE "ProductName", "useragent" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END diff --git a/useragent/windows/runner/main.cpp b/useragent/windows/runner/main.cpp index bc9bfa3..c33ff6c 100644 --- a/useragent/windows/runner/main.cpp +++ b/useragent/windows/runner/main.cpp @@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); - if (!window.Create(L"app", origin, size)) { + if (!window.Create(L"useragent", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); From 16d5b9a233d7dfe67d693202593399949dd6b2e8 Mon Sep 17 00:00:00 2001 From: hdbg Date: Sun, 15 Mar 2026 11:23:45 +0100 Subject: [PATCH 02/13] feat(useragent): settled on routing architecture --- useragent/.dart_tool/package_config.json | 36 +- useragent/.dart_tool/package_graph.json | 1391 +++++++++-------- useragent/lib/features/adaptive_switcher.dart | 118 -- useragent/lib/home.dart | 24 - useragent/lib/main.dart | 7 +- useragent/lib/providers/key.dart | 13 +- useragent/lib/providers/key.g.dart | 4 +- useragent/lib/router.dart | 46 +- useragent/lib/router.gr.dart | 80 + useragent/lib/screens/about.dart | 9 - .../lib/{features => screens}/bootstrap.dart | 23 +- useragent/lib/screens/dashboard.dart | 42 + useragent/lib/screens/dashboard/about.dart | 12 + .../lib/screens/{ => dashboard}/calc.dart | 2 + useragent/pubspec.lock | 48 +- useragent/pubspec.yaml | 2 + 16 files changed, 994 insertions(+), 863 deletions(-) delete mode 100644 useragent/lib/features/adaptive_switcher.dart delete mode 100644 useragent/lib/home.dart create mode 100644 useragent/lib/router.gr.dart delete mode 100644 useragent/lib/screens/about.dart rename useragent/lib/{features => screens}/bootstrap.dart (51%) create mode 100644 useragent/lib/screens/dashboard.dart create mode 100644 useragent/lib/screens/dashboard/about.dart rename useragent/lib/screens/{ => dashboard}/calc.dart (96%) diff --git a/useragent/.dart_tool/package_config.json b/useragent/.dart_tool/package_config.json index ce1c5aa..9c93efa 100644 --- a/useragent/.dart_tool/package_config.json +++ b/useragent/.dart_tool/package_config.json @@ -37,6 +37,18 @@ "packageUri": "lib/", "languageVersion": "3.4" }, + { + "name": "auto_route", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/auto_route-11.1.0", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "auto_route_generator", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/auto_route_generator-10.4.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, { "name": "biometric_signature", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/biometric_signature-10.2.0", @@ -213,7 +225,7 @@ }, { "name": "flutter_adaptive_scaffold", - "rootUri": "file:///Users/kaska/.pub-cache/git/flutter_adaptive_scaffold-4852b5041d8d9ed6cacb4dc4c6723086f0612e37/", + "rootUri": "file:///Users/kaska/.pub-cache/git/flutter_adaptive_scaffold-b2e3615901a7ab837cb7fc35efbfcf8b55f27638/", "packageUri": "lib/", "languageVersion": "3.10" }, @@ -355,6 +367,12 @@ "packageUri": "lib/", "languageVersion": "3.7" }, + { + "name": "hotreloader", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/hotreloader-4.3.0", + "packageUri": "lib/", + "languageVersion": "3.0" + }, { "name": "http", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/http-1.6.0", @@ -415,6 +433,12 @@ "packageUri": "lib/", "languageVersion": "3.2" }, + { + "name": "lean_builder", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/lean_builder-0.1.6", + "packageUri": "lib/", + "languageVersion": "3.8" + }, { "name": "lints", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/lints-6.1.0", @@ -813,9 +837,9 @@ }, { "name": "watcher", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/watcher-1.2.1", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/watcher-1.1.4", "packageUri": "lib/", - "languageVersion": "3.4" + "languageVersion": "3.1" }, { "name": "web", @@ -853,6 +877,12 @@ "packageUri": "lib/", "languageVersion": "3.3" }, + { + "name": "xxh3", + "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/xxh3-1.2.0", + "packageUri": "lib/", + "languageVersion": "2.16" + }, { "name": "yaml", "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/yaml-3.1.3", diff --git a/useragent/.dart_tool/package_graph.json b/useragent/.dart_tool/package_graph.json index c9e0bdd..2588cea 100644 --- a/useragent/.dart_tool/package_graph.json +++ b/useragent/.dart_tool/package_graph.json @@ -7,6 +7,7 @@ "name": "arbiter", "version": "1.0.0+1", "dependencies": [ + "auto_route", "biometric_signature", "cryptography", "cryptography_flutter", @@ -24,12 +25,28 @@ "talker" ], "devDependencies": [ + "auto_route_generator", "build_runner", "flutter_lints", "flutter_test", "riverpod_generator" ] }, + { + "name": "auto_route_generator", + "version": "10.4.0", + "dependencies": [ + "analyzer", + "auto_route", + "build", + "code_builder", + "dart_style", + "glob", + "lean_builder", + "path", + "source_gen" + ] + }, { "name": "build_runner", "version": "2.12.2", @@ -110,6 +127,17 @@ "vector_math" ] }, + { + "name": "auto_route", + "version": "11.1.0", + "dependencies": [ + "collection", + "flutter", + "meta", + "path", + "web" + ] + }, { "name": "flutter_hooks", "version": "0.21.3+1", @@ -206,19 +234,6 @@ "flutter" ] }, - { - "name": "hooks_riverpod", - "version": "3.1.0", - "dependencies": [ - "collection", - "flutter", - "flutter_hooks", - "flutter_riverpod", - "flutter_test", - "riverpod", - "state_notifier" - ] - }, { "name": "riverpod", "version": "3.1.0", @@ -240,7 +255,7 @@ }, { "name": "flutter_adaptive_scaffold", - "version": "0.3.3+1", + "version": "1.0.0+1", "dependencies": [ "flutter" ] @@ -271,123 +286,16 @@ "string_scanner" ] }, - { - "name": "web_socket_channel", - "version": "3.0.3", - "dependencies": [ - "async", - "crypto", - "stream_channel", - "web", - "web_socket" - ] - }, - { - "name": "watcher", - "version": "1.2.1", - "dependencies": [ - "async", - "path" - ] - }, - { - "name": "stream_transform", - "version": "2.1.1", - "dependencies": [] - }, - { - "name": "shelf_web_socket", - "version": "3.0.0", - "dependencies": [ - "shelf", - "stream_channel", - "web_socket_channel" - ] - }, - { - "name": "shelf", - "version": "1.4.2", - "dependencies": [ - "async", - "collection", - "http_parser", - "path", - "stack_trace", - "stream_channel" - ] - }, - { - "name": "pub_semver", - "version": "2.2.0", - "dependencies": [ - "collection" - ] - }, - { - "name": "pool", - "version": "1.5.2", - "dependencies": [ - "async", - "stack_trace" - ] - }, { "name": "path", "version": "1.9.1", "dependencies": [] }, - { - "name": "package_config", - "version": "2.2.0", - "dependencies": [ - "path" - ] - }, - { - "name": "mime", - "version": "2.0.0", - "dependencies": [] - }, { "name": "meta", "version": "1.17.0", "dependencies": [] }, - { - "name": "logging", - "version": "1.3.0", - "dependencies": [] - }, - { - "name": "json_annotation", - "version": "4.11.0", - "dependencies": [ - "meta" - ] - }, - { - "name": "io", - "version": "1.0.5", - "dependencies": [ - "meta", - "path", - "string_scanner" - ] - }, - { - "name": "http_multi_server", - "version": "3.2.2", - "dependencies": [ - "async" - ] - }, - { - "name": "graphs", - "version": "2.3.2", - "dependencies": [ - "collection" - ] - }, { "name": "glob", "version": "2.1.3", @@ -399,6 +307,11 @@ "string_scanner" ] }, + { + "name": "collection", + "version": "1.19.1", + "dependencies": [] + }, { "name": "dart_style", "version": "3.1.3", @@ -414,43 +327,64 @@ ] }, { - "name": "crypto", - "version": "3.0.7", + "name": "async", + "version": "2.13.0", "dependencies": [ - "typed_data" + "collection", + "meta" ] }, { - "name": "convert", - "version": "3.1.2", - "dependencies": [ - "typed_data" - ] - }, - { - "name": "collection", - "version": "1.19.1", + "name": "xxh3", + "version": "1.2.0", "dependencies": [] }, { - "name": "code_builder", - "version": "4.11.1", + "name": "stack_trace", + "version": "1.12.1", "dependencies": [ - "built_collection", - "built_value", - "collection", - "matcher", - "meta" + "path" ] }, { - "name": "built_value", - "version": "8.12.4", + "name": "hotreloader", + "version": "4.3.0", "dependencies": [ - "built_collection", "collection", - "fixnum", - "meta" + "logging", + "path", + "stream_transform", + "vm_service", + "watcher" + ] + }, + { + "name": "frontend_server_client", + "version": "4.0.0", + "dependencies": [ + "async", + "path" + ] + }, + { + "name": "args", + "version": "2.7.0", + "dependencies": [] + }, + { + "name": "ansicolor", + "version": "2.0.3", + "dependencies": [] + }, + { + "name": "matcher", + "version": "0.12.17", + "dependencies": [ + "async", + "meta", + "stack_trace", + "term_glyph", + "test_api" ] }, { @@ -459,21 +393,25 @@ "dependencies": [] }, { - "name": "build_daemon", - "version": "4.1.1", + "name": "test_api", + "version": "0.7.7", "dependencies": [ - "built_collection", - "built_value", - "crypto", - "http_multi_server", - "logging", - "path", - "pool", - "shelf", - "shelf_web_socket", - "stream_transform", - "watcher", - "web_socket_channel" + "async", + "boolean_selector", + "collection", + "meta", + "source_span", + "stack_trace", + "stream_channel", + "string_scanner", + "term_glyph" + ] + }, + { + "name": "stream_channel", + "version": "2.1.4", + "dependencies": [ + "async" ] }, { @@ -487,60 +425,34 @@ ] }, { - "name": "build", - "version": "4.0.4", - "dependencies": [ - "analyzer", - "crypto", - "glob", - "logging", - "package_config", - "path" - ] - }, - { - "name": "async", - "version": "2.13.0", - "dependencies": [ - "collection", - "meta" - ] - }, - { - "name": "args", - "version": "2.7.0", - "dependencies": [] - }, - { - "name": "analyzer", - "version": "8.4.1", - "dependencies": [ - "_fe_analyzer_shared", - "collection", - "convert", - "crypto", - "glob", - "meta", - "package_config", - "path", - "pub_semver", - "source_span", - "watcher", - "yaml" - ] - }, - { - "name": "source_gen", - "version": "4.2.1", + "name": "test", + "version": "1.26.3", "dependencies": [ "analyzer", "async", - "build", - "dart_style", - "glob", + "boolean_selector", + "collection", + "coverage", + "http_multi_server", + "io", + "js", + "matcher", + "node_preamble", + "package_config", "path", - "pub_semver", + "pool", + "shelf", + "shelf_packages_handler", + "shelf_static", + "shelf_web_socket", "source_span", + "stack_trace", + "stream_channel", + "test_api", + "test_core", + "typed_data", + "web_socket_channel", + "webkit_inspection_protocol", "yaml" ] }, @@ -559,44 +471,46 @@ ] }, { - "name": "mockito", - "version": "5.6.3", + "name": "test_core", + "version": "0.6.12", "dependencies": [ "analyzer", - "build", - "code_builder", + "args", + "async", + "boolean_selector", "collection", - "dart_style", - "matcher", + "coverage", + "frontend_server_client", + "glob", + "io", "meta", + "package_config", "path", - "source_gen", - "test_api" + "pool", + "source_map_stack_trace", + "source_maps", + "source_span", + "stack_trace", + "stream_channel", + "test_api", + "vm_service", + "yaml" ] }, { - "name": "analyzer_buffer", - "version": "0.1.11", + "name": "freezed_annotation", + "version": "3.1.0", "dependencies": [ - "analyzer", "collection", - "meta", - "path", - "source_gen" + "json_annotation", + "meta" ] }, { - "name": "lints", - "version": "6.1.0", + "name": "vector_math", + "version": "2.2.0", "dependencies": [] }, - { - "name": "stream_channel", - "version": "2.1.4", - "dependencies": [ - "async" - ] - }, { "name": "leak_tracker_flutter_testing", "version": "3.0.10", @@ -608,23 +522,6 @@ "meta" ] }, - { - "name": "vector_math", - "version": "2.2.0", - "dependencies": [] - }, - { - "name": "stack_trace", - "version": "1.12.1", - "dependencies": [ - "path" - ] - }, - { - "name": "clock", - "version": "1.1.2", - "dependencies": [] - }, { "name": "fake_async", "version": "1.3.3", @@ -634,30 +531,9 @@ ] }, { - "name": "matcher", - "version": "0.12.17", - "dependencies": [ - "async", - "meta", - "stack_trace", - "term_glyph", - "test_api" - ] - }, - { - "name": "test_api", - "version": "0.7.7", - "dependencies": [ - "async", - "boolean_selector", - "collection", - "meta", - "source_span", - "stack_trace", - "stream_channel", - "string_scanner", - "term_glyph" - ] + "name": "clock", + "version": "1.1.2", + "dependencies": [] }, { "name": "web", @@ -678,27 +554,6 @@ "version": "2.3.1", "dependencies": [] }, - { - "name": "http", - "version": "1.6.0", - "dependencies": [ - "async", - "http_parser", - "meta", - "web" - ] - }, - { - "name": "googleapis_auth", - "version": "2.0.0", - "dependencies": [ - "args", - "crypto", - "google_identity_services_web", - "http", - "http_parser" - ] - }, { "name": "fixnum", "version": "1.1.1", @@ -751,37 +606,10 @@ ] }, { - "name": "typed_data", - "version": "1.4.0", + "name": "flutter_web_plugins", + "version": "0.0.0", "dependencies": [ - "collection" - ] - }, - { - "name": "ffi", - "version": "2.2.0", - "dependencies": [] - }, - { - "name": "talker_flutter", - "version": "5.1.15", - "dependencies": [ - "flutter", - "group_button", - "path_provider", - "share_plus", - "talker", - "web" - ] - }, - { - "name": "rive", - "version": "0.14.4", - "dependencies": [ - "flutter", - "flutter_web_plugins", - "meta", - "rive_native" + "flutter" ] }, { @@ -791,15 +619,6 @@ "flutter" ] }, - { - "name": "freezed_annotation", - "version": "3.1.0", - "dependencies": [ - "collection", - "json_annotation", - "meta" - ] - }, { "name": "flutter_spinkit", "version": "5.2.2", @@ -807,18 +626,6 @@ "flutter" ] }, - { - "name": "flutter_riverpod", - "version": "3.1.0", - "dependencies": [ - "collection", - "flutter", - "flutter_test", - "meta", - "riverpod", - "state_notifier" - ] - }, { "name": "flutter_bloc", "version": "9.1.1", @@ -829,56 +636,17 @@ ] }, { - "name": "bloc", - "version": "9.2.0", + "name": "group_button", + "version": "5.3.4", "dependencies": [ - "meta" + "flutter" ] }, { - "name": "plugin_platform_interface", - "version": "2.1.8", - "dependencies": [ - "meta" - ] - }, - { - "name": "state_notifier", + "name": "nested", "version": "1.0.0", "dependencies": [ - "meta" - ] - }, - { - "name": "test", - "version": "1.26.3", - "dependencies": [ - "analyzer", - "async", - "boolean_selector", - "collection", - "coverage", - "http_multi_server", - "io", - "js", - "matcher", - "node_preamble", - "package_config", - "path", - "pool", - "shelf", - "shelf_packages_handler", - "shelf_static", - "shelf_web_socket", - "source_span", - "stack_trace", - "stream_channel", - "test_api", - "test_core", - "typed_data", - "web_socket_channel", - "webkit_inspection_protocol", - "yaml" + "flutter" ] }, { @@ -907,10 +675,28 @@ "dependencies": [] }, { - "name": "string_scanner", - "version": "1.4.1", + "name": "hooks_riverpod", + "version": "3.1.0", "dependencies": [ - "source_span" + "collection", + "flutter", + "flutter_hooks", + "flutter_riverpod", + "flutter_test", + "riverpod", + "state_notifier" + ] + }, + { + "name": "flutter_riverpod", + "version": "3.1.0", + "dependencies": [ + "collection", + "flutter", + "flutter_test", + "meta", + "riverpod", + "state_notifier" ] }, { @@ -923,57 +709,47 @@ ] }, { - "name": "web_socket", - "version": "1.0.1", + "name": "crypto", + "version": "3.0.7", "dependencies": [ - "web" - ] - }, - { - "name": "http_parser", - "version": "4.1.2", - "dependencies": [ - "collection", - "source_span", - "string_scanner", "typed_data" ] }, { - "name": "file", - "version": "7.0.1", + "name": "googleapis_auth", + "version": "2.0.0", + "dependencies": [ + "args", + "crypto", + "google_identity_services_web", + "http", + "http_parser" + ] + }, + { + "name": "google_identity_services_web", + "version": "0.3.3+1", "dependencies": [ "meta", - "path" + "web" ] }, { - "name": "pubspec_parse", - "version": "1.5.0", + "name": "code_builder", + "version": "4.11.1", "dependencies": [ - "checked_yaml", + "built_collection", + "built_value", "collection", - "json_annotation", - "pub_semver", - "yaml" - ] - }, - { - "name": "checked_yaml", - "version": "2.0.4", - "dependencies": [ - "json_annotation", - "source_span", - "yaml" - ] - }, - { - "name": "_fe_analyzer_shared", - "version": "91.0.0", - "dependencies": [ + "matcher", "meta" ] }, + { + "name": "lints", + "version": "6.1.0", + "dependencies": [] + }, { "name": "leak_tracker_testing", "version": "3.0.2", @@ -995,31 +771,10 @@ ] }, { - "name": "term_glyph", - "version": "1.2.2", - "dependencies": [] - }, - { - "name": "boolean_selector", - "version": "2.1.2", + "name": "state_notifier", + "version": "1.0.0", "dependencies": [ - "source_span", - "string_scanner" - ] - }, - { - "name": "google_identity_services_web", - "version": "0.3.3+1", - "dependencies": [ - "meta", - "web" - ] - }, - { - "name": "win32", - "version": "5.15.0", - "dependencies": [ - "ffi" + "meta" ] }, { @@ -1035,108 +790,90 @@ ] }, { - "name": "flutter_web_plugins", - "version": "0.0.0", + "name": "path_provider_linux", + "version": "2.2.1", "dependencies": [ - "flutter" - ] - }, - { - "name": "share_plus", - "version": "12.0.1", - "dependencies": [ - "cross_file", - "ffi", - "file", - "flutter", - "flutter_web_plugins", - "meta", - "mime", - "share_plus_platform_interface", - "url_launcher_linux", - "url_launcher_platform_interface", - "url_launcher_web", - "url_launcher_windows", - "web", - "win32" - ] - }, - { - "name": "group_button", - "version": "5.3.4", - "dependencies": [ - "flutter" - ] - }, - { - "name": "rive_native", - "version": "0.1.4", - "dependencies": [ - "args", "ffi", "flutter", - "flutter_web_plugins", - "graphs", - "http", - "meta", "path", - "plugin_platform_interface", - "vector_math", - "web" + "path_provider_platform_interface", + "xdg_directories" ] }, { - "name": "provider", - "version": "6.1.5+1", + "name": "pub_semver", + "version": "2.2.0", "dependencies": [ - "collection", - "flutter", - "nested" + "collection" ] }, { - "name": "webkit_inspection_protocol", - "version": "1.2.1", + "name": "package_config", + "version": "2.2.0", "dependencies": [ - "logging" + "path" ] }, { - "name": "test_core", - "version": "0.6.12", + "name": "term_glyph", + "version": "1.2.2", + "dependencies": [] + }, + { + "name": "lean_builder", + "version": "0.1.6", "dependencies": [ + "_fe_analyzer_shared", "analyzer", + "ansicolor", "args", - "async", - "boolean_selector", "collection", - "coverage", + "dart_style", "frontend_server_client", "glob", - "io", + "hotreloader", "meta", - "package_config", "path", - "pool", - "source_map_stack_trace", - "source_maps", "source_span", "stack_trace", - "stream_channel", - "test_api", - "vm_service", + "watcher", + "xxh3", "yaml" ] }, { - "name": "shelf_static", - "version": "1.1.3", + "name": "watcher", + "version": "1.1.4", "dependencies": [ - "convert", - "http_parser", - "mime", + "async", + "path" + ] + }, + { + "name": "boolean_selector", + "version": "2.1.2", + "dependencies": [ + "source_span", + "string_scanner" + ] + }, + { + "name": "pool", + "version": "1.5.2", + "dependencies": [ + "async", + "stack_trace" + ] + }, + { + "name": "analyzer_buffer", + "version": "0.1.11", + "dependencies": [ + "analyzer", + "collection", + "meta", "path", - "shelf" + "source_gen" ] }, { @@ -1154,37 +891,12 @@ "dependencies": [] }, { - "name": "js", - "version": "0.7.2", - "dependencies": [] - }, - { - "name": "coverage", - "version": "1.15.0", + "name": "bloc", + "version": "9.2.0", "dependencies": [ - "args", - "cli_config", - "glob", - "logging", - "meta", - "package_config", - "path", - "source_maps", - "stack_trace", - "vm_service", - "yaml" + "meta" ] }, - { - "name": "ansicolor", - "version": "2.0.3", - "dependencies": [] - }, - { - "name": "vm_service", - "version": "15.0.2", - "dependencies": [] - }, { "name": "path_provider_windows", "version": "2.3.0", @@ -1205,101 +917,10 @@ ] }, { - "name": "path_provider_linux", - "version": "2.2.1", + "name": "plugin_platform_interface", + "version": "2.1.8", "dependencies": [ - "ffi", - "flutter", - "path", - "path_provider_platform_interface", - "xdg_directories" - ] - }, - { - "name": "path_provider_foundation", - "version": "2.6.0", - "dependencies": [ - "ffi", - "flutter", - "objective_c", - "path_provider_platform_interface" - ] - }, - { - "name": "path_provider_android", - "version": "2.2.22", - "dependencies": [ - "flutter", - "path_provider_platform_interface" - ] - }, - { - "name": "url_launcher_platform_interface", - "version": "2.3.2", - "dependencies": [ - "flutter", - "plugin_platform_interface" - ] - }, - { - "name": "url_launcher_linux", - "version": "3.2.2", - "dependencies": [ - "flutter", - "url_launcher_platform_interface" - ] - }, - { - "name": "url_launcher_windows", - "version": "3.1.5", - "dependencies": [ - "flutter", - "url_launcher_platform_interface" - ] - }, - { - "name": "url_launcher_web", - "version": "2.4.2", - "dependencies": [ - "flutter", - "flutter_web_plugins", - "url_launcher_platform_interface", - "web" - ] - }, - { - "name": "share_plus_platform_interface", - "version": "6.1.0", - "dependencies": [ - "cross_file", - "flutter", - "meta", - "mime", - "path_provider", - "plugin_platform_interface", - "uuid" - ] - }, - { - "name": "cross_file", - "version": "0.3.5+2", - "dependencies": [ - "meta", - "web" - ] - }, - { - "name": "nested", - "version": "1.0.0", - "dependencies": [ - "flutter" - ] - }, - { - "name": "source_maps", - "version": "0.10.13", - "dependencies": [ - "source_span" + "meta" ] }, { @@ -1312,32 +933,291 @@ ] }, { - "name": "frontend_server_client", - "version": "4.0.0", + "name": "string_scanner", + "version": "1.4.1", "dependencies": [ - "async", + "source_span" + ] + }, + { + "name": "typed_data", + "version": "1.4.0", + "dependencies": [ + "collection" + ] + }, + { + "name": "file", + "version": "7.0.1", + "dependencies": [ + "meta", "path" ] }, { - "name": "cli_config", - "version": "0.2.0", + "name": "stream_transform", + "version": "2.1.1", + "dependencies": [] + }, + { + "name": "json_annotation", + "version": "4.11.0", "dependencies": [ - "args", + "meta" + ] + }, + { + "name": "graphs", + "version": "2.3.2", + "dependencies": [ + "collection" + ] + }, + { + "name": "analyzer", + "version": "8.4.1", + "dependencies": [ + "_fe_analyzer_shared", + "collection", + "convert", + "crypto", + "glob", + "meta", + "package_config", + "path", + "pub_semver", + "source_span", + "watcher", "yaml" ] }, { - "name": "platform", - "version": "3.1.6", + "name": "_fe_analyzer_shared", + "version": "91.0.0", + "dependencies": [ + "meta" + ] + }, + { + "name": "source_maps", + "version": "0.10.13", + "dependencies": [ + "source_span" + ] + }, + { + "name": "rive", + "version": "0.14.4", + "dependencies": [ + "flutter", + "flutter_web_plugins", + "meta", + "rive_native" + ] + }, + { + "name": "rive_native", + "version": "0.1.4", + "dependencies": [ + "args", + "ffi", + "flutter", + "flutter_web_plugins", + "graphs", + "http", + "meta", + "path", + "plugin_platform_interface", + "vector_math", + "web" + ] + }, + { + "name": "build", + "version": "4.0.4", + "dependencies": [ + "analyzer", + "crypto", + "glob", + "logging", + "package_config", + "path" + ] + }, + { + "name": "io", + "version": "1.0.5", + "dependencies": [ + "meta", + "path", + "string_scanner" + ] + }, + { + "name": "http_multi_server", + "version": "3.2.2", + "dependencies": [ + "async" + ] + }, + { + "name": "checked_yaml", + "version": "2.0.4", + "dependencies": [ + "json_annotation", + "source_span", + "yaml" + ] + }, + { + "name": "web_socket_channel", + "version": "3.0.3", + "dependencies": [ + "async", + "crypto", + "stream_channel", + "web", + "web_socket" + ] + }, + { + "name": "shelf_web_socket", + "version": "3.0.0", + "dependencies": [ + "shelf", + "stream_channel", + "web_socket_channel" + ] + }, + { + "name": "web_socket", + "version": "1.0.1", + "dependencies": [ + "web" + ] + }, + { + "name": "webkit_inspection_protocol", + "version": "1.2.1", + "dependencies": [ + "logging" + ] + }, + { + "name": "shelf_static", + "version": "1.1.3", + "dependencies": [ + "convert", + "http_parser", + "mime", + "path", + "shelf" + ] + }, + { + "name": "http", + "version": "1.6.0", + "dependencies": [ + "async", + "http_parser", + "meta", + "web" + ] + }, + { + "name": "ffi", + "version": "2.2.0", "dependencies": [] }, { - "name": "xdg_directories", - "version": "1.1.0", + "name": "convert", + "version": "3.1.2", "dependencies": [ + "typed_data" + ] + }, + { + "name": "http_parser", + "version": "4.1.2", + "dependencies": [ + "collection", + "source_span", + "string_scanner", + "typed_data" + ] + }, + { + "name": "logging", + "version": "1.3.0", + "dependencies": [] + }, + { + "name": "build_daemon", + "version": "4.1.1", + "dependencies": [ + "built_collection", + "built_value", + "crypto", + "http_multi_server", + "logging", + "path", + "pool", + "shelf", + "shelf_web_socket", + "stream_transform", + "watcher", + "web_socket_channel" + ] + }, + { + "name": "js", + "version": "0.7.2", + "dependencies": [] + }, + { + "name": "mime", + "version": "2.0.0", + "dependencies": [] + }, + { + "name": "source_gen", + "version": "4.2.1", + "dependencies": [ + "analyzer", + "async", + "build", + "dart_style", + "glob", + "path", + "pub_semver", + "source_span", + "yaml" + ] + }, + { + "name": "mockito", + "version": "5.6.3", + "dependencies": [ + "analyzer", + "build", + "code_builder", + "collection", + "dart_style", + "matcher", "meta", - "path" + "path", + "source_gen", + "test_api" + ] + }, + { + "name": "path_provider_foundation", + "version": "2.6.0", + "dependencies": [ + "ffi", + "flutter", + "objective_c", + "path_provider_platform_interface" ] }, { @@ -1354,11 +1234,11 @@ ] }, { - "name": "uuid", - "version": "4.5.3", + "name": "code_assets", + "version": "1.0.0", "dependencies": [ - "crypto", - "fixnum" + "collection", + "hooks" ] }, { @@ -1386,11 +1266,198 @@ ] }, { - "name": "code_assets", - "version": "1.0.0", + "name": "pubspec_parse", + "version": "1.5.0", + "dependencies": [ + "checked_yaml", + "collection", + "json_annotation", + "pub_semver", + "yaml" + ] + }, + { + "name": "platform", + "version": "3.1.6", + "dependencies": [] + }, + { + "name": "built_value", + "version": "8.12.4", + "dependencies": [ + "built_collection", + "collection", + "fixnum", + "meta" + ] + }, + { + "name": "shelf", + "version": "1.4.2", + "dependencies": [ + "async", + "collection", + "http_parser", + "path", + "stack_trace", + "stream_channel" + ] + }, + { + "name": "xdg_directories", + "version": "1.1.0", + "dependencies": [ + "meta", + "path" + ] + }, + { + "name": "provider", + "version": "6.1.5+1", "dependencies": [ "collection", - "hooks" + "flutter", + "nested" + ] + }, + { + "name": "vm_service", + "version": "15.0.2", + "dependencies": [] + }, + { + "name": "win32", + "version": "5.15.0", + "dependencies": [ + "ffi" + ] + }, + { + "name": "talker_flutter", + "version": "5.1.15", + "dependencies": [ + "flutter", + "group_button", + "path_provider", + "share_plus", + "talker", + "web" + ] + }, + { + "name": "share_plus", + "version": "12.0.1", + "dependencies": [ + "cross_file", + "ffi", + "file", + "flutter", + "flutter_web_plugins", + "meta", + "mime", + "share_plus_platform_interface", + "url_launcher_linux", + "url_launcher_platform_interface", + "url_launcher_web", + "url_launcher_windows", + "web", + "win32" + ] + }, + { + "name": "url_launcher_platform_interface", + "version": "2.3.2", + "dependencies": [ + "flutter", + "plugin_platform_interface" + ] + }, + { + "name": "share_plus_platform_interface", + "version": "6.1.0", + "dependencies": [ + "cross_file", + "flutter", + "meta", + "mime", + "path_provider", + "plugin_platform_interface", + "uuid" + ] + }, + { + "name": "url_launcher_windows", + "version": "3.1.5", + "dependencies": [ + "flutter", + "url_launcher_platform_interface" + ] + }, + { + "name": "url_launcher_linux", + "version": "3.2.2", + "dependencies": [ + "flutter", + "url_launcher_platform_interface" + ] + }, + { + "name": "cross_file", + "version": "0.3.5+2", + "dependencies": [ + "meta", + "web" + ] + }, + { + "name": "url_launcher_web", + "version": "2.4.2", + "dependencies": [ + "flutter", + "flutter_web_plugins", + "url_launcher_platform_interface", + "web" + ] + }, + { + "name": "path_provider_android", + "version": "2.2.22", + "dependencies": [ + "flutter", + "path_provider_platform_interface" + ] + }, + { + "name": "uuid", + "version": "4.5.3", + "dependencies": [ + "crypto", + "fixnum" + ] + }, + { + "name": "coverage", + "version": "1.15.0", + "dependencies": [ + "args", + "cli_config", + "glob", + "logging", + "meta", + "package_config", + "path", + "source_maps", + "stack_trace", + "vm_service", + "yaml" + ] + }, + { + "name": "cli_config", + "version": "0.2.0", + "dependencies": [ + "args", + "yaml" ] } ], diff --git a/useragent/lib/features/adaptive_switcher.dart b/useragent/lib/features/adaptive_switcher.dart deleted file mode 100644 index fa8a3c4..0000000 --- a/useragent/lib/features/adaptive_switcher.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:sizer/sizer.dart'; - -const transitionDuration = Duration(milliseconds: 800); - -class AdaptiveBuilders { - WidgetBuilder? buildSmall; - WidgetBuilder? buildMediumLarge; - WidgetBuilder? buildLarge; - WidgetBuilder? buildExtraLarge; - WidgetBuilder? build; - - AdaptiveBuilders({ - this.buildSmall, - this.buildMediumLarge, - this.buildLarge, - this.buildExtraLarge, - this.build, - }); -} - -class Destination { - final String label; - final String? tooltip; - final Icon icon; - final Icon? selectedIcon; - - final AdaptiveBuilders main; - final AdaptiveBuilders? secondary; - - Destination({ - required this.label, - required this.icon, - this.selectedIcon, - required this.main, - this.secondary, - this.tooltip, - }); -} - -class Switcher extends StatelessWidget { - final Widget? child; - - const Switcher({super.key, this.child}); - @override - Widget build(BuildContext context) { - return AnimatedSwitcher( - duration: Duration( - milliseconds: transitionDuration.inMilliseconds ~/ 100, - ), - transitionBuilder: (child, animation) { - return FadeTransition(opacity: animation, child: child); - }, - child: child, - ); - } -} - -WidgetBuilder? patchAnimated(WidgetBuilder? input) { - if (input == null) return null; - return (context) => Switcher(child: input(context)); -} - -class HomeRouter extends HookWidget { - final List destinations; - - HomeRouter({super.key, required this.destinations}) - : assert(destinations.isNotEmpty); - - @override - Widget build(BuildContext context) { - final selectedIndex = useState(0); - final destination = useMemoized(() => destinations[selectedIndex.value], [ - selectedIndex.value, - ]); - final dispatcher = useMemoized(() => destination.main, [ - selectedIndex.value, - ]); - final secondaryDispatcher = useMemoized(() => destination.secondary, [ - selectedIndex.value, - ]); - - return AdaptiveScaffold( - destinations: destinations - .map( - (destination) => NavigationDestination( - label: destination.label, - icon: destination.icon, - selectedIcon: destination.selectedIcon, - tooltip: destination.tooltip, - ), - ) - .toList(), - - selectedIndex: selectedIndex.value, - onSelectedIndexChange: (index) => selectedIndex.value = index, - useDrawer: true, - - smallBody: patchAnimated(dispatcher.buildSmall), - body: patchAnimated(dispatcher.build), - mediumLargeBody: patchAnimated(dispatcher.buildMediumLarge), - largeBody: patchAnimated(dispatcher.buildLarge), - extraLargeBody: patchAnimated(dispatcher.buildExtraLarge), - - smallSecondaryBody: patchAnimated(secondaryDispatcher?.buildSmall), - secondaryBody: patchAnimated(secondaryDispatcher?.build), - mediumLargeSecondaryBody: patchAnimated( - secondaryDispatcher?.buildMediumLarge, - ), - largeSecondaryBody: patchAnimated(secondaryDispatcher?.buildLarge), - extraLargeSecondaryBody: patchAnimated( - secondaryDispatcher?.buildExtraLarge, - ), - ); - } -} diff --git a/useragent/lib/home.dart b/useragent/lib/home.dart deleted file mode 100644 index 036e609..0000000 --- a/useragent/lib/home.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:arbiter/features/adaptive_switcher.dart'; -import 'package:arbiter/screens/about.dart'; -import 'package:arbiter/screens/calc.dart'; -import 'package:flutter/material.dart'; - -final _destinations = [ - Destination( - label: "About", - icon: Icon(Icons.info_outline), - main: AdaptiveBuilders(build: (_) => About()), - ), - Destination( - label: "Calc", - icon: Icon(Icons.calculate), - main: AdaptiveBuilders(build: (_) => CalcScreen()), - ), -]; - -class Home extends StatelessWidget { - @override - Widget build(BuildContext context) { - return HomeRouter(destinations: _destinations); - } -} diff --git a/useragent/lib/main.dart b/useragent/lib/main.dart index bced8fe..89df0e1 100644 --- a/useragent/lib/main.dart +++ b/useragent/lib/main.dart @@ -3,9 +3,12 @@ import 'package:flutter/material.dart' hide Router; import 'package:hooks_riverpod/hooks_riverpod.dart'; void main() { + WidgetsFlutterBinding.ensureInitialized(); runApp( - MaterialApp( - home: ProviderScope(child: Scaffold(body: Router())), + ProviderScope( + child: MaterialApp.router( + routerConfig: Router().config(), + ), ), ); } diff --git a/useragent/lib/providers/key.dart b/useragent/lib/providers/key.dart index 4636817..196fc45 100644 --- a/useragent/lib/providers/key.dart +++ b/useragent/lib/providers/key.dart @@ -1,4 +1,3 @@ -import 'package:hooks_riverpod/experimental/mutation.dart'; import 'package:mtcore/markettakers.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:arbiter/features/pk_manager.dart'; @@ -11,22 +10,22 @@ KeyManager keyManager(Ref ref) { return SimpleEd25519Manager(); } -@riverpod +@Riverpod(keepAlive: true) class Key extends _$Key { @override Future build() async { - final manager = SimpleEd25519Manager(); + final manager = ref.watch(keyManagerProvider); final keyHandle = await manager.get(); return keyHandle; } Future create() async { + final manager = ref.watch(keyManagerProvider); if (state.value != null) { return; } state = await AsyncValue.guard(() async { - final manager = SimpleEd25519Manager(); final newKeyHandle = await manager.create(); return newKeyHandle; }); @@ -34,7 +33,7 @@ class Key extends _$Key { } class KeyBootstrapper implements StageFactory { - final MutationTarget ref; + final ProviderContainer ref; KeyBootstrapper({required this.ref}); @@ -43,14 +42,14 @@ class KeyBootstrapper implements StageFactory { @override Future get isAlreadyCompleted async { - final key = await ref.container.read(keyProvider.future); + final key = await ref.read(keyProvider.future); return key != null; } @override Future start(StageController controller) async { controller.setIndefiniteProgress(); - final key = ref.container.read(keyProvider.notifier); + final key = ref.read(keyProvider.notifier); await key.create(); } diff --git a/useragent/lib/providers/key.g.dart b/useragent/lib/providers/key.g.dart index 37cfe01..c827cab 100644 --- a/useragent/lib/providers/key.g.dart +++ b/useragent/lib/providers/key.g.dart @@ -60,7 +60,7 @@ final class KeyProvider extends $AsyncNotifierProvider { argument: null, retry: null, name: r'keyProvider', - isAutoDispose: true, + isAutoDispose: false, dependencies: null, $allTransitiveDependencies: null, ); @@ -73,7 +73,7 @@ final class KeyProvider extends $AsyncNotifierProvider { Key create() => Key(); } -String _$keyHash() => r'6d66204174c4d2d5c76e27f3a8de8f9a9c88a3e0'; +String _$keyHash() => r'37b209825067adadbb75fe0b4ce936ea1c201dc8'; abstract class _$Key extends $AsyncNotifier { FutureOr build(); diff --git a/useragent/lib/router.dart b/useragent/lib/router.dart index aef8e74..bcbc314 100644 --- a/useragent/lib/router.dart +++ b/useragent/lib/router.dart @@ -1,32 +1,24 @@ -import 'dart:async'; +import 'package:arbiter/screens/dashboard/about.dart'; +import 'package:arbiter/screens/dashboard/calc.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; -import 'package:arbiter/features/bootstrap.dart'; -import 'package:arbiter/home.dart'; -import 'package:flutter/src/widgets/async.dart'; -import 'package:flutter/src/widgets/framework.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'router.gr.dart'; -final _bootstapCompleter = Completer(); - -class Router extends HookConsumerWidget { +@AutoRouterConfig(generateForDir: ['lib/screens']) +class Router extends RootStackRouter { @override - Widget build(BuildContext context, WidgetRef ref) { - final bootstrapper = useMemoized( - () => Bootstrap(completer: _bootstapCompleter), - ); - final bootstrapFuture = useFuture(_bootstapCompleter.future); + List get routes => [ + AutoRoute(page: Bootstrap.page, path: '/bootstrap', initial: true), - switch (bootstrapFuture.connectionState) { - case ConnectionState.none || - ConnectionState.waiting || - ConnectionState.active: - return bootstrapper; - - case ConnectionState.done: - break; - } - - return Home(); - } + AutoRoute( + page: DashboardRouter.page, + path: '/dashboard', + children: [ + AutoRoute(page: AboutRoute.page, path: 'about'), + AutoRoute(page: CalcRoute.page, path: 'calc'), + ], + ), + ]; } diff --git a/useragent/lib/router.gr.dart b/useragent/lib/router.gr.dart new file mode 100644 index 0000000..824cf97 --- /dev/null +++ b/useragent/lib/router.gr.dart @@ -0,0 +1,80 @@ +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// AutoRouterGenerator +// ************************************************************************** + +// ignore_for_file: type=lint +// coverage:ignore-file + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:arbiter/screens/bootstrap.dart' as _i2; +import 'package:arbiter/screens/dashboard.dart' as _i4; +import 'package:arbiter/screens/dashboard/about.dart' as _i1; +import 'package:arbiter/screens/dashboard/calc.dart' as _i3; +import 'package:auto_route/auto_route.dart' as _i5; + +/// generated route for +/// [_i1.AboutScreen] +class AboutRoute extends _i5.PageRouteInfo { + const AboutRoute({List<_i5.PageRouteInfo>? children}) + : super(AboutRoute.name, initialChildren: children); + + static const String name = 'AboutRoute'; + + static _i5.PageInfo page = _i5.PageInfo( + name, + builder: (data) { + return _i1.AboutScreen(); + }, + ); +} + +/// generated route for +/// [_i2.Bootstrap] +class Bootstrap extends _i5.PageRouteInfo { + const Bootstrap({List<_i5.PageRouteInfo>? children}) + : super(Bootstrap.name, initialChildren: children); + + static const String name = 'Bootstrap'; + + static _i5.PageInfo page = _i5.PageInfo( + name, + builder: (data) { + return const _i2.Bootstrap(); + }, + ); +} + +/// generated route for +/// [_i3.CalcScreen] +class CalcRoute extends _i5.PageRouteInfo { + const CalcRoute({List<_i5.PageRouteInfo>? children}) + : super(CalcRoute.name, initialChildren: children); + + static const String name = 'CalcRoute'; + + static _i5.PageInfo page = _i5.PageInfo( + name, + builder: (data) { + return const _i3.CalcScreen(); + }, + ); +} + +/// generated route for +/// [_i4.DashboardRouter] +class DashboardRouter extends _i5.PageRouteInfo { + const DashboardRouter({List<_i5.PageRouteInfo>? children}) + : super(DashboardRouter.name, initialChildren: children); + + static const String name = 'DashboardRouter'; + + static _i5.PageInfo page = _i5.PageInfo( + name, + builder: (data) { + return const _i4.DashboardRouter(); + }, + ); +} diff --git a/useragent/lib/screens/about.dart b/useragent/lib/screens/about.dart deleted file mode 100644 index 51291c5..0000000 --- a/useragent/lib/screens/about.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:mtcore/markettakers.dart'; - -class About extends StatelessWidget { - @override - Widget build(BuildContext context) { - return AboutScreen(decription: "Arbiter is bla bla bla"); - } -} diff --git a/useragent/lib/features/bootstrap.dart b/useragent/lib/screens/bootstrap.dart similarity index 51% rename from useragent/lib/features/bootstrap.dart rename to useragent/lib/screens/bootstrap.dart index c4de910..da215f4 100644 --- a/useragent/lib/features/bootstrap.dart +++ b/useragent/lib/screens/bootstrap.dart @@ -1,19 +1,31 @@ import 'dart:async'; import 'package:arbiter/providers/key.dart'; -import 'package:flutter/src/widgets/framework.dart'; +import 'package:arbiter/router.gr.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mtcore/markettakers.dart'; +@RoutePage() class Bootstrap extends HookConsumerWidget { - final Completer completer; - - const Bootstrap({required this.completer}); + const Bootstrap({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final container = ProviderScope.containerOf(context); + final container = ProviderScope.containerOf( context); + final completer = useMemoized(() { + final completer = Completer(); + completer.future.then((_) async { + if (context.mounted) { + final router = AutoRouter.of(context); + router.replace(const DashboardRouter()); + } + }); + + return completer; + }, []); final stages = useMemoized(() { return [KeyBootstrapper(ref: container)]; }, []); @@ -21,6 +33,7 @@ class Bootstrap extends HookConsumerWidget { () => Bootstrapper(stages: stages, onCompleted: completer), [stages], ); + return bootstrapper; } } diff --git a/useragent/lib/screens/dashboard.dart b/useragent/lib/screens/dashboard.dart new file mode 100644 index 0000000..43c2366 --- /dev/null +++ b/useragent/lib/screens/dashboard.dart @@ -0,0 +1,42 @@ +import 'package:arbiter/router.gr.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; + +const breakpoints = MaterialAdaptiveBreakpoints(); + +final routes = [AboutRoute(), CalcRoute()]; + +@RoutePage() +class DashboardRouter extends StatelessWidget { + const DashboardRouter({super.key}); + + @override + Widget build(BuildContext context) { + return AutoTabsRouter( + routes: routes, + transitionBuilder: (context, child, animation) => FadeTransition( + opacity: animation, + // the passed child is technically our animated selected-tab page + child: child, + ), + builder: (context, child) { + final tabsRouter = AutoTabsRouter.of(context); + final currentActive = tabsRouter.activeIndex; + return AdaptiveScaffold( + destinations: [ + NavigationDestination(icon: Icon(Icons.book), label: "About"), + NavigationDestination(icon: Icon(Icons.calculate), label: "Calc"), + ], + body: (ctx) => child, + onSelectedIndexChange: (index) { + tabsRouter.navigate(routes[index]); + }, + selectedIndex: currentActive, + transitionDuration: Duration(milliseconds: 800), + internalAnimations: true, + ); + }, + ); + } +} diff --git a/useragent/lib/screens/dashboard/about.dart b/useragent/lib/screens/dashboard/about.dart new file mode 100644 index 0000000..29e853d --- /dev/null +++ b/useragent/lib/screens/dashboard/about.dart @@ -0,0 +1,12 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:mtcore/markettakers.dart' as mt; + + +@RoutePage() +class AboutScreen extends StatelessWidget { + @override + Widget build(BuildContext context) { + return mt.AboutScreen(decription: "Arbiter is bla bla bla"); + } +} diff --git a/useragent/lib/screens/calc.dart b/useragent/lib/screens/dashboard/calc.dart similarity index 96% rename from useragent/lib/screens/calc.dart rename to useragent/lib/screens/dashboard/calc.dart index 3d090cf..0ba7b9c 100644 --- a/useragent/lib/screens/calc.dart +++ b/useragent/lib/screens/dashboard/calc.dart @@ -1,5 +1,7 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +@RoutePage() class CalcScreen extends StatefulWidget { const CalcScreen({super.key}); diff --git a/useragent/pubspec.lock b/useragent/pubspec.lock index 790c4b9..d7f4657 100644 --- a/useragent/pubspec.lock +++ b/useragent/pubspec.lock @@ -49,6 +49,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" + auto_route: + dependency: "direct main" + description: + name: auto_route + sha256: e9acfeb3df33d188fce4ad0239ef4238f333b7aa4d95ec52af3c2b9360dcd969 + url: "https://pub.dev" + source: hosted + version: "11.1.0" + auto_route_generator: + dependency: "direct dev" + description: + name: auto_route_generator + sha256: "04300eaf5821962aae8b5cd94f67013fd2fd326dc3be212d3ec1ae7470f09834" + url: "https://pub.dev" + source: hosted + version: "10.4.0" biometric_signature: dependency: "direct main" description: @@ -283,10 +299,10 @@ packages: description: path: "." ref: HEAD - resolved-ref: "4852b5041d8d9ed6cacb4dc4c6723086f0612e37" + resolved-ref: b2e3615901a7ab837cb7fc35efbfcf8b55f27638 url: "https://github.com/hdbg/flutter_adaptive_scaffold.git" source: git - version: "0.3.3+1" + version: "1.0.0+1" flutter_bloc: dependency: transitive description: @@ -465,6 +481,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b + url: "https://pub.dev" + source: hosted + version: "4.3.0" http: dependency: transitive description: @@ -545,6 +569,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + lean_builder: + dependency: transitive + description: + name: lean_builder + sha256: "6af3cfbf34400eb14b89fe20111e5981e7083362f00ea10b9ed2a6e833250d76" + url: "https://pub.dev" + source: hosted + version: "0.1.6" lints: dependency: transitive description: @@ -1074,10 +1106,10 @@ packages: dependency: transitive description: name: watcher - sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.1.4" web: dependency: transitive description: @@ -1126,6 +1158,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + xxh3: + dependency: transitive + description: + name: xxh3 + sha256: "399a0438f5d426785723c99da6b16e136f4953fb1e9db0bf270bd41dd4619916" + url: "https://pub.dev" + source: hosted + version: "1.2.0" yaml: dependency: transitive description: diff --git a/useragent/pubspec.yaml b/useragent/pubspec.yaml index cd83ae3..683baef 100644 --- a/useragent/pubspec.yaml +++ b/useragent/pubspec.yaml @@ -27,6 +27,7 @@ dependencies: riverpod_annotation: ^4.0.0 grpc: ^5.1.0 flutter_hooks: ^0.21.3+1 + auto_route: ^11.1.0 dev_dependencies: flutter_test: @@ -35,6 +36,7 @@ dev_dependencies: flutter_lints: ^6.0.0 riverpod_generator: ^4.0.0+1 build_runner: ^2.12.2 + auto_route_generator: ^10.4.0 flutter: uses-material-design: true \ No newline at end of file From ec0e8a980c817d620de8b5bbdae60abe49de6333 Mon Sep 17 00:00:00 2001 From: hdbg Date: Sun, 15 Mar 2026 14:51:39 +0100 Subject: [PATCH 03/13] feat(useragent): added connection info setup screen --- AGENTS.md | 128 ++ useragent/.dart_tool/package_config.json | 904 ---------- useragent/.dart_tool/package_graph.json | 1465 ----------------- useragent/.dart_tool/version | 1 - useragent/lib/features/arbiter_url.dart | 56 + .../lib/features/connection/connection.dart | 3 + useragent/lib/features/pk_manager.dart | 5 +- .../lib/features/server_info_storage.dart | 67 + useragent/lib/main.dart | 35 +- useragent/lib/providers/server_info.dart | 51 + useragent/lib/providers/server_info.g.dart | 102 ++ useragent/lib/router.dart | 5 +- useragent/lib/router.gr.dart | 45 +- useragent/lib/screens/bootstrap.dart | 4 +- useragent/lib/screens/dashboard/about.dart | 3 +- useragent/lib/screens/server_info_setup.dart | 284 ++++ .../ephemeral/Flutter-Generated.xcconfig | 13 - .../ephemeral/flutter_export_environment.sh | 14 - useragent/macos/Podfile | 2 +- useragent/macos/Podfile.lock | 2 +- .../macos/Runner.xcodeproj/project.pbxproj | 34 +- .../macos/Runner/DebugProfile.entitlements | 8 +- useragent/macos/Runner/Release.entitlements | 2 + useragent/pubspec.lock | 4 +- useragent/pubspec.yaml | 4 +- 25 files changed, 800 insertions(+), 2441 deletions(-) create mode 100644 AGENTS.md delete mode 100644 useragent/.dart_tool/package_config.json delete mode 100644 useragent/.dart_tool/package_graph.json delete mode 100644 useragent/.dart_tool/version create mode 100644 useragent/lib/features/arbiter_url.dart create mode 100644 useragent/lib/features/connection/connection.dart create mode 100644 useragent/lib/features/server_info_storage.dart create mode 100644 useragent/lib/providers/server_info.dart create mode 100644 useragent/lib/providers/server_info.g.dart create mode 100644 useragent/lib/screens/server_info_setup.dart delete mode 100644 useragent/macos/Flutter/ephemeral/Flutter-Generated.xcconfig delete mode 100755 useragent/macos/Flutter/ephemeral/flutter_export_environment.sh diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..bc166c4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,128 @@ +# AGENTS.md + +This file provides guidance to Codex (Codex.ai/code) when working with code in this repository. + +## Project Overview + +Arbiter is a **permissioned signing service** for cryptocurrency wallets. It consists of: +- **`server/`** — Rust gRPC daemon that holds encrypted keys and enforces policies +- **`useragent/`** — Flutter desktop app (macOS/Windows) with a Rust backend via Rinf +- **`protobufs/`** — Protocol Buffer definitions shared between server and client + +The vault never exposes key material; it only produces signatures when requests satisfy configured policies. + +## Toolchain Setup + +Tools are managed via [mise](https://mise.jdx.dev/). Install all required tools: +```sh +mise install +``` + +Key versions: Rust 1.93.0 (with clippy), Flutter 3.38.9-stable, protoc 29.6, diesel_cli 2.3.6 (sqlite). + +## Server (Rust workspace at `server/`) + +### Crates + +| Crate | Purpose | +|---|---| +| `arbiter-proto` | Generated gRPC stubs + protobuf types; compiled from `protobufs/*.proto` via `tonic-prost-build` | +| `arbiter-server` | Main daemon — actors, DB, EVM policy engine, gRPC service implementation | +| `arbiter-useragent` | Rust client library for the user agent side of the gRPC protocol | +| `arbiter-client` | Rust client library for SDK clients | + +### Common Commands + +```sh +cd server + +# Build +cargo build + +# Run the server daemon +cargo run -p arbiter-server + +# Run all tests (preferred over cargo test) +cargo nextest run + +# Run a single test +cargo nextest run + +# Lint +cargo clippy + +# Security audit +cargo audit + +# Check unused dependencies +cargo shear + +# Run snapshot tests and update snapshots +cargo insta review +``` + +### Architecture + +The server is actor-based using the **kameo** crate. All long-lived state lives in `GlobalActors`: + +- **`Bootstrapper`** — Manages the one-time bootstrap token written to `~/.arbiter/bootstrap_token` on first run. +- **`KeyHolder`** — Holds the encrypted root key and manages the Sealed/Unsealed vault state machine. On unseal, decrypts the root key into a `memsafe` hardened memory cell. +- **`MessageRouter`** — Coordinates streaming messages between user agents and SDK clients. +- **`EvmActor`** — Handles EVM transaction policy enforcement and signing. + +Per-connection actors live under `actors/user_agent/` and `actors/client/`, each with `auth` (challenge-response authentication) and `session` (post-auth operations) sub-modules. + +**Database:** SQLite via `diesel-async` + `bb8` connection pool. Schema managed by embedded Diesel migrations in `crates/arbiter-server/migrations/`. DB file lives at `~/.arbiter/arbiter.sqlite`. Tests use a temp-file DB via `db::create_test_pool()`. + +**Cryptography:** +- Authentication: ed25519 (challenge-response, nonce-tracked per peer) +- Encryption at rest: XChaCha20-Poly1305 (versioned via `scheme` field for transparent migration on unseal) +- Password KDF: Argon2 +- Unseal transport: X25519 ephemeral key exchange +- TLS: self-signed certificate (aws-lc-rs backend), fingerprint distributed via `ArbiterUrl` + +**Protocol:** gRPC with Protocol Buffers. The `ArbiterUrl` type encodes host, port, CA cert, and bootstrap token into a single shareable string (printed to console on first run). + +### Proto Regeneration + +When `.proto` files in `protobufs/` change, rebuild to regenerate: +```sh +cd server && cargo build -p arbiter-proto +``` + +### Database Migrations + +```sh +# Create a new migration +diesel migration generate --migration-dir crates/arbiter-server/migrations + +# Run migrations manually (server also runs them on startup) +diesel migration run --migration-dir crates/arbiter-server/migrations +``` + +## User Agent (Flutter + Rinf at `useragent/`) + +The Flutter app uses [Rinf](https://rinf.cunarist.org) to call Rust code. The Rust logic lives in `useragent/native/hub/` as a separate crate that uses `arbiter-useragent` for the gRPC client. + +Communication between Dart and Rust uses typed **signals** defined in `useragent/native/hub/src/signals/`. After modifying signal structs, regenerate Dart bindings: + +```sh +cd useragent && rinf gen +``` + +### Common Commands + +```sh +cd useragent + +# Run the app (macOS or Windows) +flutter run + +# Regenerate Rust↔Dart signal bindings +rinf gen + +# Analyze Dart code +flutter analyze +``` + +The Rinf Rust entry point is `useragent/native/hub/src/lib.rs`. It spawns actors defined in `useragent/native/hub/src/actors/` which handle Dart↔server communication via signals. diff --git a/useragent/.dart_tool/package_config.json b/useragent/.dart_tool/package_config.json deleted file mode 100644 index 9c93efa..0000000 --- a/useragent/.dart_tool/package_config.json +++ /dev/null @@ -1,904 +0,0 @@ -{ - "configVersion": 2, - "packages": [ - { - "name": "_fe_analyzer_shared", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/_fe_analyzer_shared-91.0.0", - "packageUri": "lib/", - "languageVersion": "3.9" - }, - { - "name": "analyzer", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/analyzer-8.4.1", - "packageUri": "lib/", - "languageVersion": "3.9" - }, - { - "name": "analyzer_buffer", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/analyzer_buffer-0.1.11", - "packageUri": "lib/", - "languageVersion": "3.6" - }, - { - "name": "ansicolor", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/ansicolor-2.0.3", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "args", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/args-2.7.0", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "async", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/async-2.13.0", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "auto_route", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/auto_route-11.1.0", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "auto_route_generator", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/auto_route_generator-10.4.0", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "biometric_signature", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/biometric_signature-10.2.0", - "packageUri": "lib/", - "languageVersion": "3.9" - }, - { - "name": "bloc", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/bloc-9.2.0", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "boolean_selector", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "build", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/build-4.0.4", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "build_config", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/build_config-1.3.0", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "build_daemon", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/build_daemon-4.1.1", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "build_runner", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/build_runner-2.12.2", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "built_collection", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/built_collection-5.1.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "built_value", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/built_value-8.12.4", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "characters", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/characters-1.4.0", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "checked_yaml", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/checked_yaml-2.0.4", - "packageUri": "lib/", - "languageVersion": "3.8" - }, - { - "name": "cli_config", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cli_config-0.2.0", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "clock", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/clock-1.1.2", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "code_assets", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/code_assets-1.0.0", - "packageUri": "lib/", - "languageVersion": "3.9" - }, - { - "name": "code_builder", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/code_builder-4.11.1", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "collection", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/collection-1.19.1", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "convert", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/convert-3.1.2", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "coverage", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/coverage-1.15.0", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "cross_file", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cross_file-0.3.5+2", - "packageUri": "lib/", - "languageVersion": "3.8" - }, - { - "name": "crypto", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/crypto-3.0.7", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "cryptography", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cryptography-2.9.0", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "cryptography_flutter", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cryptography_flutter-2.3.4", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "cupertino_icons", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cupertino_icons-1.0.8", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "dart_style", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/dart_style-3.1.3", - "packageUri": "lib/", - "languageVersion": "3.9" - }, - { - "name": "fake_async", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/fake_async-1.3.3", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "ffi", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/ffi-2.2.0", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "file", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/file-7.0.1", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "fixnum", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/fixnum-1.1.1", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "flutter", - "rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/packages/flutter", - "packageUri": "lib/", - "languageVersion": "3.8" - }, - { - "name": "flutter_adaptive_scaffold", - "rootUri": "file:///Users/kaska/.pub-cache/git/flutter_adaptive_scaffold-b2e3615901a7ab837cb7fc35efbfcf8b55f27638/", - "packageUri": "lib/", - "languageVersion": "3.10" - }, - { - "name": "flutter_bloc", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_bloc-9.1.1", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "flutter_hooks", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_hooks-0.21.3+1", - "packageUri": "lib/", - "languageVersion": "2.17" - }, - { - "name": "flutter_lints", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_lints-6.0.0", - "packageUri": "lib/", - "languageVersion": "3.8" - }, - { - "name": "flutter_riverpod", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_riverpod-3.1.0", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "flutter_secure_storage", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage-10.0.0", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "flutter_secure_storage_darwin", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage_darwin-0.2.0", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "flutter_secure_storage_linux", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-3.0.0", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "flutter_secure_storage_platform_interface", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage_platform_interface-2.0.1", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "flutter_secure_storage_web", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-2.1.0", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "flutter_secure_storage_windows", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-4.1.0", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "flutter_spinkit", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_spinkit-5.2.2", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "flutter_test", - "rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/packages/flutter_test", - "packageUri": "lib/", - "languageVersion": "3.8" - }, - { - "name": "flutter_web_plugins", - "rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/packages/flutter_web_plugins", - "packageUri": "lib/", - "languageVersion": "3.8" - }, - { - "name": "freezed_annotation", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/freezed_annotation-3.1.0", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "frontend_server_client", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/frontend_server_client-4.0.0", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "glob", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/glob-2.1.3", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "google_identity_services_web", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/google_identity_services_web-0.3.3+1", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "googleapis_auth", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/googleapis_auth-2.0.0", - "packageUri": "lib/", - "languageVersion": "3.6" - }, - { - "name": "graphs", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/graphs-2.3.2", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "group_button", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/group_button-5.3.4", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "grpc", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/grpc-5.1.0", - "packageUri": "lib/", - "languageVersion": "3.8" - }, - { - "name": "hooks", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/hooks-1.0.2", - "packageUri": "lib/", - "languageVersion": "3.10" - }, - { - "name": "hooks_riverpod", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/hooks_riverpod-3.1.0", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "hotreloader", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/hotreloader-4.3.0", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "http", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/http-1.6.0", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "http2", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/http2-2.3.1", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "http_multi_server", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/http_multi_server-3.2.2", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "http_parser", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/http_parser-4.1.2", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "io", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/io-1.0.5", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "js", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/js-0.7.2", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "json_annotation", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/json_annotation-4.11.0", - "packageUri": "lib/", - "languageVersion": "3.9" - }, - { - "name": "leak_tracker", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/leak_tracker-11.0.2", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "leak_tracker_flutter_testing", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.10", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "leak_tracker_testing", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/leak_tracker_testing-3.0.2", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "lean_builder", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/lean_builder-0.1.6", - "packageUri": "lib/", - "languageVersion": "3.8" - }, - { - "name": "lints", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/lints-6.1.0", - "packageUri": "lib/", - "languageVersion": "3.8" - }, - { - "name": "logging", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/logging-1.3.0", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "matcher", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/matcher-0.12.17", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "material_color_utilities", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/material_color_utilities-0.11.1", - "packageUri": "lib/", - "languageVersion": "2.17" - }, - { - "name": "meta", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/meta-1.17.0", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "mime", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/mime-2.0.0", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "mockito", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/mockito-5.6.3", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "mtcore", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/git.markettakers.org%2547api%2547packages%2547MarketTakers%2547pub%2547/mtcore-1.0.6", - "packageUri": "lib/", - "languageVersion": "3.9" - }, - { - "name": "native_toolchain_c", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/native_toolchain_c-0.17.5", - "packageUri": "lib/", - "languageVersion": "3.10" - }, - { - "name": "nested", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/nested-1.0.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "node_preamble", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/node_preamble-2.0.2", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "objective_c", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/objective_c-9.3.0", - "packageUri": "lib/", - "languageVersion": "3.10" - }, - { - "name": "package_config", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/package_config-2.2.0", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "path", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path-1.9.1", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "path_provider", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider-2.1.5", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "path_provider_android", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider_android-2.2.22", - "packageUri": "lib/", - "languageVersion": "3.9" - }, - { - "name": "path_provider_foundation", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider_foundation-2.6.0", - "packageUri": "lib/", - "languageVersion": "3.10" - }, - { - "name": "path_provider_linux", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1", - "packageUri": "lib/", - "languageVersion": "2.19" - }, - { - "name": "path_provider_platform_interface", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider_platform_interface-2.1.2", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "path_provider_windows", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "percent_indicator", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/percent_indicator-4.2.5", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "platform", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/platform-3.1.6", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "plugin_platform_interface", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.8", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "pool", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/pool-1.5.2", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "protobuf", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/protobuf-6.0.0", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "provider", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/provider-6.1.5+1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "pub_semver", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/pub_semver-2.2.0", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "pubspec_parse", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/pubspec_parse-1.5.0", - "packageUri": "lib/", - "languageVersion": "3.6" - }, - { - "name": "rive", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/rive-0.14.4", - "packageUri": "lib/", - "languageVersion": "3.6" - }, - { - "name": "rive_native", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/rive_native-0.1.4", - "packageUri": "lib/", - "languageVersion": "3.6" - }, - { - "name": "riverpod", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/riverpod-3.1.0", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "riverpod_analyzer_utils", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/riverpod_analyzer_utils-1.0.0-dev.8", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "riverpod_annotation", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/riverpod_annotation-4.0.0", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "riverpod_generator", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/riverpod_generator-4.0.0+1", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "share_plus", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/share_plus-12.0.1", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "share_plus_platform_interface", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/share_plus_platform_interface-6.1.0", - "packageUri": "lib/", - "languageVersion": "2.18" - }, - { - "name": "shelf", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/shelf-1.4.2", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "shelf_packages_handler", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/shelf_packages_handler-3.0.2", - "packageUri": "lib/", - "languageVersion": "2.17" - }, - { - "name": "shelf_static", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/shelf_static-1.1.3", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "shelf_web_socket", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/shelf_web_socket-3.0.0", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "sizer", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/sizer-3.1.3", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "sky_engine", - "rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/bin/cache/pkg/sky_engine", - "packageUri": "lib/", - "languageVersion": "3.8" - }, - { - "name": "source_gen", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/source_gen-4.2.1", - "packageUri": "lib/", - "languageVersion": "3.9" - }, - { - "name": "source_map_stack_trace", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/source_map_stack_trace-2.1.2", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "source_maps", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/source_maps-0.10.13", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "source_span", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/source_span-1.10.2", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "stack_trace", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/stack_trace-1.12.1", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "state_notifier", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/state_notifier-1.0.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "stream_channel", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/stream_channel-2.1.4", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "stream_transform", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/stream_transform-2.1.1", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "string_scanner", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/string_scanner-1.4.1", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "talker", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/talker-5.1.15", - "packageUri": "lib/", - "languageVersion": "2.17" - }, - { - "name": "talker_flutter", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/talker_flutter-5.1.15", - "packageUri": "lib/", - "languageVersion": "3.6" - }, - { - "name": "talker_logger", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/talker_logger-5.1.15", - "packageUri": "lib/", - "languageVersion": "2.15" - }, - { - "name": "term_glyph", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/term_glyph-1.2.2", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "test", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/test-1.26.3", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "test_api", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/test_api-0.7.7", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "test_core", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/test_core-0.6.12", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "typed_data", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/typed_data-1.4.0", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "url_launcher_linux", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.2", - "packageUri": "lib/", - "languageVersion": "3.8" - }, - { - "name": "url_launcher_platform_interface", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/url_launcher_platform_interface-2.3.2", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "url_launcher_web", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.2", - "packageUri": "lib/", - "languageVersion": "3.10" - }, - { - "name": "url_launcher_windows", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.5", - "packageUri": "lib/", - "languageVersion": "3.8" - }, - { - "name": "uuid", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/uuid-4.5.3", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "vector_math", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/vector_math-2.2.0", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "vm_service", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/vm_service-15.0.2", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "watcher", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/watcher-1.1.4", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "web", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/web-1.1.1", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "web_socket", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/web_socket-1.0.1", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "web_socket_channel", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/web_socket_channel-3.0.3", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "webkit_inspection_protocol", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/webkit_inspection_protocol-1.2.1", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "win32", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/win32-5.15.0", - "packageUri": "lib/", - "languageVersion": "3.8" - }, - { - "name": "xdg_directories", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/xdg_directories-1.1.0", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "xxh3", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/xxh3-1.2.0", - "packageUri": "lib/", - "languageVersion": "2.16" - }, - { - "name": "yaml", - "rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/yaml-3.1.3", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "arbiter", - "rootUri": "../", - "packageUri": "lib/", - "languageVersion": "3.10" - } - ], - "generator": "pub", - "generatorVersion": "3.10.8", - "flutterRoot": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable", - "flutterVersion": "3.38.9", - "pubCache": "file:///Users/kaska/.pub-cache" -} diff --git a/useragent/.dart_tool/package_graph.json b/useragent/.dart_tool/package_graph.json deleted file mode 100644 index 2588cea..0000000 --- a/useragent/.dart_tool/package_graph.json +++ /dev/null @@ -1,1465 +0,0 @@ -{ - "roots": [ - "arbiter" - ], - "packages": [ - { - "name": "arbiter", - "version": "1.0.0+1", - "dependencies": [ - "auto_route", - "biometric_signature", - "cryptography", - "cryptography_flutter", - "cupertino_icons", - "flutter", - "flutter_adaptive_scaffold", - "flutter_hooks", - "flutter_secure_storage", - "grpc", - "hooks_riverpod", - "mtcore", - "riverpod", - "riverpod_annotation", - "sizer", - "talker" - ], - "devDependencies": [ - "auto_route_generator", - "build_runner", - "flutter_lints", - "flutter_test", - "riverpod_generator" - ] - }, - { - "name": "auto_route_generator", - "version": "10.4.0", - "dependencies": [ - "analyzer", - "auto_route", - "build", - "code_builder", - "dart_style", - "glob", - "lean_builder", - "path", - "source_gen" - ] - }, - { - "name": "build_runner", - "version": "2.12.2", - "dependencies": [ - "analyzer", - "args", - "async", - "build", - "build_config", - "build_daemon", - "built_collection", - "built_value", - "code_builder", - "collection", - "convert", - "crypto", - "dart_style", - "glob", - "graphs", - "http_multi_server", - "io", - "json_annotation", - "logging", - "meta", - "mime", - "package_config", - "path", - "pool", - "pub_semver", - "shelf", - "shelf_web_socket", - "stream_transform", - "watcher", - "web_socket_channel", - "yaml" - ] - }, - { - "name": "riverpod_generator", - "version": "4.0.0+1", - "dependencies": [ - "analyzer", - "analyzer_buffer", - "build", - "build_config", - "collection", - "crypto", - "meta", - "mockito", - "path", - "riverpod_analyzer_utils", - "riverpod_annotation", - "source_gen" - ] - }, - { - "name": "flutter_lints", - "version": "6.0.0", - "dependencies": [ - "lints" - ] - }, - { - "name": "flutter_test", - "version": "0.0.0", - "dependencies": [ - "clock", - "collection", - "fake_async", - "flutter", - "leak_tracker_flutter_testing", - "matcher", - "meta", - "path", - "stack_trace", - "stream_channel", - "test_api", - "vector_math" - ] - }, - { - "name": "auto_route", - "version": "11.1.0", - "dependencies": [ - "collection", - "flutter", - "meta", - "path", - "web" - ] - }, - { - "name": "flutter_hooks", - "version": "0.21.3+1", - "dependencies": [ - "flutter" - ] - }, - { - "name": "grpc", - "version": "5.1.0", - "dependencies": [ - "async", - "clock", - "crypto", - "fixnum", - "googleapis_auth", - "http", - "http2", - "meta", - "protobuf", - "web" - ] - }, - { - "name": "riverpod_annotation", - "version": "4.0.0", - "dependencies": [ - "meta", - "riverpod" - ] - }, - { - "name": "cryptography_flutter", - "version": "2.3.4", - "dependencies": [ - "cryptography", - "flutter" - ] - }, - { - "name": "flutter_secure_storage", - "version": "10.0.0", - "dependencies": [ - "flutter", - "flutter_secure_storage_darwin", - "flutter_secure_storage_linux", - "flutter_secure_storage_platform_interface", - "flutter_secure_storage_web", - "flutter_secure_storage_windows", - "meta" - ] - }, - { - "name": "cryptography", - "version": "2.9.0", - "dependencies": [ - "collection", - "crypto", - "ffi", - "meta", - "typed_data" - ] - }, - { - "name": "mtcore", - "version": "1.0.6", - "dependencies": [ - "bloc", - "flutter", - "flutter_bloc", - "flutter_hooks", - "flutter_riverpod", - "flutter_spinkit", - "freezed_annotation", - "hooks_riverpod", - "percent_indicator", - "rive", - "riverpod_annotation", - "talker_flutter" - ] - }, - { - "name": "biometric_signature", - "version": "10.2.0", - "dependencies": [ - "flutter", - "plugin_platform_interface" - ] - }, - { - "name": "sizer", - "version": "3.1.3", - "dependencies": [ - "flutter" - ] - }, - { - "name": "riverpod", - "version": "3.1.0", - "dependencies": [ - "async", - "clock", - "collection", - "meta", - "state_notifier", - "test" - ] - }, - { - "name": "talker", - "version": "5.1.15", - "dependencies": [ - "talker_logger" - ] - }, - { - "name": "flutter_adaptive_scaffold", - "version": "1.0.0+1", - "dependencies": [ - "flutter" - ] - }, - { - "name": "cupertino_icons", - "version": "1.0.8", - "dependencies": [] - }, - { - "name": "flutter", - "version": "0.0.0", - "dependencies": [ - "characters", - "collection", - "material_color_utilities", - "meta", - "sky_engine", - "vector_math" - ] - }, - { - "name": "yaml", - "version": "3.1.3", - "dependencies": [ - "collection", - "source_span", - "string_scanner" - ] - }, - { - "name": "path", - "version": "1.9.1", - "dependencies": [] - }, - { - "name": "meta", - "version": "1.17.0", - "dependencies": [] - }, - { - "name": "glob", - "version": "2.1.3", - "dependencies": [ - "async", - "collection", - "file", - "path", - "string_scanner" - ] - }, - { - "name": "collection", - "version": "1.19.1", - "dependencies": [] - }, - { - "name": "dart_style", - "version": "3.1.3", - "dependencies": [ - "analyzer", - "args", - "collection", - "package_config", - "path", - "pub_semver", - "source_span", - "yaml" - ] - }, - { - "name": "async", - "version": "2.13.0", - "dependencies": [ - "collection", - "meta" - ] - }, - { - "name": "xxh3", - "version": "1.2.0", - "dependencies": [] - }, - { - "name": "stack_trace", - "version": "1.12.1", - "dependencies": [ - "path" - ] - }, - { - "name": "hotreloader", - "version": "4.3.0", - "dependencies": [ - "collection", - "logging", - "path", - "stream_transform", - "vm_service", - "watcher" - ] - }, - { - "name": "frontend_server_client", - "version": "4.0.0", - "dependencies": [ - "async", - "path" - ] - }, - { - "name": "args", - "version": "2.7.0", - "dependencies": [] - }, - { - "name": "ansicolor", - "version": "2.0.3", - "dependencies": [] - }, - { - "name": "matcher", - "version": "0.12.17", - "dependencies": [ - "async", - "meta", - "stack_trace", - "term_glyph", - "test_api" - ] - }, - { - "name": "built_collection", - "version": "5.1.1", - "dependencies": [] - }, - { - "name": "test_api", - "version": "0.7.7", - "dependencies": [ - "async", - "boolean_selector", - "collection", - "meta", - "source_span", - "stack_trace", - "stream_channel", - "string_scanner", - "term_glyph" - ] - }, - { - "name": "stream_channel", - "version": "2.1.4", - "dependencies": [ - "async" - ] - }, - { - "name": "build_config", - "version": "1.3.0", - "dependencies": [ - "checked_yaml", - "json_annotation", - "path", - "pubspec_parse" - ] - }, - { - "name": "test", - "version": "1.26.3", - "dependencies": [ - "analyzer", - "async", - "boolean_selector", - "collection", - "coverage", - "http_multi_server", - "io", - "js", - "matcher", - "node_preamble", - "package_config", - "path", - "pool", - "shelf", - "shelf_packages_handler", - "shelf_static", - "shelf_web_socket", - "source_span", - "stack_trace", - "stream_channel", - "test_api", - "test_core", - "typed_data", - "web_socket_channel", - "webkit_inspection_protocol", - "yaml" - ] - }, - { - "name": "riverpod_analyzer_utils", - "version": "1.0.0-dev.8", - "dependencies": [ - "analyzer", - "analyzer_buffer", - "collection", - "crypto", - "freezed_annotation", - "meta", - "path", - "source_span" - ] - }, - { - "name": "test_core", - "version": "0.6.12", - "dependencies": [ - "analyzer", - "args", - "async", - "boolean_selector", - "collection", - "coverage", - "frontend_server_client", - "glob", - "io", - "meta", - "package_config", - "path", - "pool", - "source_map_stack_trace", - "source_maps", - "source_span", - "stack_trace", - "stream_channel", - "test_api", - "vm_service", - "yaml" - ] - }, - { - "name": "freezed_annotation", - "version": "3.1.0", - "dependencies": [ - "collection", - "json_annotation", - "meta" - ] - }, - { - "name": "vector_math", - "version": "2.2.0", - "dependencies": [] - }, - { - "name": "leak_tracker_flutter_testing", - "version": "3.0.10", - "dependencies": [ - "flutter", - "leak_tracker", - "leak_tracker_testing", - "matcher", - "meta" - ] - }, - { - "name": "fake_async", - "version": "1.3.3", - "dependencies": [ - "clock", - "collection" - ] - }, - { - "name": "clock", - "version": "1.1.2", - "dependencies": [] - }, - { - "name": "web", - "version": "1.1.1", - "dependencies": [] - }, - { - "name": "protobuf", - "version": "6.0.0", - "dependencies": [ - "collection", - "fixnum", - "meta" - ] - }, - { - "name": "http2", - "version": "2.3.1", - "dependencies": [] - }, - { - "name": "fixnum", - "version": "1.1.1", - "dependencies": [] - }, - { - "name": "flutter_secure_storage_windows", - "version": "4.1.0", - "dependencies": [ - "ffi", - "flutter", - "flutter_secure_storage_platform_interface", - "path", - "path_provider", - "win32" - ] - }, - { - "name": "flutter_secure_storage_web", - "version": "2.1.0", - "dependencies": [ - "flutter", - "flutter_secure_storage_platform_interface", - "flutter_web_plugins", - "web" - ] - }, - { - "name": "flutter_secure_storage_platform_interface", - "version": "2.0.1", - "dependencies": [ - "flutter", - "plugin_platform_interface" - ] - }, - { - "name": "flutter_secure_storage_linux", - "version": "3.0.0", - "dependencies": [ - "flutter", - "flutter_secure_storage_platform_interface" - ] - }, - { - "name": "flutter_secure_storage_darwin", - "version": "0.2.0", - "dependencies": [ - "flutter", - "plugin_platform_interface" - ] - }, - { - "name": "flutter_web_plugins", - "version": "0.0.0", - "dependencies": [ - "flutter" - ] - }, - { - "name": "percent_indicator", - "version": "4.2.5", - "dependencies": [ - "flutter" - ] - }, - { - "name": "flutter_spinkit", - "version": "5.2.2", - "dependencies": [ - "flutter" - ] - }, - { - "name": "flutter_bloc", - "version": "9.1.1", - "dependencies": [ - "bloc", - "flutter", - "provider" - ] - }, - { - "name": "group_button", - "version": "5.3.4", - "dependencies": [ - "flutter" - ] - }, - { - "name": "nested", - "version": "1.0.0", - "dependencies": [ - "flutter" - ] - }, - { - "name": "talker_logger", - "version": "5.1.15", - "dependencies": [ - "ansicolor", - "web" - ] - }, - { - "name": "sky_engine", - "version": "0.0.0", - "dependencies": [] - }, - { - "name": "material_color_utilities", - "version": "0.11.1", - "dependencies": [ - "collection" - ] - }, - { - "name": "characters", - "version": "1.4.0", - "dependencies": [] - }, - { - "name": "hooks_riverpod", - "version": "3.1.0", - "dependencies": [ - "collection", - "flutter", - "flutter_hooks", - "flutter_riverpod", - "flutter_test", - "riverpod", - "state_notifier" - ] - }, - { - "name": "flutter_riverpod", - "version": "3.1.0", - "dependencies": [ - "collection", - "flutter", - "flutter_test", - "meta", - "riverpod", - "state_notifier" - ] - }, - { - "name": "source_span", - "version": "1.10.2", - "dependencies": [ - "collection", - "path", - "term_glyph" - ] - }, - { - "name": "crypto", - "version": "3.0.7", - "dependencies": [ - "typed_data" - ] - }, - { - "name": "googleapis_auth", - "version": "2.0.0", - "dependencies": [ - "args", - "crypto", - "google_identity_services_web", - "http", - "http_parser" - ] - }, - { - "name": "google_identity_services_web", - "version": "0.3.3+1", - "dependencies": [ - "meta", - "web" - ] - }, - { - "name": "code_builder", - "version": "4.11.1", - "dependencies": [ - "built_collection", - "built_value", - "collection", - "matcher", - "meta" - ] - }, - { - "name": "lints", - "version": "6.1.0", - "dependencies": [] - }, - { - "name": "leak_tracker_testing", - "version": "3.0.2", - "dependencies": [ - "leak_tracker", - "matcher", - "meta" - ] - }, - { - "name": "leak_tracker", - "version": "11.0.2", - "dependencies": [ - "clock", - "collection", - "meta", - "path", - "vm_service" - ] - }, - { - "name": "state_notifier", - "version": "1.0.0", - "dependencies": [ - "meta" - ] - }, - { - "name": "path_provider", - "version": "2.1.5", - "dependencies": [ - "flutter", - "path_provider_android", - "path_provider_foundation", - "path_provider_linux", - "path_provider_platform_interface", - "path_provider_windows" - ] - }, - { - "name": "path_provider_linux", - "version": "2.2.1", - "dependencies": [ - "ffi", - "flutter", - "path", - "path_provider_platform_interface", - "xdg_directories" - ] - }, - { - "name": "pub_semver", - "version": "2.2.0", - "dependencies": [ - "collection" - ] - }, - { - "name": "package_config", - "version": "2.2.0", - "dependencies": [ - "path" - ] - }, - { - "name": "term_glyph", - "version": "1.2.2", - "dependencies": [] - }, - { - "name": "lean_builder", - "version": "0.1.6", - "dependencies": [ - "_fe_analyzer_shared", - "analyzer", - "ansicolor", - "args", - "collection", - "dart_style", - "frontend_server_client", - "glob", - "hotreloader", - "meta", - "path", - "source_span", - "stack_trace", - "watcher", - "xxh3", - "yaml" - ] - }, - { - "name": "watcher", - "version": "1.1.4", - "dependencies": [ - "async", - "path" - ] - }, - { - "name": "boolean_selector", - "version": "2.1.2", - "dependencies": [ - "source_span", - "string_scanner" - ] - }, - { - "name": "pool", - "version": "1.5.2", - "dependencies": [ - "async", - "stack_trace" - ] - }, - { - "name": "analyzer_buffer", - "version": "0.1.11", - "dependencies": [ - "analyzer", - "collection", - "meta", - "path", - "source_gen" - ] - }, - { - "name": "shelf_packages_handler", - "version": "3.0.2", - "dependencies": [ - "path", - "shelf", - "shelf_static" - ] - }, - { - "name": "node_preamble", - "version": "2.0.2", - "dependencies": [] - }, - { - "name": "bloc", - "version": "9.2.0", - "dependencies": [ - "meta" - ] - }, - { - "name": "path_provider_windows", - "version": "2.3.0", - "dependencies": [ - "ffi", - "flutter", - "path", - "path_provider_platform_interface" - ] - }, - { - "name": "path_provider_platform_interface", - "version": "2.1.2", - "dependencies": [ - "flutter", - "platform", - "plugin_platform_interface" - ] - }, - { - "name": "plugin_platform_interface", - "version": "2.1.8", - "dependencies": [ - "meta" - ] - }, - { - "name": "source_map_stack_trace", - "version": "2.1.2", - "dependencies": [ - "path", - "source_maps", - "stack_trace" - ] - }, - { - "name": "string_scanner", - "version": "1.4.1", - "dependencies": [ - "source_span" - ] - }, - { - "name": "typed_data", - "version": "1.4.0", - "dependencies": [ - "collection" - ] - }, - { - "name": "file", - "version": "7.0.1", - "dependencies": [ - "meta", - "path" - ] - }, - { - "name": "stream_transform", - "version": "2.1.1", - "dependencies": [] - }, - { - "name": "json_annotation", - "version": "4.11.0", - "dependencies": [ - "meta" - ] - }, - { - "name": "graphs", - "version": "2.3.2", - "dependencies": [ - "collection" - ] - }, - { - "name": "analyzer", - "version": "8.4.1", - "dependencies": [ - "_fe_analyzer_shared", - "collection", - "convert", - "crypto", - "glob", - "meta", - "package_config", - "path", - "pub_semver", - "source_span", - "watcher", - "yaml" - ] - }, - { - "name": "_fe_analyzer_shared", - "version": "91.0.0", - "dependencies": [ - "meta" - ] - }, - { - "name": "source_maps", - "version": "0.10.13", - "dependencies": [ - "source_span" - ] - }, - { - "name": "rive", - "version": "0.14.4", - "dependencies": [ - "flutter", - "flutter_web_plugins", - "meta", - "rive_native" - ] - }, - { - "name": "rive_native", - "version": "0.1.4", - "dependencies": [ - "args", - "ffi", - "flutter", - "flutter_web_plugins", - "graphs", - "http", - "meta", - "path", - "plugin_platform_interface", - "vector_math", - "web" - ] - }, - { - "name": "build", - "version": "4.0.4", - "dependencies": [ - "analyzer", - "crypto", - "glob", - "logging", - "package_config", - "path" - ] - }, - { - "name": "io", - "version": "1.0.5", - "dependencies": [ - "meta", - "path", - "string_scanner" - ] - }, - { - "name": "http_multi_server", - "version": "3.2.2", - "dependencies": [ - "async" - ] - }, - { - "name": "checked_yaml", - "version": "2.0.4", - "dependencies": [ - "json_annotation", - "source_span", - "yaml" - ] - }, - { - "name": "web_socket_channel", - "version": "3.0.3", - "dependencies": [ - "async", - "crypto", - "stream_channel", - "web", - "web_socket" - ] - }, - { - "name": "shelf_web_socket", - "version": "3.0.0", - "dependencies": [ - "shelf", - "stream_channel", - "web_socket_channel" - ] - }, - { - "name": "web_socket", - "version": "1.0.1", - "dependencies": [ - "web" - ] - }, - { - "name": "webkit_inspection_protocol", - "version": "1.2.1", - "dependencies": [ - "logging" - ] - }, - { - "name": "shelf_static", - "version": "1.1.3", - "dependencies": [ - "convert", - "http_parser", - "mime", - "path", - "shelf" - ] - }, - { - "name": "http", - "version": "1.6.0", - "dependencies": [ - "async", - "http_parser", - "meta", - "web" - ] - }, - { - "name": "ffi", - "version": "2.2.0", - "dependencies": [] - }, - { - "name": "convert", - "version": "3.1.2", - "dependencies": [ - "typed_data" - ] - }, - { - "name": "http_parser", - "version": "4.1.2", - "dependencies": [ - "collection", - "source_span", - "string_scanner", - "typed_data" - ] - }, - { - "name": "logging", - "version": "1.3.0", - "dependencies": [] - }, - { - "name": "build_daemon", - "version": "4.1.1", - "dependencies": [ - "built_collection", - "built_value", - "crypto", - "http_multi_server", - "logging", - "path", - "pool", - "shelf", - "shelf_web_socket", - "stream_transform", - "watcher", - "web_socket_channel" - ] - }, - { - "name": "js", - "version": "0.7.2", - "dependencies": [] - }, - { - "name": "mime", - "version": "2.0.0", - "dependencies": [] - }, - { - "name": "source_gen", - "version": "4.2.1", - "dependencies": [ - "analyzer", - "async", - "build", - "dart_style", - "glob", - "path", - "pub_semver", - "source_span", - "yaml" - ] - }, - { - "name": "mockito", - "version": "5.6.3", - "dependencies": [ - "analyzer", - "build", - "code_builder", - "collection", - "dart_style", - "matcher", - "meta", - "path", - "source_gen", - "test_api" - ] - }, - { - "name": "path_provider_foundation", - "version": "2.6.0", - "dependencies": [ - "ffi", - "flutter", - "objective_c", - "path_provider_platform_interface" - ] - }, - { - "name": "objective_c", - "version": "9.3.0", - "dependencies": [ - "code_assets", - "collection", - "ffi", - "hooks", - "logging", - "native_toolchain_c", - "pub_semver" - ] - }, - { - "name": "code_assets", - "version": "1.0.0", - "dependencies": [ - "collection", - "hooks" - ] - }, - { - "name": "native_toolchain_c", - "version": "0.17.5", - "dependencies": [ - "code_assets", - "glob", - "hooks", - "logging", - "meta", - "pub_semver" - ] - }, - { - "name": "hooks", - "version": "1.0.2", - "dependencies": [ - "collection", - "crypto", - "logging", - "meta", - "pub_semver", - "yaml" - ] - }, - { - "name": "pubspec_parse", - "version": "1.5.0", - "dependencies": [ - "checked_yaml", - "collection", - "json_annotation", - "pub_semver", - "yaml" - ] - }, - { - "name": "platform", - "version": "3.1.6", - "dependencies": [] - }, - { - "name": "built_value", - "version": "8.12.4", - "dependencies": [ - "built_collection", - "collection", - "fixnum", - "meta" - ] - }, - { - "name": "shelf", - "version": "1.4.2", - "dependencies": [ - "async", - "collection", - "http_parser", - "path", - "stack_trace", - "stream_channel" - ] - }, - { - "name": "xdg_directories", - "version": "1.1.0", - "dependencies": [ - "meta", - "path" - ] - }, - { - "name": "provider", - "version": "6.1.5+1", - "dependencies": [ - "collection", - "flutter", - "nested" - ] - }, - { - "name": "vm_service", - "version": "15.0.2", - "dependencies": [] - }, - { - "name": "win32", - "version": "5.15.0", - "dependencies": [ - "ffi" - ] - }, - { - "name": "talker_flutter", - "version": "5.1.15", - "dependencies": [ - "flutter", - "group_button", - "path_provider", - "share_plus", - "talker", - "web" - ] - }, - { - "name": "share_plus", - "version": "12.0.1", - "dependencies": [ - "cross_file", - "ffi", - "file", - "flutter", - "flutter_web_plugins", - "meta", - "mime", - "share_plus_platform_interface", - "url_launcher_linux", - "url_launcher_platform_interface", - "url_launcher_web", - "url_launcher_windows", - "web", - "win32" - ] - }, - { - "name": "url_launcher_platform_interface", - "version": "2.3.2", - "dependencies": [ - "flutter", - "plugin_platform_interface" - ] - }, - { - "name": "share_plus_platform_interface", - "version": "6.1.0", - "dependencies": [ - "cross_file", - "flutter", - "meta", - "mime", - "path_provider", - "plugin_platform_interface", - "uuid" - ] - }, - { - "name": "url_launcher_windows", - "version": "3.1.5", - "dependencies": [ - "flutter", - "url_launcher_platform_interface" - ] - }, - { - "name": "url_launcher_linux", - "version": "3.2.2", - "dependencies": [ - "flutter", - "url_launcher_platform_interface" - ] - }, - { - "name": "cross_file", - "version": "0.3.5+2", - "dependencies": [ - "meta", - "web" - ] - }, - { - "name": "url_launcher_web", - "version": "2.4.2", - "dependencies": [ - "flutter", - "flutter_web_plugins", - "url_launcher_platform_interface", - "web" - ] - }, - { - "name": "path_provider_android", - "version": "2.2.22", - "dependencies": [ - "flutter", - "path_provider_platform_interface" - ] - }, - { - "name": "uuid", - "version": "4.5.3", - "dependencies": [ - "crypto", - "fixnum" - ] - }, - { - "name": "coverage", - "version": "1.15.0", - "dependencies": [ - "args", - "cli_config", - "glob", - "logging", - "meta", - "package_config", - "path", - "source_maps", - "stack_trace", - "vm_service", - "yaml" - ] - }, - { - "name": "cli_config", - "version": "0.2.0", - "dependencies": [ - "args", - "yaml" - ] - } - ], - "configVersion": 1 -} \ No newline at end of file diff --git a/useragent/.dart_tool/version b/useragent/.dart_tool/version deleted file mode 100644 index 75fffa6..0000000 --- a/useragent/.dart_tool/version +++ /dev/null @@ -1 +0,0 @@ -3.38.9 \ No newline at end of file diff --git a/useragent/lib/features/arbiter_url.dart b/useragent/lib/features/arbiter_url.dart new file mode 100644 index 0000000..d71d236 --- /dev/null +++ b/useragent/lib/features/arbiter_url.dart @@ -0,0 +1,56 @@ +import 'dart:convert'; + +class ArbiterUrl { + const ArbiterUrl({ + required this.host, + required this.port, + required this.caCert, + this.bootstrapToken, + }); + + final String host; + final int port; + final List caCert; + final String? bootstrapToken; + + static const _scheme = 'arbiter'; + static const _certQueryKey = 'cert'; + static const _bootstrapTokenQueryKey = 'bootstrap_token'; + + static ArbiterUrl parse(String value) { + final uri = Uri.tryParse(value); + if (uri == null || uri.scheme != _scheme) { + throw const FormatException("Invalid URL scheme, expected 'arbiter://'"); + } + + if (uri.host.isEmpty) { + throw const FormatException('Missing host in URL'); + } + + if (!uri.hasPort) { + throw const FormatException('Missing port in URL'); + } + + final cert = uri.queryParameters[_certQueryKey]; + if (cert == null || cert.isEmpty) { + throw const FormatException("Missing 'cert' query parameter in URL"); + } + + final decodedCert = _decodeCert(cert); + + return ArbiterUrl( + host: uri.host, + port: uri.port, + caCert: decodedCert, + bootstrapToken: uri.queryParameters[_bootstrapTokenQueryKey], + ); + } + + static List _decodeCert(String cert) { + try { + return base64Url.decode(base64Url.normalize(cert)); + } on FormatException catch (error) { + throw FormatException("Invalid base64 in 'cert' query parameter: ${error.message}"); + } + } +} diff --git a/useragent/lib/features/connection/connection.dart b/useragent/lib/features/connection/connection.dart new file mode 100644 index 0000000..944711a --- /dev/null +++ b/useragent/lib/features/connection/connection.dart @@ -0,0 +1,3 @@ + + +class Connection {} \ No newline at end of file diff --git a/useragent/lib/features/pk_manager.dart b/useragent/lib/features/pk_manager.dart index a9bfb62..ff6db23 100644 --- a/useragent/lib/features/pk_manager.dart +++ b/useragent/lib/features/pk_manager.dart @@ -1,6 +1,3 @@ - -import 'package:flutter/services.dart'; - enum KeyAlgorithm { rsa, ecdsa, ed25519 } @@ -16,4 +13,4 @@ abstract class KeyHandle { abstract class KeyManager { Future get(); Future create(); -} \ No newline at end of file +} diff --git a/useragent/lib/features/server_info_storage.dart b/useragent/lib/features/server_info_storage.dart new file mode 100644 index 0000000..704939e --- /dev/null +++ b/useragent/lib/features/server_info_storage.dart @@ -0,0 +1,67 @@ +import 'dart:convert'; + +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + +class StoredServerInfo { + const StoredServerInfo({ + required this.address, + required this.port, + required this.caCertFingerprint, + }); + + final String address; + final int port; + final String caCertFingerprint; + + Map toJson() => { + 'address': address, + 'port': port, + 'caCertFingerprint': caCertFingerprint, + }; + + factory StoredServerInfo.fromJson(Map json) { + return StoredServerInfo( + address: json['address'] as String, + port: json['port'] as int, + caCertFingerprint: json['caCertFingerprint'] as String, + ); + } +} + +abstract class ServerInfoStorage { + Future load(); + Future save(StoredServerInfo serverInfo); + Future clear(); +} + +class SecureServerInfoStorage implements ServerInfoStorage { + static const _storageKey = 'server_info'; + + const SecureServerInfoStorage(); + + static const _storage = FlutterSecureStorage(); + + @override + Future load() async { + final rawValue = await _storage.read(key: _storageKey); + if (rawValue == null) { + return null; + } + + final decoded = jsonDecode(rawValue) as Map; + return StoredServerInfo.fromJson(decoded); + } + + @override + Future save(StoredServerInfo serverInfo) { + return _storage.write( + key: _storageKey, + value: jsonEncode(serverInfo.toJson()), + ); + } + + @override + Future clear() { + return _storage.delete(key: _storageKey); + } +} diff --git a/useragent/lib/main.dart b/useragent/lib/main.dart index 89df0e1..5fac968 100644 --- a/useragent/lib/main.dart +++ b/useragent/lib/main.dart @@ -1,14 +1,35 @@ import 'package:arbiter/router.dart'; import 'package:flutter/material.dart' hide Router; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:sizer/sizer.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); - runApp( - ProviderScope( - child: MaterialApp.router( - routerConfig: Router().config(), - ), - ), - ); + runApp(const ProviderScope(child: App())); +} + +class App extends StatefulWidget { + const App({super.key}); + + @override + State createState() => _AppState(); +} + +class _AppState extends State { + late final Router _router; + + @override + void initState() { + super.initState(); + _router = Router(); + } + + @override + Widget build(BuildContext context) { + return Sizer( + builder: (context, orientation, deviceType) { + return MaterialApp.router(routerConfig: _router.config()); + }, + ); + } } diff --git a/useragent/lib/providers/server_info.dart b/useragent/lib/providers/server_info.dart new file mode 100644 index 0000000..651143e --- /dev/null +++ b/useragent/lib/providers/server_info.dart @@ -0,0 +1,51 @@ +import 'package:arbiter/features/server_info_storage.dart'; +import 'package:cryptography/cryptography.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'server_info.g.dart'; + +@riverpod +ServerInfoStorage serverInfoStorage(Ref ref) { + return const SecureServerInfoStorage(); +} + +@Riverpod(keepAlive: true) +class ServerInfo extends _$ServerInfo { + @override + Future build() { + final storage = ref.watch(serverInfoStorageProvider); + return storage.load(); + } + + Future save({ + required String address, + required int port, + required List caCert, + }) async { + final storage = ref.read(serverInfoStorageProvider); + final fingerprint = await _fingerprint(caCert); + final serverInfo = StoredServerInfo( + address: address, + port: port, + caCertFingerprint: fingerprint, + ); + + state = await AsyncValue.guard(() async { + await storage.save(serverInfo); + return serverInfo; + }); + } + + Future clear() async { + final storage = ref.read(serverInfoStorageProvider); + state = await AsyncValue.guard(() async { + await storage.clear(); + return null; + }); + } + + Future _fingerprint(List caCert) async { + final digest = await Sha256().hash(caCert); + return digest.bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(); + } +} diff --git a/useragent/lib/providers/server_info.g.dart b/useragent/lib/providers/server_info.g.dart new file mode 100644 index 0000000..ec0afbc --- /dev/null +++ b/useragent/lib/providers/server_info.g.dart @@ -0,0 +1,102 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'server_info.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(serverInfoStorage) +final serverInfoStorageProvider = ServerInfoStorageProvider._(); + +final class ServerInfoStorageProvider + extends + $FunctionalProvider< + ServerInfoStorage, + ServerInfoStorage, + ServerInfoStorage + > + with $Provider { + ServerInfoStorageProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'serverInfoStorageProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$serverInfoStorageHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + ServerInfoStorage create(Ref ref) { + return serverInfoStorage(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(ServerInfoStorage value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$serverInfoStorageHash() => r'fc06865e7314b1a2493c5de1a9347923a3d21c5c'; + +@ProviderFor(ServerInfo) +final serverInfoProvider = ServerInfoProvider._(); + +final class ServerInfoProvider + extends $AsyncNotifierProvider { + ServerInfoProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'serverInfoProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$serverInfoHash(); + + @$internal + @override + ServerInfo create() => ServerInfo(); +} + +String _$serverInfoHash() => r'6e94f52de03259695a2166b766004eec60ff45fa'; + +abstract class _$ServerInfo extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + final ref = + this.ref as $Ref, StoredServerInfo?>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, StoredServerInfo?>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/useragent/lib/router.dart b/useragent/lib/router.dart index bcbc314..5462fbd 100644 --- a/useragent/lib/router.dart +++ b/useragent/lib/router.dart @@ -1,8 +1,4 @@ -import 'package:arbiter/screens/dashboard/about.dart'; -import 'package:arbiter/screens/dashboard/calc.dart'; import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; import 'router.gr.dart'; @@ -11,6 +7,7 @@ class Router extends RootStackRouter { @override List get routes => [ AutoRoute(page: Bootstrap.page, path: '/bootstrap', initial: true), + AutoRoute(page: ServerInfoSetupRoute.page, path: '/server-info'), AutoRoute( page: DashboardRouter.page, diff --git a/useragent/lib/router.gr.dart b/useragent/lib/router.gr.dart index 824cf97..704418e 100644 --- a/useragent/lib/router.gr.dart +++ b/useragent/lib/router.gr.dart @@ -13,33 +13,34 @@ import 'package:arbiter/screens/bootstrap.dart' as _i2; import 'package:arbiter/screens/dashboard.dart' as _i4; import 'package:arbiter/screens/dashboard/about.dart' as _i1; import 'package:arbiter/screens/dashboard/calc.dart' as _i3; -import 'package:auto_route/auto_route.dart' as _i5; +import 'package:arbiter/screens/server_info_setup.dart' as _i5; +import 'package:auto_route/auto_route.dart' as _i6; /// generated route for /// [_i1.AboutScreen] -class AboutRoute extends _i5.PageRouteInfo { - const AboutRoute({List<_i5.PageRouteInfo>? children}) +class AboutRoute extends _i6.PageRouteInfo { + const AboutRoute({List<_i6.PageRouteInfo>? children}) : super(AboutRoute.name, initialChildren: children); static const String name = 'AboutRoute'; - static _i5.PageInfo page = _i5.PageInfo( + static _i6.PageInfo page = _i6.PageInfo( name, builder: (data) { - return _i1.AboutScreen(); + return const _i1.AboutScreen(); }, ); } /// generated route for /// [_i2.Bootstrap] -class Bootstrap extends _i5.PageRouteInfo { - const Bootstrap({List<_i5.PageRouteInfo>? children}) +class Bootstrap extends _i6.PageRouteInfo { + const Bootstrap({List<_i6.PageRouteInfo>? children}) : super(Bootstrap.name, initialChildren: children); static const String name = 'Bootstrap'; - static _i5.PageInfo page = _i5.PageInfo( + static _i6.PageInfo page = _i6.PageInfo( name, builder: (data) { return const _i2.Bootstrap(); @@ -49,13 +50,13 @@ class Bootstrap extends _i5.PageRouteInfo { /// generated route for /// [_i3.CalcScreen] -class CalcRoute extends _i5.PageRouteInfo { - const CalcRoute({List<_i5.PageRouteInfo>? children}) +class CalcRoute extends _i6.PageRouteInfo { + const CalcRoute({List<_i6.PageRouteInfo>? children}) : super(CalcRoute.name, initialChildren: children); static const String name = 'CalcRoute'; - static _i5.PageInfo page = _i5.PageInfo( + static _i6.PageInfo page = _i6.PageInfo( name, builder: (data) { return const _i3.CalcScreen(); @@ -65,16 +66,32 @@ class CalcRoute extends _i5.PageRouteInfo { /// generated route for /// [_i4.DashboardRouter] -class DashboardRouter extends _i5.PageRouteInfo { - const DashboardRouter({List<_i5.PageRouteInfo>? children}) +class DashboardRouter extends _i6.PageRouteInfo { + const DashboardRouter({List<_i6.PageRouteInfo>? children}) : super(DashboardRouter.name, initialChildren: children); static const String name = 'DashboardRouter'; - static _i5.PageInfo page = _i5.PageInfo( + static _i6.PageInfo page = _i6.PageInfo( name, builder: (data) { return const _i4.DashboardRouter(); }, ); } + +/// generated route for +/// [_i5.ServerInfoSetupScreen] +class ServerInfoSetupRoute extends _i6.PageRouteInfo { + const ServerInfoSetupRoute({List<_i6.PageRouteInfo>? children}) + : super(ServerInfoSetupRoute.name, initialChildren: children); + + static const String name = 'ServerInfoSetupRoute'; + + static _i6.PageInfo page = _i6.PageInfo( + name, + builder: (data) { + return const _i5.ServerInfoSetupScreen(); + }, + ); +} diff --git a/useragent/lib/screens/bootstrap.dart b/useragent/lib/screens/bootstrap.dart index da215f4..d2e2172 100644 --- a/useragent/lib/screens/bootstrap.dart +++ b/useragent/lib/screens/bootstrap.dart @@ -14,13 +14,13 @@ class Bootstrap extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final container = ProviderScope.containerOf( context); + final container = ProviderScope.containerOf(context); final completer = useMemoized(() { final completer = Completer(); completer.future.then((_) async { if (context.mounted) { final router = AutoRouter.of(context); - router.replace(const DashboardRouter()); + router.replace(const ServerInfoSetupRoute()); } }); diff --git a/useragent/lib/screens/dashboard/about.dart b/useragent/lib/screens/dashboard/about.dart index 29e853d..3745aec 100644 --- a/useragent/lib/screens/dashboard/about.dart +++ b/useragent/lib/screens/dashboard/about.dart @@ -2,9 +2,10 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:mtcore/markettakers.dart' as mt; - @RoutePage() class AboutScreen extends StatelessWidget { + const AboutScreen({super.key}); + @override Widget build(BuildContext context) { return mt.AboutScreen(decription: "Arbiter is bla bla bla"); diff --git a/useragent/lib/screens/server_info_setup.dart b/useragent/lib/screens/server_info_setup.dart new file mode 100644 index 0000000..82873cf --- /dev/null +++ b/useragent/lib/screens/server_info_setup.dart @@ -0,0 +1,284 @@ +import 'package:arbiter/features/arbiter_url.dart'; +import 'package:arbiter/features/server_info_storage.dart'; +import 'package:arbiter/providers/server_info.dart'; +import 'package:arbiter/router.gr.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:sizer/sizer.dart'; + +@RoutePage() +class ServerInfoSetupScreen extends HookConsumerWidget { + const ServerInfoSetupScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final storedServerInfo = ref.watch(serverInfoProvider); + final resolvedServerInfo = storedServerInfo.asData?.value; + final controller = useTextEditingController(); + final errorText = useState(null); + final isSaving = useState(false); + + useEffect(() { + final serverInfo = resolvedServerInfo; + if (serverInfo != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.mounted) { + context.router.replace(const DashboardRouter()); + } + }); + } + return null; + }, [context, resolvedServerInfo]); + + Future saveRemoteServerInfo() async { + errorText.value = null; + isSaving.value = true; + + try { + final arbiterUrl = ArbiterUrl.parse(controller.text.trim()); + await ref + .read(serverInfoProvider.notifier) + .save( + address: arbiterUrl.host, + port: arbiterUrl.port, + caCert: arbiterUrl.caCert, + ); + + if (context.mounted) { + context.router.replace(const DashboardRouter()); + } + } on FormatException catch (error) { + errorText.value = error.message; + } catch (_) { + errorText.value = 'Failed to store connection settings.'; + } finally { + isSaving.value = false; + } + } + + if (storedServerInfo.isLoading) { + return const Scaffold(body: Center(child: CircularProgressIndicator())); + } + + if (storedServerInfo.hasError) { + return Scaffold( + appBar: AppBar(title: const Text('Server Info Setup')), + body: Center( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 6.w), + child: Text( + 'Failed to load stored server info.', + style: Theme.of(context).textTheme.bodyLarge, + textAlign: TextAlign.center, + ), + ), + ), + ); + } + + final serverInfo = resolvedServerInfo; + if (serverInfo != null) { + return _RedirectingView(serverInfo: serverInfo); + } + + return Scaffold( + appBar: AppBar(title: const Text('Server Info Setup')), + body: LayoutBuilder( + builder: (context, constraints) { + final useRowLayout = constraints.maxWidth > constraints.maxHeight; + final gap = 2.h; + final horizontalPadding = 6.w; + final verticalPadding = 3.h; + final options = [ + const _OptionCard( + title: 'Local', + subtitle: 'Will start and connect to a local service in a future update.', + enabled: false, + child: SizedBox.shrink(), + ), + _OptionCard( + title: 'Remote', + subtitle: 'Paste an Arbiter URL to store the server address, port, and CA fingerprint.', + child: _RemoteConnectionForm( + controller: controller, + errorText: errorText.value, + isSaving: isSaving.value, + onSave: saveRemoteServerInfo, + ), + ), + ]; + + return ListView( + padding: EdgeInsets.symmetric( + horizontal: horizontalPadding, + vertical: verticalPadding, + ), + children: [ + _SetupHeader(gap: gap), + SizedBox(height: gap), + useRowLayout + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: options[0]), + SizedBox(width: 3.w), + Expanded(child: options[1]), + ], + ) + : Column( + children: [ + options[0], + SizedBox(height: gap), + options[1], + ], + ), + ], + ); + }, + ), + ); + } +} + +class _SetupHeader extends StatelessWidget { + const _SetupHeader({required this.gap}); + + final double gap; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Choose how this user agent should reach Arbiter.', + style: Theme.of(context).textTheme.headlineSmall, + ), + SizedBox(height: gap * 0.5), + Text( + 'Remote accepts the shareable Arbiter URL emitted by the server.', + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ); + } +} + +class _RedirectingView extends StatelessWidget { + const _RedirectingView({required this.serverInfo}); + + final StoredServerInfo serverInfo; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CircularProgressIndicator(), + SizedBox(height: 2.h), + Text('Using saved server ${serverInfo.address}:${serverInfo.port}'), + ], + ), + ), + ); + } +} + +class _RemoteConnectionForm extends StatelessWidget { + const _RemoteConnectionForm({ + required this.controller, + required this.errorText, + required this.isSaving, + required this.onSave, + }); + + final TextEditingController controller; + final String? errorText; + final bool isSaving; + final VoidCallback onSave; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + controller: controller, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Arbiter URL', + hintText: 'arbiter://host:port?cert=...', + ), + minLines: 2, + maxLines: 4, + ), + if (errorText != null) ...[ + SizedBox(height: 1.5.h), + Text( + errorText!, + style: TextStyle(color: Theme.of(context).colorScheme.error), + ), + ], + SizedBox(height: 2.h), + Align( + alignment: Alignment.centerLeft, + child: FilledButton( + onPressed: isSaving ? null : onSave, + child: Text(isSaving ? 'Saving...' : 'Save connection'), + ), + ), + ], + ); + } +} + +class _OptionCard extends StatelessWidget { + const _OptionCard({ + required this.title, + required this.subtitle, + required this.child, + this.enabled = true, + }); + + final String title; + final String subtitle; + final Widget child; + final bool enabled; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Card( + child: Padding( + padding: EdgeInsets.all(2.h), + child: Opacity( + opacity: enabled ? 1 : 0.55, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text(title, style: theme.textTheme.titleLarge), + if (!enabled) ...[ + SizedBox(width: 2.w), + const Chip(label: Text('Coming soon')), + ], + ], + ), + SizedBox(height: 1.h), + Text(subtitle, style: theme.textTheme.bodyMedium), + if (enabled) ...[ + SizedBox(height: 2.h), + child, + ], + ], + ), + ), + ), + ); + } +} diff --git a/useragent/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/useragent/macos/Flutter/ephemeral/Flutter-Generated.xcconfig deleted file mode 100644 index 1e123d0..0000000 --- a/useragent/macos/Flutter/ephemeral/Flutter-Generated.xcconfig +++ /dev/null @@ -1,13 +0,0 @@ -// This is a generated file; do not edit or check into version control. -FLUTTER_ROOT=/Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable -FLUTTER_APPLICATION_PATH=/Users/kaska/Documents/Projects/Major/arbiter/useragent -COCOAPODS_PARALLEL_CODE_SIGN=true -FLUTTER_TARGET=/Users/kaska/Documents/Projects/Major/arbiter/useragent/lib/main.dart -FLUTTER_BUILD_DIR=build -FLUTTER_BUILD_NAME=1.0.0 -FLUTTER_BUILD_NUMBER=1 -DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuMzguOQ==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049NjczMjNkZTI4NQ==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NTg3YzE4Zjg3Mw==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMC44 -DART_OBFUSCATION=false -TRACK_WIDGET_CREATION=true -TREE_SHAKE_ICONS=false -PACKAGE_CONFIG=/Users/kaska/Documents/Projects/Major/arbiter/useragent/.dart_tool/package_config.json diff --git a/useragent/macos/Flutter/ephemeral/flutter_export_environment.sh b/useragent/macos/Flutter/ephemeral/flutter_export_environment.sh deleted file mode 100755 index 301f1f6..0000000 --- a/useragent/macos/Flutter/ephemeral/flutter_export_environment.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -# This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=/Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable" -export "FLUTTER_APPLICATION_PATH=/Users/kaska/Documents/Projects/Major/arbiter/useragent" -export "COCOAPODS_PARALLEL_CODE_SIGN=true" -export "FLUTTER_TARGET=/Users/kaska/Documents/Projects/Major/arbiter/useragent/lib/main.dart" -export "FLUTTER_BUILD_DIR=build" -export "FLUTTER_BUILD_NAME=1.0.0" -export "FLUTTER_BUILD_NUMBER=1" -export "DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuMzguOQ==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049NjczMjNkZTI4NQ==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NTg3YzE4Zjg3Mw==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMC44" -export "DART_OBFUSCATION=false" -export "TRACK_WIDGET_CREATION=true" -export "TREE_SHAKE_ICONS=false" -export "PACKAGE_CONFIG=/Users/kaska/Documents/Projects/Major/arbiter/useragent/.dart_tool/package_config.json" diff --git a/useragent/macos/Podfile b/useragent/macos/Podfile index ff5ddb3..25eb537 100644 --- a/useragent/macos/Podfile +++ b/useragent/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.15' +platform :osx, '26.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/useragent/macos/Podfile.lock b/useragent/macos/Podfile.lock index 32a275d..c054fb2 100644 --- a/useragent/macos/Podfile.lock +++ b/useragent/macos/Podfile.lock @@ -42,6 +42,6 @@ SPEC CHECKSUMS: rive_native: 1c53d33e44c2b54424810effea4590671dd220c7 share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc -PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 +PODFILE CHECKSUM: 224cb1c0d6f5312abfc2477bcb5c7f1fca2574fb COCOAPODS: 1.16.2 diff --git a/useragent/macos/Runner.xcodeproj/project.pbxproj b/useragent/macos/Runner.xcodeproj/project.pbxproj index 28b98d2..12c3f47 100644 --- a/useragent/macos/Runner.xcodeproj/project.pbxproj +++ b/useragent/macos/Runner.xcodeproj/project.pbxproj @@ -195,7 +195,6 @@ 5385F9987FF8E7FD3BA4E87E /* Pods-RunnerTests.release.xcconfig */, ADF8B0EB51CA38AE67931C44 /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -478,6 +477,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.useragent.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -493,6 +493,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.useragent.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -508,6 +509,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.useragent.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -560,6 +562,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; @@ -572,14 +575,22 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 8L884L537J; + ENABLE_APP_SANDBOX = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = YES; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 26.0; + PRODUCT_BUNDLE_IDENTIFIER = org.markettakers.arbiter; PROVISIONING_PROFILE_SPECIFIER = ""; + RUNTIME_EXCEPTION_ALLOW_JIT = YES; SWIFT_VERSION = 5.0; }; name = Profile; @@ -588,6 +599,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; @@ -643,6 +655,7 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; @@ -692,6 +705,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; @@ -704,14 +718,22 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 8L884L537J; + ENABLE_APP_SANDBOX = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = YES; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 26.0; + PRODUCT_BUNDLE_IDENTIFIER = org.markettakers.arbiter; PROVISIONING_PROFILE_SPECIFIER = ""; + RUNTIME_EXCEPTION_ALLOW_JIT = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; @@ -724,14 +746,22 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 8L884L537J; + ENABLE_APP_SANDBOX = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = YES; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 26.0; + PRODUCT_BUNDLE_IDENTIFIER = org.markettakers.arbiter; PROVISIONING_PROFILE_SPECIFIER = ""; + RUNTIME_EXCEPTION_ALLOW_JIT = YES; SWIFT_VERSION = 5.0; }; name = Release; @@ -740,6 +770,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -748,6 +779,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; diff --git a/useragent/macos/Runner/DebugProfile.entitlements b/useragent/macos/Runner/DebugProfile.entitlements index dddb8a3..fbad023 100644 --- a/useragent/macos/Runner/DebugProfile.entitlements +++ b/useragent/macos/Runner/DebugProfile.entitlements @@ -2,11 +2,7 @@ - com.apple.security.app-sandbox - - com.apple.security.cs.allow-jit - - com.apple.security.network.server - + keychain-access-groups + diff --git a/useragent/macos/Runner/Release.entitlements b/useragent/macos/Runner/Release.entitlements index 852fa1a..0833c1b 100644 --- a/useragent/macos/Runner/Release.entitlements +++ b/useragent/macos/Runner/Release.entitlements @@ -4,5 +4,7 @@ com.apple.security.app-sandbox + keychain-access-groups + diff --git a/useragent/pubspec.lock b/useragent/pubspec.lock index d7f4657..e6982a5 100644 --- a/useragent/pubspec.lock +++ b/useragent/pubspec.lock @@ -282,7 +282,7 @@ packages: source: hosted version: "7.0.1" fixnum: - dependency: transitive + dependency: "direct main" description: name: fixnum sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be @@ -770,7 +770,7 @@ packages: source: hosted version: "1.5.2" protobuf: - dependency: transitive + dependency: "direct main" description: name: protobuf sha256: "75ec242d22e950bdcc79ee38dd520ce4ee0bc491d7fadc4ea47694604d22bf06" diff --git a/useragent/pubspec.yaml b/useragent/pubspec.yaml index 683baef..cc653e5 100644 --- a/useragent/pubspec.yaml +++ b/useragent/pubspec.yaml @@ -26,8 +26,10 @@ dependencies: cryptography_flutter: ^2.3.4 riverpod_annotation: ^4.0.0 grpc: ^5.1.0 + fixnum: ^1.1.1 flutter_hooks: ^0.21.3+1 auto_route: ^11.1.0 + protobuf: ^6.0.0 dev_dependencies: flutter_test: @@ -39,4 +41,4 @@ dev_dependencies: auto_route_generator: ^10.4.0 flutter: - uses-material-design: true \ No newline at end of file + uses-material-design: true From 27836beb75899fd5904b9f01bd371c34c8d36e8b Mon Sep 17 00:00:00 2001 From: hdbg Date: Sun, 15 Mar 2026 16:53:49 +0100 Subject: [PATCH 04/13] fix(server::user_agent::auth): not sending `AuthOk` on succesful auth --- .../src/actors/user_agent/auth/state.rs | 121 +++++++----------- .../src/actors/user_agent/mod.rs | 1 + 2 files changed, 45 insertions(+), 77 deletions(-) diff --git a/server/crates/arbiter-server/src/actors/user_agent/auth/state.rs b/server/crates/arbiter-server/src/actors/user_agent/auth/state.rs index 13bf6c6..ec78590 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/auth/state.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/auth/state.rs @@ -1,5 +1,6 @@ use arbiter_proto::proto::user_agent::{ - AuthChallenge, UserAgentResponse, user_agent_response::Payload as UserAgentResponsePayload, + AuthChallenge, AuthOk, UserAgentResponse, + user_agent_response::Payload as UserAgentResponsePayload, }; use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, update}; use diesel_async::RunQueryDsl; @@ -71,7 +72,7 @@ smlang::statemachine!( transitions: { *Init + AuthRequest(ChallengeRequest) / async prepare_challenge = SentChallenge(ChallengeContext), Init + BootstrapAuthRequest(BootstrapAuthRequest) [async verify_bootstrap_token] / provide_key_bootstrap = AuthOk(AuthPublicKey), - SentChallenge(ChallengeContext) + ReceivedSolution(ChallengeSolution) [async verify_solution] / provide_key = AuthOk(AuthPublicKey), + SentChallenge(ChallengeContext) + ReceivedSolution(ChallengeSolution) / async verify_solution = AuthOk(AuthPublicKey), } ); @@ -147,43 +148,6 @@ impl<'a> AuthContext<'a> { impl AuthStateMachineContext for AuthContext<'_> { type Error = Error; - async fn verify_solution( - &self, - ChallengeContext { challenge, key }: &ChallengeContext, - ChallengeSolution { solution }: &ChallengeSolution, - ) -> Result { - let formatted = arbiter_proto::format_challenge(challenge.nonce, &challenge.pubkey); - - let valid = match key { - AuthPublicKey::Ed25519(vk) => { - let sig = solution.as_slice().try_into().map_err(|_| { - error!(?solution, "Invalid Ed25519 signature length"); - Error::InvalidChallengeSolution - })?; - vk.verify_strict(&formatted, &sig).is_ok() - } - AuthPublicKey::EcdsaSecp256k1(vk) => { - use k256::ecdsa::signature::Verifier as _; - let sig = k256::ecdsa::Signature::try_from(solution.as_slice()).map_err(|_| { - error!(?solution, "Invalid ECDSA signature bytes"); - Error::InvalidChallengeSolution - })?; - vk.verify(&formatted, &sig).is_ok() - } - AuthPublicKey::Rsa(pk) => { - use rsa::signature::Verifier as _; - let verifying_key = rsa::pss::VerifyingKey::::new(pk.clone()); - let sig = rsa::pss::Signature::try_from(solution.as_slice()).map_err(|_| { - error!(?solution, "Invalid RSA signature bytes"); - Error::InvalidChallengeSolution - })?; - verifying_key.verify(&formatted, &sig).is_ok() - } - }; - - Ok(valid) - } - async fn prepare_challenge( &mut self, ChallengeRequest { pubkey }: ChallengeRequest, @@ -249,49 +213,52 @@ impl AuthStateMachineContext for AuthContext<'_> { Ok(event_data.pubkey) } - fn provide_key( + #[allow(missing_docs)] + #[allow(clippy::unused_unit)] + async fn verify_solution( &mut self, - state_data: &ChallengeContext, - _: ChallengeSolution, + ChallengeContext { challenge, key }: &ChallengeContext, + ChallengeSolution { solution }: ChallengeSolution, ) -> Result { - // ChallengeContext.key cannot be taken by value because smlang passes it by ref; - // we reconstruct stored bytes and return them wrapped in Ed25519 placeholder. - // Session uses only the raw bytes, so we carry them via a Vec. - // IMPORTANT: do NOT simplify this by storing the key type separately — the - // `AuthPublicKey` enum IS the source of truth for key bytes and type. - // - // smlang state-machine trait requires returning an owned value from `provide_key`, - // but `state_data` is only available by shared reference here. We extract the - // stored bytes and re-wrap as the correct variant so the caller can call - // `to_stored_bytes()` / `key_type()` without losing information. - let bytes = state_data.challenge.pubkey.clone(); - let key_type = state_data.key.key_type(); - let rebuilt = match key_type { - crate::db::models::KeyType::Ed25519 => { - let arr: &[u8; 32] = bytes - .as_slice() - .try_into() - .expect("ed25519 pubkey must be 32 bytes in challenge"); - AuthPublicKey::Ed25519( - ed25519_dalek::VerifyingKey::from_bytes(arr) - .expect("key was already validated in parse_auth_event"), - ) + let formatted = arbiter_proto::format_challenge(challenge.nonce, &challenge.pubkey); + + let valid = match key { + AuthPublicKey::Ed25519(vk) => { + let sig = solution.as_slice().try_into().map_err(|_| { + error!(?solution, "Invalid Ed25519 signature length"); + Error::InvalidChallengeSolution + })?; + vk.verify_strict(&formatted, &sig).is_ok() } - crate::db::models::KeyType::EcdsaSecp256k1 => { - // bytes are SEC1 compressed (33 bytes produced by to_encoded_point(true)) - AuthPublicKey::EcdsaSecp256k1( - k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes) - .expect("ecdsa key was already validated in parse_auth_event"), - ) + AuthPublicKey::EcdsaSecp256k1(vk) => { + use k256::ecdsa::signature::Verifier as _; + let sig = k256::ecdsa::Signature::try_from(solution.as_slice()).map_err(|_| { + error!(?solution, "Invalid ECDSA signature bytes"); + Error::InvalidChallengeSolution + })?; + vk.verify(&formatted, &sig).is_ok() } - crate::db::models::KeyType::Rsa => { - use rsa::pkcs8::DecodePublicKey as _; - AuthPublicKey::Rsa( - rsa::RsaPublicKey::from_public_key_der(&bytes) - .expect("rsa key was already validated in parse_auth_event"), - ) + AuthPublicKey::Rsa(pk) => { + use rsa::signature::Verifier as _; + let verifying_key = rsa::pss::VerifyingKey::::new(pk.clone()); + let sig = rsa::pss::Signature::try_from(solution.as_slice()).map_err(|_| { + error!(?solution, "Invalid RSA signature bytes"); + Error::InvalidChallengeSolution + })?; + verifying_key.verify(&formatted, &sig).is_ok() } }; - Ok(rebuilt) + + if valid { + self.conn + .transport + .send(Ok(UserAgentResponse { + payload: Some(UserAgentResponsePayload::AuthOk(AuthOk {})), + })) + .await + .map_err(|_| Error::Transport)?; + } + + Ok(key.clone()) } } diff --git a/server/crates/arbiter-server/src/actors/user_agent/mod.rs b/server/crates/arbiter-server/src/actors/user_agent/mod.rs index 4380b72..98d1eee 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/mod.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/mod.rs @@ -52,6 +52,7 @@ impl UserAgentConnection { pub mod auth; pub mod session; +#[tracing::instrument(skip(props))] pub async fn connect_user_agent(props: UserAgentConnection) { match auth::authenticate_and_create(props).await { Ok(session) => { From c61a9e30acabe0ae0b1b9dc434b3cb9fab2c3e10 Mon Sep 17 00:00:00 2001 From: hdbg Date: Sun, 15 Mar 2026 16:53:49 +0100 Subject: [PATCH 05/13] feat(useragent): initial connection impl --- mise.toml | 2 +- .../{ => connection}/arbiter_url.dart | 0 .../lib/features/connection/connection.dart | 131 +++++++++++++++++- .../{ => connection}/server_info_storage.dart | 20 +-- .../connection/server_info_storage.g.dart | 21 +++ .../features/{ => identity}/pk_manager.dart | 0 .../{ => identity}/simple_ed25519.dart | 3 +- useragent/lib/proto/arbiter.pb.dart | 19 --- useragent/lib/proto/arbiter.pbgrpc.dart | 90 ++++++++++++ useragent/lib/proto/arbiter.pbjson.dart | 94 ------------- useragent/lib/proto/arbiter.pbserver.dart | 56 -------- useragent/lib/proto/user_agent.pb.dart | 13 ++ useragent/lib/proto/user_agent.pbenum.dart | 25 ++++ useragent/lib/proto/user_agent.pbjson.dart | 29 +++- .../providers/connection/bootstrap_token.dart | 22 +++ .../connection/bootstrap_token.g.dart | 62 +++++++++ .../connection/connection_manager.dart | 39 ++++++ .../connection/connection_manager.g.dart | 54 ++++++++ useragent/lib/providers/key.dart | 4 +- useragent/lib/providers/server_info.dart | 2 +- useragent/lib/router.dart | 1 + useragent/lib/router.gr.dart | 93 ++++++++++--- useragent/lib/screens/server_connection.dart | 60 ++++++++ useragent/lib/screens/server_info_setup.dart | 30 ++-- .../macos/Runner/DebugProfile.entitlements | 2 + useragent/macos/Runner/Release.entitlements | 2 + useragent/pubspec.lock | 34 ++++- useragent/pubspec.yaml | 5 + 28 files changed, 688 insertions(+), 225 deletions(-) rename useragent/lib/features/{ => connection}/arbiter_url.dart (100%) rename useragent/lib/features/{ => connection}/server_info_storage.dart (76%) create mode 100644 useragent/lib/features/connection/server_info_storage.g.dart rename useragent/lib/features/{ => identity}/pk_manager.dart (100%) rename useragent/lib/features/{ => identity}/simple_ed25519.dart (96%) create mode 100644 useragent/lib/proto/arbiter.pbgrpc.dart delete mode 100644 useragent/lib/proto/arbiter.pbserver.dart create mode 100644 useragent/lib/providers/connection/bootstrap_token.dart create mode 100644 useragent/lib/providers/connection/bootstrap_token.g.dart create mode 100644 useragent/lib/providers/connection/connection_manager.dart create mode 100644 useragent/lib/providers/connection/connection_manager.g.dart create mode 100644 useragent/lib/screens/server_connection.dart diff --git a/mise.toml b/mise.toml index 7110df7..ea6265d 100644 --- a/mise.toml +++ b/mise.toml @@ -16,5 +16,5 @@ sources = ['protobufs/*.proto'] outputs = ['useragent/lib/proto/*'] run = ''' dart pub global activate protoc_plugin && \ -protoc --dart_out=useragent/lib/proto --proto_path=protobufs/ protobufs/*.proto +protoc --dart_out=grpc:useragent/lib/proto --proto_path=protobufs/ protobufs/*.proto ''' \ No newline at end of file diff --git a/useragent/lib/features/arbiter_url.dart b/useragent/lib/features/connection/arbiter_url.dart similarity index 100% rename from useragent/lib/features/arbiter_url.dart rename to useragent/lib/features/connection/arbiter_url.dart diff --git a/useragent/lib/features/connection/connection.dart b/useragent/lib/features/connection/connection.dart index 944711a..4afc46e 100644 --- a/useragent/lib/features/connection/connection.dart +++ b/useragent/lib/features/connection/connection.dart @@ -1,3 +1,132 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:arbiter/features/connection/server_info_storage.dart'; +import 'package:arbiter/features/identity/pk_manager.dart'; +import 'package:arbiter/proto/arbiter.pbgrpc.dart'; +import 'package:arbiter/proto/user_agent.pb.dart'; +import 'package:grpc/grpc.dart'; +import 'package:mtcore/markettakers.dart'; + +class Connection { + final ClientChannel channel; + final StreamController _tx; + final StreamIterator _rx; + + Connection({ + required this.channel, + required StreamController tx, + required ResponseStream rx, + }) : _tx = tx, + _rx = StreamIterator(rx); + + Future send(UserAgentRequest request) async { + _tx.add(request); + } + + Future receive() async { + await _rx.moveNext(); + return _rx.current; + + } + + Future close() async { + await _tx.close(); + await channel.shutdown(); + } +} + +Future _connect(StoredServerInfo serverInfo) async { + final channel = ClientChannel( + serverInfo.address, + port: serverInfo.port, + options: ChannelOptions( + connectTimeout: const Duration(seconds: 10), + credentials: ChannelCredentials.secure( + onBadCertificate: (cert, host) { + return true; + }, + ), + ), + ); + + final client = ArbiterServiceClient(channel); + final tx = StreamController(); + + final rx = client.userAgent(tx.stream); + + return Connection(channel: channel, tx: tx, rx: rx); +} + +List formatChallenge(AuthChallenge challenge, List pubkey) { + final encodedPubkey = base64Encode(pubkey); + final payload = "${challenge.nonce}:$encodedPubkey"; + return utf8.encode(payload); +} + +Future connectAndAuthorize( + StoredServerInfo serverInfo, + KeyHandle key, { + String? bootstrapToken, +}) async { + try { + final connection = await _connect(serverInfo); + talker.info( + 'Connected to server at ${serverInfo.address}:${serverInfo.port}', + ); + final pubkey = await key.getPublicKey(); + + final req = AuthChallengeRequest( + pubkey: pubkey, + bootstrapToken: bootstrapToken, + keyType: switch (key.alg) { + KeyAlgorithm.rsa => KeyType.KEY_TYPE_RSA, + KeyAlgorithm.ecdsa => KeyType.KEY_TYPE_ECDSA_SECP256K1, + KeyAlgorithm.ed25519 => KeyType.KEY_TYPE_ED25519, + }, + ); + await connection.send(UserAgentRequest(authChallengeRequest: req)); + talker.info( + "Sent auth challenge request with pubkey ${base64Encode(pubkey)}", + ); -class Connection {} \ No newline at end of file + final response = await connection.receive(); + + talker.info( + 'Received response from server, checking for auth challenge...', + ); + + if (!response.hasAuthChallenge()) { + throw Exception( + 'Expected AuthChallengeResponse, got ${response.whichPayload()}', + ); + } + + final challenge = formatChallenge(response.authChallenge, pubkey); + talker.info( + 'Received auth challenge, signing with key ${base64Encode(pubkey)}', + ); + + final signature = await key.sign(challenge); + + final solutionReq = AuthChallengeSolution(signature: signature); + await connection.send(UserAgentRequest(authChallengeSolution: solutionReq)); + + talker.info('Sent auth challenge solution, waiting for server response...'); + + final solutionResponse = await connection.receive(); + + if (!solutionResponse.hasAuthOk()) { + throw Exception( + 'Expected AuthChallengeSolutionResponse, got ${solutionResponse.whichPayload()}', + ); + } + + talker.info('Authentication successful, connection established'); + + return connection; + } catch (e) { + throw Exception('Failed to connect to server: $e'); + } +} diff --git a/useragent/lib/features/server_info_storage.dart b/useragent/lib/features/connection/server_info_storage.dart similarity index 76% rename from useragent/lib/features/server_info_storage.dart rename to useragent/lib/features/connection/server_info_storage.dart index 704939e..39bec59 100644 --- a/useragent/lib/features/server_info_storage.dart +++ b/useragent/lib/features/connection/server_info_storage.dart @@ -1,7 +1,11 @@ import 'dart:convert'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:json_annotation/json_annotation.dart'; +part 'server_info_storage.g.dart'; + +@JsonSerializable() class StoredServerInfo { const StoredServerInfo({ required this.address, @@ -13,19 +17,9 @@ class StoredServerInfo { final int port; final String caCertFingerprint; - Map toJson() => { - 'address': address, - 'port': port, - 'caCertFingerprint': caCertFingerprint, - }; - - factory StoredServerInfo.fromJson(Map json) { - return StoredServerInfo( - address: json['address'] as String, - port: json['port'] as int, - caCertFingerprint: json['caCertFingerprint'] as String, - ); - } + factory StoredServerInfo.fromJson(Map json) => _$StoredServerInfoFromJson(json); + Map toJson() => _$StoredServerInfoToJson(this); + } abstract class ServerInfoStorage { diff --git a/useragent/lib/features/connection/server_info_storage.g.dart b/useragent/lib/features/connection/server_info_storage.g.dart new file mode 100644 index 0000000..fcd0018 --- /dev/null +++ b/useragent/lib/features/connection/server_info_storage.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'server_info_storage.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +StoredServerInfo _$StoredServerInfoFromJson(Map json) => + StoredServerInfo( + address: json['address'] as String, + port: (json['port'] as num).toInt(), + caCertFingerprint: json['caCertFingerprint'] as String, + ); + +Map _$StoredServerInfoToJson(StoredServerInfo instance) => + { + 'address': instance.address, + 'port': instance.port, + 'caCertFingerprint': instance.caCertFingerprint, + }; diff --git a/useragent/lib/features/pk_manager.dart b/useragent/lib/features/identity/pk_manager.dart similarity index 100% rename from useragent/lib/features/pk_manager.dart rename to useragent/lib/features/identity/pk_manager.dart diff --git a/useragent/lib/features/simple_ed25519.dart b/useragent/lib/features/identity/simple_ed25519.dart similarity index 96% rename from useragent/lib/features/simple_ed25519.dart rename to useragent/lib/features/identity/simple_ed25519.dart index f5e2183..01ee24d 100644 --- a/useragent/lib/features/simple_ed25519.dart +++ b/useragent/lib/features/identity/simple_ed25519.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:cryptography/cryptography.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:arbiter/features/pk_manager.dart'; +import 'package:arbiter/features/identity/pk_manager.dart'; final storage = FlutterSecureStorage( aOptions: AndroidOptions.biometric( @@ -16,7 +16,6 @@ final storage = FlutterSecureStorage( synchronizable: false, accessControlFlags: [ AccessControlFlag.userPresence, - AccessControlFlag.privateKeyUsage, ], usesDataProtectionKeychain: true, ), diff --git a/useragent/lib/proto/arbiter.pb.dart b/useragent/lib/proto/arbiter.pb.dart index a0f96d7..58ca420 100644 --- a/useragent/lib/proto/arbiter.pb.dart +++ b/useragent/lib/proto/arbiter.pb.dart @@ -10,14 +10,10 @@ // ignore_for_file: deprecated_member_use_from_same_package, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_relative_imports -import 'dart:async' as $async; import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; -import 'client.pb.dart' as $0; -import 'user_agent.pb.dart' as $1; - export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; class ServerInfo extends $pb.GeneratedMessage { @@ -86,21 +82,6 @@ class ServerInfo extends $pb.GeneratedMessage { void clearCertPublicKey() => $_clearField(2); } -class ArbiterServiceApi { - final $pb.RpcClient _client; - - ArbiterServiceApi(this._client); - - $async.Future<$0.ClientResponse> client( - $pb.ClientContext? ctx, $0.ClientRequest request) => - _client.invoke<$0.ClientResponse>( - ctx, 'ArbiterService', 'Client', request, $0.ClientResponse()); - $async.Future<$1.UserAgentResponse> userAgent( - $pb.ClientContext? ctx, $1.UserAgentRequest request) => - _client.invoke<$1.UserAgentResponse>( - ctx, 'ArbiterService', 'UserAgent', request, $1.UserAgentResponse()); -} - const $core.bool _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); const $core.bool _omitMessageNames = diff --git a/useragent/lib/proto/arbiter.pbgrpc.dart b/useragent/lib/proto/arbiter.pbgrpc.dart new file mode 100644 index 0000000..4cc66b9 --- /dev/null +++ b/useragent/lib/proto/arbiter.pbgrpc.dart @@ -0,0 +1,90 @@ +// This is a generated file - do not edit. +// +// Generated from arbiter.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:async' as $async; +import 'dart:core' as $core; + +import 'package:grpc/service_api.dart' as $grpc; +import 'package:protobuf/protobuf.dart' as $pb; + +import 'client.pb.dart' as $0; +import 'user_agent.pb.dart' as $1; + +export 'arbiter.pb.dart'; + +@$pb.GrpcServiceName('arbiter.ArbiterService') +class ArbiterServiceClient extends $grpc.Client { + /// The hostname for this service. + static const $core.String defaultHost = ''; + + /// OAuth scopes needed for the client. + static const $core.List<$core.String> oauthScopes = [ + '', + ]; + + ArbiterServiceClient(super.channel, {super.options, super.interceptors}); + + $grpc.ResponseStream<$0.ClientResponse> client( + $async.Stream<$0.ClientRequest> request, { + $grpc.CallOptions? options, + }) { + return $createStreamingCall(_$client, request, options: options); + } + + $grpc.ResponseStream<$1.UserAgentResponse> userAgent( + $async.Stream<$1.UserAgentRequest> request, { + $grpc.CallOptions? options, + }) { + return $createStreamingCall(_$userAgent, request, options: options); + } + + // method descriptors + + static final _$client = + $grpc.ClientMethod<$0.ClientRequest, $0.ClientResponse>( + '/arbiter.ArbiterService/Client', + ($0.ClientRequest value) => value.writeToBuffer(), + $0.ClientResponse.fromBuffer); + static final _$userAgent = + $grpc.ClientMethod<$1.UserAgentRequest, $1.UserAgentResponse>( + '/arbiter.ArbiterService/UserAgent', + ($1.UserAgentRequest value) => value.writeToBuffer(), + $1.UserAgentResponse.fromBuffer); +} + +@$pb.GrpcServiceName('arbiter.ArbiterService') +abstract class ArbiterServiceBase extends $grpc.Service { + $core.String get $name => 'arbiter.ArbiterService'; + + ArbiterServiceBase() { + $addMethod($grpc.ServiceMethod<$0.ClientRequest, $0.ClientResponse>( + 'Client', + client, + true, + true, + ($core.List<$core.int> value) => $0.ClientRequest.fromBuffer(value), + ($0.ClientResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$1.UserAgentRequest, $1.UserAgentResponse>( + 'UserAgent', + userAgent, + true, + true, + ($core.List<$core.int> value) => $1.UserAgentRequest.fromBuffer(value), + ($1.UserAgentResponse value) => value.writeToBuffer())); + } + + $async.Stream<$0.ClientResponse> client( + $grpc.ServiceCall call, $async.Stream<$0.ClientRequest> request); + + $async.Stream<$1.UserAgentResponse> userAgent( + $grpc.ServiceCall call, $async.Stream<$1.UserAgentRequest> request); +} diff --git a/useragent/lib/proto/arbiter.pbjson.dart b/useragent/lib/proto/arbiter.pbjson.dart index 3300005..d93bf39 100644 --- a/useragent/lib/proto/arbiter.pbjson.dart +++ b/useragent/lib/proto/arbiter.pbjson.dart @@ -15,15 +15,6 @@ import 'dart:convert' as $convert; import 'dart:core' as $core; import 'dart:typed_data' as $typed_data; -import 'package:protobuf/well_known_types/google/protobuf/empty.pbjson.dart' - as $3; -import 'package:protobuf/well_known_types/google/protobuf/timestamp.pbjson.dart' - as $4; - -import 'client.pbjson.dart' as $0; -import 'evm.pbjson.dart' as $2; -import 'user_agent.pbjson.dart' as $1; - @$core.Deprecated('Use serverInfoDescriptor instead') const ServerInfo$json = { '1': 'ServerInfo', @@ -37,88 +28,3 @@ const ServerInfo$json = { final $typed_data.Uint8List serverInfoDescriptor = $convert.base64Decode( 'CgpTZXJ2ZXJJbmZvEhgKB3ZlcnNpb24YASABKAlSB3ZlcnNpb24SJgoPY2VydF9wdWJsaWNfa2' 'V5GAIgASgMUg1jZXJ0UHVibGljS2V5'); - -const $core.Map<$core.String, $core.dynamic> ArbiterServiceBase$json = { - '1': 'ArbiterService', - '2': [ - { - '1': 'Client', - '2': '.arbiter.client.ClientRequest', - '3': '.arbiter.client.ClientResponse', - '5': true, - '6': true - }, - { - '1': 'UserAgent', - '2': '.arbiter.user_agent.UserAgentRequest', - '3': '.arbiter.user_agent.UserAgentResponse', - '5': true, - '6': true - }, - ], -}; - -@$core.Deprecated('Use arbiterServiceDescriptor instead') -const $core.Map<$core.String, $core.Map<$core.String, $core.dynamic>> - ArbiterServiceBase$messageJson = { - '.arbiter.client.ClientRequest': $0.ClientRequest$json, - '.arbiter.client.AuthChallengeRequest': $0.AuthChallengeRequest$json, - '.arbiter.client.AuthChallengeSolution': $0.AuthChallengeSolution$json, - '.arbiter.client.ClientResponse': $0.ClientResponse$json, - '.arbiter.client.AuthChallenge': $0.AuthChallenge$json, - '.arbiter.client.AuthOk': $0.AuthOk$json, - '.arbiter.evm.EvmSignTransactionResponse': $2.EvmSignTransactionResponse$json, - '.arbiter.evm.TransactionEvalError': $2.TransactionEvalError$json, - '.google.protobuf.Empty': $3.Empty$json, - '.arbiter.evm.NoMatchingGrantError': $2.NoMatchingGrantError$json, - '.arbiter.evm.SpecificMeaning': $2.SpecificMeaning$json, - '.arbiter.evm.EtherTransferMeaning': $2.EtherTransferMeaning$json, - '.arbiter.evm.TokenTransferMeaning': $2.TokenTransferMeaning$json, - '.arbiter.evm.TokenInfo': $2.TokenInfo$json, - '.arbiter.evm.PolicyViolationsError': $2.PolicyViolationsError$json, - '.arbiter.evm.EvalViolation': $2.EvalViolation$json, - '.arbiter.evm.GasLimitExceededViolation': $2.GasLimitExceededViolation$json, - '.arbiter.evm.EvmAnalyzeTransactionResponse': - $2.EvmAnalyzeTransactionResponse$json, - '.arbiter.client.ClientConnectError': $0.ClientConnectError$json, - '.arbiter.user_agent.UserAgentRequest': $1.UserAgentRequest$json, - '.arbiter.user_agent.AuthChallengeRequest': $1.AuthChallengeRequest$json, - '.arbiter.user_agent.AuthChallengeSolution': $1.AuthChallengeSolution$json, - '.arbiter.user_agent.UnsealStart': $1.UnsealStart$json, - '.arbiter.user_agent.UnsealEncryptedKey': $1.UnsealEncryptedKey$json, - '.arbiter.evm.EvmGrantCreateRequest': $2.EvmGrantCreateRequest$json, - '.arbiter.evm.SharedSettings': $2.SharedSettings$json, - '.google.protobuf.Timestamp': $4.Timestamp$json, - '.arbiter.evm.TransactionRateLimit': $2.TransactionRateLimit$json, - '.arbiter.evm.SpecificGrant': $2.SpecificGrant$json, - '.arbiter.evm.EtherTransferSettings': $2.EtherTransferSettings$json, - '.arbiter.evm.VolumeRateLimit': $2.VolumeRateLimit$json, - '.arbiter.evm.TokenTransferSettings': $2.TokenTransferSettings$json, - '.arbiter.evm.EvmGrantDeleteRequest': $2.EvmGrantDeleteRequest$json, - '.arbiter.evm.EvmGrantListRequest': $2.EvmGrantListRequest$json, - '.arbiter.user_agent.ClientConnectionResponse': - $1.ClientConnectionResponse$json, - '.arbiter.user_agent.UserAgentResponse': $1.UserAgentResponse$json, - '.arbiter.user_agent.AuthChallenge': $1.AuthChallenge$json, - '.arbiter.user_agent.AuthOk': $1.AuthOk$json, - '.arbiter.user_agent.UnsealStartResponse': $1.UnsealStartResponse$json, - '.arbiter.evm.WalletCreateResponse': $2.WalletCreateResponse$json, - '.arbiter.evm.WalletEntry': $2.WalletEntry$json, - '.arbiter.evm.WalletListResponse': $2.WalletListResponse$json, - '.arbiter.evm.WalletList': $2.WalletList$json, - '.arbiter.evm.EvmGrantCreateResponse': $2.EvmGrantCreateResponse$json, - '.arbiter.evm.EvmGrantDeleteResponse': $2.EvmGrantDeleteResponse$json, - '.arbiter.evm.EvmGrantListResponse': $2.EvmGrantListResponse$json, - '.arbiter.evm.EvmGrantList': $2.EvmGrantList$json, - '.arbiter.evm.GrantEntry': $2.GrantEntry$json, - '.arbiter.user_agent.ClientConnectionRequest': - $1.ClientConnectionRequest$json, - '.arbiter.user_agent.ClientConnectionCancel': $1.ClientConnectionCancel$json, -}; - -/// Descriptor for `ArbiterService`. Decode as a `google.protobuf.ServiceDescriptorProto`. -final $typed_data.Uint8List arbiterServiceDescriptor = $convert.base64Decode( - 'Cg5BcmJpdGVyU2VydmljZRJLCgZDbGllbnQSHS5hcmJpdGVyLmNsaWVudC5DbGllbnRSZXF1ZX' - 'N0Gh4uYXJiaXRlci5jbGllbnQuQ2xpZW50UmVzcG9uc2UoATABElwKCVVzZXJBZ2VudBIkLmFy' - 'Yml0ZXIudXNlcl9hZ2VudC5Vc2VyQWdlbnRSZXF1ZXN0GiUuYXJiaXRlci51c2VyX2FnZW50Ll' - 'VzZXJBZ2VudFJlc3BvbnNlKAEwAQ=='); diff --git a/useragent/lib/proto/arbiter.pbserver.dart b/useragent/lib/proto/arbiter.pbserver.dart deleted file mode 100644 index 59ed28f..0000000 --- a/useragent/lib/proto/arbiter.pbserver.dart +++ /dev/null @@ -1,56 +0,0 @@ -// This is a generated file - do not edit. -// -// Generated from arbiter.proto. - -// @dart = 3.3 - -// ignore_for_file: annotate_overrides, camel_case_types, comment_references -// ignore_for_file: constant_identifier_names -// ignore_for_file: curly_braces_in_flow_control_structures -// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes -// ignore_for_file: non_constant_identifier_names, prefer_relative_imports - -import 'dart:async' as $async; -import 'dart:core' as $core; - -import 'package:protobuf/protobuf.dart' as $pb; - -import 'arbiter.pbjson.dart'; -import 'client.pb.dart' as $0; -import 'user_agent.pb.dart' as $1; - -export 'arbiter.pb.dart'; - -abstract class ArbiterServiceBase extends $pb.GeneratedService { - $async.Future<$0.ClientResponse> client( - $pb.ServerContext ctx, $0.ClientRequest request); - $async.Future<$1.UserAgentResponse> userAgent( - $pb.ServerContext ctx, $1.UserAgentRequest request); - - $pb.GeneratedMessage createRequest($core.String methodName) { - switch (methodName) { - case 'Client': - return $0.ClientRequest(); - case 'UserAgent': - return $1.UserAgentRequest(); - default: - throw $core.ArgumentError('Unknown method: $methodName'); - } - } - - $async.Future<$pb.GeneratedMessage> handleCall($pb.ServerContext ctx, - $core.String methodName, $pb.GeneratedMessage request) { - switch (methodName) { - case 'Client': - return client(ctx, request as $0.ClientRequest); - case 'UserAgent': - return userAgent(ctx, request as $1.UserAgentRequest); - default: - throw $core.ArgumentError('Unknown method: $methodName'); - } - } - - $core.Map<$core.String, $core.dynamic> get $json => ArbiterServiceBase$json; - $core.Map<$core.String, $core.Map<$core.String, $core.dynamic>> - get $messageJson => ArbiterServiceBase$messageJson; -} diff --git a/useragent/lib/proto/user_agent.pb.dart b/useragent/lib/proto/user_agent.pb.dart index 8ea3f4f..f73a61e 100644 --- a/useragent/lib/proto/user_agent.pb.dart +++ b/useragent/lib/proto/user_agent.pb.dart @@ -26,10 +26,12 @@ class AuthChallengeRequest extends $pb.GeneratedMessage { factory AuthChallengeRequest({ $core.List<$core.int>? pubkey, $core.String? bootstrapToken, + KeyType? keyType, }) { final result = create(); if (pubkey != null) result.pubkey = pubkey; if (bootstrapToken != null) result.bootstrapToken = bootstrapToken; + if (keyType != null) result.keyType = keyType; return result; } @@ -50,6 +52,8 @@ class AuthChallengeRequest extends $pb.GeneratedMessage { ..a<$core.List<$core.int>>( 1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY) ..aOS(2, _omitFieldNames ? '' : 'bootstrapToken') + ..aE(3, _omitFieldNames ? '' : 'keyType', + enumValues: KeyType.values) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -88,6 +92,15 @@ class AuthChallengeRequest extends $pb.GeneratedMessage { $core.bool hasBootstrapToken() => $_has(1); @$pb.TagNumber(2) void clearBootstrapToken() => $_clearField(2); + + @$pb.TagNumber(3) + KeyType get keyType => $_getN(2); + @$pb.TagNumber(3) + set keyType(KeyType value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasKeyType() => $_has(2); + @$pb.TagNumber(3) + void clearKeyType() => $_clearField(3); } class AuthChallenge extends $pb.GeneratedMessage { diff --git a/useragent/lib/proto/user_agent.pbenum.dart b/useragent/lib/proto/user_agent.pbenum.dart index b2b6484..9ceaae4 100644 --- a/useragent/lib/proto/user_agent.pbenum.dart +++ b/useragent/lib/proto/user_agent.pbenum.dart @@ -14,6 +14,31 @@ import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; +class KeyType extends $pb.ProtobufEnum { + static const KeyType KEY_TYPE_UNSPECIFIED = + KeyType._(0, _omitEnumNames ? '' : 'KEY_TYPE_UNSPECIFIED'); + static const KeyType KEY_TYPE_ED25519 = + KeyType._(1, _omitEnumNames ? '' : 'KEY_TYPE_ED25519'); + static const KeyType KEY_TYPE_ECDSA_SECP256K1 = + KeyType._(2, _omitEnumNames ? '' : 'KEY_TYPE_ECDSA_SECP256K1'); + static const KeyType KEY_TYPE_RSA = + KeyType._(3, _omitEnumNames ? '' : 'KEY_TYPE_RSA'); + + static const $core.List values = [ + KEY_TYPE_UNSPECIFIED, + KEY_TYPE_ED25519, + KEY_TYPE_ECDSA_SECP256K1, + KEY_TYPE_RSA, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 3); + static KeyType? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const KeyType._(super.value, super.name); +} + class UnsealResult extends $pb.ProtobufEnum { static const UnsealResult UNSEAL_RESULT_UNSPECIFIED = UnsealResult._(0, _omitEnumNames ? '' : 'UNSEAL_RESULT_UNSPECIFIED'); diff --git a/useragent/lib/proto/user_agent.pbjson.dart b/useragent/lib/proto/user_agent.pbjson.dart index 843ab59..e9a48c6 100644 --- a/useragent/lib/proto/user_agent.pbjson.dart +++ b/useragent/lib/proto/user_agent.pbjson.dart @@ -15,6 +15,22 @@ import 'dart:convert' as $convert; import 'dart:core' as $core; import 'dart:typed_data' as $typed_data; +@$core.Deprecated('Use keyTypeDescriptor instead') +const KeyType$json = { + '1': 'KeyType', + '2': [ + {'1': 'KEY_TYPE_UNSPECIFIED', '2': 0}, + {'1': 'KEY_TYPE_ED25519', '2': 1}, + {'1': 'KEY_TYPE_ECDSA_SECP256K1', '2': 2}, + {'1': 'KEY_TYPE_RSA', '2': 3}, + ], +}; + +/// Descriptor for `KeyType`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List keyTypeDescriptor = $convert.base64Decode( + 'CgdLZXlUeXBlEhgKFEtFWV9UWVBFX1VOU1BFQ0lGSUVEEAASFAoQS0VZX1RZUEVfRUQyNTUxOR' + 'ABEhwKGEtFWV9UWVBFX0VDRFNBX1NFQ1AyNTZLMRACEhAKDEtFWV9UWVBFX1JTQRAD'); + @$core.Deprecated('Use unsealResultDescriptor instead') const UnsealResult$json = { '1': 'UnsealResult', @@ -64,6 +80,14 @@ const AuthChallengeRequest$json = { '10': 'bootstrapToken', '17': true }, + { + '1': 'key_type', + '3': 3, + '4': 1, + '5': 14, + '6': '.arbiter.user_agent.KeyType', + '10': 'keyType' + }, ], '8': [ {'1': '_bootstrap_token'}, @@ -73,8 +97,9 @@ const AuthChallengeRequest$json = { /// Descriptor for `AuthChallengeRequest`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List authChallengeRequestDescriptor = $convert.base64Decode( 'ChRBdXRoQ2hhbGxlbmdlUmVxdWVzdBIWCgZwdWJrZXkYASABKAxSBnB1YmtleRIsCg9ib290c3' - 'RyYXBfdG9rZW4YAiABKAlIAFIOYm9vdHN0cmFwVG9rZW6IAQFCEgoQX2Jvb3RzdHJhcF90b2tl' - 'bg=='); + 'RyYXBfdG9rZW4YAiABKAlIAFIOYm9vdHN0cmFwVG9rZW6IAQESNgoIa2V5X3R5cGUYAyABKA4y' + 'Gy5hcmJpdGVyLnVzZXJfYWdlbnQuS2V5VHlwZVIHa2V5VHlwZUISChBfYm9vdHN0cmFwX3Rva2' + 'Vu'); @$core.Deprecated('Use authChallengeDescriptor instead') const AuthChallenge$json = { diff --git a/useragent/lib/providers/connection/bootstrap_token.dart b/useragent/lib/providers/connection/bootstrap_token.dart new file mode 100644 index 0000000..1062f31 --- /dev/null +++ b/useragent/lib/providers/connection/bootstrap_token.dart @@ -0,0 +1,22 @@ + + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'bootstrap_token.g.dart'; + +@Riverpod(keepAlive: true) +class BootstrapToken extends _$BootstrapToken { + String? build() { + return null; + } + + void set(String token) { + state = token; + } + + String? take() { + final token = state; + state = null; + return token; + } +} \ No newline at end of file diff --git a/useragent/lib/providers/connection/bootstrap_token.g.dart b/useragent/lib/providers/connection/bootstrap_token.g.dart new file mode 100644 index 0000000..4477998 --- /dev/null +++ b/useragent/lib/providers/connection/bootstrap_token.g.dart @@ -0,0 +1,62 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'bootstrap_token.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(BootstrapToken) +final bootstrapTokenProvider = BootstrapTokenProvider._(); + +final class BootstrapTokenProvider + extends $NotifierProvider { + BootstrapTokenProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'bootstrapTokenProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$bootstrapTokenHash(); + + @$internal + @override + BootstrapToken create() => BootstrapToken(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(String? value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$bootstrapTokenHash() => r'a59e679ab0561ed2ab4148660499891571d439db'; + +abstract class _$BootstrapToken extends $Notifier { + String? build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + String?, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/useragent/lib/providers/connection/connection_manager.dart b/useragent/lib/providers/connection/connection_manager.dart new file mode 100644 index 0000000..c1e5266 --- /dev/null +++ b/useragent/lib/providers/connection/connection_manager.dart @@ -0,0 +1,39 @@ +import 'package:arbiter/features/connection/connection.dart'; +import 'package:arbiter/providers/connection/bootstrap_token.dart'; +import 'package:arbiter/providers/key.dart'; +import 'package:arbiter/providers/server_info.dart'; +import 'package:mtcore/markettakers.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'connection_manager.g.dart'; + +@Riverpod(keepAlive: true) +class ConnectionManager extends _$ConnectionManager { + @override + Future build() async { + final serverInfo = await ref.watch(serverInfoProvider.future); + final key = await ref.watch(keyProvider.future); + final token = ref.watch(bootstrapTokenProvider); + + if (serverInfo == null || key == null) { + return null; + } + final Connection connection; + try { + connection = await connectAndAuthorize(serverInfo, key, bootstrapToken: token); + } catch (e) { + talker.handle(e); + rethrow; + } + + + ref.onDispose(() { + final connection = state.asData?.value; + if (connection != null) { + connection.close(); + } + }); + + return connection; + } +} diff --git a/useragent/lib/providers/connection/connection_manager.g.dart b/useragent/lib/providers/connection/connection_manager.g.dart new file mode 100644 index 0000000..3646316 --- /dev/null +++ b/useragent/lib/providers/connection/connection_manager.g.dart @@ -0,0 +1,54 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'connection_manager.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(ConnectionManager) +final connectionManagerProvider = ConnectionManagerProvider._(); + +final class ConnectionManagerProvider + extends $AsyncNotifierProvider { + ConnectionManagerProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'connectionManagerProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$connectionManagerHash(); + + @$internal + @override + ConnectionManager create() => ConnectionManager(); +} + +String _$connectionManagerHash() => r'8923346dff75a9a06127c71a0a39ca65d9733d8c'; + +abstract class _$ConnectionManager extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, Connection?>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, Connection?>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/useragent/lib/providers/key.dart b/useragent/lib/providers/key.dart index 196fc45..247a906 100644 --- a/useragent/lib/providers/key.dart +++ b/useragent/lib/providers/key.dart @@ -1,7 +1,7 @@ import 'package:mtcore/markettakers.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:arbiter/features/pk_manager.dart'; -import 'package:arbiter/features/simple_ed25519.dart'; +import 'package:arbiter/features/identity/pk_manager.dart'; +import 'package:arbiter/features/identity/simple_ed25519.dart'; part 'key.g.dart'; diff --git a/useragent/lib/providers/server_info.dart b/useragent/lib/providers/server_info.dart index 651143e..27bee2e 100644 --- a/useragent/lib/providers/server_info.dart +++ b/useragent/lib/providers/server_info.dart @@ -1,4 +1,4 @@ -import 'package:arbiter/features/server_info_storage.dart'; +import 'package:arbiter/features/connection/server_info_storage.dart'; import 'package:cryptography/cryptography.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/useragent/lib/router.dart b/useragent/lib/router.dart index 5462fbd..b809a4b 100644 --- a/useragent/lib/router.dart +++ b/useragent/lib/router.dart @@ -8,6 +8,7 @@ class Router extends RootStackRouter { List get routes => [ AutoRoute(page: Bootstrap.page, path: '/bootstrap', initial: true), AutoRoute(page: ServerInfoSetupRoute.page, path: '/server-info'), + AutoRoute(page: ServerConnectionRoute.page, path: '/server-connection'), AutoRoute( page: DashboardRouter.page, diff --git a/useragent/lib/router.gr.dart b/useragent/lib/router.gr.dart index 704418e..e1b93c6 100644 --- a/useragent/lib/router.gr.dart +++ b/useragent/lib/router.gr.dart @@ -13,18 +13,20 @@ import 'package:arbiter/screens/bootstrap.dart' as _i2; import 'package:arbiter/screens/dashboard.dart' as _i4; import 'package:arbiter/screens/dashboard/about.dart' as _i1; import 'package:arbiter/screens/dashboard/calc.dart' as _i3; -import 'package:arbiter/screens/server_info_setup.dart' as _i5; -import 'package:auto_route/auto_route.dart' as _i6; +import 'package:arbiter/screens/server_connection.dart' as _i5; +import 'package:arbiter/screens/server_info_setup.dart' as _i6; +import 'package:auto_route/auto_route.dart' as _i7; +import 'package:flutter/material.dart' as _i8; /// generated route for /// [_i1.AboutScreen] -class AboutRoute extends _i6.PageRouteInfo { - const AboutRoute({List<_i6.PageRouteInfo>? children}) +class AboutRoute extends _i7.PageRouteInfo { + const AboutRoute({List<_i7.PageRouteInfo>? children}) : super(AboutRoute.name, initialChildren: children); static const String name = 'AboutRoute'; - static _i6.PageInfo page = _i6.PageInfo( + static _i7.PageInfo page = _i7.PageInfo( name, builder: (data) { return const _i1.AboutScreen(); @@ -34,13 +36,13 @@ class AboutRoute extends _i6.PageRouteInfo { /// generated route for /// [_i2.Bootstrap] -class Bootstrap extends _i6.PageRouteInfo { - const Bootstrap({List<_i6.PageRouteInfo>? children}) +class Bootstrap extends _i7.PageRouteInfo { + const Bootstrap({List<_i7.PageRouteInfo>? children}) : super(Bootstrap.name, initialChildren: children); static const String name = 'Bootstrap'; - static _i6.PageInfo page = _i6.PageInfo( + static _i7.PageInfo page = _i7.PageInfo( name, builder: (data) { return const _i2.Bootstrap(); @@ -50,13 +52,13 @@ class Bootstrap extends _i6.PageRouteInfo { /// generated route for /// [_i3.CalcScreen] -class CalcRoute extends _i6.PageRouteInfo { - const CalcRoute({List<_i6.PageRouteInfo>? children}) +class CalcRoute extends _i7.PageRouteInfo { + const CalcRoute({List<_i7.PageRouteInfo>? children}) : super(CalcRoute.name, initialChildren: children); static const String name = 'CalcRoute'; - static _i6.PageInfo page = _i6.PageInfo( + static _i7.PageInfo page = _i7.PageInfo( name, builder: (data) { return const _i3.CalcScreen(); @@ -66,13 +68,13 @@ class CalcRoute extends _i6.PageRouteInfo { /// generated route for /// [_i4.DashboardRouter] -class DashboardRouter extends _i6.PageRouteInfo { - const DashboardRouter({List<_i6.PageRouteInfo>? children}) +class DashboardRouter extends _i7.PageRouteInfo { + const DashboardRouter({List<_i7.PageRouteInfo>? children}) : super(DashboardRouter.name, initialChildren: children); static const String name = 'DashboardRouter'; - static _i6.PageInfo page = _i6.PageInfo( + static _i7.PageInfo page = _i7.PageInfo( name, builder: (data) { return const _i4.DashboardRouter(); @@ -81,17 +83,70 @@ class DashboardRouter extends _i6.PageRouteInfo { } /// generated route for -/// [_i5.ServerInfoSetupScreen] -class ServerInfoSetupRoute extends _i6.PageRouteInfo { - const ServerInfoSetupRoute({List<_i6.PageRouteInfo>? children}) +/// [_i5.ServerConnectionScreen] +class ServerConnectionRoute + extends _i7.PageRouteInfo { + ServerConnectionRoute({ + _i8.Key? key, + String? arbiterUrl, + List<_i7.PageRouteInfo>? children, + }) : super( + ServerConnectionRoute.name, + args: ServerConnectionRouteArgs(key: key, arbiterUrl: arbiterUrl), + initialChildren: children, + ); + + static const String name = 'ServerConnectionRoute'; + + static _i7.PageInfo page = _i7.PageInfo( + name, + builder: (data) { + final args = data.argsAs( + orElse: () => const ServerConnectionRouteArgs(), + ); + return _i5.ServerConnectionScreen( + key: args.key, + arbiterUrl: args.arbiterUrl, + ); + }, + ); +} + +class ServerConnectionRouteArgs { + const ServerConnectionRouteArgs({this.key, this.arbiterUrl}); + + final _i8.Key? key; + + final String? arbiterUrl; + + @override + String toString() { + return 'ServerConnectionRouteArgs{key: $key, arbiterUrl: $arbiterUrl}'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! ServerConnectionRouteArgs) return false; + return key == other.key && arbiterUrl == other.arbiterUrl; + } + + @override + int get hashCode => key.hashCode ^ arbiterUrl.hashCode; +} + +/// generated route for +/// [_i6.ServerInfoSetupScreen] +class ServerInfoSetupRoute extends _i7.PageRouteInfo { + const ServerInfoSetupRoute({List<_i7.PageRouteInfo>? children}) : super(ServerInfoSetupRoute.name, initialChildren: children); static const String name = 'ServerInfoSetupRoute'; - static _i6.PageInfo page = _i6.PageInfo( + static _i7.PageInfo page = _i7.PageInfo( name, builder: (data) { - return const _i5.ServerInfoSetupScreen(); + return const _i6.ServerInfoSetupScreen(); }, ); } diff --git a/useragent/lib/screens/server_connection.dart b/useragent/lib/screens/server_connection.dart new file mode 100644 index 0000000..8c1727f --- /dev/null +++ b/useragent/lib/screens/server_connection.dart @@ -0,0 +1,60 @@ +import 'package:arbiter/providers/connection/connection_manager.dart'; +import 'package:arbiter/router.gr.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:sizer/sizer.dart'; + +@RoutePage() +class ServerConnectionScreen extends HookConsumerWidget { + const ServerConnectionScreen({super.key, this.arbiterUrl}); + + final String? arbiterUrl; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final connectionState = ref.watch(connectionManagerProvider); + + if (connectionState.value != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + context.router.replace(const DashboardRouter()); + }); + } + + final body = switch (connectionState) { + AsyncLoading() => const CircularProgressIndicator(), + AsyncError(:final error) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Connection failed', + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, + ), + SizedBox(height: 1.5.h), + Text('$error', textAlign: TextAlign.center), + SizedBox(height: 2.h), + + TextButton( + onPressed: () { + context.router.replace(const ServerInfoSetupRoute()); + }, + child: const Text('Back to server setup'), + ), + ], + ), + _ => const CircularProgressIndicator(), + }; + + return Scaffold( + appBar: AppBar(title: const Text('Connecting')), + body: Center( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 8.w), + child: body, + ), + ), + ); + } +} diff --git a/useragent/lib/screens/server_info_setup.dart b/useragent/lib/screens/server_info_setup.dart index 82873cf..99c319a 100644 --- a/useragent/lib/screens/server_info_setup.dart +++ b/useragent/lib/screens/server_info_setup.dart @@ -1,5 +1,6 @@ -import 'package:arbiter/features/arbiter_url.dart'; -import 'package:arbiter/features/server_info_storage.dart'; +import 'package:arbiter/features/connection/arbiter_url.dart'; +import 'package:arbiter/features/connection/server_info_storage.dart'; +import 'package:arbiter/providers/connection/bootstrap_token.dart'; import 'package:arbiter/providers/server_info.dart'; import 'package:arbiter/router.gr.dart'; import 'package:auto_route/auto_route.dart'; @@ -25,7 +26,7 @@ class ServerInfoSetupScreen extends HookConsumerWidget { if (serverInfo != null) { WidgetsBinding.instance.addPostFrameCallback((_) { if (context.mounted) { - context.router.replace(const DashboardRouter()); + context.router.replace(ServerConnectionRoute()); } }); } @@ -38,6 +39,14 @@ class ServerInfoSetupScreen extends HookConsumerWidget { try { final arbiterUrl = ArbiterUrl.parse(controller.text.trim()); + + // set token before triggering reconnection by updating server info + if (arbiterUrl.bootstrapToken != null) { + ref + .read(bootstrapTokenProvider.notifier) + .set(arbiterUrl.bootstrapToken!); + } + await ref .read(serverInfoProvider.notifier) .save( @@ -47,7 +56,9 @@ class ServerInfoSetupScreen extends HookConsumerWidget { ); if (context.mounted) { - context.router.replace(const DashboardRouter()); + context.router.replace( + ServerConnectionRoute(arbiterUrl: controller.text.trim()), + ); } } on FormatException catch (error) { errorText.value = error.message; @@ -94,13 +105,15 @@ class ServerInfoSetupScreen extends HookConsumerWidget { final options = [ const _OptionCard( title: 'Local', - subtitle: 'Will start and connect to a local service in a future update.', + subtitle: + 'Will start and connect to a local service in a future update.', enabled: false, child: SizedBox.shrink(), ), _OptionCard( title: 'Remote', - subtitle: 'Paste an Arbiter URL to store the server address, port, and CA fingerprint.', + subtitle: + 'Paste an Arbiter URL to store the server address, port, and CA fingerprint.', child: _RemoteConnectionForm( controller: controller, errorText: errorText.value, @@ -271,10 +284,7 @@ class _OptionCard extends StatelessWidget { ), SizedBox(height: 1.h), Text(subtitle, style: theme.textTheme.bodyMedium), - if (enabled) ...[ - SizedBox(height: 2.h), - child, - ], + if (enabled) ...[SizedBox(height: 2.h), child], ], ), ), diff --git a/useragent/macos/Runner/DebugProfile.entitlements b/useragent/macos/Runner/DebugProfile.entitlements index fbad023..da92e01 100644 --- a/useragent/macos/Runner/DebugProfile.entitlements +++ b/useragent/macos/Runner/DebugProfile.entitlements @@ -4,5 +4,7 @@ keychain-access-groups + com.apple.security.network.client + diff --git a/useragent/macos/Runner/Release.entitlements b/useragent/macos/Runner/Release.entitlements index 0833c1b..02b114d 100644 --- a/useragent/macos/Runner/Release.entitlements +++ b/useragent/macos/Runner/Release.entitlements @@ -6,5 +6,7 @@ keychain-access-groups + com.apple.security.network.client + diff --git a/useragent/pubspec.lock b/useragent/pubspec.lock index e6982a5..71f69d1 100644 --- a/useragent/pubspec.lock +++ b/useragent/pubspec.lock @@ -218,7 +218,7 @@ packages: source: hosted version: "0.3.5+2" crypto: - dependency: transitive + dependency: "direct main" description: name: crypto sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf @@ -401,8 +401,16 @@ packages: description: flutter source: sdk version: "0.0.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: "13065f10e135263a4f5a4391b79a8efc5fb8106f8dd555a9e49b750b45393d77" + url: "https://pub.dev" + source: hosted + version: "3.2.3" freezed_annotation: - dependency: transitive + dependency: "direct main" description: name: freezed_annotation sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" @@ -538,13 +546,21 @@ packages: source: hosted version: "0.7.2" json_annotation: - dependency: transitive + dependency: "direct main" description: name: json_annotation - sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.11.0" + version: "4.9.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: c5b2ee75210a0f263c6c7b9eeea80553dbae96ea1bf57f02484e806a3ffdffa3 + url: "https://pub.dev" + source: hosted + version: "6.11.2" leak_tracker: dependency: transitive description: @@ -918,6 +934,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.2.1" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723" + url: "https://pub.dev" + source: hosted + version: "1.3.8" source_map_stack_trace: dependency: transitive description: diff --git a/useragent/pubspec.yaml b/useragent/pubspec.yaml index cc653e5..9775044 100644 --- a/useragent/pubspec.yaml +++ b/useragent/pubspec.yaml @@ -22,6 +22,7 @@ dependencies: hosted: https://git.markettakers.org/api/packages/MarketTakers/pub/ version: ^1.0.6 cryptography: ^2.9.0 + crypto: ^3.0.6 flutter_secure_storage: ^10.0.0 cryptography_flutter: ^2.3.4 riverpod_annotation: ^4.0.0 @@ -30,6 +31,8 @@ dependencies: flutter_hooks: ^0.21.3+1 auto_route: ^11.1.0 protobuf: ^6.0.0 + freezed_annotation: ^3.1.0 + json_annotation: ^4.9.0 dev_dependencies: flutter_test: @@ -39,6 +42,8 @@ dev_dependencies: riverpod_generator: ^4.0.0+1 build_runner: ^2.12.2 auto_route_generator: ^10.4.0 + freezed: ^3.2.3 + json_serializable: ^6.11.2 flutter: uses-material-design: true From 4db102b3d14642783e9348d88e2a4acb745f227a Mon Sep 17 00:00:00 2001 From: hdbg Date: Sun, 15 Mar 2026 22:12:21 +0100 Subject: [PATCH 06/13] feat(useragent): bootstrap / unseal flow implementattion --- protobufs/user_agent.proto | 15 + .../src/actors/user_agent/session.rs | 251 +++++++---- server/crates/arbiter-useragent/src/lib.rs | 48 ++- .../lib/features/connection/connection.dart | 123 +++++- useragent/lib/proto/user_agent.pb.dart | 126 +++++- useragent/lib/proto/user_agent.pbenum.dart | 25 ++ .../providers/connection/bootstrap_token.dart | 7 +- .../connection/bootstrap_token.g.dart | 2 +- .../connection/connection_manager.dart | 10 +- .../connection/connection_manager.g.dart | 2 +- useragent/lib/providers/evm.dart | 23 + useragent/lib/providers/evm.g.dart | 55 +++ useragent/lib/providers/vault_state.dart | 27 ++ useragent/lib/providers/vault_state.g.dart | 49 +++ useragent/lib/router.dart | 1 + useragent/lib/router.gr.dart | 61 ++- useragent/lib/screens/server_connection.dart | 2 +- useragent/lib/screens/vault_setup.dart | 408 ++++++++++++++++++ useragent/lib/widgets/bottom_popup.dart | 92 ++++ 19 files changed, 1213 insertions(+), 114 deletions(-) create mode 100644 useragent/lib/providers/evm.dart create mode 100644 useragent/lib/providers/evm.g.dart create mode 100644 useragent/lib/providers/vault_state.dart create mode 100644 useragent/lib/providers/vault_state.g.dart create mode 100644 useragent/lib/screens/vault_setup.dart create mode 100644 useragent/lib/widgets/bottom_popup.dart diff --git a/protobufs/user_agent.proto b/protobufs/user_agent.proto index fcf508d..821575e 100644 --- a/protobufs/user_agent.proto +++ b/protobufs/user_agent.proto @@ -42,6 +42,12 @@ message UnsealEncryptedKey { bytes associated_data = 3; } +message BootstrapEncryptedKey { + bytes nonce = 1; + bytes ciphertext = 2; + bytes associated_data = 3; +} + enum UnsealResult { UNSEAL_RESULT_UNSPECIFIED = 0; UNSEAL_RESULT_SUCCESS = 1; @@ -49,6 +55,13 @@ enum UnsealResult { UNSEAL_RESULT_UNBOOTSTRAPPED = 3; } +enum BootstrapResult { + BOOTSTRAP_RESULT_UNSPECIFIED = 0; + BOOTSTRAP_RESULT_SUCCESS = 1; + BOOTSTRAP_RESULT_ALREADY_BOOTSTRAPPED = 2; + BOOTSTRAP_RESULT_INVALID_KEY = 3; +} + enum VaultState { VAULT_STATE_UNSPECIFIED = 0; VAULT_STATE_UNBOOTSTRAPPED = 1; @@ -80,6 +93,7 @@ message UserAgentRequest { arbiter.evm.EvmGrantDeleteRequest evm_grant_delete = 9; arbiter.evm.EvmGrantListRequest evm_grant_list = 10; ClientConnectionResponse client_connection_response = 11; + BootstrapEncryptedKey bootstrap_encrypted_key = 12; } } message UserAgentResponse { @@ -96,5 +110,6 @@ message UserAgentResponse { arbiter.evm.EvmGrantListResponse evm_grant_list = 10; ClientConnectionRequest client_connection_request = 11; ClientConnectionCancel client_connection_cancel = 12; + BootstrapResult bootstrap_result = 13; } } diff --git a/server/crates/arbiter-server/src/actors/user_agent/session.rs b/server/crates/arbiter-server/src/actors/user_agent/session.rs index 5ef3b20..376273d 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/session.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/session.rs @@ -3,9 +3,9 @@ use std::{ops::DerefMut, sync::Mutex}; use arbiter_proto::proto::{ evm as evm_proto, user_agent::{ - ClientConnectionCancel, ClientConnectionRequest, UnsealEncryptedKey, UnsealResult, - UnsealStart, UnsealStartResponse, UserAgentRequest, UserAgentResponse, - user_agent_request::Payload as UserAgentRequestPayload, + BootstrapEncryptedKey, BootstrapResult, ClientConnectionCancel, ClientConnectionRequest, + UnsealEncryptedKey, UnsealResult, UnsealStart, UnsealStartResponse, UserAgentRequest, + UserAgentResponse, user_agent_request::Payload as UserAgentRequestPayload, user_agent_response::Payload as UserAgentResponsePayload, }, }; @@ -19,7 +19,7 @@ use x25519_dalek::{EphemeralSecret, PublicKey}; use crate::actors::{ evm::{Generate, ListWallets}, - keyholder::{self, TryUnseal}, + keyholder::{self, Bootstrap, TryUnseal}, router::RegisterUserAgent, user_agent::{TransportResponseError, UserAgentConnection}, }; @@ -168,9 +168,11 @@ impl UserAgentSession { UserAgentRequestPayload::UnsealEncryptedKey(unseal_encrypted_key) => { self.handle_unseal_encrypted_key(unseal_encrypted_key).await } - UserAgentRequestPayload::QueryVaultState(_) => { - self.handle_query_vault_state().await + UserAgentRequestPayload::BootstrapEncryptedKey(bootstrap_encrypted_key) => { + self.handle_bootstrap_encrypted_key(bootstrap_encrypted_key) + .await } + UserAgentRequestPayload::QueryVaultState(_) => self.handle_query_vault_state().await, UserAgentRequestPayload::EvmWalletCreate(_) => self.handle_evm_wallet_create().await, UserAgentRequestPayload::EvmWalletList(_) => self.handle_evm_wallet_list().await, _ => Err(TransportResponseError::UnexpectedRequestPayload), @@ -187,6 +189,59 @@ fn response(payload: UserAgentResponsePayload) -> UserAgentResponse { } impl UserAgentSession { + fn take_unseal_secret( + &mut self, + ) -> Result<(EphemeralSecret, PublicKey), TransportResponseError> { + let UserAgentStates::WaitingForUnsealKey(unseal_context) = self.state.state() else { + error!("Received encrypted key in invalid state"); + return Err(TransportResponseError::InvalidStateForUnsealEncryptedKey); + }; + + let ephemeral_secret = { + let mut secret_lock = unseal_context.secret.lock().unwrap(); + let secret = secret_lock.take(); + match secret { + Some(secret) => secret, + None => { + drop(secret_lock); + error!("Ephemeral secret already taken"); + return Err(TransportResponseError::StateTransitionFailed); + } + } + }; + + Ok((ephemeral_secret, unseal_context.client_public_key)) + } + + fn decrypt_client_key_material( + ephemeral_secret: EphemeralSecret, + client_public_key: PublicKey, + nonce: &[u8], + ciphertext: &[u8], + associated_data: &[u8], + ) -> Result>, ()> { + let nonce = XNonce::from_slice(nonce); + + let shared_secret = ephemeral_secret.diffie_hellman(&client_public_key); + let cipher = XChaCha20Poly1305::new(shared_secret.as_bytes().into()); + + let mut key_buffer = MemSafe::new(ciphertext.to_vec()).unwrap(); + + let decryption_result = { + let mut write_handle = key_buffer.write().unwrap(); + let write_handle = write_handle.deref_mut(); + cipher.decrypt_in_place(nonce, associated_data, write_handle) + }; + + match decryption_result { + Ok(_) => Ok(key_buffer), + Err(err) => { + error!(?err, "Failed to decrypt encrypted key material"); + Err(()) + } + } + } + async fn handle_unseal_request(&mut self, req: UnsealStart) -> Output { let secret = EphemeralSecret::random(); let public_key = PublicKey::from(&secret); @@ -211,92 +266,140 @@ impl UserAgentSession { } async fn handle_unseal_encrypted_key(&mut self, req: UnsealEncryptedKey) -> Output { - let UserAgentStates::WaitingForUnsealKey(unseal_context) = self.state.state() else { - error!("Received unseal encrypted key in invalid state"); - return Err(TransportResponseError::InvalidStateForUnsealEncryptedKey); + let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() { + Ok(values) => values, + Err(TransportResponseError::StateTransitionFailed) => { + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + return Ok(response(UserAgentResponsePayload::UnsealResult( + UnsealResult::InvalidKey.into(), + ))); + } + Err(err) => return Err(err), }; - let ephemeral_secret = { - let mut secret_lock = unseal_context.secret.lock().unwrap(); - let secret = secret_lock.take(); - match secret { - Some(secret) => secret, - None => { - drop(secret_lock); - error!("Ephemeral secret already taken"); - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - return Ok(response(UserAgentResponsePayload::UnsealResult( - UnsealResult::InvalidKey.into(), - ))); - } + + let seal_key_buffer = match Self::decrypt_client_key_material( + ephemeral_secret, + client_public_key, + &req.nonce, + &req.ciphertext, + &req.associated_data, + ) { + Ok(buffer) => buffer, + Err(()) => { + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + return Ok(response(UserAgentResponsePayload::UnsealResult( + UnsealResult::InvalidKey.into(), + ))); } }; - let nonce = XNonce::from_slice(&req.nonce); - - let shared_secret = ephemeral_secret.diffie_hellman(&unseal_context.client_public_key); - let cipher = XChaCha20Poly1305::new(shared_secret.as_bytes().into()); - - let mut seal_key_buffer = MemSafe::new(req.ciphertext.clone()).unwrap(); - - let decryption_result = { - let mut write_handle = seal_key_buffer.write().unwrap(); - let write_handle = write_handle.deref_mut(); - cipher.decrypt_in_place(nonce, &req.associated_data, write_handle) - }; - - match decryption_result { + match self + .props + .actors + .key_holder + .ask(TryUnseal { + seal_key_raw: seal_key_buffer, + }) + .await + { Ok(_) => { - match self - .props - .actors - .key_holder - .ask(TryUnseal { - seal_key_raw: seal_key_buffer, - }) - .await - { - Ok(_) => { - info!("Successfully unsealed key with client-provided key"); - self.transition(UserAgentEvents::ReceivedValidKey)?; - Ok(response(UserAgentResponsePayload::UnsealResult( - UnsealResult::Success.into(), - ))) - } - Err(SendError::HandlerError(keyholder::Error::InvalidKey)) => { - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Ok(response(UserAgentResponsePayload::UnsealResult( - UnsealResult::InvalidKey.into(), - ))) - } - Err(SendError::HandlerError(err)) => { - error!(?err, "Keyholder failed to unseal key"); - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Ok(response(UserAgentResponsePayload::UnsealResult( - UnsealResult::InvalidKey.into(), - ))) - } - Err(err) => { - error!(?err, "Failed to send unseal request to keyholder"); - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Err(TransportResponseError::KeyHolderActorUnreachable) - } - } + info!("Successfully unsealed key with client-provided key"); + self.transition(UserAgentEvents::ReceivedValidKey)?; + Ok(response(UserAgentResponsePayload::UnsealResult( + UnsealResult::Success.into(), + ))) } - Err(err) => { - error!(?err, "Failed to decrypt unseal key"); + Err(SendError::HandlerError(keyholder::Error::InvalidKey)) => { self.transition(UserAgentEvents::ReceivedInvalidKey)?; Ok(response(UserAgentResponsePayload::UnsealResult( UnsealResult::InvalidKey.into(), ))) } + Err(SendError::HandlerError(err)) => { + error!(?err, "Keyholder failed to unseal key"); + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + Ok(response(UserAgentResponsePayload::UnsealResult( + UnsealResult::InvalidKey.into(), + ))) + } + Err(err) => { + error!(?err, "Failed to send unseal request to keyholder"); + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + Err(TransportResponseError::KeyHolderActorUnreachable) + } + } + } + + async fn handle_bootstrap_encrypted_key(&mut self, req: BootstrapEncryptedKey) -> Output { + let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() { + Ok(values) => values, + Err(TransportResponseError::StateTransitionFailed) => { + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + return Ok(response(UserAgentResponsePayload::BootstrapResult( + BootstrapResult::InvalidKey.into(), + ))); + } + Err(err) => return Err(err), + }; + + let seal_key_buffer = match Self::decrypt_client_key_material( + ephemeral_secret, + client_public_key, + &req.nonce, + &req.ciphertext, + &req.associated_data, + ) { + Ok(buffer) => buffer, + Err(()) => { + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + return Ok(response(UserAgentResponsePayload::BootstrapResult( + BootstrapResult::InvalidKey.into(), + ))); + } + }; + + match self + .props + .actors + .key_holder + .ask(Bootstrap { + seal_key_raw: seal_key_buffer, + }) + .await + { + Ok(_) => { + info!("Successfully bootstrapped vault with client-provided key"); + self.transition(UserAgentEvents::ReceivedValidKey)?; + Ok(response(UserAgentResponsePayload::BootstrapResult( + BootstrapResult::Success.into(), + ))) + } + Err(SendError::HandlerError(keyholder::Error::AlreadyBootstrapped)) => { + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + Ok(response(UserAgentResponsePayload::BootstrapResult( + BootstrapResult::AlreadyBootstrapped.into(), + ))) + } + Err(SendError::HandlerError(err)) => { + error!(?err, "Keyholder failed to bootstrap vault"); + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + Ok(response(UserAgentResponsePayload::BootstrapResult( + BootstrapResult::InvalidKey.into(), + ))) + } + Err(err) => { + error!(?err, "Failed to send bootstrap request to keyholder"); + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + Err(TransportResponseError::KeyHolderActorUnreachable) + } } } } impl UserAgentSession { async fn handle_query_vault_state(&mut self) -> Output { - use arbiter_proto::proto::user_agent::VaultState; use crate::actors::keyholder::{GetState, StateDiscriminants}; + use arbiter_proto::proto::user_agent::VaultState; let vault_state = match self.props.actors.key_holder.ask(GetState {}).await { Ok(StateDiscriminants::Unbootstrapped) => VaultState::Unbootstrapped, diff --git a/server/crates/arbiter-useragent/src/lib.rs b/server/crates/arbiter-useragent/src/lib.rs index c21b4a3..a9e86bb 100644 --- a/server/crates/arbiter-useragent/src/lib.rs +++ b/server/crates/arbiter-useragent/src/lib.rs @@ -254,12 +254,11 @@ where } mod grpc; -pub use grpc::{connect_grpc, ConnectError, UserAgentGrpc}; +pub use grpc::{ConnectError, UserAgentGrpc, connect_grpc}; use arbiter_proto::proto::user_agent::{ - UnsealEncryptedKey, UnsealStart, - user_agent_request::Payload as RequestPayload, - user_agent_response::Payload as ResponsePayload, + BootstrapEncryptedKey, UnsealEncryptedKey, UnsealStart, + user_agent_request::Payload as RequestPayload, user_agent_response::Payload as ResponsePayload, }; /// Send an `UnsealStart` request and await the server's `UnsealStartResponse`. @@ -274,6 +273,13 @@ pub struct SendUnsealEncryptedKey { pub associated_data: Vec, } +/// Send a `BootstrapEncryptedKey` request and await the server's `BootstrapResult`. +pub struct SendBootstrapEncryptedKey { + pub nonce: Vec, + pub ciphertext: Vec, + pub associated_data: Vec, +} + /// Query the server for the current `VaultState`. pub struct QueryVaultState; @@ -350,6 +356,40 @@ where } } +impl kameo::message::Message for UserAgentActor +where + Transport: Bi, +{ + type Reply = Result; + + async fn handle( + &mut self, + msg: SendBootstrapEncryptedKey, + _ctx: &mut kameo::message::Context, + ) -> Self::Reply { + self.transport + .send(UserAgentRequest { + payload: Some(RequestPayload::BootstrapEncryptedKey( + BootstrapEncryptedKey { + nonce: msg.nonce, + ciphertext: msg.ciphertext, + associated_data: msg.associated_data, + }, + )), + }) + .await + .map_err(|_| SessionError::TransportSendFailed)?; + + match self.transport.recv().await { + Some(resp) => match resp.payload { + Some(ResponsePayload::BootstrapResult(r)) => Ok(r), + _ => Err(SessionError::UnexpectedResponse), + }, + None => Err(SessionError::TransportClosed), + } + } +} + impl kameo::message::Message for UserAgentActor where Transport: Bi, diff --git a/useragent/lib/features/connection/connection.dart b/useragent/lib/features/connection/connection.dart index 4afc46e..4e6436f 100644 --- a/useragent/lib/features/connection/connection.dart +++ b/useragent/lib/features/connection/connection.dart @@ -5,6 +5,7 @@ import 'package:arbiter/features/connection/server_info_storage.dart'; import 'package:arbiter/features/identity/pk_manager.dart'; import 'package:arbiter/proto/arbiter.pbgrpc.dart'; import 'package:arbiter/proto/user_agent.pb.dart'; +import 'package:cryptography/cryptography.dart'; import 'package:grpc/grpc.dart'; import 'package:mtcore/markettakers.dart'; @@ -25,9 +26,11 @@ class Connection { } Future receive() async { - await _rx.moveNext(); + final hasValue = await _rx.moveNext(); + if (!hasValue) { + throw Exception('Connection closed while waiting for server response.'); + } return _rx.current; - } Future close() async { @@ -64,6 +67,112 @@ List formatChallenge(AuthChallenge challenge, List pubkey) { return utf8.encode(payload); } +const _vaultKeyAssociatedData = 'arbiter.vault.password'; + +Future bootstrapVault( + Connection connection, + String password, +) async { + final encryptedKey = await _encryptVaultKeyMaterial(connection, password); + + await connection.send( + UserAgentRequest( + bootstrapEncryptedKey: BootstrapEncryptedKey( + nonce: encryptedKey.nonce, + ciphertext: encryptedKey.ciphertext, + associatedData: encryptedKey.associatedData, + ), + ), + ); + + final response = await connection.receive(); + if (!response.hasBootstrapResult()) { + throw Exception( + 'Expected bootstrap result, got ${response.whichPayload()}', + ); + } + + return response.bootstrapResult; +} + +Future unsealVault(Connection connection, String password) async { + final encryptedKey = await _encryptVaultKeyMaterial(connection, password); + + await connection.send( + UserAgentRequest( + unsealEncryptedKey: UnsealEncryptedKey( + nonce: encryptedKey.nonce, + ciphertext: encryptedKey.ciphertext, + associatedData: encryptedKey.associatedData, + ), + ), + ); + + final response = await connection.receive(); + if (!response.hasUnsealResult()) { + throw Exception('Expected unseal result, got ${response.whichPayload()}'); + } + + return response.unsealResult; +} + +Future<_EncryptedVaultKey> _encryptVaultKeyMaterial( + Connection connection, + String password, +) async { + final keyExchange = X25519(); + final cipher = Xchacha20.poly1305Aead(); + final clientKeyPair = await keyExchange.newKeyPair(); + final clientPublicKey = await clientKeyPair.extractPublicKey(); + + await connection.send( + UserAgentRequest( + unsealStart: UnsealStart(clientPubkey: clientPublicKey.bytes), + ), + ); + + final handshakeResponse = await connection.receive(); + if (!handshakeResponse.hasUnsealStartResponse()) { + throw Exception( + 'Expected unseal handshake response, got ${handshakeResponse.whichPayload()}', + ); + } + + final serverPublicKey = SimplePublicKey( + handshakeResponse.unsealStartResponse.serverPubkey, + type: KeyPairType.x25519, + ); + final sharedSecret = await keyExchange.sharedSecretKey( + keyPair: clientKeyPair, + remotePublicKey: serverPublicKey, + ); + + final secretBox = await cipher.encrypt( + utf8.encode(password), + secretKey: sharedSecret, + nonce: cipher.newNonce(), + aad: utf8.encode(_vaultKeyAssociatedData), + ); + + return _EncryptedVaultKey( + nonce: secretBox.nonce, + ciphertext: [...secretBox.cipherText, ...secretBox.mac.bytes], + associatedData: utf8.encode(_vaultKeyAssociatedData), + ); +} + +class _EncryptedVaultKey { + const _EncryptedVaultKey({ + required this.nonce, + required this.ciphertext, + required this.associatedData, + }); + + final List nonce; + final List ciphertext; + final List associatedData; +} + Future connectAndAuthorize( StoredServerInfo serverInfo, KeyHandle key, { @@ -90,12 +199,14 @@ Future connectAndAuthorize( "Sent auth challenge request with pubkey ${base64Encode(pubkey)}", ); - final response = await connection.receive(); - talker.info( - 'Received response from server, checking for auth challenge...', - ); + talker.info('Received response from server, checking auth flow...'); + + if (response.hasAuthOk()) { + talker.info('Authentication successful, connection established'); + return connection; + } if (!response.hasAuthChallenge()) { throw Exception( diff --git a/useragent/lib/proto/user_agent.pb.dart b/useragent/lib/proto/user_agent.pb.dart index f73a61e..e7e96a1 100644 --- a/useragent/lib/proto/user_agent.pb.dart +++ b/useragent/lib/proto/user_agent.pb.dart @@ -460,6 +460,89 @@ class UnsealEncryptedKey extends $pb.GeneratedMessage { void clearAssociatedData() => $_clearField(3); } +class BootstrapEncryptedKey extends $pb.GeneratedMessage { + factory BootstrapEncryptedKey({ + $core.List<$core.int>? nonce, + $core.List<$core.int>? ciphertext, + $core.List<$core.int>? associatedData, + }) { + final result = create(); + if (nonce != null) result.nonce = nonce; + if (ciphertext != null) result.ciphertext = ciphertext; + if (associatedData != null) result.associatedData = associatedData; + return result; + } + + BootstrapEncryptedKey._(); + + factory BootstrapEncryptedKey.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory BootstrapEncryptedKey.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'BootstrapEncryptedKey', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'), + createEmptyInstance: create) + ..a<$core.List<$core.int>>( + 1, _omitFieldNames ? '' : 'nonce', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>( + 2, _omitFieldNames ? '' : 'ciphertext', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>( + 3, _omitFieldNames ? '' : 'associatedData', $pb.PbFieldType.OY) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + BootstrapEncryptedKey clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + BootstrapEncryptedKey copyWith( + void Function(BootstrapEncryptedKey) updates) => + super.copyWith((message) => updates(message as BootstrapEncryptedKey)) + as BootstrapEncryptedKey; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static BootstrapEncryptedKey create() => BootstrapEncryptedKey._(); + @$core.override + BootstrapEncryptedKey createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static BootstrapEncryptedKey getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static BootstrapEncryptedKey? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get nonce => $_getN(0); + @$pb.TagNumber(1) + set nonce($core.List<$core.int> value) => $_setBytes(0, value); + @$pb.TagNumber(1) + $core.bool hasNonce() => $_has(0); + @$pb.TagNumber(1) + void clearNonce() => $_clearField(1); + + @$pb.TagNumber(2) + $core.List<$core.int> get ciphertext => $_getN(1); + @$pb.TagNumber(2) + set ciphertext($core.List<$core.int> value) => $_setBytes(1, value); + @$pb.TagNumber(2) + $core.bool hasCiphertext() => $_has(1); + @$pb.TagNumber(2) + void clearCiphertext() => $_clearField(2); + + @$pb.TagNumber(3) + $core.List<$core.int> get associatedData => $_getN(2); + @$pb.TagNumber(3) + set associatedData($core.List<$core.int> value) => $_setBytes(2, value); + @$pb.TagNumber(3) + $core.bool hasAssociatedData() => $_has(2); + @$pb.TagNumber(3) + void clearAssociatedData() => $_clearField(3); +} + class ClientConnectionRequest extends $pb.GeneratedMessage { factory ClientConnectionRequest({ $core.List<$core.int>? pubkey, @@ -625,6 +708,7 @@ enum UserAgentRequest_Payload { evmGrantDelete, evmGrantList, clientConnectionResponse, + bootstrapEncryptedKey, notSet } @@ -641,6 +725,7 @@ class UserAgentRequest extends $pb.GeneratedMessage { $1.EvmGrantDeleteRequest? evmGrantDelete, $1.EvmGrantListRequest? evmGrantList, ClientConnectionResponse? clientConnectionResponse, + BootstrapEncryptedKey? bootstrapEncryptedKey, }) { final result = create(); if (authChallengeRequest != null) @@ -658,6 +743,8 @@ class UserAgentRequest extends $pb.GeneratedMessage { if (evmGrantList != null) result.evmGrantList = evmGrantList; if (clientConnectionResponse != null) result.clientConnectionResponse = clientConnectionResponse; + if (bootstrapEncryptedKey != null) + result.bootstrapEncryptedKey = bootstrapEncryptedKey; return result; } @@ -683,6 +770,7 @@ class UserAgentRequest extends $pb.GeneratedMessage { 9: UserAgentRequest_Payload.evmGrantDelete, 10: UserAgentRequest_Payload.evmGrantList, 11: UserAgentRequest_Payload.clientConnectionResponse, + 12: UserAgentRequest_Payload.bootstrapEncryptedKey, 0: UserAgentRequest_Payload.notSet }; static final $pb.BuilderInfo _i = $pb.BuilderInfo( @@ -690,7 +778,7 @@ class UserAgentRequest extends $pb.GeneratedMessage { package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'), createEmptyInstance: create) - ..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + ..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) ..aOM( 1, _omitFieldNames ? '' : 'authChallengeRequest', subBuilder: AuthChallengeRequest.create) @@ -716,6 +804,9 @@ class UserAgentRequest extends $pb.GeneratedMessage { ..aOM( 11, _omitFieldNames ? '' : 'clientConnectionResponse', subBuilder: ClientConnectionResponse.create) + ..aOM( + 12, _omitFieldNames ? '' : 'bootstrapEncryptedKey', + subBuilder: BootstrapEncryptedKey.create) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -748,6 +839,7 @@ class UserAgentRequest extends $pb.GeneratedMessage { @$pb.TagNumber(9) @$pb.TagNumber(10) @$pb.TagNumber(11) + @$pb.TagNumber(12) UserAgentRequest_Payload whichPayload() => _UserAgentRequest_PayloadByTag[$_whichOneof(0)]!; @$pb.TagNumber(1) @@ -761,6 +853,7 @@ class UserAgentRequest extends $pb.GeneratedMessage { @$pb.TagNumber(9) @$pb.TagNumber(10) @$pb.TagNumber(11) + @$pb.TagNumber(12) void clearPayload() => $_clearField($_whichOneof(0)); @$pb.TagNumber(1) @@ -885,6 +978,18 @@ class UserAgentRequest extends $pb.GeneratedMessage { void clearClientConnectionResponse() => $_clearField(11); @$pb.TagNumber(11) ClientConnectionResponse ensureClientConnectionResponse() => $_ensure(10); + + @$pb.TagNumber(12) + BootstrapEncryptedKey get bootstrapEncryptedKey => $_getN(11); + @$pb.TagNumber(12) + set bootstrapEncryptedKey(BootstrapEncryptedKey value) => + $_setField(12, value); + @$pb.TagNumber(12) + $core.bool hasBootstrapEncryptedKey() => $_has(11); + @$pb.TagNumber(12) + void clearBootstrapEncryptedKey() => $_clearField(12); + @$pb.TagNumber(12) + BootstrapEncryptedKey ensureBootstrapEncryptedKey() => $_ensure(11); } enum UserAgentResponse_Payload { @@ -900,6 +1005,7 @@ enum UserAgentResponse_Payload { evmGrantList, clientConnectionRequest, clientConnectionCancel, + bootstrapResult, notSet } @@ -917,6 +1023,7 @@ class UserAgentResponse extends $pb.GeneratedMessage { $1.EvmGrantListResponse? evmGrantList, ClientConnectionRequest? clientConnectionRequest, ClientConnectionCancel? clientConnectionCancel, + BootstrapResult? bootstrapResult, }) { final result = create(); if (authChallenge != null) result.authChallenge = authChallenge; @@ -934,6 +1041,7 @@ class UserAgentResponse extends $pb.GeneratedMessage { result.clientConnectionRequest = clientConnectionRequest; if (clientConnectionCancel != null) result.clientConnectionCancel = clientConnectionCancel; + if (bootstrapResult != null) result.bootstrapResult = bootstrapResult; return result; } @@ -960,6 +1068,7 @@ class UserAgentResponse extends $pb.GeneratedMessage { 10: UserAgentResponse_Payload.evmGrantList, 11: UserAgentResponse_Payload.clientConnectionRequest, 12: UserAgentResponse_Payload.clientConnectionCancel, + 13: UserAgentResponse_Payload.bootstrapResult, 0: UserAgentResponse_Payload.notSet }; static final $pb.BuilderInfo _i = $pb.BuilderInfo( @@ -967,7 +1076,7 @@ class UserAgentResponse extends $pb.GeneratedMessage { package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'), createEmptyInstance: create) - ..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + ..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) ..aOM(1, _omitFieldNames ? '' : 'authChallenge', subBuilder: AuthChallenge.create) ..aOM(2, _omitFieldNames ? '' : 'authOk', subBuilder: AuthOk.create) @@ -993,6 +1102,8 @@ class UserAgentResponse extends $pb.GeneratedMessage { ..aOM( 12, _omitFieldNames ? '' : 'clientConnectionCancel', subBuilder: ClientConnectionCancel.create) + ..aE(13, _omitFieldNames ? '' : 'bootstrapResult', + enumValues: BootstrapResult.values) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -1026,6 +1137,7 @@ class UserAgentResponse extends $pb.GeneratedMessage { @$pb.TagNumber(10) @$pb.TagNumber(11) @$pb.TagNumber(12) + @$pb.TagNumber(13) UserAgentResponse_Payload whichPayload() => _UserAgentResponse_PayloadByTag[$_whichOneof(0)]!; @$pb.TagNumber(1) @@ -1040,6 +1152,7 @@ class UserAgentResponse extends $pb.GeneratedMessage { @$pb.TagNumber(10) @$pb.TagNumber(11) @$pb.TagNumber(12) + @$pb.TagNumber(13) void clearPayload() => $_clearField($_whichOneof(0)); @$pb.TagNumber(1) @@ -1171,6 +1284,15 @@ class UserAgentResponse extends $pb.GeneratedMessage { void clearClientConnectionCancel() => $_clearField(12); @$pb.TagNumber(12) ClientConnectionCancel ensureClientConnectionCancel() => $_ensure(11); + + @$pb.TagNumber(13) + BootstrapResult get bootstrapResult => $_getN(12); + @$pb.TagNumber(13) + set bootstrapResult(BootstrapResult value) => $_setField(13, value); + @$pb.TagNumber(13) + $core.bool hasBootstrapResult() => $_has(12); + @$pb.TagNumber(13) + void clearBootstrapResult() => $_clearField(13); } const $core.bool _omitFieldNames = diff --git a/useragent/lib/proto/user_agent.pbenum.dart b/useragent/lib/proto/user_agent.pbenum.dart index 9ceaae4..41b6c7e 100644 --- a/useragent/lib/proto/user_agent.pbenum.dart +++ b/useragent/lib/proto/user_agent.pbenum.dart @@ -64,6 +64,31 @@ class UnsealResult extends $pb.ProtobufEnum { const UnsealResult._(super.value, super.name); } +class BootstrapResult extends $pb.ProtobufEnum { + static const BootstrapResult BOOTSTRAP_RESULT_UNSPECIFIED = + BootstrapResult._(0, _omitEnumNames ? '' : 'BOOTSTRAP_RESULT_UNSPECIFIED'); + static const BootstrapResult BOOTSTRAP_RESULT_SUCCESS = + BootstrapResult._(1, _omitEnumNames ? '' : 'BOOTSTRAP_RESULT_SUCCESS'); + static const BootstrapResult BOOTSTRAP_RESULT_ALREADY_BOOTSTRAPPED = + BootstrapResult._(2, _omitEnumNames ? '' : 'BOOTSTRAP_RESULT_ALREADY_BOOTSTRAPPED'); + static const BootstrapResult BOOTSTRAP_RESULT_INVALID_KEY = + BootstrapResult._(3, _omitEnumNames ? '' : 'BOOTSTRAP_RESULT_INVALID_KEY'); + + static const $core.List values = [ + BOOTSTRAP_RESULT_UNSPECIFIED, + BOOTSTRAP_RESULT_SUCCESS, + BOOTSTRAP_RESULT_ALREADY_BOOTSTRAPPED, + BOOTSTRAP_RESULT_INVALID_KEY, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 3); + static BootstrapResult? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const BootstrapResult._(super.value, super.name); +} + class VaultState extends $pb.ProtobufEnum { static const VaultState VAULT_STATE_UNSPECIFIED = VaultState._(0, _omitEnumNames ? '' : 'VAULT_STATE_UNSPECIFIED'); diff --git a/useragent/lib/providers/connection/bootstrap_token.dart b/useragent/lib/providers/connection/bootstrap_token.dart index 1062f31..48b8291 100644 --- a/useragent/lib/providers/connection/bootstrap_token.dart +++ b/useragent/lib/providers/connection/bootstrap_token.dart @@ -6,6 +6,7 @@ part 'bootstrap_token.g.dart'; @Riverpod(keepAlive: true) class BootstrapToken extends _$BootstrapToken { + @override String? build() { return null; } @@ -14,9 +15,13 @@ class BootstrapToken extends _$BootstrapToken { state = token; } + void clear() { + state = null; + } + String? take() { final token = state; state = null; return token; } -} \ No newline at end of file +} diff --git a/useragent/lib/providers/connection/bootstrap_token.g.dart b/useragent/lib/providers/connection/bootstrap_token.g.dart index 4477998..0c46387 100644 --- a/useragent/lib/providers/connection/bootstrap_token.g.dart +++ b/useragent/lib/providers/connection/bootstrap_token.g.dart @@ -41,7 +41,7 @@ final class BootstrapTokenProvider } } -String _$bootstrapTokenHash() => r'a59e679ab0561ed2ab4148660499891571d439db'; +String _$bootstrapTokenHash() => r'5c09ea4480fc3a7fd0d0a0bced712912542cca5d'; abstract class _$BootstrapToken extends $Notifier { String? build(); diff --git a/useragent/lib/providers/connection/connection_manager.dart b/useragent/lib/providers/connection/connection_manager.dart index c1e5266..08f8d0f 100644 --- a/useragent/lib/providers/connection/connection_manager.dart +++ b/useragent/lib/providers/connection/connection_manager.dart @@ -20,12 +20,18 @@ class ConnectionManager extends _$ConnectionManager { } final Connection connection; try { - connection = await connectAndAuthorize(serverInfo, key, bootstrapToken: token); + connection = await connectAndAuthorize( + serverInfo, + key, + bootstrapToken: token, + ); + if (token != null) { + ref.read(bootstrapTokenProvider.notifier).clear(); + } } catch (e) { talker.handle(e); rethrow; } - ref.onDispose(() { final connection = state.asData?.value; diff --git a/useragent/lib/providers/connection/connection_manager.g.dart b/useragent/lib/providers/connection/connection_manager.g.dart index 3646316..4869eff 100644 --- a/useragent/lib/providers/connection/connection_manager.g.dart +++ b/useragent/lib/providers/connection/connection_manager.g.dart @@ -33,7 +33,7 @@ final class ConnectionManagerProvider ConnectionManager create() => ConnectionManager(); } -String _$connectionManagerHash() => r'8923346dff75a9a06127c71a0a39ca65d9733d8c'; +String _$connectionManagerHash() => r'd01084e550f315bc6cadfe74413a7f959426a80e'; abstract class _$ConnectionManager extends $AsyncNotifier { FutureOr build(); diff --git a/useragent/lib/providers/evm.dart b/useragent/lib/providers/evm.dart new file mode 100644 index 0000000..17734f4 --- /dev/null +++ b/useragent/lib/providers/evm.dart @@ -0,0 +1,23 @@ +import 'package:arbiter/proto/evm.pb.dart'; +import 'package:arbiter/proto/user_agent.pb.dart'; +import 'package:arbiter/providers/connection/connection_manager.dart'; +import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'evm.g.dart'; + +@riverpod +class Evm extends _$Evm { + Future?> build() async { + final connection = await ref.watch(connectionManagerProvider.future); + if (connection == null) { + return null; + } + + await connection.send(UserAgentRequest( + evmWalletList: Empty() + )); + + final response = await connection.receive(); + } +} diff --git a/useragent/lib/providers/evm.g.dart b/useragent/lib/providers/evm.g.dart new file mode 100644 index 0000000..60eca52 --- /dev/null +++ b/useragent/lib/providers/evm.g.dart @@ -0,0 +1,55 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'evm.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(Evm) +final evmProvider = EvmProvider._(); + +final class EvmProvider + extends $AsyncNotifierProvider?> { + EvmProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'evmProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$evmHash(); + + @$internal + @override + Evm create() => Evm(); +} + +String _$evmHash() => r'6d2e0baf7b78a0850d7b99b0be7abde206e088c7'; + +abstract class _$Evm extends $AsyncNotifier?> { + FutureOr?> build(); + @$mustCallSuper + @override + void runBuild() { + final ref = + this.ref as $Ref?>, List?>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier?>, List?>, + AsyncValue?>, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/useragent/lib/providers/vault_state.dart b/useragent/lib/providers/vault_state.dart new file mode 100644 index 0000000..de02c5e --- /dev/null +++ b/useragent/lib/providers/vault_state.dart @@ -0,0 +1,27 @@ +import 'package:arbiter/proto/user_agent.pb.dart'; +import 'package:arbiter/providers/connection/connection_manager.dart'; +import 'package:mtcore/markettakers.dart'; +import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'vault_state.g.dart'; + +@riverpod +Future vaultState(Ref ref) async { + final conn = await ref.watch(connectionManagerProvider.future); + if (conn == null) { + return null; + } + + await conn.send(UserAgentRequest(queryVaultState: Empty())); + + final resp = await conn.receive(); + if (resp.whichPayload() != UserAgentResponse_Payload.vaultState) { + talker.warning('Expected vault state response, got ${resp.whichPayload()}'); + return null; + } + + final vaultState = resp.vaultState; + + return vaultState; +} diff --git a/useragent/lib/providers/vault_state.g.dart b/useragent/lib/providers/vault_state.g.dart new file mode 100644 index 0000000..aa4f953 --- /dev/null +++ b/useragent/lib/providers/vault_state.g.dart @@ -0,0 +1,49 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'vault_state.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(vaultState) +final vaultStateProvider = VaultStateProvider._(); + +final class VaultStateProvider + extends + $FunctionalProvider< + AsyncValue, + VaultState?, + FutureOr + > + with $FutureModifier, $FutureProvider { + VaultStateProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'vaultStateProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$vaultStateHash(); + + @$internal + @override + $FutureProviderElement $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr create(Ref ref) { + return vaultState(ref); + } +} + +String _$vaultStateHash() => r'1fd975a9661de1f62beef9eb1c7c439f377a8b88'; diff --git a/useragent/lib/router.dart b/useragent/lib/router.dart index b809a4b..78f14a2 100644 --- a/useragent/lib/router.dart +++ b/useragent/lib/router.dart @@ -9,6 +9,7 @@ class Router extends RootStackRouter { AutoRoute(page: Bootstrap.page, path: '/bootstrap', initial: true), AutoRoute(page: ServerInfoSetupRoute.page, path: '/server-info'), AutoRoute(page: ServerConnectionRoute.page, path: '/server-connection'), + AutoRoute(page: VaultSetupRoute.page, path: '/vault'), AutoRoute( page: DashboardRouter.page, diff --git a/useragent/lib/router.gr.dart b/useragent/lib/router.gr.dart index e1b93c6..aa123e8 100644 --- a/useragent/lib/router.gr.dart +++ b/useragent/lib/router.gr.dart @@ -15,18 +15,19 @@ import 'package:arbiter/screens/dashboard/about.dart' as _i1; import 'package:arbiter/screens/dashboard/calc.dart' as _i3; import 'package:arbiter/screens/server_connection.dart' as _i5; import 'package:arbiter/screens/server_info_setup.dart' as _i6; -import 'package:auto_route/auto_route.dart' as _i7; -import 'package:flutter/material.dart' as _i8; +import 'package:arbiter/screens/vault_setup.dart' as _i7; +import 'package:auto_route/auto_route.dart' as _i8; +import 'package:flutter/material.dart' as _i9; /// generated route for /// [_i1.AboutScreen] -class AboutRoute extends _i7.PageRouteInfo { - const AboutRoute({List<_i7.PageRouteInfo>? children}) +class AboutRoute extends _i8.PageRouteInfo { + const AboutRoute({List<_i8.PageRouteInfo>? children}) : super(AboutRoute.name, initialChildren: children); static const String name = 'AboutRoute'; - static _i7.PageInfo page = _i7.PageInfo( + static _i8.PageInfo page = _i8.PageInfo( name, builder: (data) { return const _i1.AboutScreen(); @@ -36,13 +37,13 @@ class AboutRoute extends _i7.PageRouteInfo { /// generated route for /// [_i2.Bootstrap] -class Bootstrap extends _i7.PageRouteInfo { - const Bootstrap({List<_i7.PageRouteInfo>? children}) +class Bootstrap extends _i8.PageRouteInfo { + const Bootstrap({List<_i8.PageRouteInfo>? children}) : super(Bootstrap.name, initialChildren: children); static const String name = 'Bootstrap'; - static _i7.PageInfo page = _i7.PageInfo( + static _i8.PageInfo page = _i8.PageInfo( name, builder: (data) { return const _i2.Bootstrap(); @@ -52,13 +53,13 @@ class Bootstrap extends _i7.PageRouteInfo { /// generated route for /// [_i3.CalcScreen] -class CalcRoute extends _i7.PageRouteInfo { - const CalcRoute({List<_i7.PageRouteInfo>? children}) +class CalcRoute extends _i8.PageRouteInfo { + const CalcRoute({List<_i8.PageRouteInfo>? children}) : super(CalcRoute.name, initialChildren: children); static const String name = 'CalcRoute'; - static _i7.PageInfo page = _i7.PageInfo( + static _i8.PageInfo page = _i8.PageInfo( name, builder: (data) { return const _i3.CalcScreen(); @@ -68,13 +69,13 @@ class CalcRoute extends _i7.PageRouteInfo { /// generated route for /// [_i4.DashboardRouter] -class DashboardRouter extends _i7.PageRouteInfo { - const DashboardRouter({List<_i7.PageRouteInfo>? children}) +class DashboardRouter extends _i8.PageRouteInfo { + const DashboardRouter({List<_i8.PageRouteInfo>? children}) : super(DashboardRouter.name, initialChildren: children); static const String name = 'DashboardRouter'; - static _i7.PageInfo page = _i7.PageInfo( + static _i8.PageInfo page = _i8.PageInfo( name, builder: (data) { return const _i4.DashboardRouter(); @@ -85,11 +86,11 @@ class DashboardRouter extends _i7.PageRouteInfo { /// generated route for /// [_i5.ServerConnectionScreen] class ServerConnectionRoute - extends _i7.PageRouteInfo { + extends _i8.PageRouteInfo { ServerConnectionRoute({ - _i8.Key? key, + _i9.Key? key, String? arbiterUrl, - List<_i7.PageRouteInfo>? children, + List<_i8.PageRouteInfo>? children, }) : super( ServerConnectionRoute.name, args: ServerConnectionRouteArgs(key: key, arbiterUrl: arbiterUrl), @@ -98,7 +99,7 @@ class ServerConnectionRoute static const String name = 'ServerConnectionRoute'; - static _i7.PageInfo page = _i7.PageInfo( + static _i8.PageInfo page = _i8.PageInfo( name, builder: (data) { final args = data.argsAs( @@ -115,7 +116,7 @@ class ServerConnectionRoute class ServerConnectionRouteArgs { const ServerConnectionRouteArgs({this.key, this.arbiterUrl}); - final _i8.Key? key; + final _i9.Key? key; final String? arbiterUrl; @@ -137,16 +138,32 @@ class ServerConnectionRouteArgs { /// generated route for /// [_i6.ServerInfoSetupScreen] -class ServerInfoSetupRoute extends _i7.PageRouteInfo { - const ServerInfoSetupRoute({List<_i7.PageRouteInfo>? children}) +class ServerInfoSetupRoute extends _i8.PageRouteInfo { + const ServerInfoSetupRoute({List<_i8.PageRouteInfo>? children}) : super(ServerInfoSetupRoute.name, initialChildren: children); static const String name = 'ServerInfoSetupRoute'; - static _i7.PageInfo page = _i7.PageInfo( + static _i8.PageInfo page = _i8.PageInfo( name, builder: (data) { return const _i6.ServerInfoSetupScreen(); }, ); } + +/// generated route for +/// [_i7.VaultSetupScreen] +class VaultSetupRoute extends _i8.PageRouteInfo { + const VaultSetupRoute({List<_i8.PageRouteInfo>? children}) + : super(VaultSetupRoute.name, initialChildren: children); + + static const String name = 'VaultSetupRoute'; + + static _i8.PageInfo page = _i8.PageInfo( + name, + builder: (data) { + return const _i7.VaultSetupScreen(); + }, + ); +} diff --git a/useragent/lib/screens/server_connection.dart b/useragent/lib/screens/server_connection.dart index 8c1727f..9634287 100644 --- a/useragent/lib/screens/server_connection.dart +++ b/useragent/lib/screens/server_connection.dart @@ -18,7 +18,7 @@ class ServerConnectionScreen extends HookConsumerWidget { if (connectionState.value != null) { WidgetsBinding.instance.addPostFrameCallback((_) { - context.router.replace(const DashboardRouter()); + context.router.replace(const VaultSetupRoute()); }); } diff --git a/useragent/lib/screens/vault_setup.dart b/useragent/lib/screens/vault_setup.dart new file mode 100644 index 0000000..5c1ce6e --- /dev/null +++ b/useragent/lib/screens/vault_setup.dart @@ -0,0 +1,408 @@ +import 'package:arbiter/features/connection/connection.dart'; +import 'package:arbiter/proto/user_agent.pbenum.dart'; +import 'package:arbiter/providers/connection/connection_manager.dart'; +import 'package:arbiter/providers/vault_state.dart'; +import 'package:arbiter/router.gr.dart'; +import 'package:arbiter/widgets/bottom_popup.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:sizer/sizer.dart'; + +@RoutePage() +class VaultSetupScreen extends HookConsumerWidget { + const VaultSetupScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final vaultState = ref.watch(vaultStateProvider); + final bootstrapPasswordController = useTextEditingController(); + final bootstrapConfirmController = useTextEditingController(); + final unsealPasswordController = useTextEditingController(); + final errorText = useState(null); + final isSubmitting = useState(false); + + useEffect(() { + if (vaultState.asData?.value == VaultState.VAULT_STATE_UNSEALED) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.mounted) { + context.router.replace(const DashboardRouter()); + } + }); + } + + return null; + }, [context, vaultState.asData?.value]); + + Future refreshVaultState() async { + ref.invalidate(vaultStateProvider); + await ref.read(vaultStateProvider.future); + } + + Future submitBootstrap() async { + final password = bootstrapPasswordController.text; + final confirmation = bootstrapConfirmController.text; + + if (password.isEmpty || confirmation.isEmpty) { + errorText.value = 'Enter the password twice.'; + return; + } + + if (password != confirmation) { + errorText.value = 'Passwords do not match.'; + return; + } + + final confirmed = await showBottomPopup( + context: context, + builder: (popupContext) { + return _WarningPopup( + title: 'Bootstrap vault?', + body: + 'This password cannot be recovered. If you lose it, the vault cannot be unsealed.', + confirmLabel: 'Bootstrap', + onCancel: () => Navigator.of(popupContext).pop(false), + onConfirm: () => Navigator.of(popupContext).pop(true), + ); + }, + ); + + if (confirmed != true) { + return; + } + + errorText.value = null; + isSubmitting.value = true; + + try { + final connection = await ref.read(connectionManagerProvider.future); + if (connection == null) { + throw Exception('Not connected to the server.'); + } + + final result = await bootstrapVault(connection, password); + switch (result) { + case BootstrapResult.BOOTSTRAP_RESULT_SUCCESS: + bootstrapPasswordController.clear(); + bootstrapConfirmController.clear(); + await refreshVaultState(); + break; + case BootstrapResult.BOOTSTRAP_RESULT_ALREADY_BOOTSTRAPPED: + errorText.value = + 'The vault was already bootstrapped. Refreshing vault state.'; + await refreshVaultState(); + break; + case BootstrapResult.BOOTSTRAP_RESULT_INVALID_KEY: + case BootstrapResult.BOOTSTRAP_RESULT_UNSPECIFIED: + errorText.value = 'Failed to bootstrap the vault.'; + break; + } + } catch (error) { + errorText.value = _formatVaultError(error); + } finally { + isSubmitting.value = false; + } + } + + Future submitUnseal() async { + final password = unsealPasswordController.text; + if (password.isEmpty) { + errorText.value = 'Enter the vault password.'; + return; + } + + errorText.value = null; + isSubmitting.value = true; + + try { + final connection = await ref.read(connectionManagerProvider.future); + if (connection == null) { + throw Exception('Not connected to the server.'); + } + + final result = await unsealVault(connection, password); + switch (result) { + case UnsealResult.UNSEAL_RESULT_SUCCESS: + unsealPasswordController.clear(); + await refreshVaultState(); + break; + case UnsealResult.UNSEAL_RESULT_INVALID_KEY: + errorText.value = 'Incorrect password.'; + break; + case UnsealResult.UNSEAL_RESULT_UNBOOTSTRAPPED: + errorText.value = + 'The vault is not bootstrapped yet. Refreshing vault state.'; + await refreshVaultState(); + break; + case UnsealResult.UNSEAL_RESULT_UNSPECIFIED: + errorText.value = 'Failed to unseal the vault.'; + break; + } + } catch (error) { + errorText.value = _formatVaultError(error); + } finally { + isSubmitting.value = false; + } + } + + final body = switch (vaultState) { + AsyncLoading() => const Center(child: CircularProgressIndicator()), + AsyncError(:final error) => _VaultCard( + title: 'Vault unavailable', + subtitle: _formatVaultError(error), + child: FilledButton( + onPressed: () => ref.invalidate(vaultStateProvider), + child: const Text('Retry'), + ), + ), + AsyncData(:final value) => switch (value) { + VaultState.VAULT_STATE_UNBOOTSTRAPPED => _VaultCard( + title: 'Create vault password', + subtitle: + 'Choose the password that will be required to unseal this vault.', + child: _PasswordForm( + errorText: errorText.value, + isSubmitting: isSubmitting.value, + submitLabel: 'Bootstrap vault', + onSubmit: submitBootstrap, + fields: [ + _PasswordFieldConfig( + controller: bootstrapPasswordController, + label: 'Password', + textInputAction: TextInputAction.next, + ), + _PasswordFieldConfig( + controller: bootstrapConfirmController, + label: 'Confirm password', + textInputAction: TextInputAction.done, + onSubmitted: (_) => submitBootstrap(), + ), + ], + ), + ), + VaultState.VAULT_STATE_SEALED => _VaultCard( + title: 'Unseal vault', + subtitle: 'Enter the vault password to continue.', + child: _PasswordForm( + errorText: errorText.value, + isSubmitting: isSubmitting.value, + submitLabel: 'Unseal vault', + onSubmit: submitUnseal, + fields: [ + _PasswordFieldConfig( + controller: unsealPasswordController, + label: 'Password', + textInputAction: TextInputAction.done, + onSubmitted: (_) => submitUnseal(), + ), + ], + ), + ), + VaultState.VAULT_STATE_UNSEALED => const Center( + child: CircularProgressIndicator(), + ), + VaultState.VAULT_STATE_ERROR => _VaultCard( + title: 'Vault state unavailable', + subtitle: 'Unable to determine the current vault state.', + child: FilledButton( + onPressed: () => ref.invalidate(vaultStateProvider), + child: const Text('Retry'), + ), + ), + VaultState.VAULT_STATE_UNSPECIFIED => _VaultCard( + title: 'Vault state unavailable', + subtitle: 'Unable to determine the current vault state.', + child: FilledButton( + onPressed: () => ref.invalidate(vaultStateProvider), + child: const Text('Retry'), + ), + ), + null => _VaultCard( + title: 'Vault state unavailable', + subtitle: 'Unable to determine the current vault state.', + child: FilledButton( + onPressed: () => ref.invalidate(vaultStateProvider), + child: const Text('Retry'), + ), + ), + _ => _VaultCard( + title: 'Vault state unavailable', + subtitle: 'Unable to determine the current vault state.', + child: FilledButton( + onPressed: () => ref.invalidate(vaultStateProvider), + child: const Text('Retry'), + ), + ), + }, + }; + + return Scaffold( + appBar: AppBar(title: const Text('Vault Setup')), + body: Center( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 3.h), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 520), + child: body, + ), + ), + ), + ); + } +} + +class _VaultCard extends StatelessWidget { + const _VaultCard({ + required this.title, + required this.subtitle, + required this.child, + }); + + final String title; + final String subtitle; + final Widget child; + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: Theme.of(context).textTheme.headlineSmall), + const SizedBox(height: 12), + Text(subtitle, style: Theme.of(context).textTheme.bodyMedium), + const SizedBox(height: 24), + child, + ], + ), + ), + ); + } +} + +class _PasswordForm extends StatelessWidget { + const _PasswordForm({ + required this.fields, + required this.errorText, + required this.isSubmitting, + required this.submitLabel, + required this.onSubmit, + }); + + final List<_PasswordFieldConfig> fields; + final String? errorText; + final bool isSubmitting; + final String submitLabel; + final Future Function() onSubmit; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (final field in fields) ...[ + TextField( + controller: field.controller, + obscureText: true, + enableSuggestions: false, + autocorrect: false, + textInputAction: field.textInputAction, + onSubmitted: field.onSubmitted, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: field.label, + ), + ), + const SizedBox(height: 16), + ], + if (errorText != null) ...[ + Text( + errorText!, + style: TextStyle(color: Theme.of(context).colorScheme.error), + ), + const SizedBox(height: 16), + ], + Align( + alignment: Alignment.centerLeft, + child: FilledButton( + onPressed: isSubmitting ? null : () => onSubmit(), + child: Text(isSubmitting ? 'Working...' : submitLabel), + ), + ), + ], + ); + } +} + +class _PasswordFieldConfig { + const _PasswordFieldConfig({ + required this.controller, + required this.label, + required this.textInputAction, + this.onSubmitted, + }); + + final TextEditingController controller; + final String label; + final TextInputAction textInputAction; + final ValueChanged? onSubmitted; +} + +class _WarningPopup extends StatelessWidget { + const _WarningPopup({ + required this.title, + required this.body, + required this.confirmLabel, + required this.onCancel, + required this.onConfirm, + }); + + final String title; + final String body; + final String confirmLabel; + final VoidCallback onCancel; + final VoidCallback onConfirm; + + @override + Widget build(BuildContext context) { + return Material( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(24), + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: Theme.of(context).textTheme.titleLarge), + const SizedBox(height: 12), + Text(body, style: Theme.of(context).textTheme.bodyMedium), + const SizedBox(height: 24), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton(onPressed: onCancel, child: const Text('Cancel')), + const SizedBox(width: 12), + FilledButton(onPressed: onConfirm, child: Text(confirmLabel)), + ], + ), + ], + ), + ), + ); + } +} + +String _formatVaultError(Object error) { + final message = error.toString(); + + if (message.contains('GrpcError')) { + return 'The server rejected the vault request. Check the password and try again.'; + } + + return message.replaceFirst('Exception: ', ''); +} diff --git a/useragent/lib/widgets/bottom_popup.dart b/useragent/lib/widgets/bottom_popup.dart new file mode 100644 index 0000000..875ef08 --- /dev/null +++ b/useragent/lib/widgets/bottom_popup.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; + +Future showBottomPopup({ + required BuildContext context, + required WidgetBuilder builder, + bool barrierDismissible = true, +}) { + return showGeneralDialog( + context: context, + barrierDismissible: barrierDismissible, + barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, + barrierColor: Colors.transparent, + transitionDuration: const Duration(milliseconds: 320), + pageBuilder: (dialogContext, animation, secondaryAnimation) { + return _BottomPopupRoute( + animation: animation, + builder: builder, + barrierDismissible: barrierDismissible, + ); + }, + ); +} + +class _BottomPopupRoute extends StatelessWidget { + const _BottomPopupRoute({ + required this.animation, + required this.builder, + required this.barrierDismissible, + }); + + final Animation animation; + final WidgetBuilder builder; + final bool barrierDismissible; + + @override + Widget build(BuildContext context) { + final barrierAnimation = CurvedAnimation( + parent: animation, + curve: const Interval(0, 0.3125, curve: Curves.easeOut), + ); + final popupAnimation = CurvedAnimation( + parent: animation, + curve: const Interval(0.3125, 1, curve: Curves.easeOutCubic), + ); + + return Material( + type: MaterialType.transparency, + child: Stack( + children: [ + Positioned.fill( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: barrierDismissible ? () => Navigator.of(context).pop() : null, + child: AnimatedBuilder( + animation: barrierAnimation, + builder: (context, child) { + return ColoredBox( + color: Colors.black.withValues( + alpha: 0.35 * barrierAnimation.value, + ), + ); + }, + ), + ), + ), + SafeArea( + child: Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.all(16), + child: FadeTransition( + opacity: popupAnimation, + child: SlideTransition( + position: + Tween( + begin: const Offset(0, 0.08), + end: Offset.zero, + ).animate(popupAnimation), + child: GestureDetector( + onTap: () {}, + child: Builder(builder: builder), + ), + ), + ), + ), + ), + ), + ], + ), + ); + } +} From 549a0f5f52963d9fbff1af711c5da1b266bbb7a2 Mon Sep 17 00:00:00 2001 From: hdbg Date: Sun, 15 Mar 2026 23:11:07 +0100 Subject: [PATCH 07/13] refactor(server): removed grpc adapter and replaced with concrete implementations --- server/Cargo.lock | 24 - server/crates/arbiter-proto/src/transport.rs | 237 +--------- .../arbiter-server/src/actors/bootstrap.rs | 8 +- .../arbiter-server/src/actors/client/auth.rs | 59 +-- .../arbiter-server/src/actors/client/mod.rs | 27 +- .../src/actors/client/session.rs | 23 +- .../arbiter-server/src/actors/evm/mod.rs | 57 ++- .../src/actors/keyholder/mod.rs | 4 +- .../src/actors/user_agent/auth.rs | 66 +-- .../src/actors/user_agent/auth/state.rs | 68 +-- .../src/actors/user_agent/mod.rs | 114 ++++- .../src/actors/user_agent/session.rs | 262 +++++------ .../crates/arbiter-server/src/context/tls.rs | 7 +- server/crates/arbiter-server/src/evm/mod.rs | 10 +- .../crates/arbiter-server/src/evm/policies.rs | 1 - .../src/evm/policies/ether_transfer/tests.rs | 4 +- .../src/evm/policies/token_transfers/tests.rs | 119 +++-- .../arbiter-server/src/evm/safe_signer.rs | 12 +- .../crates/arbiter-server/src/grpc/client.rs | 137 ++++++ server/crates/arbiter-server/src/grpc/mod.rs | 65 +++ .../arbiter-server/src/grpc/user_agent.rs | 288 ++++++++++++ server/crates/arbiter-server/src/lib.rs | 181 +------- .../arbiter-server/tests/client/auth.rs | 37 +- .../arbiter-server/tests/user_agent/auth.rs | 59 +-- .../arbiter-server/tests/user_agent/unseal.rs | 89 ++-- server/crates/arbiter-useragent/Cargo.toml | 29 -- server/crates/arbiter-useragent/src/grpc.rs | 70 --- server/crates/arbiter-useragent/src/lib.rs | 419 ------------------ server/crates/arbiter-useragent/tests/auth.rs | 146 ------ 29 files changed, 1002 insertions(+), 1620 deletions(-) create mode 100644 server/crates/arbiter-server/src/grpc/client.rs create mode 100644 server/crates/arbiter-server/src/grpc/mod.rs create mode 100644 server/crates/arbiter-server/src/grpc/user_agent.rs delete mode 100644 server/crates/arbiter-useragent/Cargo.toml delete mode 100644 server/crates/arbiter-useragent/src/grpc.rs delete mode 100644 server/crates/arbiter-useragent/src/lib.rs delete mode 100644 server/crates/arbiter-useragent/tests/auth.rs diff --git a/server/Cargo.lock b/server/Cargo.lock index aa3fbc9..a77dfb1 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -755,30 +755,6 @@ dependencies = [ "alloy", ] -[[package]] -name = "arbiter-useragent" -version = "0.1.0" -dependencies = [ - "arbiter-proto", - "async-trait", - "ed25519-dalek", - "http", - "k256", - "kameo", - "rand 0.10.0", - "rsa", - "rustls-webpki", - "sha2 0.10.9", - "smlang", - "spki", - "thiserror", - "tokio", - "tokio-stream", - "tonic", - "tracing", - "x25519-dalek", -] - [[package]] name = "argon2" version = "0.5.3" diff --git a/server/crates/arbiter-proto/src/transport.rs b/server/crates/arbiter-proto/src/transport.rs index 6f89b80..55415c8 100644 --- a/server/crates/arbiter-proto/src/transport.rs +++ b/server/crates/arbiter-proto/src/transport.rs @@ -1,78 +1,39 @@ -//! Transport-facing abstractions for protocol/session code. +//! Transport-facing abstractions shared by protocol/session code. //! -//! This module separates three concerns: -//! -//! - protocol/session logic wants a small duplex interface ([`Bi`]) -//! - transport adapters push concrete stream items to an underlying IO layer -//! - transport boundaries translate between protocol-facing and transport-facing -//! item types via direction-specific converters +//! This module defines a small duplex interface, [`Bi`], that actors and other +//! protocol code can depend on without knowing anything about the concrete +//! transport underneath. //! //! [`Bi`] is intentionally minimal and transport-agnostic: -//! - [`Bi::recv`] yields inbound protocol messages -//! - [`Bi::send`] accepts outbound protocol/domain items +//! - [`Bi::recv`] yields inbound messages +//! - [`Bi::send`] accepts outbound messages +//! +//! Transport-specific adapters, including protobuf or gRPC bridges, live in the +//! crates that own those boundaries rather than in `arbiter-proto`. //! //! # Generic Ordering Rule //! -//! This module uses a single convention consistently: when a type or trait is -//! parameterized by protocol message directions, the generic parameters are -//! declared as `Inbound` first, then `Outbound`. +//! This module consistently uses `Inbound` first and `Outbound` second in +//! generic parameter lists. //! //! For [`Bi`], that means `Bi`: //! - `recv() -> Option` //! - `send(Outbound)` //! -//! For adapter types that are parameterized by direction-specific converters, -//! inbound-related converter parameters are declared before outbound-related -//! converter parameters. +//! [`expect_message`] is a small helper for request/response style flows: it +//! reads one inbound message from a transport and extracts a typed value from +//! it, failing if the channel closes or the message shape is not what the +//! caller expected. //! -//! [`RecvConverter`] and [`SendConverter`] are infallible conversion traits used -//! by adapters to map between protocol-facing and transport-facing item types. -//! The traits themselves are not result-aware; adapters decide how transport -//! errors are handled before (or instead of) conversion. -//! -//! [`grpc::GrpcAdapter`] combines: -//! - a tonic inbound stream -//! - a Tokio sender for outbound transport items -//! - a [`RecvConverter`] for the receive path -//! - a [`SendConverter`] for the send path -//! -//! [`DummyTransport`] is a no-op implementation useful for tests and local actor -//! execution where no real network stream exists. -//! -//! # Component Interaction -//! -//! ```text -//! inbound (network -> protocol) -//! ============================ -//! -//! tonic::Streaming -//! -> grpc::GrpcAdapter::recv() -//! | -//! +--> on `Ok(item)`: RecvConverter::convert(RecvTransport) -> Inbound -//! +--> on `Err(status)`: log error and close stream (`None`) -//! -> Bi::recv() -//! -> protocol/session actor -//! -//! outbound (protocol -> network) -//! ============================== -//! -//! protocol/session actor -//! -> Bi::send(Outbound) -//! -> grpc::GrpcAdapter::send() -//! | -//! +--> SendConverter::convert(Outbound) -> SendTransport -//! -> Tokio mpsc::Sender -//! -> tonic response stream -//! ``` +//! [`DummyTransport`] is a no-op implementation useful for tests and local +//! actor execution where no real stream exists. //! //! # Design Notes //! -//! - `send()` returns [`Error`] only for transport delivery failures (for -//! example, when the outbound channel is closed). -//! - [`grpc::GrpcAdapter`] logs tonic receive errors and treats them as stream -//! closure (`None`). -//! - When protocol-facing and transport-facing types are identical, use -//! [`IdentityRecvConverter`] / [`IdentitySendConverter`]. +//! - [`Bi::send`] returns [`Error`] only for transport delivery failures, such +//! as a closed outbound channel. +//! - [`Bi::recv`] returns `None` when the underlying transport closes. +//! - Message translation is intentionally out of scope for this module. use std::marker::PhantomData; @@ -114,162 +75,6 @@ pub trait Bi: Send + Sync + 'static { async fn recv(&mut self) -> Option; } -/// Converts transport-facing inbound items into protocol-facing inbound items. -pub trait RecvConverter: Send + Sync + 'static { - type Input; - type Output; - - fn convert(&self, item: Self::Input) -> Self::Output; -} - -/// Converts protocol/domain outbound items into transport-facing outbound items. -pub trait SendConverter: Send + Sync + 'static { - type Input; - type Output; - - fn convert(&self, item: Self::Input) -> Self::Output; -} - -/// A [`RecvConverter`] that forwards values unchanged. -pub struct IdentityRecvConverter { - _marker: PhantomData, -} - -impl IdentityRecvConverter { - pub fn new() -> Self { - Self { - _marker: PhantomData, - } - } -} - -impl Default for IdentityRecvConverter { - fn default() -> Self { - Self::new() - } -} - -impl RecvConverter for IdentityRecvConverter -where - T: Send + Sync + 'static, -{ - type Input = T; - type Output = T; - - fn convert(&self, item: Self::Input) -> Self::Output { - item - } -} - -/// A [`SendConverter`] that forwards values unchanged. -pub struct IdentitySendConverter { - _marker: PhantomData, -} - -impl IdentitySendConverter { - pub fn new() -> Self { - Self { - _marker: PhantomData, - } - } -} - -impl Default for IdentitySendConverter { - fn default() -> Self { - Self::new() - } -} - -impl SendConverter for IdentitySendConverter -where - T: Send + Sync + 'static, -{ - type Input = T; - type Output = T; - - fn convert(&self, item: Self::Input) -> Self::Output { - item - } -} - -/// gRPC-specific transport adapters and helpers. -pub mod grpc { - use async_trait::async_trait; - use futures::StreamExt; - use tokio::sync::mpsc; - use tonic::Streaming; - - use super::{Bi, Error, RecvConverter, SendConverter}; - - /// [`Bi`] adapter backed by a tonic gRPC bidirectional stream. - /// - /// Tonic receive errors are logged and treated as stream closure (`None`). - /// The receive converter is only invoked for successful inbound transport - /// items. - pub struct GrpcAdapter - where - InboundConverter: RecvConverter, - OutboundConverter: SendConverter, - { - sender: mpsc::Sender, - receiver: Streaming, - inbound_converter: InboundConverter, - outbound_converter: OutboundConverter, - } - - impl - GrpcAdapter - where - InboundConverter: RecvConverter, - OutboundConverter: SendConverter, - { - pub fn new( - sender: mpsc::Sender, - receiver: Streaming, - inbound_converter: InboundConverter, - outbound_converter: OutboundConverter, - ) -> Self { - Self { - sender, - receiver, - inbound_converter, - outbound_converter, - } - } - } - - #[async_trait] - impl Bi - for GrpcAdapter - where - InboundConverter: RecvConverter, - OutboundConverter: SendConverter, - OutboundConverter::Input: Send + 'static, - OutboundConverter::Output: Send + 'static, - { - #[tracing::instrument(level = "trace", skip(self, item))] - async fn send(&mut self, item: OutboundConverter::Input) -> Result<(), Error> { - let outbound = self.outbound_converter.convert(item); - self.sender - .send(outbound) - .await - .map_err(|_| Error::ChannelClosed) - } - - #[tracing::instrument(level = "trace", skip(self))] - async fn recv(&mut self) -> Option { - match self.receiver.next().await { - Some(Ok(item)) => Some(self.inbound_converter.convert(item)), - Some(Err(error)) => { - tracing::error!(error = ?error, "grpc transport recv failed; closing stream"); - None - } - None => None, - } - } - } -} - /// No-op [`Bi`] transport for tests and manual actor usage. /// /// `send` drops all items and succeeds. [`Bi::recv`] never resolves and therefore diff --git a/server/crates/arbiter-server/src/actors/bootstrap.rs b/server/crates/arbiter-server/src/actors/bootstrap.rs index 366a91a..515ad54 100644 --- a/server/crates/arbiter-server/src/actors/bootstrap.rs +++ b/server/crates/arbiter-server/src/actors/bootstrap.rs @@ -3,12 +3,7 @@ use diesel::QueryDsl; use diesel_async::RunQueryDsl; use kameo::{Actor, messages}; use miette::Diagnostic; -use rand::{ - RngExt, - distr::{Alphanumeric}, - make_rng, - rngs::StdRng, -}; +use rand::{RngExt, distr::Alphanumeric, make_rng, rngs::StdRng}; use thiserror::Error; use crate::db::{self, DatabasePool, schema}; @@ -61,7 +56,6 @@ impl Bootstrapper { drop(conn); - let token = if row_count == 0 { let token = generate_token().await?; Some(token) diff --git a/server/crates/arbiter-server/src/actors/client/auth.rs b/server/crates/arbiter-server/src/actors/client/auth.rs index cb11d9a..c69fb77 100644 --- a/server/crates/arbiter-server/src/actors/client/auth.rs +++ b/server/crates/arbiter-server/src/actors/client/auth.rs @@ -1,13 +1,4 @@ -use arbiter_proto::{ - format_challenge, - proto::client::{ - AuthChallenge, AuthChallengeSolution, ClientConnectError, ClientRequest, ClientResponse, - client_connect_error::Code as ConnectErrorCode, - client_request::Payload as ClientRequestPayload, - client_response::Payload as ClientResponsePayload, - }, - transport::expect_message, -}; +use arbiter_proto::{format_challenge, transport::expect_message}; use diesel::{ ExpressionMethods as _, OptionalExtension as _, QueryDsl as _, dsl::insert_into, update, }; @@ -18,7 +9,7 @@ use tracing::error; use crate::{ actors::{ - client::ClientConnection, + client::{ClientConnection, ConnectErrorCode, Request, Response}, router::{self, RequestClientApproval}, }, db::{self, schema::program_client}, @@ -155,15 +146,13 @@ async fn challenge_client( pubkey: VerifyingKey, nonce: i32, ) -> Result<(), Error> { - let challenge = AuthChallenge { - pubkey: pubkey.as_bytes().to_vec(), - nonce, - }; + let challenge_pubkey = pubkey.as_bytes().to_vec(); props .transport - .send(Ok(ClientResponse { - payload: Some(ClientResponsePayload::AuthChallenge(challenge.clone())), + .send(Ok(Response::AuthChallenge { + pubkey: challenge_pubkey.clone(), + nonce, })) .await .map_err(|e| { @@ -171,20 +160,17 @@ async fn challenge_client( Error::Transport })?; - let AuthChallengeSolution { signature } = - expect_message(&mut *props.transport, |req: ClientRequest| { - match req.payload? { - ClientRequestPayload::AuthChallengeSolution(s) => Some(s), - _ => None, - } - }) - .await - .map_err(|e| { - error!(error = ?e, "Failed to receive challenge solution"); - Error::Transport - })?; + let signature = expect_message(&mut *props.transport, |req: Request| match req { + Request::AuthChallengeSolution { signature } => Some(signature), + _ => None, + }) + .await + .map_err(|e| { + error!(error = ?e, "Failed to receive challenge solution"); + Error::Transport + })?; - let formatted = format_challenge(nonce, &challenge.pubkey); + let formatted = format_challenge(nonce, &challenge_pubkey); let sig = signature.as_slice().try_into().map_err(|_| { error!("Invalid signature length"); Error::InvalidChallengeSolution @@ -209,15 +195,14 @@ fn connect_error_code(err: &Error) -> ConnectErrorCode { } async fn authenticate(props: &mut ClientConnection) -> Result { - let Some(ClientRequest { - payload: Some(ClientRequestPayload::AuthChallengeRequest(challenge)), + let Some(Request::AuthChallengeRequest { + pubkey: challenge_pubkey, }) = props.transport.recv().await else { return Err(Error::Transport); }; - let pubkey_bytes = challenge - .pubkey + let pubkey_bytes = challenge_pubkey .as_array() .ok_or(Error::InvalidClientPubkeyLength)?; let pubkey = @@ -244,11 +229,7 @@ pub async fn authenticate_and_create(mut props: ClientConnection) -> Result> + Send>; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ConnectErrorCode { + Unknown, + ApprovalDenied, + NoUserAgentsOnline, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Request { + AuthChallengeRequest { pubkey: Vec }, + AuthChallengeSolution { signature: Vec }, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Response { + AuthChallenge { pubkey: Vec, nonce: i32 }, + AuthOk, + ClientConnectError { code: ConnectErrorCode }, +} + +pub type Transport = Box> + Send>; pub struct ClientConnection { pub(crate) db: db::DatabasePool, diff --git a/server/crates/arbiter-server/src/actors/client/session.rs b/server/crates/arbiter-server/src/actors/client/session.rs index a2ae4a4..fb18feb 100644 --- a/server/crates/arbiter-server/src/actors/client/session.rs +++ b/server/crates/arbiter-server/src/actors/client/session.rs @@ -1,11 +1,15 @@ -use arbiter_proto::proto::client::{ClientRequest, ClientResponse}; use kameo::Actor; use tokio::select; use tracing::{error, info}; -use crate::{actors::{ - GlobalActors, client::{ClientError, ClientConnection}, router::RegisterClient -}, db}; +use crate::{ + actors::{ + GlobalActors, + client::{ClientConnection, ClientError, Request, Response}, + router::RegisterClient, + }, + db, +}; pub struct ClientSession { props: ClientConnection, @@ -16,18 +20,13 @@ impl ClientSession { Self { props } } - pub async fn process_transport_inbound(&mut self, req: ClientRequest) -> Output { - let msg = req.payload.ok_or_else(|| { - error!(actor = "client", "Received message with no payload"); - ClientError::MissingRequestPayload - })?; - - let _ = msg; + pub async fn process_transport_inbound(&mut self, req: Request) -> Output { + let _ = req; Err(ClientError::UnexpectedRequestPayload) } } -type Output = Result; +type Output = Result; impl Actor for ClientSession { type Args = Self; diff --git a/server/crates/arbiter-server/src/actors/evm/mod.rs b/server/crates/arbiter-server/src/actors/evm/mod.rs index 012b41c..5c7ff3e 100644 --- a/server/crates/arbiter-server/src/actors/evm/mod.rs +++ b/server/crates/arbiter-server/src/actors/evm/mod.rs @@ -1,5 +1,7 @@ use alloy::{consensus::TxEip1559, primitives::Address, signers::Signature}; -use diesel::{ExpressionMethods, OptionalExtension as _, QueryDsl, SelectableHelper as _, dsl::insert_into}; +use diesel::{ + ExpressionMethods, OptionalExtension as _, QueryDsl, SelectableHelper as _, dsl::insert_into, +}; use diesel_async::RunQueryDsl; use kameo::{Actor, actor::ActorRef, messages}; use memsafe::MemSafe; @@ -7,13 +9,16 @@ use rand::{SeedableRng, rng, rngs::StdRng}; use crate::{ actors::keyholder::{CreateNew, Decrypt, KeyHolder}, - db::{self, DatabasePool, models::{self, EvmBasicGrant, SqliteTimestamp}, schema}, + db::{ + self, DatabasePool, + models::{self, EvmBasicGrant, SqliteTimestamp}, + schema, + }, evm::{ self, RunKind, policies::{ FullGrant, SharedGrantSettings, SpecificGrant, SpecificMeaning, - ether_transfer::EtherTransfer, - token_transfers::TokenTransfer, + ether_transfer::EtherTransfer, token_transfers::TokenTransfer, }, }, }; @@ -88,7 +93,12 @@ impl EvmActor { // todo: audit let rng = StdRng::from_rng(&mut rng()); let engine = evm::Engine::new(db.clone()); - Self { keyholder, db, rng, engine } + Self { + keyholder, + db, + rng, + engine, + } } } @@ -149,12 +159,24 @@ impl EvmActor { match grant { SpecificGrant::EtherTransfer(settings) => { self.engine - .create_grant::(client_id, FullGrant { basic, specific: settings }) + .create_grant::( + client_id, + FullGrant { + basic, + specific: settings, + }, + ) .await } SpecificGrant::TokenTransfer(settings) => { self.engine - .create_grant::(client_id, FullGrant { basic, specific: settings }) + .create_grant::( + client_id, + FullGrant { + basic, + specific: settings, + }, + ) .await } } @@ -204,8 +226,14 @@ impl EvmActor { .ok_or(SignTransactionError::WalletNotFound)?; drop(conn); - let meaning = self.engine - .evaluate_transaction(wallet.id, client_id, transaction.clone(), RunKind::Execution) + let meaning = self + .engine + .evaluate_transaction( + wallet.id, + client_id, + transaction.clone(), + RunKind::Execution, + ) .await?; Ok(meaning) @@ -230,14 +258,21 @@ impl EvmActor { let raw_key: MemSafe> = self .keyholder - .ask(Decrypt { aead_id: wallet.aead_encrypted_id }) + .ask(Decrypt { + aead_id: wallet.aead_encrypted_id, + }) .await .map_err(|_| SignTransactionError::KeyholderSend)?; let signer = safe_signer::SafeSigner::from_memsafe(raw_key)?; self.engine - .evaluate_transaction(wallet.id, client_id, transaction.clone(), RunKind::Execution) + .evaluate_transaction( + wallet.id, + client_id, + transaction.clone(), + RunKind::Execution, + ) .await?; use alloy::network::TxSignerSync as _; diff --git a/server/crates/arbiter-server/src/actors/keyholder/mod.rs b/server/crates/arbiter-server/src/actors/keyholder/mod.rs index c8e97ab..f7cc8cc 100644 --- a/server/crates/arbiter-server/src/actors/keyholder/mod.rs +++ b/server/crates/arbiter-server/src/actors/keyholder/mod.rs @@ -313,7 +313,7 @@ impl KeyHolder { current_nonce: nonce.to_vec(), schema_version: 1, associated_root_key_id: *root_key_history_id, - created_at: Utc::now().into() + created_at: Utc::now().into(), }) .returning(schema::aead_encrypted::id) .get_result(&mut conn) @@ -346,7 +346,7 @@ impl KeyHolder { #[cfg(test)] mod tests { use diesel::SelectableHelper; - + use diesel_async::RunQueryDsl; use memsafe::MemSafe; diff --git a/server/crates/arbiter-server/src/actors/user_agent/auth.rs b/server/crates/arbiter-server/src/actors/user_agent/auth.rs index 1e0fe20..eab7acf 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/auth.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/auth.rs @@ -1,12 +1,9 @@ -use arbiter_proto::proto::user_agent::{ - AuthChallengeRequest, AuthChallengeSolution, KeyType as ProtoKeyType, UserAgentRequest, - user_agent_request::Payload as UserAgentRequestPayload, -}; use tracing::error; use crate::actors::user_agent::{ - UserAgentConnection, - auth::state::{AuthContext, AuthPublicKey, AuthStateMachine}, + Request, UserAgentConnection, + auth::state::{AuthContext, AuthStateMachine}, + AuthPublicKey, session::UserAgentSession, }; @@ -37,54 +34,20 @@ pub enum Error { mod state; use state::*; -fn parse_pubkey(key_type: ProtoKeyType, pubkey: Vec) -> Result { - match key_type { - // UNSPECIFIED treated as Ed25519 for backward compatibility - ProtoKeyType::Unspecified | ProtoKeyType::Ed25519 => { - let pubkey_bytes = pubkey.as_array().ok_or(Error::InvalidClientPubkeyLength)?; - let key = ed25519_dalek::VerifyingKey::from_bytes(pubkey_bytes) - .map_err(|_| Error::InvalidAuthPubkeyEncoding)?; - Ok(AuthPublicKey::Ed25519(key)) - } - ProtoKeyType::EcdsaSecp256k1 => { - // Public key is sent as 33-byte SEC1 compressed point - let key = k256::ecdsa::VerifyingKey::from_sec1_bytes(&pubkey) - .map_err(|_| Error::InvalidAuthPubkeyEncoding)?; - Ok(AuthPublicKey::EcdsaSecp256k1(key)) - } - ProtoKeyType::Rsa => { - use rsa::pkcs8::DecodePublicKey as _; - let key = rsa::RsaPublicKey::from_public_key_der(&pubkey) - .map_err(|_| Error::InvalidAuthPubkeyEncoding)?; - Ok(AuthPublicKey::Rsa(key)) - } - } -} - -fn parse_auth_event(payload: UserAgentRequestPayload) -> Result { +fn parse_auth_event(payload: Request) -> Result { match payload { - UserAgentRequestPayload::AuthChallengeRequest(AuthChallengeRequest { + Request::AuthChallengeRequest { pubkey, bootstrap_token: None, - key_type, - }) => { - let kt = ProtoKeyType::try_from(key_type).unwrap_or(ProtoKeyType::Unspecified); - Ok(AuthEvents::AuthRequest(ChallengeRequest { - pubkey: parse_pubkey(kt, pubkey)?, - })) - } - UserAgentRequestPayload::AuthChallengeRequest(AuthChallengeRequest { + } => Ok(AuthEvents::AuthRequest(ChallengeRequest { pubkey })), + Request::AuthChallengeRequest { pubkey, bootstrap_token: Some(token), - key_type, - }) => { - let kt = ProtoKeyType::try_from(key_type).unwrap_or(ProtoKeyType::Unspecified); - Ok(AuthEvents::BootstrapAuthRequest(BootstrapAuthRequest { - pubkey: parse_pubkey(kt, pubkey)?, - token, - })) - } - UserAgentRequestPayload::AuthChallengeSolution(AuthChallengeSolution { signature }) => { + } => Ok(AuthEvents::BootstrapAuthRequest(BootstrapAuthRequest { + pubkey, + token, + })), + Request::AuthChallengeSolution { signature } => { Ok(AuthEvents::ReceivedSolution(ChallengeSolution { solution: signature, })) @@ -99,10 +62,7 @@ pub async fn authenticate(props: &mut UserAgentConnection) -> Result Vec { - match self { - AuthPublicKey::Ed25519(k) => k.to_bytes().to_vec(), - // SEC1 compressed (33 bytes) is the natural compact format for secp256k1 - AuthPublicKey::EcdsaSecp256k1(k) => k.to_encoded_point(true).as_bytes().to_vec(), - AuthPublicKey::Rsa(k) => { - use rsa::pkcs8::EncodePublicKey as _; - k.to_public_key_der() - .expect("rsa SPKI encoding is infallible") - .to_vec() - } - } - } - - pub fn key_type(&self) -> KeyType { - match self { - AuthPublicKey::Ed25519(_) => KeyType::Ed25519, - AuthPublicKey::EcdsaSecp256k1(_) => KeyType::EcdsaSecp256k1, - AuthPublicKey::Rsa(_) => KeyType::Rsa, - } - } -} - pub struct ChallengeRequest { pub pubkey: AuthPublicKey, } @@ -58,7 +21,7 @@ pub struct BootstrapAuthRequest { } pub struct ChallengeContext { - pub challenge: AuthChallenge, + pub challenge_nonce: i32, pub key: AuthPublicKey, } @@ -155,16 +118,9 @@ impl AuthStateMachineContext for AuthContext<'_> { let stored_bytes = pubkey.to_stored_bytes(); let nonce = create_nonce(&self.conn.db, &stored_bytes).await?; - let challenge = AuthChallenge { - pubkey: stored_bytes, - nonce, - }; - self.conn .transport - .send(Ok(UserAgentResponse { - payload: Some(UserAgentResponsePayload::AuthChallenge(challenge.clone())), - })) + .send(Ok(Response::AuthChallenge { nonce })) .await .map_err(|e| { error!(?e, "Failed to send auth challenge"); @@ -172,7 +128,7 @@ impl AuthStateMachineContext for AuthContext<'_> { })?; Ok(ChallengeContext { - challenge, + challenge_nonce: nonce, key: pubkey, }) } @@ -217,10 +173,10 @@ impl AuthStateMachineContext for AuthContext<'_> { #[allow(clippy::unused_unit)] async fn verify_solution( &mut self, - ChallengeContext { challenge, key }: &ChallengeContext, + ChallengeContext { challenge_nonce, key }: &ChallengeContext, ChallengeSolution { solution }: ChallengeSolution, ) -> Result { - let formatted = arbiter_proto::format_challenge(challenge.nonce, &challenge.pubkey); + let formatted = arbiter_proto::format_challenge(*challenge_nonce, &key.to_stored_bytes()); let valid = match key { AuthPublicKey::Ed25519(vk) => { @@ -252,9 +208,7 @@ impl AuthStateMachineContext for AuthContext<'_> { if valid { self.conn .transport - .send(Ok(UserAgentResponse { - payload: Some(UserAgentResponsePayload::AuthOk(AuthOk {})), - })) + .send(Ok(Response::AuthOk)) .await .map_err(|_| Error::Transport)?; } diff --git a/server/crates/arbiter-server/src/actors/user_agent/mod.rs b/server/crates/arbiter-server/src/actors/user_agent/mod.rs index 98d1eee..866219b 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/mod.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/mod.rs @@ -1,19 +1,15 @@ -use arbiter_proto::{ - proto::user_agent::{UserAgentRequest, UserAgentResponse}, - transport::Bi, -}; +use alloy::primitives::Address; +use arbiter_proto::transport::Bi; use kameo::actor::Spawn as _; use tracing::{error, info}; use crate::{ - actors::{GlobalActors, user_agent::session::UserAgentSession}, - db::{self}, + actors::{GlobalActors, evm, user_agent::session::UserAgentSession}, + db::{self, models::KeyType}, }; #[derive(Debug, thiserror::Error, PartialEq)] pub enum TransportResponseError { - #[error("Expected message with payload")] - MissingRequestPayload, #[error("Unexpected request payload")] UnexpectedRequestPayload, #[error("Invalid state for unseal encrypted key")] @@ -30,8 +26,106 @@ pub enum TransportResponseError { ConnectionRegistrationFailed, } -pub type Transport = - Box> + Send>; +/// Abstraction over Ed25519 / ECDSA-secp256k1 / RSA public keys used during the auth handshake. +#[derive(Clone, Debug)] +pub enum AuthPublicKey { + Ed25519(ed25519_dalek::VerifyingKey), + /// Compressed SEC1 public key; signature bytes are raw 64-byte (r||s). + EcdsaSecp256k1(k256::ecdsa::VerifyingKey), + /// RSA-2048+ public key (Windows Hello / KeyCredentialManager); signature bytes are PSS+SHA-256. + Rsa(rsa::RsaPublicKey), +} + +impl AuthPublicKey { + /// Canonical bytes stored in DB and echoed back in the challenge. + /// Ed25519: raw 32 bytes. ECDSA: SEC1 compressed 33 bytes. RSA: DER-encoded SPKI. + pub fn to_stored_bytes(&self) -> Vec { + match self { + AuthPublicKey::Ed25519(k) => k.to_bytes().to_vec(), + // SEC1 compressed (33 bytes) is the natural compact format for secp256k1 + AuthPublicKey::EcdsaSecp256k1(k) => k.to_encoded_point(true).as_bytes().to_vec(), + AuthPublicKey::Rsa(k) => { + use rsa::pkcs8::EncodePublicKey as _; + k.to_public_key_der() + .expect("rsa SPKI encoding is infallible") + .to_vec() + } + } + } + + pub fn key_type(&self) -> KeyType { + match self { + AuthPublicKey::Ed25519(_) => KeyType::Ed25519, + AuthPublicKey::EcdsaSecp256k1(_) => KeyType::EcdsaSecp256k1, + AuthPublicKey::Rsa(_) => KeyType::Rsa, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UnsealError { + InvalidKey, + Unbootstrapped, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BootstrapError { + AlreadyBootstrapped, + InvalidKey, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VaultState { + Unbootstrapped, + Sealed, + Unsealed, +} + +#[derive(Debug, Clone)] +pub enum Request { + AuthChallengeRequest { + pubkey: AuthPublicKey, + bootstrap_token: Option, + }, + AuthChallengeSolution { + signature: Vec, + }, + UnsealStart { + client_pubkey: x25519_dalek::PublicKey, + }, + UnsealEncryptedKey { + nonce: Vec, + ciphertext: Vec, + associated_data: Vec, + }, + BootstrapEncryptedKey { + nonce: Vec, + ciphertext: Vec, + associated_data: Vec, + }, + QueryVaultState, + EvmWalletCreate, + EvmWalletList, + ClientConnectionResponse { + approved: bool, + }, +} + +#[derive(Debug)] +pub enum Response { + AuthChallenge { nonce: i32 }, + AuthOk, + UnsealStartResponse { server_pubkey: x25519_dalek::PublicKey }, + UnsealResult(Result<(), UnsealError>), + BootstrapResult(Result<(), BootstrapError>), + VaultState(VaultState), + ClientConnectionRequest { pubkey: ed25519_dalek::VerifyingKey }, + ClientConnectionCancel, + EvmWalletCreate(Result<(), evm::Error>), + EvmWalletList(Vec
), +} + +pub type Transport = Box> + Send>; pub struct UserAgentConnection { db: db::DatabasePool, diff --git a/server/crates/arbiter-server/src/actors/user_agent/session.rs b/server/crates/arbiter-server/src/actors/user_agent/session.rs index 376273d..0a9f893 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/session.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/session.rs @@ -1,14 +1,5 @@ use std::{ops::DerefMut, sync::Mutex}; -use arbiter_proto::proto::{ - evm as evm_proto, - user_agent::{ - BootstrapEncryptedKey, BootstrapResult, ClientConnectionCancel, ClientConnectionRequest, - UnsealEncryptedKey, UnsealResult, UnsealStart, UnsealStartResponse, UserAgentRequest, - UserAgentResponse, user_agent_request::Payload as UserAgentRequestPayload, - user_agent_response::Payload as UserAgentResponsePayload, - }, -}; use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit}; use ed25519_dalek::VerifyingKey; use kameo::{Actor, error::SendError, messages, prelude::Context}; @@ -21,7 +12,10 @@ use crate::actors::{ evm::{Generate, ListWallets}, keyholder::{self, Bootstrap, TryUnseal}, router::RegisterUserAgent, - user_agent::{TransportResponseError, UserAgentConnection}, + user_agent::{ + BootstrapError, Request, Response, TransportResponseError, UnsealError, + UserAgentConnection, VaultState, + }, }; mod state; @@ -60,21 +54,17 @@ impl UserAgentSession { async fn send_msg( &mut self, - msg: UserAgentResponsePayload, + msg: Response, _ctx: &mut Context, ) -> Result<(), Error> { - self.props - .transport - .send(Ok(response(msg))) - .await - .map_err(|_| { - error!( - actor = "useragent", - reason = "channel closed", - "send.failed" - ); - Error::ConnectionLost - }) + self.props.transport.send(Ok(msg)).await.map_err(|_| { + error!( + actor = "useragent", + reason = "channel closed", + "send.failed" + ); + Error::ConnectionLost + }) } async fn expect_msg( @@ -83,7 +73,7 @@ impl UserAgentSession { ctx: &mut Context, ) -> Result where - Extractor: FnOnce(UserAgentRequestPayload) -> Option, + Extractor: FnOnce(Request) -> Option, Reply: kameo::Reply, { let msg = self.props.transport.recv().await.ok_or_else(|| { @@ -96,7 +86,7 @@ impl UserAgentSession { Error::ConnectionLost })?; - msg.payload.and_then(extractor).ok_or_else(|| { + extractor(msg).ok_or_else(|| { error!( actor = "useragent", reason = "unexpected message", @@ -119,18 +109,16 @@ impl UserAgentSession { ctx: &mut Context>, ) -> Result { self.send_msg( - UserAgentResponsePayload::ClientConnectionRequest(ClientConnectionRequest { - pubkey: client_pubkey.as_bytes().to_vec(), - }), + Response::ClientConnectionRequest { + pubkey: client_pubkey, + }, ctx, ) .await?; let extractor = |msg| { - if let UserAgentRequestPayload::ClientConnectionResponse(client_connection_response) = - msg - { - Some(client_connection_response) + if let Request::ClientConnectionResponse { approved } = msg { + Some(approved) } else { None } @@ -140,53 +128,55 @@ impl UserAgentSession { _ = cancel_flag.changed() => { info!(actor = "useragent", "client connection approval cancelled"); self.send_msg( - UserAgentResponsePayload::ClientConnectionCancel(ClientConnectionCancel {}), + Response::ClientConnectionCancel, ctx, ).await?; Ok(false) } result = self.expect_msg(extractor, ctx) => { let result = result?; - info!(actor = "useragent", "received client connection approval result: approved={}", result.approved); - Ok(result.approved) + info!(actor = "useragent", "received client connection approval result: approved={}", result); + Ok(result) } } } } impl UserAgentSession { - pub async fn process_transport_inbound(&mut self, req: UserAgentRequest) -> Output { - let msg = req.payload.ok_or_else(|| { - error!(actor = "useragent", "Received message with no payload"); - TransportResponseError::MissingRequestPayload - })?; - - match msg { - UserAgentRequestPayload::UnsealStart(unseal_start) => { - self.handle_unseal_request(unseal_start).await + pub async fn process_transport_inbound(&mut self, req: Request) -> Output { + match req { + Request::UnsealStart { client_pubkey } => { + self.handle_unseal_request(client_pubkey).await } - UserAgentRequestPayload::UnsealEncryptedKey(unseal_encrypted_key) => { - self.handle_unseal_encrypted_key(unseal_encrypted_key).await - } - UserAgentRequestPayload::BootstrapEncryptedKey(bootstrap_encrypted_key) => { - self.handle_bootstrap_encrypted_key(bootstrap_encrypted_key) + Request::UnsealEncryptedKey { + nonce, + ciphertext, + associated_data, + } => { + self.handle_unseal_encrypted_key(nonce, ciphertext, associated_data) .await } - UserAgentRequestPayload::QueryVaultState(_) => self.handle_query_vault_state().await, - UserAgentRequestPayload::EvmWalletCreate(_) => self.handle_evm_wallet_create().await, - UserAgentRequestPayload::EvmWalletList(_) => self.handle_evm_wallet_list().await, - _ => Err(TransportResponseError::UnexpectedRequestPayload), + Request::BootstrapEncryptedKey { + nonce, + ciphertext, + associated_data, + } => { + self.handle_bootstrap_encrypted_key(nonce, ciphertext, associated_data) + .await + } + Request::QueryVaultState => self.handle_query_vault_state().await, + Request::EvmWalletCreate => self.handle_evm_wallet_create().await, + Request::EvmWalletList => self.handle_evm_wallet_list().await, + Request::AuthChallengeRequest { .. } + | Request::AuthChallengeSolution { .. } + | Request::ClientConnectionResponse { .. } => { + Err(TransportResponseError::UnexpectedRequestPayload) + } } } } -type Output = Result; - -fn response(payload: UserAgentResponsePayload) -> UserAgentResponse { - UserAgentResponse { - payload: Some(payload), - } -} +type Output = Result; impl UserAgentSession { fn take_unseal_secret( @@ -242,37 +232,31 @@ impl UserAgentSession { } } - async fn handle_unseal_request(&mut self, req: UnsealStart) -> Output { + async fn handle_unseal_request(&mut self, client_pubkey: x25519_dalek::PublicKey) -> Output { let secret = EphemeralSecret::random(); let public_key = PublicKey::from(&secret); - let client_pubkey_bytes: [u8; 32] = req - .client_pubkey - .try_into() - .map_err(|_| TransportResponseError::InvalidClientPubkeyLength)?; - - let client_public_key = PublicKey::from(client_pubkey_bytes); - self.transition(UserAgentEvents::UnsealRequest(UnsealContext { secret: Mutex::new(Some(secret)), - client_public_key, + client_public_key: client_pubkey }))?; - Ok(response(UserAgentResponsePayload::UnsealStartResponse( - UnsealStartResponse { - server_pubkey: public_key.as_bytes().to_vec(), - }, - ))) + Ok(Response::UnsealStartResponse { + server_pubkey: public_key, + }) } - async fn handle_unseal_encrypted_key(&mut self, req: UnsealEncryptedKey) -> Output { + async fn handle_unseal_encrypted_key( + &mut self, + nonce: Vec, + ciphertext: Vec, + associated_data: Vec, + ) -> Output { let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() { Ok(values) => values, Err(TransportResponseError::StateTransitionFailed) => { self.transition(UserAgentEvents::ReceivedInvalidKey)?; - return Ok(response(UserAgentResponsePayload::UnsealResult( - UnsealResult::InvalidKey.into(), - ))); + return Ok(Response::UnsealResult(Err(UnsealError::InvalidKey))); } Err(err) => return Err(err), }; @@ -280,16 +264,14 @@ impl UserAgentSession { let seal_key_buffer = match Self::decrypt_client_key_material( ephemeral_secret, client_public_key, - &req.nonce, - &req.ciphertext, - &req.associated_data, + &nonce, + &ciphertext, + &associated_data, ) { Ok(buffer) => buffer, Err(()) => { self.transition(UserAgentEvents::ReceivedInvalidKey)?; - return Ok(response(UserAgentResponsePayload::UnsealResult( - UnsealResult::InvalidKey.into(), - ))); + return Ok(Response::UnsealResult(Err(UnsealError::InvalidKey))); } }; @@ -305,22 +287,16 @@ impl UserAgentSession { Ok(_) => { info!("Successfully unsealed key with client-provided key"); self.transition(UserAgentEvents::ReceivedValidKey)?; - Ok(response(UserAgentResponsePayload::UnsealResult( - UnsealResult::Success.into(), - ))) + Ok(Response::UnsealResult(Ok(()))) } Err(SendError::HandlerError(keyholder::Error::InvalidKey)) => { self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Ok(response(UserAgentResponsePayload::UnsealResult( - UnsealResult::InvalidKey.into(), - ))) + Ok(Response::UnsealResult(Err(UnsealError::InvalidKey))) } Err(SendError::HandlerError(err)) => { error!(?err, "Keyholder failed to unseal key"); self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Ok(response(UserAgentResponsePayload::UnsealResult( - UnsealResult::InvalidKey.into(), - ))) + Ok(Response::UnsealResult(Err(UnsealError::InvalidKey))) } Err(err) => { error!(?err, "Failed to send unseal request to keyholder"); @@ -330,14 +306,17 @@ impl UserAgentSession { } } - async fn handle_bootstrap_encrypted_key(&mut self, req: BootstrapEncryptedKey) -> Output { + async fn handle_bootstrap_encrypted_key( + &mut self, + nonce: Vec, + ciphertext: Vec, + associated_data: Vec, + ) -> Output { let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() { Ok(values) => values, Err(TransportResponseError::StateTransitionFailed) => { self.transition(UserAgentEvents::ReceivedInvalidKey)?; - return Ok(response(UserAgentResponsePayload::BootstrapResult( - BootstrapResult::InvalidKey.into(), - ))); + return Ok(Response::BootstrapResult(Err(BootstrapError::InvalidKey))); } Err(err) => return Err(err), }; @@ -345,16 +324,14 @@ impl UserAgentSession { let seal_key_buffer = match Self::decrypt_client_key_material( ephemeral_secret, client_public_key, - &req.nonce, - &req.ciphertext, - &req.associated_data, + &nonce, + &ciphertext, + &associated_data, ) { Ok(buffer) => buffer, Err(()) => { self.transition(UserAgentEvents::ReceivedInvalidKey)?; - return Ok(response(UserAgentResponsePayload::BootstrapResult( - BootstrapResult::InvalidKey.into(), - ))); + return Ok(Response::BootstrapResult(Err(BootstrapError::InvalidKey))); } }; @@ -370,22 +347,18 @@ impl UserAgentSession { Ok(_) => { info!("Successfully bootstrapped vault with client-provided key"); self.transition(UserAgentEvents::ReceivedValidKey)?; - Ok(response(UserAgentResponsePayload::BootstrapResult( - BootstrapResult::Success.into(), - ))) + Ok(Response::BootstrapResult(Ok(()))) } Err(SendError::HandlerError(keyholder::Error::AlreadyBootstrapped)) => { self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Ok(response(UserAgentResponsePayload::BootstrapResult( - BootstrapResult::AlreadyBootstrapped.into(), + Ok(Response::BootstrapResult(Err( + BootstrapError::AlreadyBootstrapped, ))) } Err(SendError::HandlerError(err)) => { error!(?err, "Keyholder failed to bootstrap vault"); self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Ok(response(UserAgentResponsePayload::BootstrapResult( - BootstrapResult::InvalidKey.into(), - ))) + Ok(Response::BootstrapResult(Err(BootstrapError::InvalidKey))) } Err(err) => { error!(?err, "Failed to send bootstrap request to keyholder"); @@ -399,7 +372,6 @@ impl UserAgentSession { impl UserAgentSession { async fn handle_query_vault_state(&mut self) -> Output { use crate::actors::keyholder::{GetState, StateDiscriminants}; - use arbiter_proto::proto::user_agent::VaultState; let vault_state = match self.props.actors.key_holder.ask(GetState {}).await { Ok(StateDiscriminants::Unbootstrapped) => VaultState::Unbootstrapped, @@ -407,70 +379,34 @@ impl UserAgentSession { Ok(StateDiscriminants::Unsealed) => VaultState::Unsealed, Err(err) => { error!(?err, actor = "useragent", "keyholder.query.failed"); - VaultState::Error + return Err(TransportResponseError::KeyHolderActorUnreachable); } }; - Ok(response(UserAgentResponsePayload::VaultState( - vault_state.into(), - ))) + Ok(Response::VaultState(vault_state)) } } impl UserAgentSession { async fn handle_evm_wallet_create(&mut self) -> Output { - use evm_proto::wallet_create_response::Result as CreateResult; - let result = match self.props.actors.evm.ask(Generate {}).await { - Ok(address) => CreateResult::Wallet(evm_proto::WalletEntry { - address: address.as_slice().to_vec(), - }), - Err(err) => CreateResult::Error(map_evm_error("wallet create", err).into()), + Ok(_address) => return Ok(Response::EvmWalletCreate(Ok(()))), + Err(SendError::HandlerError(err)) => Err(err), + Err(err) => { + error!(?err, "EVM actor unreachable during wallet create"); + return Err(TransportResponseError::KeyHolderActorUnreachable); + } }; - - Ok(response(UserAgentResponsePayload::EvmWalletCreate( - evm_proto::WalletCreateResponse { - result: Some(result), - }, - ))) + Ok(Response::EvmWalletCreate(result)) } async fn handle_evm_wallet_list(&mut self) -> Output { - use evm_proto::wallet_list_response::Result as ListResult; - - let result = match self.props.actors.evm.ask(ListWallets {}).await { - Ok(wallets) => ListResult::Wallets(evm_proto::WalletList { - wallets: wallets - .into_iter() - .map(|addr| evm_proto::WalletEntry { - address: addr.as_slice().to_vec(), - }) - .collect(), - }), - Err(err) => ListResult::Error(map_evm_error("wallet list", err).into()), - }; - - Ok(response(UserAgentResponsePayload::EvmWalletList( - evm_proto::WalletListResponse { - result: Some(result), - }, - ))) - } -} - -fn map_evm_error(op: &str, err: SendError) -> evm_proto::EvmError { - use crate::actors::{evm::Error as EvmError, keyholder::Error as KhError}; - match err { - SendError::HandlerError(EvmError::Keyholder(KhError::NotBootstrapped)) => { - evm_proto::EvmError::VaultSealed - } - SendError::HandlerError(err) => { - error!(?err, "EVM {op} failed"); - evm_proto::EvmError::Internal - } - _ => { - error!("EVM actor unreachable during {op}"); - evm_proto::EvmError::Internal + match self.props.actors.evm.ask(ListWallets {}).await { + Ok(wallets) => Ok(Response::EvmWalletList(wallets)), + Err(err) => { + error!(?err, "EVM wallet list failed"); + Err(TransportResponseError::KeyHolderActorUnreachable) + } } } } diff --git a/server/crates/arbiter-server/src/context/tls.rs b/server/crates/arbiter-server/src/context/tls.rs index 4a27764..85196ec 100644 --- a/server/crates/arbiter-server/src/context/tls.rs +++ b/server/crates/arbiter-server/src/context/tls.rs @@ -8,7 +8,7 @@ use rcgen::{ BasicConstraints, Certificate, CertificateParams, CertifiedIssuer, DistinguishedName, DnType, IsCa, Issuer, KeyPair, KeyUsagePurpose, }; -use rustls::pki_types::{pem::PemObject}; +use rustls::pki_types::pem::PemObject; use thiserror::Error; use tonic::transport::CertificateDer; @@ -59,10 +59,7 @@ pub enum InitError { pub type PemCert = String; pub fn encode_cert_to_pem(cert: &CertificateDer) -> PemCert { - pem::encode_config( - &Pem::new("CERTIFICATE", cert.to_vec()), - ENCODE_CONFIG, - ) + pem::encode_config(&Pem::new("CERTIFICATE", cert.to_vec()), ENCODE_CONFIG) } #[allow(unused)] diff --git a/server/crates/arbiter-server/src/evm/mod.rs b/server/crates/arbiter-server/src/evm/mod.rs index f295dc8..503735b 100644 --- a/server/crates/arbiter-server/src/evm/mod.rs +++ b/server/crates/arbiter-server/src/evm/mod.rs @@ -117,9 +117,7 @@ async fn check_shared_constraints( let now = Utc::now(); // Validity window - if shared.valid_from.is_some_and(|t| now < t) - || shared.valid_until.is_some_and(|t| now > t) - { + if shared.valid_from.is_some_and(|t| now < t) || shared.valid_until.is_some_and(|t| now > t) { violations.push(EvalViolation::InvalidTime); } @@ -127,9 +125,9 @@ async fn check_shared_constraints( let fee_exceeded = shared .max_gas_fee_per_gas .is_some_and(|cap| U256::from(context.max_fee_per_gas) > cap); - let priority_exceeded = shared.max_priority_fee_per_gas.is_some_and(|cap| { - U256::from(context.max_priority_fee_per_gas) > cap - }); + let priority_exceeded = shared + .max_priority_fee_per_gas + .is_some_and(|cap| U256::from(context.max_priority_fee_per_gas) > cap); if fee_exceeded || priority_exceeded { violations.push(EvalViolation::GasLimitExceeded { max_gas_fee_per_gas: shared.max_gas_fee_per_gas, diff --git a/server/crates/arbiter-server/src/evm/policies.rs b/server/crates/arbiter-server/src/evm/policies.rs index 23c3444..5a968cd 100644 --- a/server/crates/arbiter-server/src/evm/policies.rs +++ b/server/crates/arbiter-server/src/evm/policies.rs @@ -73,7 +73,6 @@ pub struct Grant { pub settings: PolicySettings, } - pub trait Policy: Sized { type Settings: Send + Sync + 'static + Into; type Meaning: Display + std::fmt::Debug + Send + Sync + 'static + Into; diff --git a/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs b/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs index 55a7744..0e52c19 100644 --- a/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs +++ b/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs @@ -9,9 +9,7 @@ use crate::db::{ schema::{evm_basic_grant, evm_transaction_log}, }; use crate::evm::{ - policies::{ - EvalContext, EvalViolation, Grant, Policy, SharedGrantSettings, VolumeRateLimit, - }, + policies::{EvalContext, EvalViolation, Grant, Policy, SharedGrantSettings, VolumeRateLimit}, utils, }; diff --git a/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs b/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs index 6cd8246..95d852b 100644 --- a/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs +++ b/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs @@ -140,10 +140,18 @@ async fn evaluate_rejects_nonzero_eth_value() { let mut context = ctx(DAI, calldata); context.value = U256::from(1u64); // ETH attached to an ERC-20 call - let m = TokenTransfer::analyze(&EvalContext { value: U256::ZERO, ..context.clone() }) + let m = TokenTransfer::analyze(&EvalContext { + value: U256::ZERO, + ..context.clone() + }) + .unwrap(); + let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn) + .await .unwrap(); - let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn).await.unwrap(); - assert!(v.iter().any(|e| matches!(e, EvalViolation::InvalidTransactionType))); + assert!( + v.iter() + .any(|e| matches!(e, EvalViolation::InvalidTransactionType)) + ); } #[tokio::test] @@ -160,7 +168,9 @@ async fn evaluate_passes_any_recipient_when_no_restriction() { let calldata = transfer_calldata(RECIPIENT, U256::from(100u64)); let context = ctx(DAI, calldata); let m = TokenTransfer::analyze(&context).unwrap(); - let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn).await.unwrap(); + let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn) + .await + .unwrap(); assert!(v.is_empty()); } @@ -178,7 +188,9 @@ async fn evaluate_passes_matching_restricted_recipient() { let calldata = transfer_calldata(RECIPIENT, U256::from(100u64)); let context = ctx(DAI, calldata); let m = TokenTransfer::analyze(&context).unwrap(); - let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn).await.unwrap(); + let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn) + .await + .unwrap(); assert!(v.is_empty()); } @@ -196,8 +208,13 @@ async fn evaluate_rejects_wrong_restricted_recipient() { let calldata = transfer_calldata(OTHER, U256::from(100u64)); let context = ctx(DAI, calldata); let m = TokenTransfer::analyze(&context).unwrap(); - let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn).await.unwrap(); - assert!(v.iter().any(|e| matches!(e, EvalViolation::InvalidTarget { .. }))); + let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn) + .await + .unwrap(); + assert!( + v.iter() + .any(|e| matches!(e, EvalViolation::InvalidTarget { .. })) + ); } #[tokio::test] @@ -207,7 +224,9 @@ async fn evaluate_passes_volume_within_limit() { let basic = insert_basic(&mut conn, false).await; let settings = make_settings(None, Some(1_000)); - let grant_id = TokenTransfer::create_grant(&basic, &settings, &mut *conn).await.unwrap(); + let grant_id = TokenTransfer::create_grant(&basic, &settings, &mut *conn) + .await + .unwrap(); // Record a past transfer of 500 (within 1000 limit) use crate::db::{models::NewEvmTokenTransferLog, schema::evm_token_transfer_log}; @@ -224,12 +243,22 @@ async fn evaluate_passes_volume_within_limit() { .await .unwrap(); - let grant = Grant { id: grant_id, shared_grant_id: basic.id, shared: shared(), settings }; + let grant = Grant { + id: grant_id, + shared_grant_id: basic.id, + shared: shared(), + settings, + }; let calldata = transfer_calldata(RECIPIENT, U256::from(100u64)); let context = ctx(DAI, calldata); let m = TokenTransfer::analyze(&context).unwrap(); - let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn).await.unwrap(); - assert!(!v.iter().any(|e| matches!(e, EvalViolation::VolumetricLimitExceeded))); + let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn) + .await + .unwrap(); + assert!( + !v.iter() + .any(|e| matches!(e, EvalViolation::VolumetricLimitExceeded)) + ); } #[tokio::test] @@ -239,7 +268,9 @@ async fn evaluate_rejects_volume_over_limit() { let basic = insert_basic(&mut conn, false).await; let settings = make_settings(None, Some(1_000)); - let grant_id = TokenTransfer::create_grant(&basic, &settings, &mut *conn).await.unwrap(); + let grant_id = TokenTransfer::create_grant(&basic, &settings, &mut *conn) + .await + .unwrap(); use crate::db::{models::NewEvmTokenTransferLog, schema::evm_token_transfer_log}; insert_into(evm_token_transfer_log::table) @@ -255,12 +286,22 @@ async fn evaluate_rejects_volume_over_limit() { .await .unwrap(); - let grant = Grant { id: grant_id, shared_grant_id: basic.id, shared: shared(), settings }; + let grant = Grant { + id: grant_id, + shared_grant_id: basic.id, + shared: shared(), + settings, + }; let calldata = transfer_calldata(RECIPIENT, U256::from(100u64)); let context = ctx(DAI, calldata); let m = TokenTransfer::analyze(&context).unwrap(); - let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn).await.unwrap(); - assert!(v.iter().any(|e| matches!(e, EvalViolation::VolumetricLimitExceeded))); + let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn) + .await + .unwrap(); + assert!( + v.iter() + .any(|e| matches!(e, EvalViolation::VolumetricLimitExceeded)) + ); } #[tokio::test] @@ -277,8 +318,13 @@ async fn evaluate_no_volume_limits_always_passes() { let calldata = transfer_calldata(RECIPIENT, U256::from(u64::MAX)); let context = ctx(DAI, calldata); let m = TokenTransfer::analyze(&context).unwrap(); - let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn).await.unwrap(); - assert!(!v.iter().any(|e| matches!(e, EvalViolation::VolumetricLimitExceeded))); + let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn) + .await + .unwrap(); + assert!( + !v.iter() + .any(|e| matches!(e, EvalViolation::VolumetricLimitExceeded)) + ); } // ── try_find_grant ─────────────────────────────────────────────────────── @@ -290,7 +336,9 @@ async fn try_find_grant_roundtrip() { let basic = insert_basic(&mut conn, false).await; let settings = make_settings(Some(RECIPIENT), Some(5_000)); - TokenTransfer::create_grant(&basic, &settings, &mut *conn).await.unwrap(); + TokenTransfer::create_grant(&basic, &settings, &mut *conn) + .await + .unwrap(); let calldata = transfer_calldata(RECIPIENT, U256::from(100u64)); let found = TokenTransfer::try_find_grant(&ctx(DAI, calldata), &mut *conn) @@ -312,7 +360,9 @@ async fn try_find_grant_revoked_returns_none() { let basic = insert_basic(&mut conn, true).await; let settings = make_settings(None, None); - TokenTransfer::create_grant(&basic, &settings, &mut *conn).await.unwrap(); + TokenTransfer::create_grant(&basic, &settings, &mut *conn) + .await + .unwrap(); let calldata = transfer_calldata(RECIPIENT, U256::from(1u64)); let found = TokenTransfer::try_find_grant(&ctx(DAI, calldata), &mut *conn) @@ -328,7 +378,9 @@ async fn try_find_grant_unknown_token_returns_none() { let basic = insert_basic(&mut conn, false).await; let settings = make_settings(None, None); - TokenTransfer::create_grant(&basic, &settings, &mut *conn).await.unwrap(); + TokenTransfer::create_grant(&basic, &settings, &mut *conn) + .await + .unwrap(); // Query with a different token contract let calldata = transfer_calldata(RECIPIENT, U256::from(1u64)); @@ -355,9 +407,13 @@ async fn find_all_grants_excludes_revoked() { let settings = make_settings(None, Some(1_000)); let active = insert_basic(&mut conn, false).await; - TokenTransfer::create_grant(&active, &settings, &mut *conn).await.unwrap(); + TokenTransfer::create_grant(&active, &settings, &mut *conn) + .await + .unwrap(); let revoked = insert_basic(&mut conn, true).await; - TokenTransfer::create_grant(&revoked, &settings, &mut *conn).await.unwrap(); + TokenTransfer::create_grant(&revoked, &settings, &mut *conn) + .await + .unwrap(); let all = TokenTransfer::find_all_grants(&mut *conn).await.unwrap(); assert_eq!(all.len(), 1); @@ -370,12 +426,17 @@ async fn find_all_grants_loads_volume_limits() { let basic = insert_basic(&mut conn, false).await; let settings = make_settings(None, Some(9_999)); - TokenTransfer::create_grant(&basic, &settings, &mut *conn).await.unwrap(); + TokenTransfer::create_grant(&basic, &settings, &mut *conn) + .await + .unwrap(); let all = TokenTransfer::find_all_grants(&mut *conn).await.unwrap(); assert_eq!(all.len(), 1); assert_eq!(all[0].settings.volume_limits.len(), 1); - assert_eq!(all[0].settings.volume_limits[0].max_volume, U256::from(9_999u64)); + assert_eq!( + all[0].settings.volume_limits[0].max_volume, + U256::from(9_999u64) + ); } #[tokio::test] @@ -388,9 +449,13 @@ async fn find_all_grants_multiple_grants_batch_loaded() { .await .unwrap(); let b2 = insert_basic(&mut conn, false).await; - TokenTransfer::create_grant(&b2, &make_settings(Some(RECIPIENT), Some(2_000)), &mut *conn) - .await - .unwrap(); + TokenTransfer::create_grant( + &b2, + &make_settings(Some(RECIPIENT), Some(2_000)), + &mut *conn, + ) + .await + .unwrap(); let all = TokenTransfer::find_all_grants(&mut *conn).await.unwrap(); assert_eq!(all.len(), 2); diff --git a/server/crates/arbiter-server/src/evm/safe_signer.rs b/server/crates/arbiter-server/src/evm/safe_signer.rs index 1e10031..fd39189 100644 --- a/server/crates/arbiter-server/src/evm/safe_signer.rs +++ b/server/crates/arbiter-server/src/evm/safe_signer.rs @@ -3,11 +3,11 @@ use std::sync::Mutex; use alloy::{ consensus::SignableTransaction, network::{TxSigner, TxSignerSync}, - primitives::{Address, ChainId, Signature, B256}, + primitives::{Address, B256, ChainId, Signature}, signers::{Error, Result, Signer, SignerSync, utils::secret_key_to_address}, }; use async_trait::async_trait; -use k256::ecdsa::{self, signature::hazmat::PrehashSigner, RecoveryId, SigningKey}; +use k256::ecdsa::{self, RecoveryId, SigningKey, signature::hazmat::PrehashSigner}; use memsafe::MemSafe; /// An Ethereum signer that stores its secp256k1 secret key inside a @@ -90,10 +90,7 @@ impl SafeSigner { Ok(sig.into()) } - fn sign_tx_inner( - &self, - tx: &mut dyn SignableTransaction, - ) -> Result { + fn sign_tx_inner(&self, tx: &mut dyn SignableTransaction) -> Result { if let Some(chain_id) = self.chain_id && !tx.set_chain_id_checked(chain_id) { @@ -102,7 +99,8 @@ impl SafeSigner { tx: tx.chain_id().unwrap(), }); } - self.sign_hash_inner(&tx.signature_hash()).map_err(Error::other) + self.sign_hash_inner(&tx.signature_hash()) + .map_err(Error::other) } } diff --git a/server/crates/arbiter-server/src/grpc/client.rs b/server/crates/arbiter-server/src/grpc/client.rs new file mode 100644 index 0000000..1e9e072 --- /dev/null +++ b/server/crates/arbiter-server/src/grpc/client.rs @@ -0,0 +1,137 @@ +use arbiter_proto::{ + proto::client::{ + AuthChallenge as ProtoAuthChallenge, + AuthChallengeRequest as ProtoAuthChallengeRequest, + AuthChallengeSolution as ProtoAuthChallengeSolution, AuthOk as ProtoAuthOk, + ClientConnectError, ClientRequest, ClientResponse, + client_connect_error::Code as ProtoClientConnectErrorCode, + client_request::Payload as ClientRequestPayload, + client_response::Payload as ClientResponsePayload, + }, + transport::{Bi, Error as TransportError}, +}; +use async_trait::async_trait; +use futures::StreamExt as _; +use tokio::sync::mpsc; +use tonic::{Status, Streaming}; + +use crate::actors::client::{ + self, ClientError, ConnectErrorCode, Request as DomainRequest, Response as DomainResponse, +}; + +pub struct GrpcTransport { + sender: mpsc::Sender>, + receiver: Streaming, +} + +impl GrpcTransport { + pub fn new( + sender: mpsc::Sender>, + receiver: Streaming, + ) -> Self { + Self { sender, receiver } + } + + fn request_to_domain(request: ClientRequest) -> Result { + match request.payload { + Some(ClientRequestPayload::AuthChallengeRequest(ProtoAuthChallengeRequest { + pubkey, + })) => Ok(DomainRequest::AuthChallengeRequest { pubkey }), + Some(ClientRequestPayload::AuthChallengeSolution( + ProtoAuthChallengeSolution { signature }, + )) => Ok(DomainRequest::AuthChallengeSolution { signature }), + None => Err(Status::invalid_argument("Missing client request payload")), + } + } + + fn response_to_proto(response: DomainResponse) -> ClientResponse { + let payload = match response { + DomainResponse::AuthChallenge { pubkey, nonce } => { + ClientResponsePayload::AuthChallenge(ProtoAuthChallenge { pubkey, nonce }) + } + DomainResponse::AuthOk => ClientResponsePayload::AuthOk(ProtoAuthOk {}), + DomainResponse::ClientConnectError { code } => { + ClientResponsePayload::ClientConnectError(ClientConnectError { + code: match code { + ConnectErrorCode::Unknown => ProtoClientConnectErrorCode::Unknown, + ConnectErrorCode::ApprovalDenied => { + ProtoClientConnectErrorCode::ApprovalDenied + } + ConnectErrorCode::NoUserAgentsOnline => { + ProtoClientConnectErrorCode::NoUserAgentsOnline + } + } + .into(), + }) + } + }; + + ClientResponse { + payload: Some(payload), + } + } + + fn error_to_status(value: ClientError) -> Status { + match value { + ClientError::MissingRequestPayload | ClientError::UnexpectedRequestPayload => { + Status::invalid_argument("Expected message with payload") + } + ClientError::StateTransitionFailed => Status::internal("State machine error"), + ClientError::Auth(ref err) => auth_error_status(err), + ClientError::ConnectionRegistrationFailed => { + Status::internal("Connection registration failed") + } + } + } +} + +#[async_trait] +impl Bi> for GrpcTransport { + async fn send(&mut self, item: Result) -> Result<(), TransportError> { + let outbound = match item { + Ok(message) => Ok(Self::response_to_proto(message)), + Err(err) => Err(Self::error_to_status(err)), + }; + + self.sender + .send(outbound) + .await + .map_err(|_| TransportError::ChannelClosed) + } + + async fn recv(&mut self) -> Option { + match self.receiver.next().await { + Some(Ok(item)) => match Self::request_to_domain(item) { + Ok(request) => Some(request), + Err(status) => { + let _ = self.sender.send(Err(status)).await; + None + } + }, + Some(Err(error)) => { + tracing::error!(error = ?error, "grpc client recv failed; closing stream"); + None + } + None => None, + } + } +} + +fn auth_error_status(value: &client::auth::Error) -> Status { + use client::auth::Error; + + match value { + Error::UnexpectedMessagePayload | Error::InvalidClientPubkeyLength => { + Status::invalid_argument(value.to_string()) + } + Error::InvalidAuthPubkeyEncoding => { + Status::invalid_argument("Failed to convert pubkey to VerifyingKey") + } + Error::InvalidChallengeSolution => Status::unauthenticated(value.to_string()), + Error::ApproveError(_) => Status::permission_denied(value.to_string()), + Error::Transport => Status::internal("Transport error"), + Error::DatabasePoolUnavailable => Status::internal("Database pool error"), + Error::DatabaseOperationFailed => Status::internal("Database error"), + Error::InternalError => Status::internal("Internal error"), + } +} diff --git a/server/crates/arbiter-server/src/grpc/mod.rs b/server/crates/arbiter-server/src/grpc/mod.rs new file mode 100644 index 0000000..18b9f70 --- /dev/null +++ b/server/crates/arbiter-server/src/grpc/mod.rs @@ -0,0 +1,65 @@ + +use arbiter_proto::proto::{ + client::{ClientRequest, ClientResponse}, + user_agent::{UserAgentRequest, UserAgentResponse}, +}; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; +use tonic::{Request, Response, Status, async_trait}; +use tracing::info; + +use crate::{ + DEFAULT_CHANNEL_SIZE, + actors::{client::{ClientConnection, connect_client}, user_agent::{UserAgentConnection, connect_user_agent}}, +}; + +pub mod client; +pub mod user_agent; + +#[async_trait] +impl arbiter_proto::proto::arbiter_service_server::ArbiterService for super::Server { + type UserAgentStream = ReceiverStream>; + type ClientStream = ReceiverStream>; + + #[tracing::instrument(level = "debug", skip(self))] + async fn client( + &self, + request: Request>, + ) -> Result, Status> { + let req_stream = request.into_inner(); + let (tx, rx) = mpsc::channel(DEFAULT_CHANNEL_SIZE); + + let transport = client::GrpcTransport::new(tx, req_stream); + let props = ClientConnection::new( + self.context.db.clone(), + Box::new(transport), + self.context.actors.clone(), + ); + tokio::spawn(connect_client(props)); + + info!(event = "connection established", "grpc.client"); + + Ok(Response::new(ReceiverStream::new(rx))) + } + + #[tracing::instrument(level = "debug", skip(self))] + async fn user_agent( + &self, + request: Request>, + ) -> Result, Status> { + let req_stream = request.into_inner(); + let (tx, rx) = mpsc::channel(DEFAULT_CHANNEL_SIZE); + + let transport = user_agent::GrpcTransport::new(tx, req_stream); + let props = UserAgentConnection::new( + self.context.db.clone(), + self.context.actors.clone(), + Box::new(transport), + ); + tokio::spawn(connect_user_agent(props)); + + info!(event = "connection established", "grpc.user_agent"); + + Ok(Response::new(ReceiverStream::new(rx))) + } +} diff --git a/server/crates/arbiter-server/src/grpc/user_agent.rs b/server/crates/arbiter-server/src/grpc/user_agent.rs new file mode 100644 index 0000000..ade2444 --- /dev/null +++ b/server/crates/arbiter-server/src/grpc/user_agent.rs @@ -0,0 +1,288 @@ +use arbiter_proto::{ + proto::{ + evm::{ + EvmError as ProtoEvmError, WalletCreateResponse, WalletEntry, WalletList, + WalletListResponse, wallet_create_response::Result as WalletCreateResult, + wallet_list_response::Result as WalletListResult, + }, + user_agent::{ + AuthChallenge as ProtoAuthChallenge, + AuthChallengeRequest as ProtoAuthChallengeRequest, + AuthChallengeSolution as ProtoAuthChallengeSolution, AuthOk as ProtoAuthOk, + BootstrapEncryptedKey as ProtoBootstrapEncryptedKey, + BootstrapResult as ProtoBootstrapResult, ClientConnectionCancel, + ClientConnectionRequest, ClientConnectionResponse, KeyType as ProtoKeyType, + UnsealEncryptedKey as ProtoUnsealEncryptedKey, UnsealResult as ProtoUnsealResult, + UnsealStart, UnsealStartResponse, UserAgentRequest, UserAgentResponse, + VaultState as ProtoVaultState, + user_agent_request::Payload as UserAgentRequestPayload, + user_agent_response::Payload as UserAgentResponsePayload, + }, + }, + transport::{Bi, Error as TransportError}, +}; +use async_trait::async_trait; +use futures::StreamExt as _; +use tokio::sync::mpsc; +use tonic::{Status, Streaming}; + +use crate::actors::user_agent::{ + self, AuthPublicKey, BootstrapError, Request as DomainRequest, Response as DomainResponse, + TransportResponseError, UnsealError, VaultState, +}; + +pub struct GrpcTransport { + sender: mpsc::Sender>, + receiver: Streaming, +} + +impl GrpcTransport { + pub fn new( + sender: mpsc::Sender>, + receiver: Streaming, + ) -> Self { + Self { sender, receiver } + } + + fn request_to_domain(request: UserAgentRequest) -> Result { + match request.payload { + Some(UserAgentRequestPayload::AuthChallengeRequest( + ProtoAuthChallengeRequest { + pubkey, + bootstrap_token, + key_type, + }, + )) => Ok(DomainRequest::AuthChallengeRequest { + pubkey: parse_auth_pubkey(key_type, pubkey)?, + bootstrap_token, + }), + Some(UserAgentRequestPayload::AuthChallengeSolution( + ProtoAuthChallengeSolution { signature }, + )) => Ok(DomainRequest::AuthChallengeSolution { signature }), + Some(UserAgentRequestPayload::UnsealStart(UnsealStart { client_pubkey })) => { + let client_pubkey: [u8; 32] = client_pubkey + .as_slice() + .try_into() + .map_err(|_| Status::invalid_argument("client_pubkey must be 32 bytes"))?; + Ok(DomainRequest::UnsealStart { + client_pubkey: x25519_dalek::PublicKey::from(client_pubkey), + }) + } + Some(UserAgentRequestPayload::UnsealEncryptedKey(ProtoUnsealEncryptedKey { + nonce, + ciphertext, + associated_data, + })) => Ok(DomainRequest::UnsealEncryptedKey { + nonce, + ciphertext, + associated_data, + }), + Some(UserAgentRequestPayload::BootstrapEncryptedKey( + ProtoBootstrapEncryptedKey { + nonce, + ciphertext, + associated_data, + }, + )) => Ok(DomainRequest::BootstrapEncryptedKey { + nonce, + ciphertext, + associated_data, + }), + Some(UserAgentRequestPayload::QueryVaultState(_)) => { + Ok(DomainRequest::QueryVaultState) + } + Some(UserAgentRequestPayload::EvmWalletCreate(_)) => Ok(DomainRequest::EvmWalletCreate), + Some(UserAgentRequestPayload::EvmWalletList(_)) => Ok(DomainRequest::EvmWalletList), + Some(UserAgentRequestPayload::ClientConnectionResponse( + ClientConnectionResponse { approved }, + )) => Ok(DomainRequest::ClientConnectionResponse { approved }), + Some(_) => Err(Status::invalid_argument( + "Unexpected user-agent request payload", + )), + None => Err(Status::invalid_argument("Missing user-agent request payload")), + } + } + + fn response_to_proto(response: DomainResponse) -> UserAgentResponse { + let payload = match response { + DomainResponse::AuthChallenge { nonce } => { + UserAgentResponsePayload::AuthChallenge(ProtoAuthChallenge { + pubkey: Vec::new(), + nonce, + }) + } + DomainResponse::AuthOk => UserAgentResponsePayload::AuthOk(ProtoAuthOk {}), + DomainResponse::UnsealStartResponse { server_pubkey } => { + UserAgentResponsePayload::UnsealStartResponse(UnsealStartResponse { + server_pubkey: server_pubkey.as_bytes().to_vec(), + }) + } + DomainResponse::UnsealResult(result) => UserAgentResponsePayload::UnsealResult( + match result { + Ok(()) => ProtoUnsealResult::Success, + Err(UnsealError::InvalidKey) => ProtoUnsealResult::InvalidKey, + Err(UnsealError::Unbootstrapped) => ProtoUnsealResult::Unbootstrapped, + } + .into(), + ), + DomainResponse::BootstrapResult(result) => UserAgentResponsePayload::BootstrapResult( + match result { + Ok(()) => ProtoBootstrapResult::Success, + Err(BootstrapError::AlreadyBootstrapped) => { + ProtoBootstrapResult::AlreadyBootstrapped + } + Err(BootstrapError::InvalidKey) => ProtoBootstrapResult::InvalidKey, + } + .into(), + ), + DomainResponse::VaultState(state) => UserAgentResponsePayload::VaultState( + match state { + VaultState::Unbootstrapped => ProtoVaultState::Unbootstrapped, + VaultState::Sealed => ProtoVaultState::Sealed, + VaultState::Unsealed => ProtoVaultState::Unsealed, + } + .into(), + ), + DomainResponse::ClientConnectionRequest { pubkey } => { + UserAgentResponsePayload::ClientConnectionRequest(ClientConnectionRequest { + pubkey: pubkey.to_bytes().to_vec(), + }) + } + DomainResponse::ClientConnectionCancel => { + UserAgentResponsePayload::ClientConnectionCancel(ClientConnectionCancel {}) + } + DomainResponse::EvmWalletCreate(result) => { + UserAgentResponsePayload::EvmWalletCreate(WalletCreateResponse { + result: Some(match result { + Ok(()) => WalletCreateResult::Wallet(WalletEntry { + address: Vec::new(), + }), + Err(_) => WalletCreateResult::Error(ProtoEvmError::Internal.into()), + }), + }) + } + DomainResponse::EvmWalletList(wallets) => { + UserAgentResponsePayload::EvmWalletList(WalletListResponse { + result: Some(WalletListResult::Wallets(WalletList { + wallets: wallets + .into_iter() + .map(|addr| WalletEntry { + address: addr.as_slice().to_vec(), + }) + .collect(), + })), + }) + } + }; + + UserAgentResponse { + payload: Some(payload), + } + } + + fn error_to_status(value: TransportResponseError) -> Status { + match value { + TransportResponseError::UnexpectedRequestPayload => { + Status::invalid_argument("Expected message with payload") + } + TransportResponseError::InvalidStateForUnsealEncryptedKey => { + Status::failed_precondition("Invalid state for unseal encrypted key") + } + TransportResponseError::InvalidClientPubkeyLength => { + Status::invalid_argument("client_pubkey must be 32 bytes") + } + TransportResponseError::StateTransitionFailed => Status::internal("State machine error"), + TransportResponseError::KeyHolderActorUnreachable => { + Status::internal("Vault is not available") + } + TransportResponseError::Auth(ref err) => auth_error_status(err), + TransportResponseError::ConnectionRegistrationFailed => { + Status::internal("Failed registering connection") + } + } + } +} + +#[async_trait] +impl Bi> for GrpcTransport { + async fn send( + &mut self, + item: Result, + ) -> Result<(), TransportError> { + let outbound = match item { + Ok(message) => Ok(Self::response_to_proto(message)), + Err(err) => Err(Self::error_to_status(err)), + }; + + self.sender + .send(outbound) + .await + .map_err(|_| TransportError::ChannelClosed) + } + + async fn recv(&mut self) -> Option { + match self.receiver.next().await { + Some(Ok(item)) => match Self::request_to_domain(item) { + Ok(request) => Some(request), + Err(status) => { + let _ = self.sender.send(Err(status)).await; + None + } + }, + Some(Err(error)) => { + tracing::error!(error = ?error, "grpc user-agent recv failed; closing stream"); + None + } + None => None, + } + } +} + +fn parse_auth_pubkey(key_type: i32, pubkey: Vec) -> Result { + match ProtoKeyType::try_from(key_type).unwrap_or(ProtoKeyType::Unspecified) { + ProtoKeyType::Unspecified | ProtoKeyType::Ed25519 => { + let bytes: [u8; 32] = pubkey + .as_slice() + .try_into() + .map_err(|_| Status::invalid_argument("invalid Ed25519 public key length"))?; + let key = ed25519_dalek::VerifyingKey::from_bytes(&bytes) + .map_err(|_| Status::invalid_argument("invalid Ed25519 public key encoding"))?; + Ok(AuthPublicKey::Ed25519(key)) + } + ProtoKeyType::EcdsaSecp256k1 => { + let key = k256::ecdsa::VerifyingKey::from_sec1_bytes(&pubkey) + .map_err(|_| Status::invalid_argument("invalid secp256k1 public key encoding"))?; + Ok(AuthPublicKey::EcdsaSecp256k1(key)) + } + ProtoKeyType::Rsa => { + use rsa::pkcs8::DecodePublicKey as _; + + let key = rsa::RsaPublicKey::from_public_key_der(&pubkey) + .map_err(|_| Status::invalid_argument("invalid RSA public key encoding"))?; + Ok(AuthPublicKey::Rsa(key)) + } + } +} + +fn auth_error_status(value: &user_agent::auth::Error) -> Status { + use user_agent::auth::Error; + + match value { + Error::UnexpectedMessagePayload | Error::InvalidClientPubkeyLength => { + Status::invalid_argument(value.to_string()) + } + Error::InvalidAuthPubkeyEncoding => { + Status::invalid_argument("Failed to convert pubkey to VerifyingKey") + } + Error::PublicKeyNotRegistered | Error::InvalidChallengeSolution => { + Status::unauthenticated(value.to_string()) + } + Error::InvalidBootstrapToken => Status::invalid_argument("Invalid bootstrap token"), + Error::Transport => Status::internal("Transport error"), + Error::BootstrapperActorUnreachable => { + Status::internal("Bootstrap token consumption failed") + } + Error::DatabasePoolUnavailable => Status::internal("Database pool error"), + Error::DatabaseOperationFailed => Status::internal("Database error"), + } +} diff --git a/server/crates/arbiter-server/src/lib.rs b/server/crates/arbiter-server/src/lib.rs index d712992..2c06328 100644 --- a/server/crates/arbiter-server/src/lib.rs +++ b/server/crates/arbiter-server/src/lib.rs @@ -1,137 +1,16 @@ #![forbid(unsafe_code)] -use arbiter_proto::{ - proto::{ - client::{ClientRequest, ClientResponse}, - user_agent::{UserAgentRequest, UserAgentResponse}, - }, - transport::{IdentityRecvConverter, SendConverter, grpc}, -}; -use async_trait::async_trait; -use tokio_stream::wrappers::ReceiverStream; -use tokio::sync::mpsc; -use tonic::{Request, Response, Status}; -use tracing::info; -use crate::{ - actors::{ - client::{self, ClientConnection as ClientConnectionProps, ClientError, connect_client}, - user_agent::{self, TransportResponseError, UserAgentConnection, connect_user_agent}, - }, - context::ServerContext, -}; +use crate::context::ServerContext; pub mod actors; pub mod context; pub mod db; pub mod evm; +pub mod grpc; const DEFAULT_CHANNEL_SIZE: usize = 1000; -struct UserAgentGrpcSender; - -impl SendConverter for UserAgentGrpcSender { - type Input = Result; - type Output = Result; - - fn convert(&self, item: Self::Input) -> Self::Output { - match item { - Ok(message) => Ok(message), - Err(err) => Err(user_agent_error_status(err)), - } - } -} - -struct ClientGrpcSender; - -impl SendConverter for ClientGrpcSender { - type Input = Result; - type Output = Result; - - fn convert(&self, item: Self::Input) -> Self::Output { - match item { - Ok(message) => Ok(message), - Err(err) => Err(client_error_status(err)), - } - } -} - -fn client_error_status(value: ClientError) -> Status { - match value { - ClientError::MissingRequestPayload | ClientError::UnexpectedRequestPayload => { - Status::invalid_argument("Expected message with payload") - } - ClientError::StateTransitionFailed => Status::internal("State machine error"), - ClientError::Auth(ref err) => client_auth_error_status(err), - ClientError::ConnectionRegistrationFailed => { - Status::internal("Connection registration failed") - } - } -} - -fn client_auth_error_status(value: &client::auth::Error) -> Status { - use client::auth::Error; - match value { - Error::UnexpectedMessagePayload | Error::InvalidClientPubkeyLength => { - Status::invalid_argument(value.to_string()) - } - Error::InvalidAuthPubkeyEncoding => { - Status::invalid_argument("Failed to convert pubkey to VerifyingKey") - } - Error::InvalidChallengeSolution => Status::unauthenticated(value.to_string()), - Error::ApproveError(_) => Status::permission_denied(value.to_string()), - Error::Transport => Status::internal("Transport error"), - Error::DatabasePoolUnavailable => Status::internal("Database pool error"), - Error::DatabaseOperationFailed => Status::internal("Database error"), - Error::InternalError => Status::internal("Internal error"), - } -} - -fn user_agent_error_status(value: TransportResponseError) -> Status { - match value { - TransportResponseError::MissingRequestPayload - | TransportResponseError::UnexpectedRequestPayload => { - Status::invalid_argument("Expected message with payload") - } - TransportResponseError::InvalidStateForUnsealEncryptedKey => { - Status::failed_precondition("Invalid state for unseal encrypted key") - } - TransportResponseError::InvalidClientPubkeyLength => { - Status::invalid_argument("client_pubkey must be 32 bytes") - } - TransportResponseError::StateTransitionFailed => Status::internal("State machine error"), - TransportResponseError::KeyHolderActorUnreachable => { - Status::internal("Vault is not available") - } - TransportResponseError::Auth(ref err) => auth_error_status(err), - TransportResponseError::ConnectionRegistrationFailed => { - Status::internal("Failed registering connection") - } - } -} - -fn auth_error_status(value: &user_agent::auth::Error) -> Status { - use user_agent::auth::Error; - match value { - Error::UnexpectedMessagePayload | Error::InvalidClientPubkeyLength => { - Status::invalid_argument(value.to_string()) - } - Error::InvalidAuthPubkeyEncoding => { - Status::invalid_argument("Failed to convert pubkey to VerifyingKey") - } - Error::PublicKeyNotRegistered | Error::InvalidChallengeSolution => { - Status::unauthenticated(value.to_string()) - } - Error::InvalidBootstrapToken => Status::invalid_argument("Invalid bootstrap token"), - Error::Transport => Status::internal("Transport error"), - Error::BootstrapperActorUnreachable => { - Status::internal("Bootstrap token consumption failed") - } - Error::DatabasePoolUnavailable => Status::internal("Database pool error"), - Error::DatabaseOperationFailed => Status::internal("Database error"), - } -} - pub struct Server { context: ServerContext, } @@ -142,60 +21,4 @@ impl Server { } } -#[async_trait] -impl arbiter_proto::proto::arbiter_service_server::ArbiterService for Server { - type UserAgentStream = ReceiverStream>; - type ClientStream = ReceiverStream>; - #[tracing::instrument(level = "debug", skip(self))] - async fn client( - &self, - request: Request>, - ) -> Result, Status> { - let req_stream = request.into_inner(); - let (tx, rx) = mpsc::channel(DEFAULT_CHANNEL_SIZE); - - let transport = grpc::GrpcAdapter::new( - tx, - req_stream, - IdentityRecvConverter::::new(), - ClientGrpcSender, - ); - let props = ClientConnectionProps::new( - self.context.db.clone(), - Box::new(transport), - self.context.actors.clone(), - ); - tokio::spawn(connect_client(props)); - - info!(event = "connection established", "grpc.client"); - - Ok(Response::new(ReceiverStream::new(rx))) - } - - #[tracing::instrument(level = "debug", skip(self))] - async fn user_agent( - &self, - request: Request>, - ) -> Result, Status> { - let req_stream = request.into_inner(); - let (tx, rx) = mpsc::channel(DEFAULT_CHANNEL_SIZE); - - let transport = grpc::GrpcAdapter::new( - tx, - req_stream, - IdentityRecvConverter::::new(), - UserAgentGrpcSender, - ); - let props = UserAgentConnection::new( - self.context.db.clone(), - self.context.actors.clone(), - Box::new(transport), - ); - tokio::spawn(connect_user_agent(props)); - - info!(event = "connection established", "grpc.user_agent"); - - Ok(Response::new(ReceiverStream::new(rx))) - } -} diff --git a/server/crates/arbiter-server/tests/client/auth.rs b/server/crates/arbiter-server/tests/client/auth.rs index 6228a58..ca4f38b 100644 --- a/server/crates/arbiter-server/tests/client/auth.rs +++ b/server/crates/arbiter-server/tests/client/auth.rs @@ -1,12 +1,7 @@ -use arbiter_proto::proto::client::{ - AuthChallengeRequest, AuthChallengeSolution, ClientRequest, - client_request::Payload as ClientRequestPayload, - client_response::Payload as ClientResponsePayload, -}; use arbiter_proto::transport::Bi; use arbiter_server::actors::GlobalActors; use arbiter_server::{ - actors::client::{ClientConnection, connect_client}, + actors::client::{ClientConnection, Request, Response, connect_client}, db::{self, schema}, }; use diesel::{ExpressionMethods as _, insert_into}; @@ -29,12 +24,8 @@ pub async fn test_unregistered_pubkey_rejected() { let pubkey_bytes = new_key.verifying_key().to_bytes().to_vec(); test_transport - .send(ClientRequest { - payload: Some(ClientRequestPayload::AuthChallengeRequest( - AuthChallengeRequest { - pubkey: pubkey_bytes, - }, - )), + .send(Request::AuthChallengeRequest { + pubkey: pubkey_bytes, }) .await .unwrap(); @@ -68,12 +59,8 @@ pub async fn test_challenge_auth() { // Send challenge request test_transport - .send(ClientRequest { - payload: Some(ClientRequestPayload::AuthChallengeRequest( - AuthChallengeRequest { - pubkey: pubkey_bytes, - }, - )), + .send(Request::AuthChallengeRequest { + pubkey: pubkey_bytes, }) .await .unwrap(); @@ -84,24 +71,20 @@ pub async fn test_challenge_auth() { .await .expect("should receive challenge"); let challenge = match response { - Ok(resp) => match resp.payload { - Some(ClientResponsePayload::AuthChallenge(c)) => c, + Ok(resp) => match resp { + Response::AuthChallenge { pubkey, nonce } => (pubkey, nonce), other => panic!("Expected AuthChallenge, got {other:?}"), }, Err(err) => panic!("Expected Ok response, got Err({err:?})"), }; // Sign the challenge and send solution - let formatted_challenge = arbiter_proto::format_challenge(challenge.nonce, &challenge.pubkey); + let formatted_challenge = arbiter_proto::format_challenge(challenge.1, &challenge.0); let signature = new_key.sign(&formatted_challenge); test_transport - .send(ClientRequest { - payload: Some(ClientRequestPayload::AuthChallengeSolution( - AuthChallengeSolution { - signature: signature.to_bytes().to_vec(), - }, - )), + .send(Request::AuthChallengeSolution { + signature: signature.to_bytes().to_vec(), }) .await .unwrap(); diff --git a/server/crates/arbiter-server/tests/user_agent/auth.rs b/server/crates/arbiter-server/tests/user_agent/auth.rs index edcc1d2..4f23e9d 100644 --- a/server/crates/arbiter-server/tests/user_agent/auth.rs +++ b/server/crates/arbiter-server/tests/user_agent/auth.rs @@ -1,14 +1,9 @@ -use arbiter_proto::proto::user_agent::{ - AuthChallengeRequest, AuthChallengeSolution, KeyType as ProtoKeyType, UserAgentRequest, - user_agent_request::Payload as UserAgentRequestPayload, - user_agent_response::Payload as UserAgentResponsePayload, -}; use arbiter_proto::transport::Bi; use arbiter_server::{ actors::{ GlobalActors, bootstrap::GetToken, - user_agent::{UserAgentConnection, connect_user_agent}, + user_agent::{AuthPublicKey, Request, Response, UserAgentConnection, connect_user_agent}, }, db::{self, schema}, }; @@ -30,17 +25,10 @@ pub async fn test_bootstrap_token_auth() { let task = tokio::spawn(connect_user_agent(props)); let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng()); - let pubkey_bytes = new_key.verifying_key().to_bytes().to_vec(); - test_transport - .send(UserAgentRequest { - payload: Some(UserAgentRequestPayload::AuthChallengeRequest( - AuthChallengeRequest { - pubkey: pubkey_bytes, - bootstrap_token: Some(token), - key_type: ProtoKeyType::Ed25519.into(), - }, - )), + .send(Request::AuthChallengeRequest { + pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()), + bootstrap_token: Some(token), }) .await .unwrap(); @@ -67,17 +55,10 @@ pub async fn test_bootstrap_invalid_token_auth() { let task = tokio::spawn(connect_user_agent(props)); let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng()); - let pubkey_bytes = new_key.verifying_key().to_bytes().to_vec(); - test_transport - .send(UserAgentRequest { - payload: Some(UserAgentRequestPayload::AuthChallengeRequest( - AuthChallengeRequest { - pubkey: pubkey_bytes, - bootstrap_token: Some("invalid_token".to_string()), - key_type: ProtoKeyType::Ed25519.into(), - }, - )), + .send(Request::AuthChallengeRequest { + pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()), + bootstrap_token: Some("invalid_token".to_string()), }) .await .unwrap(); @@ -123,14 +104,9 @@ pub async fn test_challenge_auth() { // Send challenge request test_transport - .send(UserAgentRequest { - payload: Some(UserAgentRequestPayload::AuthChallengeRequest( - AuthChallengeRequest { - pubkey: pubkey_bytes, - bootstrap_token: None, - key_type: ProtoKeyType::Ed25519.into(), - }, - )), + .send(Request::AuthChallengeRequest { + pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()), + bootstrap_token: None, }) .await .unwrap(); @@ -141,24 +117,19 @@ pub async fn test_challenge_auth() { .await .expect("should receive challenge"); let challenge = match response { - Ok(resp) => match resp.payload { - Some(UserAgentResponsePayload::AuthChallenge(c)) => c, + Ok(resp) => match resp { + Response::AuthChallenge { nonce } => nonce, other => panic!("Expected AuthChallenge, got {other:?}"), }, Err(err) => panic!("Expected Ok response, got Err({err:?})"), }; - // Sign the challenge and send solution - let formatted_challenge = arbiter_proto::format_challenge(challenge.nonce, &challenge.pubkey); + let formatted_challenge = arbiter_proto::format_challenge(challenge, &pubkey_bytes); let signature = new_key.sign(&formatted_challenge); test_transport - .send(UserAgentRequest { - payload: Some(UserAgentRequestPayload::AuthChallengeSolution( - AuthChallengeSolution { - signature: signature.to_bytes().to_vec(), - }, - )), + .send(Request::AuthChallengeSolution { + signature: signature.to_bytes().to_vec(), }) .await .unwrap(); diff --git a/server/crates/arbiter-server/tests/user_agent/unseal.rs b/server/crates/arbiter-server/tests/user_agent/unseal.rs index 4e30ff4..486cb5d 100644 --- a/server/crates/arbiter-server/tests/user_agent/unseal.rs +++ b/server/crates/arbiter-server/tests/user_agent/unseal.rs @@ -1,13 +1,8 @@ -use arbiter_proto::proto::user_agent::{ - UnsealEncryptedKey, UnsealResult, UnsealStart, UserAgentRequest, - user_agent_request::Payload as UserAgentRequestPayload, - user_agent_response::Payload as UserAgentResponsePayload, -}; use arbiter_server::{ actors::{ GlobalActors, keyholder::{Bootstrap, Seal}, - user_agent::session::UserAgentSession, + user_agent::{Request, Response, UnsealError, session::UserAgentSession}, }, db, }; @@ -15,9 +10,7 @@ use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit}; use memsafe::MemSafe; use x25519_dalek::{EphemeralSecret, PublicKey}; -async fn setup_sealed_user_agent( - seal_key: &[u8], -) -> (db::DatabasePool, UserAgentSession) { +async fn setup_sealed_user_agent(seal_key: &[u8]) -> (db::DatabasePool, UserAgentSession) { let db = db::create_test_pool().await; let actors = GlobalActors::spawn(db.clone()).await.unwrap(); @@ -35,29 +28,23 @@ async fn setup_sealed_user_agent( (db, session) } -async fn client_dh_encrypt( - user_agent: &mut UserAgentSession, - key_to_send: &[u8], -) -> UnsealEncryptedKey { +async fn client_dh_encrypt(user_agent: &mut UserAgentSession, key_to_send: &[u8]) -> Request { let client_secret = EphemeralSecret::random(); let client_public = PublicKey::from(&client_secret); let response = user_agent - .process_transport_inbound(UserAgentRequest { - payload: Some(UserAgentRequestPayload::UnsealStart(UnsealStart { - client_pubkey: client_public.as_bytes().to_vec(), - })), + .process_transport_inbound(Request::UnsealStart { + client_pubkey: client_public, }) .await .unwrap(); - let server_pubkey = match response.payload.unwrap() { - UserAgentResponsePayload::UnsealStartResponse(resp) => resp.server_pubkey, + let server_pubkey = match response { + Response::UnsealStartResponse { server_pubkey } => server_pubkey, other => panic!("Expected UnsealStartResponse, got {other:?}"), }; - let server_public = PublicKey::from(<[u8; 32]>::try_from(server_pubkey.as_slice()).unwrap()); - let shared_secret = client_secret.diffie_hellman(&server_public); + let shared_secret = client_secret.diffie_hellman(&server_pubkey); let cipher = XChaCha20Poly1305::new(shared_secret.as_bytes().into()); let nonce = XNonce::from([0u8; 24]); let associated_data = b"unseal"; @@ -66,19 +53,13 @@ async fn client_dh_encrypt( .encrypt_in_place(&nonce, associated_data, &mut ciphertext) .unwrap(); - UnsealEncryptedKey { + Request::UnsealEncryptedKey { nonce: nonce.to_vec(), ciphertext, associated_data: associated_data.to_vec(), } } -fn unseal_key_request(req: UnsealEncryptedKey) -> UserAgentRequest { - UserAgentRequest { - payload: Some(UserAgentRequestPayload::UnsealEncryptedKey(req)), - } -} - #[tokio::test] #[test_log::test] pub async fn test_unseal_success() { @@ -88,14 +69,11 @@ pub async fn test_unseal_success() { let encrypted_key = client_dh_encrypt(&mut user_agent, seal_key).await; let response = user_agent - .process_transport_inbound(unseal_key_request(encrypted_key)) + .process_transport_inbound(encrypted_key) .await .unwrap(); - assert_eq!( - response.payload.unwrap(), - UserAgentResponsePayload::UnsealResult(UnsealResult::Success.into()), - ); + assert!(matches!(response, Response::UnsealResult(Ok(())))); } #[tokio::test] @@ -106,14 +84,14 @@ pub async fn test_unseal_wrong_seal_key() { let encrypted_key = client_dh_encrypt(&mut user_agent, b"wrong-key").await; let response = user_agent - .process_transport_inbound(unseal_key_request(encrypted_key)) + .process_transport_inbound(encrypted_key) .await .unwrap(); - assert_eq!( - response.payload.unwrap(), - UserAgentResponsePayload::UnsealResult(UnsealResult::InvalidKey.into()), - ); + assert!(matches!( + response, + Response::UnsealResult(Err(UnsealError::InvalidKey)) + )); } #[tokio::test] @@ -125,27 +103,25 @@ pub async fn test_unseal_corrupted_ciphertext() { let client_public = PublicKey::from(&client_secret); user_agent - .process_transport_inbound(UserAgentRequest { - payload: Some(UserAgentRequestPayload::UnsealStart(UnsealStart { - client_pubkey: client_public.as_bytes().to_vec(), - })), + .process_transport_inbound(Request::UnsealStart { + client_pubkey: client_public, }) .await .unwrap(); let response = user_agent - .process_transport_inbound(unseal_key_request(UnsealEncryptedKey { + .process_transport_inbound(Request::UnsealEncryptedKey { nonce: vec![0u8; 24], ciphertext: vec![0u8; 32], associated_data: vec![], - })) + }) .await .unwrap(); - assert_eq!( - response.payload.unwrap(), - UserAgentResponsePayload::UnsealResult(UnsealResult::InvalidKey.into()), - ); + assert!(matches!( + response, + Response::UnsealResult(Err(UnsealError::InvalidKey)) + )); } #[tokio::test] @@ -158,27 +134,24 @@ pub async fn test_unseal_retry_after_invalid_key() { let encrypted_key = client_dh_encrypt(&mut user_agent, b"wrong-key").await; let response = user_agent - .process_transport_inbound(unseal_key_request(encrypted_key)) + .process_transport_inbound(encrypted_key) .await .unwrap(); - assert_eq!( - response.payload.unwrap(), - UserAgentResponsePayload::UnsealResult(UnsealResult::InvalidKey.into()), - ); + assert!(matches!( + response, + Response::UnsealResult(Err(UnsealError::InvalidKey)) + )); } { let encrypted_key = client_dh_encrypt(&mut user_agent, seal_key).await; let response = user_agent - .process_transport_inbound(unseal_key_request(encrypted_key)) + .process_transport_inbound(encrypted_key) .await .unwrap(); - assert_eq!( - response.payload.unwrap(), - UserAgentResponsePayload::UnsealResult(UnsealResult::Success.into()), - ); + assert!(matches!(response, Response::UnsealResult(Ok(())))); } } diff --git a/server/crates/arbiter-useragent/Cargo.toml b/server/crates/arbiter-useragent/Cargo.toml deleted file mode 100644 index 4b7337a..0000000 --- a/server/crates/arbiter-useragent/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "arbiter-useragent" -version = "0.1.0" -edition = "2024" -license = "Apache-2.0" - -[lints] -workspace = true - -[dependencies] -arbiter-proto.path = "../arbiter-proto" -kameo.workspace = true -tokio = {workspace = true, features = ["net"]} -tonic.workspace = true -tonic.features = ["tls-aws-lc"] -tracing.workspace = true -ed25519-dalek.workspace = true -smlang.workspace = true -x25519-dalek.workspace = true -k256.workspace = true -rsa.workspace = true -sha2.workspace = true -spki.workspace = true -rand.workspace = true -thiserror.workspace = true -tokio-stream.workspace = true -http = "1.4.0" -rustls-webpki = { version = "0.103.9", features = ["aws-lc-rs"] } -async-trait.workspace = true diff --git a/server/crates/arbiter-useragent/src/grpc.rs b/server/crates/arbiter-useragent/src/grpc.rs deleted file mode 100644 index 1c15995..0000000 --- a/server/crates/arbiter-useragent/src/grpc.rs +++ /dev/null @@ -1,70 +0,0 @@ -use arbiter_proto::{ - proto::{ - arbiter_service_client::ArbiterServiceClient, - user_agent::{UserAgentRequest, UserAgentResponse}, - }, - transport::{IdentityRecvConverter, IdentitySendConverter, grpc}, - url::ArbiterUrl, -}; -use kameo::actor::{ActorRef, Spawn}; - -use tokio::sync::mpsc; -use tokio_stream::wrappers::ReceiverStream; - -use tonic::transport::ClientTlsConfig; - -use super::{SigningKeyEnum, UserAgentActor}; - -#[derive(Debug, thiserror::Error)] -pub enum ConnectError { - #[error("Could establish connection")] - Connection(#[from] tonic::transport::Error), - - #[error("Invalid server URI")] - InvalidUri(#[from] http::uri::InvalidUri), - - #[error("Invalid CA certificate")] - InvalidCaCert(#[from] webpki::Error), - - #[error("gRPC error")] - Grpc(#[from] tonic::Status), -} - -pub type UserAgentGrpc = ActorRef< - UserAgentActor< - grpc::GrpcAdapter< - IdentityRecvConverter, - IdentitySendConverter, - >, - >, ->; -pub async fn connect_grpc( - url: ArbiterUrl, - key: SigningKeyEnum, -) -> Result { - let bootstrap_token = url.bootstrap_token.clone(); - let anchor = webpki::anchor_from_trusted_cert(&url.ca_cert)?.to_owned(); - let tls = ClientTlsConfig::new().trust_anchor(anchor); - - // TODO: if `host` is localhost, we need to verify server's process authenticity - let channel = tonic::transport::Channel::from_shared(format!("{}:{}", url.host, url.port))? - .tls_config(tls)? - .connect() - .await?; - - let mut client = ArbiterServiceClient::new(channel); - let (tx, rx) = mpsc::channel(16); - let bistream = client.user_agent(ReceiverStream::new(rx)).await?; - let bistream = bistream.into_inner(); - - let adapter = grpc::GrpcAdapter::new( - tx, - bistream, - IdentityRecvConverter::new(), - IdentitySendConverter::new(), - ); - - let actor = UserAgentActor::spawn(UserAgentActor::new(key, bootstrap_token, adapter)); - - Ok(actor) -} diff --git a/server/crates/arbiter-useragent/src/lib.rs b/server/crates/arbiter-useragent/src/lib.rs deleted file mode 100644 index a9e86bb..0000000 --- a/server/crates/arbiter-useragent/src/lib.rs +++ /dev/null @@ -1,419 +0,0 @@ -use arbiter_proto::{ - format_challenge, - proto::user_agent::{ - AuthChallengeRequest, AuthChallengeSolution, AuthOk, KeyType as ProtoKeyType, - UserAgentRequest, UserAgentResponse, - user_agent_request::Payload as UserAgentRequestPayload, - user_agent_response::Payload as UserAgentResponsePayload, - }, - transport::Bi, -}; -use kameo::{Actor, actor::ActorRef}; -use smlang::statemachine; -use tokio::select; -use tracing::{error, info}; - -/// Signing key variants supported by the user-agent auth protocol. -pub enum SigningKeyEnum { - Ed25519(ed25519_dalek::SigningKey), - /// secp256k1 ECDSA; public key is sent as SEC1 compressed 33 bytes; signature is raw 64-byte (r||s). - EcdsaSecp256k1(k256::ecdsa::SigningKey), - /// RSA for Windows Hello (KeyCredentialManager); public key is DER SPKI; signature is PSS+SHA-256. - Rsa(rsa::RsaPrivateKey), -} - -impl SigningKeyEnum { - /// Returns the canonical public key bytes to include in `AuthChallengeRequest.pubkey`. - pub fn pubkey_bytes(&self) -> Vec { - match self { - SigningKeyEnum::Ed25519(k) => k.verifying_key().to_bytes().to_vec(), - // 33-byte SEC1 compressed point — compact and natively supported by secp256k1 tooling - SigningKeyEnum::EcdsaSecp256k1(k) => { - k.verifying_key().to_encoded_point(true).as_bytes().to_vec() - } - SigningKeyEnum::Rsa(k) => { - use rsa::pkcs8::EncodePublicKey as _; - k.to_public_key() - .to_public_key_der() - .expect("rsa SPKI encoding is infallible") - .to_vec() - } - } - } - - /// Returns the proto `KeyType` discriminant to send in `AuthChallengeRequest.key_type`. - pub fn proto_key_type(&self) -> ProtoKeyType { - match self { - SigningKeyEnum::Ed25519(_) => ProtoKeyType::Ed25519, - SigningKeyEnum::EcdsaSecp256k1(_) => ProtoKeyType::EcdsaSecp256k1, - SigningKeyEnum::Rsa(_) => ProtoKeyType::Rsa, - } - } - - /// Signs `msg` and returns raw signature bytes matching the server-side verification. - pub fn sign(&self, msg: &[u8]) -> Vec { - match self { - SigningKeyEnum::Ed25519(k) => { - use ed25519_dalek::Signer as _; - k.sign(msg).to_bytes().to_vec() - } - SigningKeyEnum::EcdsaSecp256k1(k) => { - use k256::ecdsa::signature::Signer as _; - let sig: k256::ecdsa::Signature = k.sign(msg); - sig.to_bytes().to_vec() - } - SigningKeyEnum::Rsa(k) => { - use rsa::signature::RandomizedSigner as _; - let signing_key = rsa::pss::BlindedSigningKey::::new(k.clone()); - // Use rand_core OsRng from the rsa crate's re-exported rand_core (0.6.x), - // which is the version rsa's signature API expects. - let sig = signing_key.sign_with_rng(&mut rsa::rand_core::OsRng, msg); - use rsa::signature::SignatureEncoding as _; - sig.to_vec() - } - } - } -} - -statemachine! { - name: UserAgent, - custom_error: false, - transitions: { - *Init + SentAuthChallengeRequest = WaitingForServerAuth, - WaitingForServerAuth + ReceivedAuthChallenge = WaitingForAuthOk, - WaitingForServerAuth + ReceivedAuthOk = Authenticated, - WaitingForAuthOk + ReceivedAuthOk = Authenticated, - } -} - -pub struct DummyContext; -impl UserAgentStateMachineContext for DummyContext {} - -#[derive(Debug, thiserror::Error)] -pub enum InboundError { - #[error("Invalid user agent response")] - InvalidResponse, - #[error("Expected response payload")] - MissingResponsePayload, - #[error("Unexpected response payload")] - UnexpectedResponsePayload, - #[error("Invalid state for auth challenge")] - InvalidStateForAuthChallenge, - #[error("Invalid state for auth ok")] - InvalidStateForAuthOk, - #[error("State machine error")] - StateTransitionFailed, - #[error("Transport send failed")] - TransportSendFailed, -} - -pub struct UserAgentActor -where - Transport: Bi, -{ - key: SigningKeyEnum, - bootstrap_token: Option, - state: UserAgentStateMachine, - transport: Transport, -} - -impl UserAgentActor -where - Transport: Bi, -{ - pub fn new(key: SigningKeyEnum, bootstrap_token: Option, transport: Transport) -> Self { - Self { - key, - bootstrap_token, - state: UserAgentStateMachine::new(DummyContext), - transport, - } - } - - fn transition(&mut self, event: UserAgentEvents) -> Result<(), InboundError> { - self.state.process_event(event).map_err(|e| { - error!(?e, "useragent state transition failed"); - InboundError::StateTransitionFailed - })?; - Ok(()) - } - - async fn send_auth_challenge_request(&mut self) -> Result<(), InboundError> { - let req = AuthChallengeRequest { - pubkey: self.key.pubkey_bytes(), - bootstrap_token: self.bootstrap_token.take(), - key_type: self.key.proto_key_type().into(), - }; - - self.transition(UserAgentEvents::SentAuthChallengeRequest)?; - - self.transport - .send(UserAgentRequest { - payload: Some(UserAgentRequestPayload::AuthChallengeRequest(req)), - }) - .await - .map_err(|_| InboundError::TransportSendFailed)?; - - info!(actor = "useragent", "auth.request.sent"); - Ok(()) - } - - async fn handle_auth_challenge( - &mut self, - challenge: arbiter_proto::proto::user_agent::AuthChallenge, - ) -> Result<(), InboundError> { - self.transition(UserAgentEvents::ReceivedAuthChallenge)?; - - let formatted = format_challenge(challenge.nonce, &challenge.pubkey); - let signature_bytes = self.key.sign(&formatted); - let solution = AuthChallengeSolution { - signature: signature_bytes, - }; - - self.transport - .send(UserAgentRequest { - payload: Some(UserAgentRequestPayload::AuthChallengeSolution(solution)), - }) - .await - .map_err(|_| InboundError::TransportSendFailed)?; - - info!(actor = "useragent", "auth.solution.sent"); - Ok(()) - } - - fn handle_auth_ok(&mut self, _ok: AuthOk) -> Result<(), InboundError> { - self.transition(UserAgentEvents::ReceivedAuthOk)?; - info!(actor = "useragent", "auth.ok"); - Ok(()) - } - - pub async fn process_inbound_transport( - &mut self, - inbound: UserAgentResponse, - ) -> Result<(), InboundError> { - let payload = inbound - .payload - .ok_or(InboundError::MissingResponsePayload)?; - - match payload { - UserAgentResponsePayload::AuthChallenge(challenge) => { - self.handle_auth_challenge(challenge).await - } - UserAgentResponsePayload::AuthOk(ok) => self.handle_auth_ok(ok), - _ => Err(InboundError::UnexpectedResponsePayload), - } - } -} - -impl Actor for UserAgentActor -where - Transport: Bi, -{ - type Args = Self; - - type Error = (); - - async fn on_start( - mut args: Self::Args, - _actor_ref: ActorRef, - ) -> Result { - if let Err(err) = args.send_auth_challenge_request().await { - error!(?err, actor = "useragent", "auth.start.failed"); - return Err(()); - } - Ok(args) - } - - async fn next( - &mut self, - _actor_ref: kameo::prelude::WeakActorRef, - mailbox_rx: &mut kameo::prelude::MailboxReceiver, - ) -> Option> { - loop { - select! { - signal = mailbox_rx.recv() => { - return signal; - } - inbound = self.transport.recv() => { - match inbound { - Some(inbound) => { - if let Err(err) = self.process_inbound_transport(inbound).await { - error!(?err, actor = "useragent", "transport.inbound.failed"); - return Some(kameo::mailbox::Signal::Stop); - } - } - None => { - info!(actor = "useragent", "transport.closed"); - return Some(kameo::mailbox::Signal::Stop); - } - } - } - } - } - } -} - -mod grpc; -pub use grpc::{ConnectError, UserAgentGrpc, connect_grpc}; - -use arbiter_proto::proto::user_agent::{ - BootstrapEncryptedKey, UnsealEncryptedKey, UnsealStart, - user_agent_request::Payload as RequestPayload, user_agent_response::Payload as ResponsePayload, -}; - -/// Send an `UnsealStart` request and await the server's `UnsealStartResponse`. -pub struct SendUnsealStart { - pub client_pubkey: Vec, -} - -/// Send an `UnsealEncryptedKey` request and await the server's `UnsealResult`. -pub struct SendUnsealEncryptedKey { - pub nonce: Vec, - pub ciphertext: Vec, - pub associated_data: Vec, -} - -/// Send a `BootstrapEncryptedKey` request and await the server's `BootstrapResult`. -pub struct SendBootstrapEncryptedKey { - pub nonce: Vec, - pub ciphertext: Vec, - pub associated_data: Vec, -} - -/// Query the server for the current `VaultState`. -pub struct QueryVaultState; - -/// Errors that can occur during post-authentication session operations. -#[derive(Debug, thiserror::Error)] -pub enum SessionError { - #[error("Transport send failed")] - TransportSendFailed, - #[error("Transport closed unexpectedly")] - TransportClosed, - #[error("Server sent an unexpected response payload")] - UnexpectedResponse, -} - -impl kameo::message::Message for UserAgentActor -where - Transport: Bi, -{ - type Reply = Result; - - async fn handle( - &mut self, - msg: SendUnsealStart, - _ctx: &mut kameo::message::Context, - ) -> Self::Reply { - self.transport - .send(UserAgentRequest { - payload: Some(RequestPayload::UnsealStart(UnsealStart { - client_pubkey: msg.client_pubkey, - })), - }) - .await - .map_err(|_| SessionError::TransportSendFailed)?; - - match self.transport.recv().await { - Some(resp) => match resp.payload { - Some(ResponsePayload::UnsealStartResponse(r)) => Ok(r), - _ => Err(SessionError::UnexpectedResponse), - }, - None => Err(SessionError::TransportClosed), - } - } -} - -impl kameo::message::Message for UserAgentActor -where - Transport: Bi, -{ - type Reply = Result; - - async fn handle( - &mut self, - msg: SendUnsealEncryptedKey, - _ctx: &mut kameo::message::Context, - ) -> Self::Reply { - self.transport - .send(UserAgentRequest { - payload: Some(RequestPayload::UnsealEncryptedKey(UnsealEncryptedKey { - nonce: msg.nonce, - ciphertext: msg.ciphertext, - associated_data: msg.associated_data, - })), - }) - .await - .map_err(|_| SessionError::TransportSendFailed)?; - - match self.transport.recv().await { - Some(resp) => match resp.payload { - Some(ResponsePayload::UnsealResult(r)) => Ok(r), - _ => Err(SessionError::UnexpectedResponse), - }, - None => Err(SessionError::TransportClosed), - } - } -} - -impl kameo::message::Message for UserAgentActor -where - Transport: Bi, -{ - type Reply = Result; - - async fn handle( - &mut self, - msg: SendBootstrapEncryptedKey, - _ctx: &mut kameo::message::Context, - ) -> Self::Reply { - self.transport - .send(UserAgentRequest { - payload: Some(RequestPayload::BootstrapEncryptedKey( - BootstrapEncryptedKey { - nonce: msg.nonce, - ciphertext: msg.ciphertext, - associated_data: msg.associated_data, - }, - )), - }) - .await - .map_err(|_| SessionError::TransportSendFailed)?; - - match self.transport.recv().await { - Some(resp) => match resp.payload { - Some(ResponsePayload::BootstrapResult(r)) => Ok(r), - _ => Err(SessionError::UnexpectedResponse), - }, - None => Err(SessionError::TransportClosed), - } - } -} - -impl kameo::message::Message for UserAgentActor -where - Transport: Bi, -{ - type Reply = Result; - - async fn handle( - &mut self, - _msg: QueryVaultState, - _ctx: &mut kameo::message::Context, - ) -> Self::Reply { - self.transport - .send(UserAgentRequest { - payload: Some(RequestPayload::QueryVaultState(())), - }) - .await - .map_err(|_| SessionError::TransportSendFailed)?; - - match self.transport.recv().await { - Some(resp) => match resp.payload { - Some(ResponsePayload::VaultState(v)) => Ok(v), - _ => Err(SessionError::UnexpectedResponse), - }, - None => Err(SessionError::TransportClosed), - } - } -} diff --git a/server/crates/arbiter-useragent/tests/auth.rs b/server/crates/arbiter-useragent/tests/auth.rs deleted file mode 100644 index 3b6b35a..0000000 --- a/server/crates/arbiter-useragent/tests/auth.rs +++ /dev/null @@ -1,146 +0,0 @@ -use arbiter_proto::{ - format_challenge, - proto::user_agent::{ - AuthChallenge, AuthOk, UserAgentRequest, UserAgentResponse, - user_agent_request::Payload as UserAgentRequestPayload, - user_agent_response::Payload as UserAgentResponsePayload, - }, - transport::Bi, -}; -use arbiter_useragent::{SigningKeyEnum, UserAgentActor}; -use async_trait::async_trait; -use ed25519_dalek::SigningKey; -use kameo::actor::Spawn; -use tokio::sync::mpsc; -use tokio::time::{Duration, timeout}; - -struct TestTransport { - inbound_rx: mpsc::Receiver, - outbound_tx: mpsc::Sender, -} - -#[async_trait] -impl Bi for TestTransport { - async fn send( - &mut self, - item: UserAgentRequest, - ) -> Result<(), arbiter_proto::transport::Error> { - self.outbound_tx - .send(item) - .await - .map_err(|_| arbiter_proto::transport::Error::ChannelClosed) - } - - async fn recv(&mut self) -> Option { - self.inbound_rx.recv().await - } -} - -fn make_transport() -> ( - TestTransport, - mpsc::Sender, - mpsc::Receiver, -) { - let (inbound_tx, inbound_rx) = mpsc::channel(8); - let (outbound_tx, outbound_rx) = mpsc::channel(8); - ( - TestTransport { - inbound_rx, - outbound_tx, - }, - inbound_tx, - outbound_rx, - ) -} - -fn test_key() -> SigningKeyEnum { - SigningKeyEnum::Ed25519(SigningKey::from_bytes(&[7u8; 32])) -} - -#[tokio::test] -async fn sends_auth_request_on_start_with_bootstrap_token() { - let key = test_key(); - let pubkey = key.pubkey_bytes(); - let bootstrap_token = Some("bootstrap-123".to_string()); - let (transport, inbound_tx, mut outbound_rx) = make_transport(); - - let actor = UserAgentActor::spawn(UserAgentActor::new(key, bootstrap_token.clone(), transport)); - - let outbound = timeout(Duration::from_secs(1), outbound_rx.recv()) - .await - .expect("timed out waiting for auth request") - .expect("channel closed before auth request"); - - let UserAgentRequest { - payload: Some(UserAgentRequestPayload::AuthChallengeRequest(req)), - } = outbound - else { - panic!("expected auth challenge request"); - }; - - assert_eq!(req.pubkey, pubkey); - assert_eq!(req.bootstrap_token, bootstrap_token); - - drop(inbound_tx); - drop(actor); -} - -#[tokio::test] -async fn challenge_flow_sends_solution_from_transport_inbound() { - let key = test_key(); - let pubkey_bytes = key.pubkey_bytes(); - let (transport, inbound_tx, mut outbound_rx) = make_transport(); - - let actor = UserAgentActor::spawn(UserAgentActor::new(key, None, transport)); - - let _initial_auth_request = timeout(Duration::from_secs(1), outbound_rx.recv()) - .await - .expect("timed out waiting for initial auth request") - .expect("missing initial auth request"); - - let challenge = AuthChallenge { - pubkey: pubkey_bytes.clone(), - nonce: 42, - }; - inbound_tx - .send(UserAgentResponse { - payload: Some(UserAgentResponsePayload::AuthChallenge(challenge.clone())), - }) - .await - .unwrap(); - - let outbound = timeout(Duration::from_secs(1), outbound_rx.recv()) - .await - .expect("timed out waiting for challenge solution") - .expect("missing challenge solution"); - - let UserAgentRequest { - payload: Some(UserAgentRequestPayload::AuthChallengeSolution(solution)), - } = outbound - else { - panic!("expected auth challenge solution"); - }; - - // Verify the signature using the Ed25519 verifying key - let formatted = format_challenge(challenge.nonce, &challenge.pubkey); - let raw_key = SigningKey::from_bytes(&[7u8; 32]); - let sig: ed25519_dalek::Signature = solution - .signature - .as_slice() - .try_into() - .expect("signature bytes length"); - raw_key - .verifying_key() - .verify_strict(&formatted, &sig) - .expect("solution signature should verify"); - - inbound_tx - .send(UserAgentResponse { - payload: Some(UserAgentResponsePayload::AuthOk(AuthOk {})), - }) - .await - .unwrap(); - - drop(inbound_tx); - drop(actor); -} From fac312d86077df3331db67a4536ab7622033419d Mon Sep 17 00:00:00 2001 From: hdbg Date: Mon, 16 Mar 2026 03:13:05 +0100 Subject: [PATCH 08/13] refactor(server): move connection-related handlers into separate module --- .../src/actors/user_agent/session.rs | 304 +----------------- .../actors/user_agent/session/connection.rs | 288 +++++++++++++++++ 2 files changed, 304 insertions(+), 288 deletions(-) create mode 100644 server/crates/arbiter-server/src/actors/user_agent/session/connection.rs diff --git a/server/crates/arbiter-server/src/actors/user_agent/session.rs b/server/crates/arbiter-server/src/actors/user_agent/session.rs index 0a9f893..9fe8d91 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/session.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/session.rs @@ -1,25 +1,20 @@ -use std::{ops::DerefMut, sync::Mutex}; -use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit}; +use chacha20poly1305::aead::KeyInit; use ed25519_dalek::VerifyingKey; -use kameo::{Actor, error::SendError, messages, prelude::Context}; -use memsafe::MemSafe; +use kameo::{Actor, messages, prelude::Context}; use tokio::{select, sync::watch}; use tracing::{error, info}; -use x25519_dalek::{EphemeralSecret, PublicKey}; use crate::actors::{ - evm::{Generate, ListWallets}, - keyholder::{self, Bootstrap, TryUnseal}, router::RegisterUserAgent, user_agent::{ - BootstrapError, Request, Response, TransportResponseError, UnsealError, - UserAgentConnection, VaultState, + Request, Response, TransportResponseError, + UserAgentConnection, }, }; mod state; -use state::{DummyContext, UnsealContext, UserAgentEvents, UserAgentStateMachine, UserAgentStates}; +use state::{DummyContext, UserAgentEvents, UserAgentStateMachine}; // Error for consumption by other actors #[derive(Debug, thiserror::Error, PartialEq)] @@ -36,6 +31,8 @@ pub struct UserAgentSession { state: UserAgentStateMachine, } +mod connection; + impl UserAgentSession { pub(crate) fn new(props: UserAgentConnection) -> Self { Self { @@ -44,15 +41,7 @@ impl UserAgentSession { } } - fn transition(&mut self, event: UserAgentEvents) -> Result<(), TransportResponseError> { - self.state.process_event(event).map_err(|e| { - error!(?e, "State transition failed"); - TransportResponseError::StateTransitionFailed - })?; - Ok(()) - } - - async fn send_msg( + pub(super) async fn send_msg( &mut self, msg: Response, _ctx: &mut Context, @@ -96,6 +85,14 @@ impl UserAgentSession { Error::UnexpectedMessage }) } + + fn transition(&mut self, event: UserAgentEvents) -> Result<(), TransportResponseError> { + self.state.process_event(event).map_err(|e| { + error!(?e, "State transition failed"); + TransportResponseError::StateTransitionFailed + })?; + Ok(()) + } } #[messages] @@ -142,275 +139,6 @@ impl UserAgentSession { } } -impl UserAgentSession { - pub async fn process_transport_inbound(&mut self, req: Request) -> Output { - match req { - Request::UnsealStart { client_pubkey } => { - self.handle_unseal_request(client_pubkey).await - } - Request::UnsealEncryptedKey { - nonce, - ciphertext, - associated_data, - } => { - self.handle_unseal_encrypted_key(nonce, ciphertext, associated_data) - .await - } - Request::BootstrapEncryptedKey { - nonce, - ciphertext, - associated_data, - } => { - self.handle_bootstrap_encrypted_key(nonce, ciphertext, associated_data) - .await - } - Request::QueryVaultState => self.handle_query_vault_state().await, - Request::EvmWalletCreate => self.handle_evm_wallet_create().await, - Request::EvmWalletList => self.handle_evm_wallet_list().await, - Request::AuthChallengeRequest { .. } - | Request::AuthChallengeSolution { .. } - | Request::ClientConnectionResponse { .. } => { - Err(TransportResponseError::UnexpectedRequestPayload) - } - } - } -} - -type Output = Result; - -impl UserAgentSession { - fn take_unseal_secret( - &mut self, - ) -> Result<(EphemeralSecret, PublicKey), TransportResponseError> { - let UserAgentStates::WaitingForUnsealKey(unseal_context) = self.state.state() else { - error!("Received encrypted key in invalid state"); - return Err(TransportResponseError::InvalidStateForUnsealEncryptedKey); - }; - - let ephemeral_secret = { - let mut secret_lock = unseal_context.secret.lock().unwrap(); - let secret = secret_lock.take(); - match secret { - Some(secret) => secret, - None => { - drop(secret_lock); - error!("Ephemeral secret already taken"); - return Err(TransportResponseError::StateTransitionFailed); - } - } - }; - - Ok((ephemeral_secret, unseal_context.client_public_key)) - } - - fn decrypt_client_key_material( - ephemeral_secret: EphemeralSecret, - client_public_key: PublicKey, - nonce: &[u8], - ciphertext: &[u8], - associated_data: &[u8], - ) -> Result>, ()> { - let nonce = XNonce::from_slice(nonce); - - let shared_secret = ephemeral_secret.diffie_hellman(&client_public_key); - let cipher = XChaCha20Poly1305::new(shared_secret.as_bytes().into()); - - let mut key_buffer = MemSafe::new(ciphertext.to_vec()).unwrap(); - - let decryption_result = { - let mut write_handle = key_buffer.write().unwrap(); - let write_handle = write_handle.deref_mut(); - cipher.decrypt_in_place(nonce, associated_data, write_handle) - }; - - match decryption_result { - Ok(_) => Ok(key_buffer), - Err(err) => { - error!(?err, "Failed to decrypt encrypted key material"); - Err(()) - } - } - } - - async fn handle_unseal_request(&mut self, client_pubkey: x25519_dalek::PublicKey) -> Output { - let secret = EphemeralSecret::random(); - let public_key = PublicKey::from(&secret); - - self.transition(UserAgentEvents::UnsealRequest(UnsealContext { - secret: Mutex::new(Some(secret)), - client_public_key: client_pubkey - }))?; - - Ok(Response::UnsealStartResponse { - server_pubkey: public_key, - }) - } - - async fn handle_unseal_encrypted_key( - &mut self, - nonce: Vec, - ciphertext: Vec, - associated_data: Vec, - ) -> Output { - let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() { - Ok(values) => values, - Err(TransportResponseError::StateTransitionFailed) => { - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - return Ok(Response::UnsealResult(Err(UnsealError::InvalidKey))); - } - Err(err) => return Err(err), - }; - - let seal_key_buffer = match Self::decrypt_client_key_material( - ephemeral_secret, - client_public_key, - &nonce, - &ciphertext, - &associated_data, - ) { - Ok(buffer) => buffer, - Err(()) => { - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - return Ok(Response::UnsealResult(Err(UnsealError::InvalidKey))); - } - }; - - match self - .props - .actors - .key_holder - .ask(TryUnseal { - seal_key_raw: seal_key_buffer, - }) - .await - { - Ok(_) => { - info!("Successfully unsealed key with client-provided key"); - self.transition(UserAgentEvents::ReceivedValidKey)?; - Ok(Response::UnsealResult(Ok(()))) - } - Err(SendError::HandlerError(keyholder::Error::InvalidKey)) => { - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Ok(Response::UnsealResult(Err(UnsealError::InvalidKey))) - } - Err(SendError::HandlerError(err)) => { - error!(?err, "Keyholder failed to unseal key"); - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Ok(Response::UnsealResult(Err(UnsealError::InvalidKey))) - } - Err(err) => { - error!(?err, "Failed to send unseal request to keyholder"); - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Err(TransportResponseError::KeyHolderActorUnreachable) - } - } - } - - async fn handle_bootstrap_encrypted_key( - &mut self, - nonce: Vec, - ciphertext: Vec, - associated_data: Vec, - ) -> Output { - let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() { - Ok(values) => values, - Err(TransportResponseError::StateTransitionFailed) => { - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - return Ok(Response::BootstrapResult(Err(BootstrapError::InvalidKey))); - } - Err(err) => return Err(err), - }; - - let seal_key_buffer = match Self::decrypt_client_key_material( - ephemeral_secret, - client_public_key, - &nonce, - &ciphertext, - &associated_data, - ) { - Ok(buffer) => buffer, - Err(()) => { - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - return Ok(Response::BootstrapResult(Err(BootstrapError::InvalidKey))); - } - }; - - match self - .props - .actors - .key_holder - .ask(Bootstrap { - seal_key_raw: seal_key_buffer, - }) - .await - { - Ok(_) => { - info!("Successfully bootstrapped vault with client-provided key"); - self.transition(UserAgentEvents::ReceivedValidKey)?; - Ok(Response::BootstrapResult(Ok(()))) - } - Err(SendError::HandlerError(keyholder::Error::AlreadyBootstrapped)) => { - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Ok(Response::BootstrapResult(Err( - BootstrapError::AlreadyBootstrapped, - ))) - } - Err(SendError::HandlerError(err)) => { - error!(?err, "Keyholder failed to bootstrap vault"); - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Ok(Response::BootstrapResult(Err(BootstrapError::InvalidKey))) - } - Err(err) => { - error!(?err, "Failed to send bootstrap request to keyholder"); - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Err(TransportResponseError::KeyHolderActorUnreachable) - } - } - } -} - -impl UserAgentSession { - async fn handle_query_vault_state(&mut self) -> Output { - use crate::actors::keyholder::{GetState, StateDiscriminants}; - - let vault_state = match self.props.actors.key_holder.ask(GetState {}).await { - Ok(StateDiscriminants::Unbootstrapped) => VaultState::Unbootstrapped, - Ok(StateDiscriminants::Sealed) => VaultState::Sealed, - Ok(StateDiscriminants::Unsealed) => VaultState::Unsealed, - Err(err) => { - error!(?err, actor = "useragent", "keyholder.query.failed"); - return Err(TransportResponseError::KeyHolderActorUnreachable); - } - }; - - Ok(Response::VaultState(vault_state)) - } -} - -impl UserAgentSession { - async fn handle_evm_wallet_create(&mut self) -> Output { - let result = match self.props.actors.evm.ask(Generate {}).await { - Ok(_address) => return Ok(Response::EvmWalletCreate(Ok(()))), - Err(SendError::HandlerError(err)) => Err(err), - Err(err) => { - error!(?err, "EVM actor unreachable during wallet create"); - return Err(TransportResponseError::KeyHolderActorUnreachable); - } - }; - Ok(Response::EvmWalletCreate(result)) - } - - async fn handle_evm_wallet_list(&mut self) -> Output { - match self.props.actors.evm.ask(ListWallets {}).await { - Ok(wallets) => Ok(Response::EvmWalletList(wallets)), - Err(err) => { - error!(?err, "EVM wallet list failed"); - Err(TransportResponseError::KeyHolderActorUnreachable) - } - } - } -} - impl Actor for UserAgentSession { type Args = Self; diff --git a/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs b/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs new file mode 100644 index 0000000..81892dc --- /dev/null +++ b/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs @@ -0,0 +1,288 @@ +use std::{ops::DerefMut, sync::Mutex}; + +use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit}; +use kameo::error::SendError; +use memsafe::MemSafe; +use tracing::{error, info}; +use x25519_dalek::{EphemeralSecret, PublicKey}; + +use crate::actors::{ + evm::{Generate, ListWallets}, + keyholder::{self, Bootstrap, TryUnseal}, + user_agent::{ + BootstrapError, Request, Response, TransportResponseError, UnsealError, VaultState, + session::{ + UserAgentSession, + state::{UnsealContext, UserAgentEvents, UserAgentStates}, + }, + }, +}; + +impl UserAgentSession { + pub async fn process_transport_inbound(&mut self, req: Request) -> Output { + match req { + Request::UnsealStart { client_pubkey } => { + self.handle_unseal_request(client_pubkey).await + } + Request::UnsealEncryptedKey { + nonce, + ciphertext, + associated_data, + } => { + self.handle_unseal_encrypted_key(nonce, ciphertext, associated_data) + .await + } + Request::BootstrapEncryptedKey { + nonce, + ciphertext, + associated_data, + } => { + self.handle_bootstrap_encrypted_key(nonce, ciphertext, associated_data) + .await + } + Request::QueryVaultState => self.handle_query_vault_state().await, + Request::EvmWalletCreate => self.handle_evm_wallet_create().await, + Request::EvmWalletList => self.handle_evm_wallet_list().await, + Request::AuthChallengeRequest { .. } + | Request::AuthChallengeSolution { .. } + | Request::ClientConnectionResponse { .. } => { + Err(TransportResponseError::UnexpectedRequestPayload) + } + } + } +} + +type Output = Result; + +impl UserAgentSession { + fn take_unseal_secret( + &mut self, + ) -> Result<(EphemeralSecret, PublicKey), TransportResponseError> { + let UserAgentStates::WaitingForUnsealKey(unseal_context) = self.state.state() else { + error!("Received encrypted key in invalid state"); + return Err(TransportResponseError::InvalidStateForUnsealEncryptedKey); + }; + + let ephemeral_secret = { + let mut secret_lock = unseal_context.secret.lock().unwrap(); + let secret = secret_lock.take(); + match secret { + Some(secret) => secret, + None => { + drop(secret_lock); + error!("Ephemeral secret already taken"); + return Err(TransportResponseError::StateTransitionFailed); + } + } + }; + + Ok((ephemeral_secret, unseal_context.client_public_key)) + } + + fn decrypt_client_key_material( + ephemeral_secret: EphemeralSecret, + client_public_key: PublicKey, + nonce: &[u8], + ciphertext: &[u8], + associated_data: &[u8], + ) -> Result>, ()> { + let nonce = XNonce::from_slice(nonce); + + let shared_secret = ephemeral_secret.diffie_hellman(&client_public_key); + let cipher = XChaCha20Poly1305::new(shared_secret.as_bytes().into()); + + let mut key_buffer = MemSafe::new(ciphertext.to_vec()).unwrap(); + + let decryption_result = { + let mut write_handle = key_buffer.write().unwrap(); + let write_handle = write_handle.deref_mut(); + cipher.decrypt_in_place(nonce, associated_data, write_handle) + }; + + match decryption_result { + Ok(_) => Ok(key_buffer), + Err(err) => { + error!(?err, "Failed to decrypt encrypted key material"); + Err(()) + } + } + } + + async fn handle_unseal_request(&mut self, client_pubkey: x25519_dalek::PublicKey) -> Output { + let secret = EphemeralSecret::random(); + let public_key = PublicKey::from(&secret); + + self.transition(UserAgentEvents::UnsealRequest(UnsealContext { + secret: Mutex::new(Some(secret)), + client_public_key: client_pubkey, + }))?; + + Ok(Response::UnsealStartResponse { + server_pubkey: public_key, + }) + } + + async fn handle_unseal_encrypted_key( + &mut self, + nonce: Vec, + ciphertext: Vec, + associated_data: Vec, + ) -> Output { + let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() { + Ok(values) => values, + Err(TransportResponseError::StateTransitionFailed) => { + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + return Ok(Response::UnsealResult(Err(UnsealError::InvalidKey))); + } + Err(err) => return Err(err), + }; + + let seal_key_buffer = match Self::decrypt_client_key_material( + ephemeral_secret, + client_public_key, + &nonce, + &ciphertext, + &associated_data, + ) { + Ok(buffer) => buffer, + Err(()) => { + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + return Ok(Response::UnsealResult(Err(UnsealError::InvalidKey))); + } + }; + + match self + .props + .actors + .key_holder + .ask(TryUnseal { + seal_key_raw: seal_key_buffer, + }) + .await + { + Ok(_) => { + info!("Successfully unsealed key with client-provided key"); + self.transition(UserAgentEvents::ReceivedValidKey)?; + Ok(Response::UnsealResult(Ok(()))) + } + Err(SendError::HandlerError(keyholder::Error::InvalidKey)) => { + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + Ok(Response::UnsealResult(Err(UnsealError::InvalidKey))) + } + Err(SendError::HandlerError(err)) => { + error!(?err, "Keyholder failed to unseal key"); + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + Ok(Response::UnsealResult(Err(UnsealError::InvalidKey))) + } + Err(err) => { + error!(?err, "Failed to send unseal request to keyholder"); + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + Err(TransportResponseError::KeyHolderActorUnreachable) + } + } + } + + async fn handle_bootstrap_encrypted_key( + &mut self, + nonce: Vec, + ciphertext: Vec, + associated_data: Vec, + ) -> Output { + let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() { + Ok(values) => values, + Err(TransportResponseError::StateTransitionFailed) => { + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + return Ok(Response::BootstrapResult(Err(BootstrapError::InvalidKey))); + } + Err(err) => return Err(err), + }; + + let seal_key_buffer = match Self::decrypt_client_key_material( + ephemeral_secret, + client_public_key, + &nonce, + &ciphertext, + &associated_data, + ) { + Ok(buffer) => buffer, + Err(()) => { + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + return Ok(Response::BootstrapResult(Err(BootstrapError::InvalidKey))); + } + }; + + match self + .props + .actors + .key_holder + .ask(Bootstrap { + seal_key_raw: seal_key_buffer, + }) + .await + { + Ok(_) => { + info!("Successfully bootstrapped vault with client-provided key"); + self.transition(UserAgentEvents::ReceivedValidKey)?; + Ok(Response::BootstrapResult(Ok(()))) + } + Err(SendError::HandlerError(keyholder::Error::AlreadyBootstrapped)) => { + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + Ok(Response::BootstrapResult(Err( + BootstrapError::AlreadyBootstrapped, + ))) + } + Err(SendError::HandlerError(err)) => { + error!(?err, "Keyholder failed to bootstrap vault"); + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + Ok(Response::BootstrapResult(Err(BootstrapError::InvalidKey))) + } + Err(err) => { + error!(?err, "Failed to send bootstrap request to keyholder"); + self.transition(UserAgentEvents::ReceivedInvalidKey)?; + Err(TransportResponseError::KeyHolderActorUnreachable) + } + } + } +} + +impl UserAgentSession { + async fn handle_query_vault_state(&mut self) -> Output { + use crate::actors::keyholder::{GetState, StateDiscriminants}; + + let vault_state = match self.props.actors.key_holder.ask(GetState {}).await { + Ok(StateDiscriminants::Unbootstrapped) => VaultState::Unbootstrapped, + Ok(StateDiscriminants::Sealed) => VaultState::Sealed, + Ok(StateDiscriminants::Unsealed) => VaultState::Unsealed, + Err(err) => { + error!(?err, actor = "useragent", "keyholder.query.failed"); + return Err(TransportResponseError::KeyHolderActorUnreachable); + } + }; + + Ok(Response::VaultState(vault_state)) + } +} + +impl UserAgentSession { + async fn handle_evm_wallet_create(&mut self) -> Output { + let result = match self.props.actors.evm.ask(Generate {}).await { + Ok(_address) => return Ok(Response::EvmWalletCreate(Ok(()))), + Err(SendError::HandlerError(err)) => Err(err), + Err(err) => { + error!(?err, "EVM actor unreachable during wallet create"); + return Err(TransportResponseError::KeyHolderActorUnreachable); + } + }; + Ok(Response::EvmWalletCreate(result)) + } + + async fn handle_evm_wallet_list(&mut self) -> Output { + match self.props.actors.evm.ask(ListWallets {}).await { + Ok(wallets) => Ok(Response::EvmWalletList(wallets)), + Err(err) => { + error!(?err, "EVM wallet list failed"); + Err(TransportResponseError::KeyHolderActorUnreachable) + } + } + } +} From 6ed8150e4891f1486ecbd6db217a25f6dd12c3a6 Mon Sep 17 00:00:00 2001 From: hdbg Date: Mon, 16 Mar 2026 03:13:05 +0100 Subject: [PATCH 09/13] feat(useragent::evm): basic list & creation --- .../lib/features/connection/connection.dart | 55 ++ useragent/lib/providers/evm.dart | 29 +- useragent/lib/providers/evm.g.dart | 2 +- useragent/lib/router.dart | 2 +- useragent/lib/router.gr.dart | 40 +- useragent/lib/screens/dashboard.dart | 20 +- useragent/lib/screens/dashboard/calc.dart | 76 --- useragent/lib/screens/dashboard/evm.dart | 489 ++++++++++++++++++ useragent/lib/screens/server_connection.dart | 1 - 9 files changed, 603 insertions(+), 111 deletions(-) delete mode 100644 useragent/lib/screens/dashboard/calc.dart create mode 100644 useragent/lib/screens/dashboard/evm.dart diff --git a/useragent/lib/features/connection/connection.dart b/useragent/lib/features/connection/connection.dart index 4e6436f..6020c39 100644 --- a/useragent/lib/features/connection/connection.dart +++ b/useragent/lib/features/connection/connection.dart @@ -4,15 +4,18 @@ import 'dart:convert'; import 'package:arbiter/features/connection/server_info_storage.dart'; import 'package:arbiter/features/identity/pk_manager.dart'; import 'package:arbiter/proto/arbiter.pbgrpc.dart'; +import 'package:arbiter/proto/evm.pb.dart'; import 'package:arbiter/proto/user_agent.pb.dart'; import 'package:cryptography/cryptography.dart'; import 'package:grpc/grpc.dart'; import 'package:mtcore/markettakers.dart'; +import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart'; class Connection { final ClientChannel channel; final StreamController _tx; final StreamIterator _rx; + Future _requestQueue = Future.value(); Connection({ required this.channel, @@ -69,6 +72,48 @@ List formatChallenge(AuthChallenge challenge, List pubkey) { const _vaultKeyAssociatedData = 'arbiter.vault.password'; +Future> listEvmWallets(Connection connection) async { + await connection.send(UserAgentRequest(evmWalletList: Empty())); + + final response = await connection.receive(); + if (!response.hasEvmWalletList()) { + throw Exception( + 'Expected EVM wallet list response, got ${response.whichPayload()}', + ); + } + + final result = response.evmWalletList; + switch (result.whichResult()) { + case WalletListResponse_Result.wallets: + return result.wallets.wallets.toList(growable: false); + case WalletListResponse_Result.error: + throw Exception(_describeEvmError(result.error)); + case WalletListResponse_Result.notSet: + throw Exception('EVM wallet list response was empty.'); + } +} + +Future createEvmWallet(Connection connection) async { + await connection.send(UserAgentRequest(evmWalletCreate: Empty())); + + final response = await connection.receive(); + if (!response.hasEvmWalletCreate()) { + throw Exception( + 'Expected EVM wallet create response, got ${response.whichPayload()}', + ); + } + + final result = response.evmWalletCreate; + switch (result.whichResult()) { + case WalletCreateResponse_Result.wallet: + return; + case WalletCreateResponse_Result.error: + throw Exception(_describeEvmError(result.error)); + case WalletCreateResponse_Result.notSet: + throw Exception('Wallet creation returned no result.'); + } +} + Future bootstrapVault( Connection connection, String password, @@ -241,3 +286,13 @@ Future connectAndAuthorize( throw Exception('Failed to connect to server: $e'); } } + +String _describeEvmError(EvmError error) { + return switch (error) { + EvmError.EVM_ERROR_VAULT_SEALED => + 'The vault is sealed. Unseal it before using EVM wallets.', + EvmError.EVM_ERROR_INTERNAL || EvmError.EVM_ERROR_UNSPECIFIED => + 'The server failed to process the EVM request.', + _ => 'The server failed to process the EVM request.', + }; +} diff --git a/useragent/lib/providers/evm.dart b/useragent/lib/providers/evm.dart index 17734f4..497fc7e 100644 --- a/useragent/lib/providers/evm.dart +++ b/useragent/lib/providers/evm.dart @@ -1,23 +1,40 @@ +import 'package:arbiter/features/connection/connection.dart'; import 'package:arbiter/proto/evm.pb.dart'; -import 'package:arbiter/proto/user_agent.pb.dart'; import 'package:arbiter/providers/connection/connection_manager.dart'; -import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'evm.g.dart'; @riverpod class Evm extends _$Evm { + @override Future?> build() async { final connection = await ref.watch(connectionManagerProvider.future); if (connection == null) { return null; } - await connection.send(UserAgentRequest( - evmWalletList: Empty() - )); + return listEvmWallets(connection); + } - final response = await connection.receive(); + Future refreshWallets() async { + final connection = await ref.read(connectionManagerProvider.future); + if (connection == null) { + state = const AsyncData(null); + return; + } + + state = const AsyncLoading(); + state = await AsyncValue.guard(() => listEvmWallets(connection)); + } + + Future createWallet() async { + final connection = await ref.read(connectionManagerProvider.future); + if (connection == null) { + throw Exception('Not connected to the server.'); + } + + await createEvmWallet(connection); + state = await AsyncValue.guard(() => listEvmWallets(connection)); } } diff --git a/useragent/lib/providers/evm.g.dart b/useragent/lib/providers/evm.g.dart index 60eca52..8c5bb02 100644 --- a/useragent/lib/providers/evm.g.dart +++ b/useragent/lib/providers/evm.g.dart @@ -33,7 +33,7 @@ final class EvmProvider Evm create() => Evm(); } -String _$evmHash() => r'6d2e0baf7b78a0850d7b99b0be7abde206e088c7'; +String _$evmHash() => r'f5d05bfa7b820d0b96026a47ca47702a3793af5d'; abstract class _$Evm extends $AsyncNotifier?> { FutureOr?> build(); diff --git a/useragent/lib/router.dart b/useragent/lib/router.dart index 78f14a2..0e7fea9 100644 --- a/useragent/lib/router.dart +++ b/useragent/lib/router.dart @@ -15,8 +15,8 @@ class Router extends RootStackRouter { page: DashboardRouter.page, path: '/dashboard', children: [ + AutoRoute(page: EvmRoute.page, path: 'evm'), AutoRoute(page: AboutRoute.page, path: 'about'), - AutoRoute(page: CalcRoute.page, path: 'calc'), ], ), ]; diff --git a/useragent/lib/router.gr.dart b/useragent/lib/router.gr.dart index aa123e8..da281ae 100644 --- a/useragent/lib/router.gr.dart +++ b/useragent/lib/router.gr.dart @@ -10,9 +10,9 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:arbiter/screens/bootstrap.dart' as _i2; -import 'package:arbiter/screens/dashboard.dart' as _i4; +import 'package:arbiter/screens/dashboard.dart' as _i3; import 'package:arbiter/screens/dashboard/about.dart' as _i1; -import 'package:arbiter/screens/dashboard/calc.dart' as _i3; +import 'package:arbiter/screens/dashboard/evm.dart' as _i4; import 'package:arbiter/screens/server_connection.dart' as _i5; import 'package:arbiter/screens/server_info_setup.dart' as _i6; import 'package:arbiter/screens/vault_setup.dart' as _i7; @@ -52,23 +52,7 @@ class Bootstrap extends _i8.PageRouteInfo { } /// generated route for -/// [_i3.CalcScreen] -class CalcRoute extends _i8.PageRouteInfo { - const CalcRoute({List<_i8.PageRouteInfo>? children}) - : super(CalcRoute.name, initialChildren: children); - - static const String name = 'CalcRoute'; - - static _i8.PageInfo page = _i8.PageInfo( - name, - builder: (data) { - return const _i3.CalcScreen(); - }, - ); -} - -/// generated route for -/// [_i4.DashboardRouter] +/// [_i3.DashboardRouter] class DashboardRouter extends _i8.PageRouteInfo { const DashboardRouter({List<_i8.PageRouteInfo>? children}) : super(DashboardRouter.name, initialChildren: children); @@ -78,7 +62,23 @@ class DashboardRouter extends _i8.PageRouteInfo { static _i8.PageInfo page = _i8.PageInfo( name, builder: (data) { - return const _i4.DashboardRouter(); + return const _i3.DashboardRouter(); + }, + ); +} + +/// generated route for +/// [_i4.EvmScreen] +class EvmRoute extends _i8.PageRouteInfo { + const EvmRoute({List<_i8.PageRouteInfo>? children}) + : super(EvmRoute.name, initialChildren: children); + + static const String name = 'EvmRoute'; + + static _i8.PageInfo page = _i8.PageInfo( + name, + builder: (data) { + return const _i4.EvmScreen(); }, ); } diff --git a/useragent/lib/screens/dashboard.dart b/useragent/lib/screens/dashboard.dart index 43c2366..82acdaa 100644 --- a/useragent/lib/screens/dashboard.dart +++ b/useragent/lib/screens/dashboard.dart @@ -5,7 +5,7 @@ import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; const breakpoints = MaterialAdaptiveBreakpoints(); -final routes = [AboutRoute(), CalcRoute()]; +final routes = [EvmRoute(), AboutRoute()]; @RoutePage() class DashboardRouter extends StatelessWidget { @@ -24,17 +24,25 @@ class DashboardRouter extends StatelessWidget { final tabsRouter = AutoTabsRouter.of(context); final currentActive = tabsRouter.activeIndex; return AdaptiveScaffold( - destinations: [ - NavigationDestination(icon: Icon(Icons.book), label: "About"), - NavigationDestination(icon: Icon(Icons.calculate), label: "Calc"), + destinations: const [ + NavigationDestination( + icon: Icon(Icons.account_balance_wallet_outlined), + selectedIcon: Icon(Icons.account_balance_wallet), + label: "Wallets", + ), + NavigationDestination( + icon: Icon(Icons.info_outline), + selectedIcon: Icon(Icons.info), + label: "About", + ), ], body: (ctx) => child, onSelectedIndexChange: (index) { tabsRouter.navigate(routes[index]); }, selectedIndex: currentActive, - transitionDuration: Duration(milliseconds: 800), - internalAnimations: true, + transitionDuration: const Duration(milliseconds: 800), + internalAnimations: true, ); }, ); diff --git a/useragent/lib/screens/dashboard/calc.dart b/useragent/lib/screens/dashboard/calc.dart deleted file mode 100644 index 0ba7b9c..0000000 --- a/useragent/lib/screens/dashboard/calc.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; - -@RoutePage() -class CalcScreen extends StatefulWidget { - const CalcScreen({super.key}); - - @override - State createState() => _CalcScreenState(); -} - -class _CalcScreenState extends State { - int _count = 0; - - void _increment() { - setState(() { - _count++; - }); - } - - void _decrement() { - setState(() { - _count--; - }); - } - - void _reset() { - setState(() { - _count = 0; - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Counter'), - ), - body: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Current count'), - const SizedBox(height: 8), - Text( - '$_count', - style: Theme.of(context).textTheme.displaySmall, - ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - IconButton( - onPressed: _decrement, - icon: const Icon(Icons.remove_circle_outline), - tooltip: 'Decrement', - ), - const SizedBox(width: 12), - ElevatedButton( - onPressed: _reset, - child: const Text('Reset'), - ), - const SizedBox(width: 12), - IconButton( - onPressed: _increment, - icon: const Icon(Icons.add_circle_outline), - tooltip: 'Increment', - ), - ], - ), - ], - ), - ), - ); - } -} diff --git a/useragent/lib/screens/dashboard/evm.dart b/useragent/lib/screens/dashboard/evm.dart new file mode 100644 index 0000000..8565e0a --- /dev/null +++ b/useragent/lib/screens/dashboard/evm.dart @@ -0,0 +1,489 @@ +import 'dart:math' as math; + +import 'package:arbiter/proto/evm.pb.dart'; +import 'package:arbiter/providers/connection/connection_manager.dart'; +import 'package:arbiter/providers/evm.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:sizer/sizer.dart'; + +@RoutePage() +class EvmScreen extends HookConsumerWidget { + const EvmScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final walletsAsync = ref.watch(evmProvider); + final isCreating = useState(false); + + final wallets = walletsAsync.asData?.value; + final loadedWallets = wallets ?? const []; + final isConnected = + ref.watch(connectionManagerProvider).asData?.value != null; + + void showMessage(String message) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message), behavior: SnackBarBehavior.floating), + ); + } + + Future refreshWallets() async { + try { + await ref.read(evmProvider.notifier).refreshWallets(); + } catch (error) { + showMessage(_formatError(error)); + } + } + + Future createWallet() async { + if (isCreating.value) { + return; + } + + isCreating.value = true; + try { + await ref.read(evmProvider.notifier).createWallet(); + showMessage('Wallet created.'); + } catch (error) { + showMessage(_formatError(error)); + } finally { + isCreating.value = false; + } + } + + final content = switch (walletsAsync) { + AsyncLoading() when wallets == null => const _StatePanel( + icon: Icons.hourglass_top, + title: 'Loading wallets', + body: 'Pulling wallet registry from Arbiter.', + busy: true, + ), + AsyncError(:final error) => _StatePanel( + icon: Icons.sync_problem, + title: 'Wallet registry unavailable', + body: _formatError(error), + actionLabel: 'Retry', + onAction: refreshWallets, + ), + _ when !isConnected => _StatePanel( + icon: Icons.portable_wifi_off, + title: 'No active server connection', + body: 'Reconnect to Arbiter to list or create EVM wallets.', + actionLabel: 'Refresh', + onAction: refreshWallets, + ), + _ when loadedWallets.isEmpty => _StatePanel( + icon: Icons.account_balance_wallet_outlined, + title: 'No wallets yet', + body: + 'Create the first vault-backed wallet to start building your EVM registry.', + actionLabel: isCreating.value ? 'Creating...' : 'Create wallet', + onAction: isCreating.value ? null : createWallet, + ), + _ => _WalletTable(wallets: loadedWallets), + }; + + return Scaffold( + body: SafeArea( + child: RefreshIndicator.adaptive( + color: _Palette.ink, + backgroundColor: Colors.white, + onRefresh: refreshWallets, + child: ListView( + physics: const BouncingScrollPhysics( + parent: AlwaysScrollableScrollPhysics(), + ), + padding: EdgeInsets.fromLTRB(2.4.w, 2.4.h, 2.4.w, 3.2.h), + children: [ + _Header( + isBusy: walletsAsync.isLoading, + isCreating: isCreating.value, + onCreate: createWallet, + onRefresh: refreshWallets, + ), + SizedBox(height: 1.8.h), + content, + ], + ), + ), + ), + ); + } +} + +class _Palette { + static const ink = Color(0xFF15263C); + static const coral = Color(0xFFE26254); + static const cream = Color(0xFFFFFAF4); + static const line = Color(0x1A15263C); +} + +double get _accentStripWidth => 0.8.w; +double get _cellHorizontalPadding => 1.8.w; +double get _walletColumnWidth => 18.w; +double get _columnGap => 1.8.w; +double get _tableMinWidth => 72.w; + +class _Header extends StatelessWidget { + const _Header({ + required this.isBusy, + required this.isCreating, + required this.onCreate, + required this.onRefresh, + }); + + final bool isBusy; + final bool isCreating; + final Future Function() onCreate; + final Future Function() onRefresh; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Container( + padding: EdgeInsets.symmetric(horizontal: 1.6.w, vertical: 1.2.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18), + color: _Palette.cream, + border: Border.all(color: _Palette.line), + ), + child: Row( + children: [ + Expanded( + child: Text( + 'EVM Wallet Vault', + style: theme.textTheme.titleMedium?.copyWith( + color: _Palette.ink, + fontWeight: FontWeight.w800, + ), + ), + ), + if (isBusy) ...[ + Text( + 'Syncing', + style: theme.textTheme.bodySmall?.copyWith( + color: _Palette.ink.withValues(alpha: 0.62), + fontWeight: FontWeight.w700, + ), + ), + SizedBox(width: 1.w), + ], + FilledButton.icon( + onPressed: isCreating ? null : () => onCreate(), + style: FilledButton.styleFrom( + backgroundColor: _Palette.ink, + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric(horizontal: 1.4.w, vertical: 1.2.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + ), + icon: isCreating + ? SizedBox( + width: 1.6.h, + height: 1.6.h, + child: CircularProgressIndicator(strokeWidth: 2.2), + ) + : const Icon(Icons.add_circle_outline, size: 18), + label: Text(isCreating ? 'Creating...' : 'Create'), + ), + SizedBox(width: 1.w), + OutlinedButton.icon( + onPressed: () => onRefresh(), + style: OutlinedButton.styleFrom( + foregroundColor: _Palette.ink, + side: BorderSide(color: _Palette.line), + padding: EdgeInsets.symmetric(horizontal: 1.4.w, vertical: 1.2.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + ), + icon: const Icon(Icons.refresh, size: 18), + label: const Text('Refresh'), + ), + ], + ), + ); + } +} + +class _WalletTable extends StatelessWidget { + const _WalletTable({required this.wallets}); + + final List wallets; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + color: _Palette.cream.withValues(alpha: 0.92), + border: Border.all(color: _Palette.line), + ), + child: Padding( + padding: EdgeInsets.all(2.h), + child: LayoutBuilder( + builder: (context, constraints) { + final tableWidth = math.max(_tableMinWidth, constraints.maxWidth); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Managed wallets', + style: theme.textTheme.titleLarge?.copyWith( + color: _Palette.ink, + fontWeight: FontWeight.w800, + ), + ), + SizedBox(height: 0.6.h), + Text( + 'Every address here is generated and held by Arbiter.', + style: theme.textTheme.bodyMedium?.copyWith( + color: _Palette.ink.withValues(alpha: 0.70), + height: 1.4, + ), + ), + SizedBox(height: 1.6.h), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: tableWidth, + child: Column( + children: [ + const _WalletTableHeader(), + SizedBox(height: 1.h), + for (var i = 0; i < wallets.length; i++) + Padding( + padding: EdgeInsets.only( + bottom: i == wallets.length - 1 ? 0 : 1.h, + ), + child: _WalletTableRow( + wallet: wallets[i], + index: i, + ), + ), + ], + ), + ), + ), + ], + ); + }, + ), + ), + ); + } +} + +class _WalletTableHeader extends StatelessWidget { + const _WalletTableHeader(); + + @override + Widget build(BuildContext context) { + final style = Theme.of(context).textTheme.labelLarge?.copyWith( + color: _Palette.ink.withValues(alpha: 0.72), + fontWeight: FontWeight.w800, + letterSpacing: 0.3, + ); + + return Container( + padding: EdgeInsets.symmetric(vertical: 1.4.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: _Palette.ink.withValues(alpha: 0.04), + ), + child: Row( + children: [ + SizedBox(width: _accentStripWidth + _cellHorizontalPadding), + SizedBox( + width: _walletColumnWidth, + child: Text('Wallet', style: style), + ), + SizedBox(width: _columnGap), + Expanded(child: Text('Address', style: style)), + SizedBox(width: _cellHorizontalPadding), + ], + ), + ); + } +} + +class _WalletTableRow extends StatelessWidget { + const _WalletTableRow({required this.wallet, required this.index}); + + final WalletEntry wallet; + final int index; + + @override + Widget build(BuildContext context) { + final accent = _accentColor(wallet.address); + final address = _hexAddress(wallet.address); + final rowHeight = 5.h; + final walletStyle = Theme.of( + context, + ).textTheme.bodyLarge?.copyWith(color: _Palette.ink); + final addressStyle = Theme.of( + context, + ).textTheme.bodyMedium?.copyWith(color: _Palette.ink); + + return Container( + height: rowHeight, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18), + color: accent.withValues(alpha: 0.10), + border: Border.all(color: accent.withValues(alpha: 0.28)), + ), + child: Row( + children: [ + Container( + width: _accentStripWidth, + height: rowHeight, + decoration: BoxDecoration( + color: accent, + borderRadius: const BorderRadius.horizontal( + left: Radius.circular(18), + ), + ), + ), + Expanded( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: _cellHorizontalPadding), + child: Row( + children: [ + SizedBox( + width: _walletColumnWidth, + child: Row( + children: [ + Container( + width: 1.2.h, + height: 1.2.h, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: accent, + ), + ), + SizedBox(width: 1.w), + Text( + 'Wallet ${(index + 1).toString().padLeft(2, '0')}', + style: walletStyle, + ), + ], + ), + ), + SizedBox(width: _columnGap), + Expanded( + child: Text( + address, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: addressStyle, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} + +class _StatePanel extends StatelessWidget { + const _StatePanel({ + required this.icon, + required this.title, + required this.body, + this.actionLabel, + this.onAction, + this.busy = false, + }); + + final IconData icon; + final String title; + final String body; + final String? actionLabel; + final Future Function()? onAction; + final bool busy; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + color: _Palette.cream.withValues(alpha: 0.92), + border: Border.all(color: _Palette.line), + ), + child: Padding( + padding: EdgeInsets.all(2.8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (busy) + SizedBox( + width: 2.8.h, + height: 2.8.h, + child: CircularProgressIndicator(strokeWidth: 2.5), + ) + else + Icon(icon, size: 34, color: _Palette.coral), + SizedBox(height: 1.8.h), + Text( + title, + style: theme.textTheme.headlineSmall?.copyWith( + color: _Palette.ink, + fontWeight: FontWeight.w800, + ), + ), + SizedBox(height: 1.h), + Text( + body, + style: theme.textTheme.bodyLarge?.copyWith( + color: _Palette.ink.withValues(alpha: 0.72), + height: 1.5, + ), + ), + if (actionLabel != null && onAction != null) ...[ + SizedBox(height: 2.h), + OutlinedButton.icon( + onPressed: () => onAction!(), + icon: const Icon(Icons.refresh), + label: Text(actionLabel!), + ), + ], + ], + ), + ), + ); + } +} + +String _hexAddress(List bytes) { + final hex = bytes + .map((byte) => byte.toRadixString(16).padLeft(2, '0')) + .join(); + return '0x$hex'; +} + +Color _accentColor(List bytes) { + final seed = bytes.fold(0, (value, byte) => value + byte); + final hue = (seed * 17) % 360; + return HSLColor.fromAHSL(1, hue.toDouble(), 0.68, 0.54).toColor(); +} + +String _formatError(Object error) { + final message = error.toString(); + if (message.startsWith('Exception: ')) { + return message.substring('Exception: '.length); + } + return message; +} diff --git a/useragent/lib/screens/server_connection.dart b/useragent/lib/screens/server_connection.dart index 9634287..3a407cf 100644 --- a/useragent/lib/screens/server_connection.dart +++ b/useragent/lib/screens/server_connection.dart @@ -2,7 +2,6 @@ import 'package:arbiter/providers/connection/connection_manager.dart'; import 'package:arbiter/router.gr.dart'; import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:sizer/sizer.dart'; From 088fa6fe72d430734e8a07c1439aecc553103ae0 Mon Sep 17 00:00:00 2001 From: hdbg Date: Mon, 16 Mar 2026 04:40:36 +0100 Subject: [PATCH 10/13] feat(evm): add grant management for EVM wallets --- server/Cargo.lock | 1 + server/crates/arbiter-server/Cargo.toml | 1 + .../arbiter-server/src/actors/evm/mod.rs | 21 +- .../src/actors/user_agent/auth/state.rs | 23 +- .../src/actors/user_agent/mod.rs | 19 +- .../actors/user_agent/session/connection.rs | 63 +- .../crates/arbiter-server/src/evm/policies.rs | 4 + .../src/evm/policies/ether_transfer/mod.rs | 5 +- .../src/evm/policies/ether_transfer/tests.rs | 1 + .../src/evm/policies/token_transfers/mod.rs | 7 +- .../src/evm/policies/token_transfers/tests.rs | 1 + .../arbiter-server/src/grpc/user_agent.rs | 295 ++++- server/crates/arbiter-server/src/lib.rs | 6 +- useragent/lib/features/connection/auth.dart | 103 ++ .../lib/features/connection/connection.dart | 265 +---- useragent/lib/features/connection/evm.dart | 56 + .../lib/features/connection/evm/grants.dart | 122 ++ .../connection/server_info_storage.dart | 1 + useragent/lib/features/connection/vault.dart | 107 ++ .../connection/connection_manager.dart | 1 + useragent/lib/providers/evm.dart | 2 +- useragent/lib/providers/evm_grants.dart | 120 ++ .../lib/providers/evm_grants.freezed.dart | 280 +++++ useragent/lib/providers/evm_grants.g.dart | 54 + useragent/lib/router.dart | 2 + useragent/lib/router.gr.dart | 114 +- useragent/lib/screens/bootstrap.dart | 2 +- useragent/lib/screens/dashboard.dart | 7 +- .../screens/dashboard/evm_grant_create.dart | 824 ++++++++++++++ .../lib/screens/dashboard/evm_grants.dart | 1007 +++++++++++++++++ useragent/lib/screens/vault_setup.dart | 2 +- 31 files changed, 3138 insertions(+), 378 deletions(-) create mode 100644 useragent/lib/features/connection/auth.dart create mode 100644 useragent/lib/features/connection/evm.dart create mode 100644 useragent/lib/features/connection/evm/grants.dart create mode 100644 useragent/lib/features/connection/vault.dart create mode 100644 useragent/lib/providers/evm_grants.dart create mode 100644 useragent/lib/providers/evm_grants.freezed.dart create mode 100644 useragent/lib/providers/evm_grants.g.dart create mode 100644 useragent/lib/screens/dashboard/evm_grant_create.dart create mode 100644 useragent/lib/screens/dashboard/evm_grants.dart diff --git a/server/Cargo.lock b/server/Cargo.lock index a77dfb1..30ec3d7 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -727,6 +727,7 @@ dependencies = [ "memsafe", "miette", "pem", + "prost-types", "rand 0.10.0", "rcgen", "restructed", diff --git a/server/crates/arbiter-server/Cargo.toml b/server/crates/arbiter-server/Cargo.toml index 4629d81..62a9afa 100644 --- a/server/crates/arbiter-server/Cargo.toml +++ b/server/crates/arbiter-server/Cargo.toml @@ -50,6 +50,7 @@ rsa.workspace = true sha2.workspace = true spki.workspace = true alloy.workspace = true +prost-types.workspace = true arbiter-tokens-registry.path = "../arbiter-tokens-registry" [dev-dependencies] diff --git a/server/crates/arbiter-server/src/actors/evm/mod.rs b/server/crates/arbiter-server/src/actors/evm/mod.rs index 5c7ff3e..ce3f177 100644 --- a/server/crates/arbiter-server/src/actors/evm/mod.rs +++ b/server/crates/arbiter-server/src/actors/evm/mod.rs @@ -15,9 +15,9 @@ use crate::{ schema, }, evm::{ - self, RunKind, + self, ListGrantsError, RunKind, policies::{ - FullGrant, SharedGrantSettings, SpecificGrant, SpecificMeaning, + FullGrant, Grant, SharedGrantSettings, SpecificGrant, SpecificMeaning, ether_transfer::EtherTransfer, token_transfers::TokenTransfer, }, }, @@ -194,19 +194,12 @@ impl EvmActor { } #[message] - pub async fn useragent_list_grants( - &mut self, - wallet_id: Option, - ) -> Result, Error> { - let mut conn = self.db.get().await?; - let mut query = schema::evm_basic_grant::table - .select(EvmBasicGrant::as_select()) - .filter(schema::evm_basic_grant::revoked_at.is_null()) - .into_boxed(); - if let Some(wid) = wallet_id { - query = query.filter(schema::evm_basic_grant::wallet_id.eq(wid)); + pub async fn useragent_list_grants(&mut self) -> Result>, Error> { + match self.engine.list_all_grants().await { + Ok(grants) => Ok(grants), + Err(ListGrantsError::Database(db)) => Err(Error::Database(db)), + Err(ListGrantsError::Pool(pool)) => Err(Error::DatabasePool(pool)), } - Ok(query.load(&mut conn).await?) } #[message] diff --git a/server/crates/arbiter-server/src/actors/user_agent/auth/state.rs b/server/crates/arbiter-server/src/actors/user_agent/auth/state.rs index f44e6c8..608f3a7 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/auth/state.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/auth/state.rs @@ -34,7 +34,7 @@ smlang::statemachine!( custom_error: true, transitions: { *Init + AuthRequest(ChallengeRequest) / async prepare_challenge = SentChallenge(ChallengeContext), - Init + BootstrapAuthRequest(BootstrapAuthRequest) [async verify_bootstrap_token] / provide_key_bootstrap = AuthOk(AuthPublicKey), + Init + BootstrapAuthRequest(BootstrapAuthRequest) / async verify_bootstrap_token = AuthOk(AuthPublicKey), SentChallenge(ChallengeContext) + ReceivedSolution(ChallengeSolution) / async verify_solution = AuthOk(AuthPublicKey), } ); @@ -136,9 +136,9 @@ impl AuthStateMachineContext for AuthContext<'_> { #[allow(missing_docs)] #[allow(clippy::result_unit_err)] async fn verify_bootstrap_token( - &self, - BootstrapAuthRequest { pubkey, token }: &BootstrapAuthRequest, - ) -> Result { + &mut self, + BootstrapAuthRequest { pubkey, token }: BootstrapAuthRequest, + ) -> Result { let token_ok: bool = self .conn .actors @@ -157,16 +157,15 @@ impl AuthStateMachineContext for AuthContext<'_> { return Err(Error::InvalidBootstrapToken); } - register_key(&self.conn.db, pubkey).await?; + register_key(&self.conn.db, &pubkey).await?; - Ok(true) - } + self.conn + .transport + .send(Ok(Response::AuthOk)) + .await + .map_err(|_| Error::Transport)?; - fn provide_key_bootstrap( - &mut self, - event_data: BootstrapAuthRequest, - ) -> Result { - Ok(event_data.pubkey) + Ok(pubkey) } #[allow(missing_docs)] diff --git a/server/crates/arbiter-server/src/actors/user_agent/mod.rs b/server/crates/arbiter-server/src/actors/user_agent/mod.rs index 866219b..b4e048b 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/mod.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/mod.rs @@ -1,11 +1,12 @@ use alloy::primitives::Address; -use arbiter_proto::transport::Bi; +use arbiter_proto::{transport::Bi}; use kameo::actor::Spawn as _; use tracing::{error, info}; use crate::{ actors::{GlobalActors, evm, user_agent::session::UserAgentSession}, - db::{self, models::KeyType}, + db::{self, models::KeyType}, evm::policies::{Grant, SpecificGrant}, + evm::policies::SharedGrantSettings, }; #[derive(Debug, thiserror::Error, PartialEq)] @@ -109,6 +110,16 @@ pub enum Request { ClientConnectionResponse { approved: bool, }, + + ListGrants, + EvmGrantCreate { + client_id: i32, + shared: SharedGrantSettings, + specific: SpecificGrant, + }, + EvmGrantDelete { + grant_id: i32, + }, } #[derive(Debug)] @@ -123,6 +134,10 @@ pub enum Response { ClientConnectionCancel, EvmWalletCreate(Result<(), evm::Error>), EvmWalletList(Vec
), + + ListGrants(Vec>), + EvmGrantCreate(Result), + EvmGrantDelete(Result<(), evm::Error>), } pub type Transport = Box> + Send>; diff --git a/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs b/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs index 81892dc..e303ef6 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs @@ -7,7 +7,8 @@ use tracing::{error, info}; use x25519_dalek::{EphemeralSecret, PublicKey}; use crate::actors::{ - evm::{Generate, ListWallets}, + evm::{Generate, ListWallets, UseragentListGrants}, + evm::{UseragentCreateGrant, UseragentDeleteGrant}, keyholder::{self, Bootstrap, TryUnseal}, user_agent::{ BootstrapError, Request, Response, TransportResponseError, UnsealError, VaultState, @@ -40,6 +41,7 @@ impl UserAgentSession { self.handle_bootstrap_encrypted_key(nonce, ciphertext, associated_data) .await } + Request::ListGrants => self.handle_grant_list().await, Request::QueryVaultState => self.handle_query_vault_state().await, Request::EvmWalletCreate => self.handle_evm_wallet_create().await, Request::EvmWalletList => self.handle_evm_wallet_list().await, @@ -48,6 +50,12 @@ impl UserAgentSession { | Request::ClientConnectionResponse { .. } => { Err(TransportResponseError::UnexpectedRequestPayload) } + Request::EvmGrantCreate { + client_id, + shared, + specific, + } => self.handle_grant_create(client_id, shared, specific).await, + Request::EvmGrantDelete { grant_id } => self.handle_grant_delete(grant_id).await, } } } @@ -286,3 +294,56 @@ impl UserAgentSession { } } } + +impl UserAgentSession { + async fn handle_grant_list(&mut self) -> Output { + match self.props.actors.evm.ask(UseragentListGrants {}).await { + Ok(grants) => Ok(Response::ListGrants(grants)), + Err(err) => { + error!(?err, "EVM grant list failed"); + Err(TransportResponseError::KeyHolderActorUnreachable) + } + } + } + + async fn handle_grant_create( + &mut self, + client_id: i32, + basic: crate::evm::policies::SharedGrantSettings, + grant: crate::evm::policies::SpecificGrant, + ) -> Output { + match self + .props + .actors + .evm + .ask(UseragentCreateGrant { + client_id, + basic, + grant, + }) + .await + { + Ok(grant_id) => Ok(Response::EvmGrantCreate(Ok(grant_id))), + Err(err) => { + error!(?err, "EVM grant create failed"); + Err(TransportResponseError::KeyHolderActorUnreachable) + } + } + } + + async fn handle_grant_delete(&mut self, grant_id: i32) -> Output { + match self + .props + .actors + .evm + .ask(UseragentDeleteGrant { grant_id }) + .await + { + Ok(()) => Ok(Response::EvmGrantDelete(Ok(()))), + Err(err) => { + error!(?err, "EVM grant delete failed"); + Err(TransportResponseError::KeyHolderActorUnreachable) + } + } + } +} diff --git a/server/crates/arbiter-server/src/evm/policies.rs b/server/crates/arbiter-server/src/evm/policies.rs index 5a968cd..32d3cd3 100644 --- a/server/crates/arbiter-server/src/evm/policies.rs +++ b/server/crates/arbiter-server/src/evm/policies.rs @@ -66,6 +66,7 @@ pub enum EvalViolation { pub type DatabaseID = i32; +#[derive(Debug)] pub struct Grant { pub id: DatabaseID, pub shared_grant_id: DatabaseID, // ID of the basic grant for shared-logic checks like rate limits and validity periods @@ -145,6 +146,7 @@ pub struct VolumeRateLimit { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct SharedGrantSettings { pub wallet_id: i32, + pub client_id: i32, pub chain: ChainId, pub valid_from: Option>, @@ -160,6 +162,7 @@ impl SharedGrantSettings { fn try_from_model(model: EvmBasicGrant) -> QueryResult { Ok(Self { wallet_id: model.wallet_id, + client_id: model.client_id, chain: model.chain_id as u64, // safe because chain_id is stored as i32 but is guaranteed to be a valid ChainId by the API when creating grants valid_from: model.valid_from.map(Into::into), valid_until: model.valid_until.map(Into::into), @@ -197,6 +200,7 @@ impl SharedGrantSettings { } } +#[derive(Debug, Clone)] pub enum SpecificGrant { EtherTransfer(ether_transfer::Settings), TokenTransfer(token_transfers::Settings), diff --git a/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs b/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs index e1f01c5..e77d994 100644 --- a/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs +++ b/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs @@ -51,9 +51,10 @@ impl From for SpecificMeaning { } // A grant for ether transfers, which can be scoped to specific target addresses and volume limits +#[derive(Debug, Clone)] pub struct Settings { - target: Vec
, - limit: VolumeRateLimit, + pub target: Vec
, + pub limit: VolumeRateLimit, } impl From for SpecificGrant { diff --git a/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs b/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs index 0e52c19..7e5dd9d 100644 --- a/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs +++ b/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs @@ -74,6 +74,7 @@ fn shared() -> SharedGrantSettings { max_gas_fee_per_gas: None, max_priority_fee_per_gas: None, rate_limit: None, + client_id: CLIENT_ID, } } diff --git a/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs b/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs index 856370f..34378ed 100644 --- a/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs +++ b/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs @@ -58,10 +58,11 @@ impl From for SpecificMeaning { } // A grant for token transfers, which can be scoped to specific target addresses and volume limits +#[derive(Debug, Clone)] pub struct Settings { - token_contract: Address, - target: Option
, - volume_limits: Vec, + pub token_contract: Address, + pub target: Option
, + pub volume_limits: Vec, } impl From for SpecificGrant { fn from(val: Settings) -> SpecificGrant { diff --git a/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs b/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs index 95d852b..e41772a 100644 --- a/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs +++ b/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs @@ -93,6 +93,7 @@ fn shared() -> SharedGrantSettings { max_gas_fee_per_gas: None, max_priority_fee_per_gas: None, rate_limit: None, + client_id: CLIENT_ID, } } diff --git a/server/crates/arbiter-server/src/grpc/user_agent.rs b/server/crates/arbiter-server/src/grpc/user_agent.rs index ade2444..a309b52 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent.rs @@ -1,21 +1,30 @@ use arbiter_proto::{ proto::{ + self, evm::{ - EvmError as ProtoEvmError, WalletCreateResponse, WalletEntry, WalletList, - WalletListResponse, wallet_create_response::Result as WalletCreateResult, + EtherTransferSettings as ProtoEtherTransferSettings, EvmError as ProtoEvmError, + EvmGrantCreateRequest, EvmGrantCreateResponse, EvmGrantDeleteRequest, + EvmGrantDeleteResponse, EvmGrantList, EvmGrantListResponse, GrantEntry, + SharedSettings as ProtoSharedSettings, SpecificGrant as ProtoSpecificGrant, + SpecificGrant as ProtoGrantSpecificGrant, + TokenTransferSettings as ProtoTokenTransferSettings, + VolumeRateLimit as ProtoVolumeRateLimit, WalletCreateResponse, WalletEntry, WalletList, + WalletListResponse, evm_grant_create_response::Result as EvmGrantCreateResult, + evm_grant_delete_response::Result as EvmGrantDeleteResult, + evm_grant_list_response::Result as EvmGrantListResult, + specific_grant::Grant as ProtoSpecificGrantType, + wallet_create_response::Result as WalletCreateResult, wallet_list_response::Result as WalletListResult, }, user_agent::{ - AuthChallenge as ProtoAuthChallenge, - AuthChallengeRequest as ProtoAuthChallengeRequest, + AuthChallenge as ProtoAuthChallenge, AuthChallengeRequest as ProtoAuthChallengeRequest, AuthChallengeSolution as ProtoAuthChallengeSolution, AuthOk as ProtoAuthOk, BootstrapEncryptedKey as ProtoBootstrapEncryptedKey, BootstrapResult as ProtoBootstrapResult, ClientConnectionCancel, ClientConnectionRequest, ClientConnectionResponse, KeyType as ProtoKeyType, UnsealEncryptedKey as ProtoUnsealEncryptedKey, UnsealResult as ProtoUnsealResult, UnsealStart, UnsealStartResponse, UserAgentRequest, UserAgentResponse, - VaultState as ProtoVaultState, - user_agent_request::Payload as UserAgentRequestPayload, + VaultState as ProtoVaultState, user_agent_request::Payload as UserAgentRequestPayload, user_agent_response::Payload as UserAgentResponsePayload, }, }, @@ -23,13 +32,26 @@ use arbiter_proto::{ }; use async_trait::async_trait; use futures::StreamExt as _; +use prost_types::Timestamp; use tokio::sync::mpsc; use tonic::{Status, Streaming}; -use crate::actors::user_agent::{ - self, AuthPublicKey, BootstrapError, Request as DomainRequest, Response as DomainResponse, - TransportResponseError, UnsealError, VaultState, +use crate::{ + actors::user_agent::{ + self, AuthPublicKey, BootstrapError, Request as DomainRequest, Response as DomainResponse, + TransportResponseError, UnsealError, VaultState, + }, + evm::{ + self, + policies::{Grant, SpecificGrant}, + policies::{ + SharedGrantSettings, TransactionRateLimit, VolumeRateLimit, ether_transfer, + token_transfers, + }, + }, }; +use alloy::primitives::{Address, U256}; +use chrono::{DateTime, TimeZone, Utc}; pub struct GrpcTransport { sender: mpsc::Sender>, @@ -46,19 +68,17 @@ impl GrpcTransport { fn request_to_domain(request: UserAgentRequest) -> Result { match request.payload { - Some(UserAgentRequestPayload::AuthChallengeRequest( - ProtoAuthChallengeRequest { - pubkey, - bootstrap_token, - key_type, - }, - )) => Ok(DomainRequest::AuthChallengeRequest { + Some(UserAgentRequestPayload::AuthChallengeRequest(ProtoAuthChallengeRequest { + pubkey, + bootstrap_token, + key_type, + })) => Ok(DomainRequest::AuthChallengeRequest { pubkey: parse_auth_pubkey(key_type, pubkey)?, bootstrap_token, }), - Some(UserAgentRequestPayload::AuthChallengeSolution( - ProtoAuthChallengeSolution { signature }, - )) => Ok(DomainRequest::AuthChallengeSolution { signature }), + Some(UserAgentRequestPayload::AuthChallengeSolution(ProtoAuthChallengeSolution { + signature, + })) => Ok(DomainRequest::AuthChallengeSolution { signature }), Some(UserAgentRequestPayload::UnsealStart(UnsealStart { client_pubkey })) => { let client_pubkey: [u8; 32] = client_pubkey .as_slice() @@ -77,29 +97,42 @@ impl GrpcTransport { ciphertext, associated_data, }), - Some(UserAgentRequestPayload::BootstrapEncryptedKey( - ProtoBootstrapEncryptedKey { - nonce, - ciphertext, - associated_data, - }, - )) => Ok(DomainRequest::BootstrapEncryptedKey { + Some(UserAgentRequestPayload::BootstrapEncryptedKey(ProtoBootstrapEncryptedKey { + nonce, + ciphertext, + associated_data, + })) => Ok(DomainRequest::BootstrapEncryptedKey { nonce, ciphertext, associated_data, }), - Some(UserAgentRequestPayload::QueryVaultState(_)) => { - Ok(DomainRequest::QueryVaultState) - } + Some(UserAgentRequestPayload::QueryVaultState(_)) => Ok(DomainRequest::QueryVaultState), Some(UserAgentRequestPayload::EvmWalletCreate(_)) => Ok(DomainRequest::EvmWalletCreate), Some(UserAgentRequestPayload::EvmWalletList(_)) => Ok(DomainRequest::EvmWalletList), - Some(UserAgentRequestPayload::ClientConnectionResponse( - ClientConnectionResponse { approved }, - )) => Ok(DomainRequest::ClientConnectionResponse { approved }), - Some(_) => Err(Status::invalid_argument( - "Unexpected user-agent request payload", + Some(UserAgentRequestPayload::ClientConnectionResponse(ClientConnectionResponse { + approved, + })) => Ok(DomainRequest::ClientConnectionResponse { approved }), + + Some(UserAgentRequestPayload::EvmGrantList(_)) => Ok(DomainRequest::ListGrants), + Some(UserAgentRequestPayload::EvmGrantCreate(EvmGrantCreateRequest { + client_id, + shared, + specific, + })) => { + let shared = parse_shared_settings(client_id, shared)?; + let specific = parse_specific_grant(specific)?; + Ok(DomainRequest::EvmGrantCreate { + client_id, + shared, + specific, + }) + } + Some(UserAgentRequestPayload::EvmGrantDelete(EvmGrantDeleteRequest { grant_id })) => { + Ok(DomainRequest::EvmGrantDelete { grant_id }) + } + None => Err(Status::invalid_argument( + "Missing user-agent request payload", )), - None => Err(Status::invalid_argument("Missing user-agent request payload")), } } @@ -173,6 +206,29 @@ impl GrpcTransport { })), }) } + DomainResponse::ListGrants(grants) => { + UserAgentResponsePayload::EvmGrantList(EvmGrantListResponse { + result: Some(EvmGrantListResult::Grants(EvmGrantList { + grants: grants.into_iter().map(grant_to_proto).collect(), + })), + }) + } + DomainResponse::EvmGrantCreate(result) => { + UserAgentResponsePayload::EvmGrantCreate(EvmGrantCreateResponse { + result: Some(match result { + Ok(grant_id) => EvmGrantCreateResult::GrantId(grant_id), + Err(_) => EvmGrantCreateResult::Error(ProtoEvmError::Internal.into()), + }), + }) + } + DomainResponse::EvmGrantDelete(result) => { + UserAgentResponsePayload::EvmGrantDelete(EvmGrantDeleteResponse { + result: Some(match result { + Ok(()) => EvmGrantDeleteResult::Ok(()), + Err(_) => EvmGrantDeleteResult::Error(ProtoEvmError::Internal.into()), + }), + }) + } }; UserAgentResponse { @@ -191,7 +247,9 @@ impl GrpcTransport { TransportResponseError::InvalidClientPubkeyLength => { Status::invalid_argument("client_pubkey must be 32 bytes") } - TransportResponseError::StateTransitionFailed => Status::internal("State machine error"), + TransportResponseError::StateTransitionFailed => { + Status::internal("State machine error") + } TransportResponseError::KeyHolderActorUnreachable => { Status::internal("Vault is not available") } @@ -238,6 +296,171 @@ impl Bi> for GrpcT } } +fn grant_to_proto(grant: Grant) -> proto::evm::GrantEntry { + GrantEntry { + id: grant.id, + specific: Some(match grant.settings { + SpecificGrant::EtherTransfer(settings) => ProtoSpecificGrant { + grant: Some(ProtoSpecificGrantType::EtherTransfer( + ProtoEtherTransferSettings { + targets: settings + .target + .into_iter() + .map(|addr| addr.as_slice().to_vec()) + .collect(), + limit: Some(proto::evm::VolumeRateLimit { + max_volume: settings.limit.max_volume.to_be_bytes_vec(), + window_secs: settings.limit.window.num_seconds(), + }), + }, + )), + }, + SpecificGrant::TokenTransfer(settings) => ProtoSpecificGrant { + grant: Some(ProtoSpecificGrantType::TokenTransfer( + ProtoTokenTransferSettings { + token_contract: settings.token_contract.as_slice().to_vec(), + target: settings.target.map(|addr| addr.as_slice().to_vec()), + volume_limits: settings + .volume_limits + .into_iter() + .map(|vrl| proto::evm::VolumeRateLimit { + max_volume: vrl.max_volume.to_be_bytes_vec(), + window_secs: vrl.window.num_seconds(), + }) + .collect(), + }, + )), + }, + }), + client_id: grant.shared.client_id, + shared: Some(proto::evm::SharedSettings { + wallet_id: grant.shared.wallet_id, + chain_id: grant.shared.chain, + valid_from: grant.shared.valid_from.map(|dt| Timestamp { + seconds: dt.timestamp(), + nanos: 0, + }), + valid_until: grant.shared.valid_until.map(|dt| Timestamp { + seconds: dt.timestamp(), + nanos: 0, + }), + max_gas_fee_per_gas: grant + .shared + .max_gas_fee_per_gas + .map(|fee| fee.to_be_bytes_vec()), + max_priority_fee_per_gas: grant + .shared + .max_priority_fee_per_gas + .map(|fee| fee.to_be_bytes_vec()), + rate_limit: grant + .shared + .rate_limit + .map(|limit| proto::evm::TransactionRateLimit { + count: limit.count, + window_secs: limit.window.num_seconds(), + }), + }), + } +} + +fn parse_volume_rate_limit(vrl: ProtoVolumeRateLimit) -> Result { + Ok(VolumeRateLimit { + max_volume: U256::from_be_slice(&vrl.max_volume), + window: chrono::Duration::seconds(vrl.window_secs), + }) +} + +fn parse_shared_settings( + client_id: i32, + proto: Option, +) -> Result { + let s = proto.ok_or_else(|| Status::invalid_argument("missing shared settings"))?; + let parse_u256 = |b: Vec| -> Result { + if b.is_empty() { + Err(Status::invalid_argument("U256 bytes must not be empty")) + } else { + Ok(U256::from_be_slice(&b)) + } + }; + let parse_ts = |ts: prost_types::Timestamp| -> Result, Status> { + Utc.timestamp_opt(ts.seconds, ts.nanos as u32) + .single() + .ok_or_else(|| Status::invalid_argument("invalid timestamp")) + }; + Ok(SharedGrantSettings { + wallet_id: s.wallet_id, + client_id, + chain: s.chain_id, + valid_from: s.valid_from.map(parse_ts).transpose()?, + valid_until: s.valid_until.map(parse_ts).transpose()?, + max_gas_fee_per_gas: s.max_gas_fee_per_gas.map(parse_u256).transpose()?, + max_priority_fee_per_gas: s.max_priority_fee_per_gas.map(parse_u256).transpose()?, + rate_limit: s.rate_limit.map(|rl| TransactionRateLimit { + count: rl.count, + window: chrono::Duration::seconds(rl.window_secs), + }), + }) +} + +fn parse_specific_grant(proto: Option) -> Result { + use proto::evm::specific_grant::Grant as ProtoGrant; + let g = proto + .and_then(|sg| sg.grant) + .ok_or_else(|| Status::invalid_argument("missing specific grant"))?; + match g { + ProtoGrant::EtherTransfer(s) => { + let limit = parse_volume_rate_limit( + s.limit + .ok_or_else(|| Status::invalid_argument("missing ether transfer limit"))?, + )?; + let target = s + .targets + .into_iter() + .map(|b| { + if b.len() == 20 { + Ok(Address::from_slice(&b)) + } else { + Err(Status::invalid_argument( + "ether transfer target must be 20 bytes", + )) + } + }) + .collect::, _>>()?; + Ok(SpecificGrant::EtherTransfer(ether_transfer::Settings { + target, + limit, + })) + } + ProtoGrant::TokenTransfer(s) => { + if s.token_contract.len() != 20 { + return Err(Status::invalid_argument("token_contract must be 20 bytes")); + } + let target = s + .target + .map(|b| { + if b.len() == 20 { + Ok(Address::from_slice(&b)) + } else { + Err(Status::invalid_argument( + "token transfer target must be 20 bytes", + )) + } + }) + .transpose()?; + let volume_limits = s + .volume_limits + .into_iter() + .map(parse_volume_rate_limit) + .collect::, _>>()?; + Ok(SpecificGrant::TokenTransfer(token_transfers::Settings { + token_contract: Address::from_slice(&s.token_contract), + target, + volume_limits, + })) + } + } +} + fn parse_auth_pubkey(key_type: i32, pubkey: Vec) -> Result { match ProtoKeyType::try_from(key_type).unwrap_or(ProtoKeyType::Unspecified) { ProtoKeyType::Unspecified | ProtoKeyType::Ed25519 => { diff --git a/server/crates/arbiter-server/src/lib.rs b/server/crates/arbiter-server/src/lib.rs index 2c06328..70f1f80 100644 --- a/server/crates/arbiter-server/src/lib.rs +++ b/server/crates/arbiter-server/src/lib.rs @@ -1,5 +1,9 @@ #![forbid(unsafe_code)] - +#![deny( + clippy::unwrap_used, + clippy::expect_used, + clippy::panic +)] use crate::context::ServerContext; diff --git a/useragent/lib/features/connection/auth.dart b/useragent/lib/features/connection/auth.dart new file mode 100644 index 0000000..9e432b8 --- /dev/null +++ b/useragent/lib/features/connection/auth.dart @@ -0,0 +1,103 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:arbiter/features/connection/connection.dart'; +import 'package:arbiter/features/connection/server_info_storage.dart'; +import 'package:arbiter/features/identity/pk_manager.dart'; +import 'package:arbiter/proto/arbiter.pbgrpc.dart'; +import 'package:arbiter/proto/user_agent.pb.dart'; +import 'package:grpc/grpc.dart'; +import 'package:mtcore/markettakers.dart'; + +Future connectAndAuthorize( + StoredServerInfo serverInfo, + KeyHandle key, { + String? bootstrapToken, +}) async { + try { + final connection = await _connect(serverInfo); + talker.info( + 'Connected to server at ${serverInfo.address}:${serverInfo.port}', + ); + final pubkey = await key.getPublicKey(); + + final req = AuthChallengeRequest( + pubkey: pubkey, + bootstrapToken: bootstrapToken, + keyType: switch (key.alg) { + KeyAlgorithm.rsa => KeyType.KEY_TYPE_RSA, + KeyAlgorithm.ecdsa => KeyType.KEY_TYPE_ECDSA_SECP256K1, + KeyAlgorithm.ed25519 => KeyType.KEY_TYPE_ED25519, + }, + ); + await connection.send(UserAgentRequest(authChallengeRequest: req)); + talker.info( + "Sent auth challenge request with pubkey ${base64Encode(pubkey)}", + ); + + final response = await connection.receive(); + talker.info('Received response from server, checking auth flow...'); + + if (response.hasAuthOk()) { + talker.info('Authentication successful, connection established'); + return connection; + } + + if (!response.hasAuthChallenge()) { + throw Exception( + 'Expected AuthChallengeResponse, got ${response.whichPayload()}', + ); + } + + final challenge = _formatChallenge(response.authChallenge, pubkey); + talker.info( + 'Received auth challenge, signing with key ${base64Encode(pubkey)}', + ); + + final signature = await key.sign(challenge); + await connection.send( + UserAgentRequest(authChallengeSolution: AuthChallengeSolution(signature: signature)), + ); + + talker.info('Sent auth challenge solution, waiting for server response...'); + + final solutionResponse = await connection.receive(); + if (!solutionResponse.hasAuthOk()) { + throw Exception( + 'Expected AuthChallengeSolutionResponse, got ${solutionResponse.whichPayload()}', + ); + } + + talker.info('Authentication successful, connection established'); + return connection; + } catch (e) { + throw Exception('Failed to connect to server: $e'); + } +} + +Future _connect(StoredServerInfo serverInfo) async { + final channel = ClientChannel( + serverInfo.address, + port: serverInfo.port, + options: ChannelOptions( + connectTimeout: const Duration(seconds: 10), + credentials: ChannelCredentials.secure( + onBadCertificate: (cert, host) { + return true; + }, + ), + ), + ); + + final client = ArbiterServiceClient(channel); + final tx = StreamController(); + final rx = client.userAgent(tx.stream); + + return Connection(channel: channel, tx: tx, rx: rx); +} + +List _formatChallenge(AuthChallenge challenge, List pubkey) { + final encodedPubkey = base64Encode(pubkey); + final payload = "${challenge.nonce}:$encodedPubkey"; + return utf8.encode(payload); +} diff --git a/useragent/lib/features/connection/connection.dart b/useragent/lib/features/connection/connection.dart index 6020c39..726a8d5 100644 --- a/useragent/lib/features/connection/connection.dart +++ b/useragent/lib/features/connection/connection.dart @@ -1,21 +1,13 @@ import 'dart:async'; -import 'dart:convert'; -import 'package:arbiter/features/connection/server_info_storage.dart'; -import 'package:arbiter/features/identity/pk_manager.dart'; -import 'package:arbiter/proto/arbiter.pbgrpc.dart'; -import 'package:arbiter/proto/evm.pb.dart'; import 'package:arbiter/proto/user_agent.pb.dart'; -import 'package:cryptography/cryptography.dart'; import 'package:grpc/grpc.dart'; import 'package:mtcore/markettakers.dart'; -import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart'; class Connection { final ClientChannel channel; final StreamController _tx; final StreamIterator _rx; - Future _requestQueue = Future.value(); Connection({ required this.channel, @@ -25,6 +17,7 @@ class Connection { _rx = StreamIterator(rx); Future send(UserAgentRequest request) async { + talker.debug('Sending request: ${request.toDebugString()}'); _tx.add(request); } @@ -33,6 +26,7 @@ class Connection { if (!hasValue) { throw Exception('Connection closed while waiting for server response.'); } + talker.debug('Received response: ${_rx.current.toDebugString()}'); return _rx.current; } @@ -41,258 +35,3 @@ class Connection { await channel.shutdown(); } } - -Future _connect(StoredServerInfo serverInfo) async { - final channel = ClientChannel( - serverInfo.address, - port: serverInfo.port, - options: ChannelOptions( - connectTimeout: const Duration(seconds: 10), - credentials: ChannelCredentials.secure( - onBadCertificate: (cert, host) { - return true; - }, - ), - ), - ); - - final client = ArbiterServiceClient(channel); - final tx = StreamController(); - - final rx = client.userAgent(tx.stream); - - return Connection(channel: channel, tx: tx, rx: rx); -} - -List formatChallenge(AuthChallenge challenge, List pubkey) { - final encodedPubkey = base64Encode(pubkey); - final payload = "${challenge.nonce}:$encodedPubkey"; - return utf8.encode(payload); -} - -const _vaultKeyAssociatedData = 'arbiter.vault.password'; - -Future> listEvmWallets(Connection connection) async { - await connection.send(UserAgentRequest(evmWalletList: Empty())); - - final response = await connection.receive(); - if (!response.hasEvmWalletList()) { - throw Exception( - 'Expected EVM wallet list response, got ${response.whichPayload()}', - ); - } - - final result = response.evmWalletList; - switch (result.whichResult()) { - case WalletListResponse_Result.wallets: - return result.wallets.wallets.toList(growable: false); - case WalletListResponse_Result.error: - throw Exception(_describeEvmError(result.error)); - case WalletListResponse_Result.notSet: - throw Exception('EVM wallet list response was empty.'); - } -} - -Future createEvmWallet(Connection connection) async { - await connection.send(UserAgentRequest(evmWalletCreate: Empty())); - - final response = await connection.receive(); - if (!response.hasEvmWalletCreate()) { - throw Exception( - 'Expected EVM wallet create response, got ${response.whichPayload()}', - ); - } - - final result = response.evmWalletCreate; - switch (result.whichResult()) { - case WalletCreateResponse_Result.wallet: - return; - case WalletCreateResponse_Result.error: - throw Exception(_describeEvmError(result.error)); - case WalletCreateResponse_Result.notSet: - throw Exception('Wallet creation returned no result.'); - } -} - -Future bootstrapVault( - Connection connection, - String password, -) async { - final encryptedKey = await _encryptVaultKeyMaterial(connection, password); - - await connection.send( - UserAgentRequest( - bootstrapEncryptedKey: BootstrapEncryptedKey( - nonce: encryptedKey.nonce, - ciphertext: encryptedKey.ciphertext, - associatedData: encryptedKey.associatedData, - ), - ), - ); - - final response = await connection.receive(); - if (!response.hasBootstrapResult()) { - throw Exception( - 'Expected bootstrap result, got ${response.whichPayload()}', - ); - } - - return response.bootstrapResult; -} - -Future unsealVault(Connection connection, String password) async { - final encryptedKey = await _encryptVaultKeyMaterial(connection, password); - - await connection.send( - UserAgentRequest( - unsealEncryptedKey: UnsealEncryptedKey( - nonce: encryptedKey.nonce, - ciphertext: encryptedKey.ciphertext, - associatedData: encryptedKey.associatedData, - ), - ), - ); - - final response = await connection.receive(); - if (!response.hasUnsealResult()) { - throw Exception('Expected unseal result, got ${response.whichPayload()}'); - } - - return response.unsealResult; -} - -Future<_EncryptedVaultKey> _encryptVaultKeyMaterial( - Connection connection, - String password, -) async { - final keyExchange = X25519(); - final cipher = Xchacha20.poly1305Aead(); - final clientKeyPair = await keyExchange.newKeyPair(); - final clientPublicKey = await clientKeyPair.extractPublicKey(); - - await connection.send( - UserAgentRequest( - unsealStart: UnsealStart(clientPubkey: clientPublicKey.bytes), - ), - ); - - final handshakeResponse = await connection.receive(); - if (!handshakeResponse.hasUnsealStartResponse()) { - throw Exception( - 'Expected unseal handshake response, got ${handshakeResponse.whichPayload()}', - ); - } - - final serverPublicKey = SimplePublicKey( - handshakeResponse.unsealStartResponse.serverPubkey, - type: KeyPairType.x25519, - ); - final sharedSecret = await keyExchange.sharedSecretKey( - keyPair: clientKeyPair, - remotePublicKey: serverPublicKey, - ); - - final secretBox = await cipher.encrypt( - utf8.encode(password), - secretKey: sharedSecret, - nonce: cipher.newNonce(), - aad: utf8.encode(_vaultKeyAssociatedData), - ); - - return _EncryptedVaultKey( - nonce: secretBox.nonce, - ciphertext: [...secretBox.cipherText, ...secretBox.mac.bytes], - associatedData: utf8.encode(_vaultKeyAssociatedData), - ); -} - -class _EncryptedVaultKey { - const _EncryptedVaultKey({ - required this.nonce, - required this.ciphertext, - required this.associatedData, - }); - - final List nonce; - final List ciphertext; - final List associatedData; -} - -Future connectAndAuthorize( - StoredServerInfo serverInfo, - KeyHandle key, { - String? bootstrapToken, -}) async { - try { - final connection = await _connect(serverInfo); - talker.info( - 'Connected to server at ${serverInfo.address}:${serverInfo.port}', - ); - final pubkey = await key.getPublicKey(); - - final req = AuthChallengeRequest( - pubkey: pubkey, - bootstrapToken: bootstrapToken, - keyType: switch (key.alg) { - KeyAlgorithm.rsa => KeyType.KEY_TYPE_RSA, - KeyAlgorithm.ecdsa => KeyType.KEY_TYPE_ECDSA_SECP256K1, - KeyAlgorithm.ed25519 => KeyType.KEY_TYPE_ED25519, - }, - ); - await connection.send(UserAgentRequest(authChallengeRequest: req)); - talker.info( - "Sent auth challenge request with pubkey ${base64Encode(pubkey)}", - ); - - final response = await connection.receive(); - - talker.info('Received response from server, checking auth flow...'); - - if (response.hasAuthOk()) { - talker.info('Authentication successful, connection established'); - return connection; - } - - if (!response.hasAuthChallenge()) { - throw Exception( - 'Expected AuthChallengeResponse, got ${response.whichPayload()}', - ); - } - - final challenge = formatChallenge(response.authChallenge, pubkey); - talker.info( - 'Received auth challenge, signing with key ${base64Encode(pubkey)}', - ); - - final signature = await key.sign(challenge); - - final solutionReq = AuthChallengeSolution(signature: signature); - await connection.send(UserAgentRequest(authChallengeSolution: solutionReq)); - - talker.info('Sent auth challenge solution, waiting for server response...'); - - final solutionResponse = await connection.receive(); - - if (!solutionResponse.hasAuthOk()) { - throw Exception( - 'Expected AuthChallengeSolutionResponse, got ${solutionResponse.whichPayload()}', - ); - } - - talker.info('Authentication successful, connection established'); - - return connection; - } catch (e) { - throw Exception('Failed to connect to server: $e'); - } -} - -String _describeEvmError(EvmError error) { - return switch (error) { - EvmError.EVM_ERROR_VAULT_SEALED => - 'The vault is sealed. Unseal it before using EVM wallets.', - EvmError.EVM_ERROR_INTERNAL || EvmError.EVM_ERROR_UNSPECIFIED => - 'The server failed to process the EVM request.', - _ => 'The server failed to process the EVM request.', - }; -} diff --git a/useragent/lib/features/connection/evm.dart b/useragent/lib/features/connection/evm.dart new file mode 100644 index 0000000..5ceb422 --- /dev/null +++ b/useragent/lib/features/connection/evm.dart @@ -0,0 +1,56 @@ +import 'package:arbiter/features/connection/connection.dart'; +import 'package:arbiter/proto/evm.pb.dart'; +import 'package:arbiter/proto/user_agent.pb.dart'; +import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart'; + +Future> listEvmWallets(Connection connection) async { + await connection.send(UserAgentRequest(evmWalletList: Empty())); + + final response = await connection.receive(); + if (!response.hasEvmWalletList()) { + throw Exception( + 'Expected EVM wallet list response, got ${response.whichPayload()}', + ); + } + + final result = response.evmWalletList; + switch (result.whichResult()) { + case WalletListResponse_Result.wallets: + return result.wallets.wallets.toList(growable: false); + case WalletListResponse_Result.error: + throw Exception(_describeEvmError(result.error)); + case WalletListResponse_Result.notSet: + throw Exception('EVM wallet list response was empty.'); + } +} + +Future createEvmWallet(Connection connection) async { + await connection.send(UserAgentRequest(evmWalletCreate: Empty())); + + final response = await connection.receive(); + if (!response.hasEvmWalletCreate()) { + throw Exception( + 'Expected EVM wallet create response, got ${response.whichPayload()}', + ); + } + + final result = response.evmWalletCreate; + switch (result.whichResult()) { + case WalletCreateResponse_Result.wallet: + return; + case WalletCreateResponse_Result.error: + throw Exception(_describeEvmError(result.error)); + case WalletCreateResponse_Result.notSet: + throw Exception('Wallet creation returned no result.'); + } +} + +String _describeEvmError(EvmError error) { + return switch (error) { + EvmError.EVM_ERROR_VAULT_SEALED => + 'The vault is sealed. Unseal it before using EVM wallets.', + EvmError.EVM_ERROR_INTERNAL || EvmError.EVM_ERROR_UNSPECIFIED => + 'The server failed to process the EVM request.', + _ => 'The server failed to process the EVM request.', + }; +} diff --git a/useragent/lib/features/connection/evm/grants.dart b/useragent/lib/features/connection/evm/grants.dart new file mode 100644 index 0000000..f4bfd6f --- /dev/null +++ b/useragent/lib/features/connection/evm/grants.dart @@ -0,0 +1,122 @@ +import 'package:arbiter/features/connection/connection.dart'; +import 'package:arbiter/proto/evm.pb.dart'; +import 'package:arbiter/proto/user_agent.pb.dart'; +import 'package:fixnum/fixnum.dart'; +import 'package:protobuf/well_known_types/google/protobuf/timestamp.pb.dart'; + +Future> listEvmGrants( + Connection connection, { + int? walletId, +}) async { + final request = EvmGrantListRequest(); + if (walletId != null) { + request.walletId = walletId; + } + + await connection.send(UserAgentRequest(evmGrantList: request)); + + final response = await connection.receive(); + if (!response.hasEvmGrantList()) { + throw Exception( + 'Expected EVM grant list response, got ${response.whichPayload()}', + ); + } + + final result = response.evmGrantList; + switch (result.whichResult()) { + case EvmGrantListResponse_Result.grants: + return result.grants.grants.toList(growable: false); + case EvmGrantListResponse_Result.error: + throw Exception(_describeGrantError(result.error)); + case EvmGrantListResponse_Result.notSet: + throw Exception('EVM grant list response was empty.'); + } +} + +Future createEvmGrant( + Connection connection, { + required int clientId, + required int walletId, + required Int64 chainId, + DateTime? validFrom, + DateTime? validUntil, + List? maxGasFeePerGas, + List? maxPriorityFeePerGas, + TransactionRateLimit? rateLimit, + required SpecificGrant specific, +}) async { + await connection.send( + UserAgentRequest( + evmGrantCreate: EvmGrantCreateRequest( + clientId: clientId, + shared: SharedSettings( + walletId: walletId, + chainId: chainId, + validFrom: validFrom == null ? null : _toTimestamp(validFrom), + validUntil: validUntil == null ? null : _toTimestamp(validUntil), + maxGasFeePerGas: maxGasFeePerGas, + maxPriorityFeePerGas: maxPriorityFeePerGas, + rateLimit: rateLimit, + ), + specific: specific, + ), + ), + ); + + final response = await connection.receive(); + if (!response.hasEvmGrantCreate()) { + throw Exception( + 'Expected EVM grant create response, got ${response.whichPayload()}', + ); + } + + final result = response.evmGrantCreate; + switch (result.whichResult()) { + case EvmGrantCreateResponse_Result.grantId: + return result.grantId; + case EvmGrantCreateResponse_Result.error: + throw Exception(_describeGrantError(result.error)); + case EvmGrantCreateResponse_Result.notSet: + throw Exception('Grant creation returned no result.'); + } +} + +Future deleteEvmGrant(Connection connection, int grantId) async { + await connection.send( + UserAgentRequest(evmGrantDelete: EvmGrantDeleteRequest(grantId: grantId)), + ); + + final response = await connection.receive(); + if (!response.hasEvmGrantDelete()) { + throw Exception( + 'Expected EVM grant delete response, got ${response.whichPayload()}', + ); + } + + final result = response.evmGrantDelete; + switch (result.whichResult()) { + case EvmGrantDeleteResponse_Result.ok: + return; + case EvmGrantDeleteResponse_Result.error: + throw Exception(_describeGrantError(result.error)); + case EvmGrantDeleteResponse_Result.notSet: + throw Exception('Grant revoke returned no result.'); + } +} + +Timestamp _toTimestamp(DateTime value) { + final utc = value.toUtc(); + return Timestamp() + ..seconds = Int64(utc.millisecondsSinceEpoch ~/ 1000) + ..nanos = (utc.microsecondsSinceEpoch % 1000000) * 1000; +} + +String _describeGrantError(EvmError error) { + return switch (error) { + EvmError.EVM_ERROR_VAULT_SEALED => + 'The vault is sealed. Unseal it before using EVM grants.', + EvmError.EVM_ERROR_INTERNAL || EvmError.EVM_ERROR_UNSPECIFIED => + 'The server failed to process the EVM grant request.', + _ => 'The server failed to process the EVM grant request.', + }; +} diff --git a/useragent/lib/features/connection/server_info_storage.dart b/useragent/lib/features/connection/server_info_storage.dart index 39bec59..d84ca52 100644 --- a/useragent/lib/features/connection/server_info_storage.dart +++ b/useragent/lib/features/connection/server_info_storage.dart @@ -37,6 +37,7 @@ class SecureServerInfoStorage implements ServerInfoStorage { @override Future load() async { + return null; final rawValue = await _storage.read(key: _storageKey); if (rawValue == null) { return null; diff --git a/useragent/lib/features/connection/vault.dart b/useragent/lib/features/connection/vault.dart new file mode 100644 index 0000000..92f3048 --- /dev/null +++ b/useragent/lib/features/connection/vault.dart @@ -0,0 +1,107 @@ +import 'package:arbiter/features/connection/connection.dart'; +import 'package:arbiter/proto/user_agent.pb.dart'; +import 'package:cryptography/cryptography.dart'; + +const _vaultKeyAssociatedData = 'arbiter.vault.password'; + +Future bootstrapVault( + Connection connection, + String password, +) async { + final encryptedKey = await _encryptVaultKeyMaterial(connection, password); + + await connection.send( + UserAgentRequest( + bootstrapEncryptedKey: BootstrapEncryptedKey( + nonce: encryptedKey.nonce, + ciphertext: encryptedKey.ciphertext, + associatedData: encryptedKey.associatedData, + ), + ), + ); + + final response = await connection.receive(); + if (!response.hasBootstrapResult()) { + throw Exception( + 'Expected bootstrap result, got ${response.whichPayload()}', + ); + } + + return response.bootstrapResult; +} + +Future unsealVault(Connection connection, String password) async { + final encryptedKey = await _encryptVaultKeyMaterial(connection, password); + + await connection.send( + UserAgentRequest( + unsealEncryptedKey: UnsealEncryptedKey( + nonce: encryptedKey.nonce, + ciphertext: encryptedKey.ciphertext, + associatedData: encryptedKey.associatedData, + ), + ), + ); + + final response = await connection.receive(); + if (!response.hasUnsealResult()) { + throw Exception('Expected unseal result, got ${response.whichPayload()}'); + } + + return response.unsealResult; +} + +Future<_EncryptedVaultKey> _encryptVaultKeyMaterial( + Connection connection, + String password, +) async { + final keyExchange = X25519(); + final cipher = Xchacha20.poly1305Aead(); + final clientKeyPair = await keyExchange.newKeyPair(); + final clientPublicKey = await clientKeyPair.extractPublicKey(); + + await connection.send( + UserAgentRequest(unsealStart: UnsealStart(clientPubkey: clientPublicKey.bytes)), + ); + + final handshakeResponse = await connection.receive(); + if (!handshakeResponse.hasUnsealStartResponse()) { + throw Exception( + 'Expected unseal handshake response, got ${handshakeResponse.whichPayload()}', + ); + } + + final serverPublicKey = SimplePublicKey( + handshakeResponse.unsealStartResponse.serverPubkey, + type: KeyPairType.x25519, + ); + final sharedSecret = await keyExchange.sharedSecretKey( + keyPair: clientKeyPair, + remotePublicKey: serverPublicKey, + ); + + final secretBox = await cipher.encrypt( + password.codeUnits, + secretKey: sharedSecret, + nonce: cipher.newNonce(), + aad: _vaultKeyAssociatedData.codeUnits, + ); + + return _EncryptedVaultKey( + nonce: secretBox.nonce, + ciphertext: [...secretBox.cipherText, ...secretBox.mac.bytes], + associatedData: _vaultKeyAssociatedData.codeUnits, + ); +} + +class _EncryptedVaultKey { + const _EncryptedVaultKey({ + required this.nonce, + required this.ciphertext, + required this.associatedData, + }); + + final List nonce; + final List ciphertext; + final List associatedData; +} diff --git a/useragent/lib/providers/connection/connection_manager.dart b/useragent/lib/providers/connection/connection_manager.dart index 08f8d0f..9eda993 100644 --- a/useragent/lib/providers/connection/connection_manager.dart +++ b/useragent/lib/providers/connection/connection_manager.dart @@ -1,3 +1,4 @@ +import 'package:arbiter/features/connection/auth.dart'; import 'package:arbiter/features/connection/connection.dart'; import 'package:arbiter/providers/connection/bootstrap_token.dart'; import 'package:arbiter/providers/key.dart'; diff --git a/useragent/lib/providers/evm.dart b/useragent/lib/providers/evm.dart index 497fc7e..7cb89f3 100644 --- a/useragent/lib/providers/evm.dart +++ b/useragent/lib/providers/evm.dart @@ -1,4 +1,4 @@ -import 'package:arbiter/features/connection/connection.dart'; +import 'package:arbiter/features/connection/evm.dart'; import 'package:arbiter/proto/evm.pb.dart'; import 'package:arbiter/providers/connection/connection_manager.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/useragent/lib/providers/evm_grants.dart b/useragent/lib/providers/evm_grants.dart new file mode 100644 index 0000000..ae4a817 --- /dev/null +++ b/useragent/lib/providers/evm_grants.dart @@ -0,0 +1,120 @@ +import 'package:arbiter/features/connection/evm/grants.dart'; +import 'package:arbiter/proto/evm.pb.dart'; +import 'package:arbiter/providers/connection/connection_manager.dart'; +import 'package:fixnum/fixnum.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:hooks_riverpod/experimental/mutation.dart'; +import 'package:mtcore/markettakers.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'evm_grants.freezed.dart'; +part 'evm_grants.g.dart'; + +final createEvmGrantMutation = Mutation(); +final revokeEvmGrantMutation = Mutation(); + +@freezed +abstract class EvmGrantsState with _$EvmGrantsState { + const EvmGrantsState._(); + + const factory EvmGrantsState({ + required List grants, + @Default(false) bool showRevoked, + }) = _EvmGrantsState; + + bool get revokedFilterBackedByServer => false; +} + +@riverpod +class EvmGrants extends _$EvmGrants { + @override + Future build() async { + final connection = await ref.watch(connectionManagerProvider.future); + if (connection == null) { + return null; + } + + try { + final grants = await listEvmGrants(connection); + return EvmGrantsState(grants: grants); + } catch (e, st) { + talker.handle(e, st); + rethrow; + } + } + + void toggleShowRevoked(bool value) { + final current = state.asData?.value; + if (current == null) { + return; + } + state = AsyncData(current.copyWith(showRevoked: value)); + } + + Future refresh() async { + final connection = await ref.read(connectionManagerProvider.future); + if (connection == null) { + state = const AsyncData(null); + return; + } + + final previous = state.asData?.value; + state = const AsyncLoading(); + + state = await AsyncValue.guard(() async { + final grants = await listEvmGrants(connection); + return EvmGrantsState( + grants: grants, + showRevoked: previous?.showRevoked ?? false, + ); + }); + } +} + +Future executeCreateEvmGrant( + MutationTarget ref, { + required int clientId, + required int walletId, + required Int64 chainId, + DateTime? validFrom, + DateTime? validUntil, + List? maxGasFeePerGas, + List? maxPriorityFeePerGas, + TransactionRateLimit? rateLimit, + required SpecificGrant specific, +}) { + return createEvmGrantMutation.run(ref, (tsx) async { + final connection = await tsx.get(connectionManagerProvider.future); + if (connection == null) { + throw Exception('Not connected to the server.'); + } + + final grantId = await createEvmGrant( + connection, + clientId: clientId, + walletId: walletId, + chainId: chainId, + validFrom: validFrom, + validUntil: validUntil, + maxGasFeePerGas: maxGasFeePerGas, + maxPriorityFeePerGas: maxPriorityFeePerGas, + rateLimit: rateLimit, + specific: specific, + ); + + await tsx.get(evmGrantsProvider.notifier).refresh(); + return grantId; + }); +} + +Future executeRevokeEvmGrant(MutationTarget ref, {required int grantId}) { + return revokeEvmGrantMutation.run(ref, (tsx) async { + final connection = await tsx.get(connectionManagerProvider.future); + if (connection == null) { + throw Exception('Not connected to the server.'); + } + + await deleteEvmGrant(connection, grantId); + await tsx.get(evmGrantsProvider.notifier).refresh(); + }); +} diff --git a/useragent/lib/providers/evm_grants.freezed.dart b/useragent/lib/providers/evm_grants.freezed.dart new file mode 100644 index 0000000..9797bb7 --- /dev/null +++ b/useragent/lib/providers/evm_grants.freezed.dart @@ -0,0 +1,280 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'evm_grants.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$EvmGrantsState { + + List get grants; bool get showRevoked; +/// Create a copy of EvmGrantsState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$EvmGrantsStateCopyWith get copyWith => _$EvmGrantsStateCopyWithImpl(this as EvmGrantsState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is EvmGrantsState&&const DeepCollectionEquality().equals(other.grants, grants)&&(identical(other.showRevoked, showRevoked) || other.showRevoked == showRevoked)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(grants),showRevoked); + +@override +String toString() { + return 'EvmGrantsState(grants: $grants, showRevoked: $showRevoked)'; +} + + +} + +/// @nodoc +abstract mixin class $EvmGrantsStateCopyWith<$Res> { + factory $EvmGrantsStateCopyWith(EvmGrantsState value, $Res Function(EvmGrantsState) _then) = _$EvmGrantsStateCopyWithImpl; +@useResult +$Res call({ + List grants, bool showRevoked +}); + + + + +} +/// @nodoc +class _$EvmGrantsStateCopyWithImpl<$Res> + implements $EvmGrantsStateCopyWith<$Res> { + _$EvmGrantsStateCopyWithImpl(this._self, this._then); + + final EvmGrantsState _self; + final $Res Function(EvmGrantsState) _then; + +/// Create a copy of EvmGrantsState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? grants = null,Object? showRevoked = null,}) { + return _then(_self.copyWith( +grants: null == grants ? _self.grants : grants // ignore: cast_nullable_to_non_nullable +as List,showRevoked: null == showRevoked ? _self.showRevoked : showRevoked // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +} + + +/// Adds pattern-matching-related methods to [EvmGrantsState]. +extension EvmGrantsStatePatterns on EvmGrantsState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _EvmGrantsState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _EvmGrantsState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _EvmGrantsState value) $default,){ +final _that = this; +switch (_that) { +case _EvmGrantsState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _EvmGrantsState value)? $default,){ +final _that = this; +switch (_that) { +case _EvmGrantsState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( List grants, bool showRevoked)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _EvmGrantsState() when $default != null: +return $default(_that.grants,_that.showRevoked);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( List grants, bool showRevoked) $default,) {final _that = this; +switch (_that) { +case _EvmGrantsState(): +return $default(_that.grants,_that.showRevoked);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List grants, bool showRevoked)? $default,) {final _that = this; +switch (_that) { +case _EvmGrantsState() when $default != null: +return $default(_that.grants,_that.showRevoked);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _EvmGrantsState extends EvmGrantsState { + const _EvmGrantsState({required final List grants, this.showRevoked = false}): _grants = grants,super._(); + + + final List _grants; +@override List get grants { + if (_grants is EqualUnmodifiableListView) return _grants; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_grants); +} + +@override@JsonKey() final bool showRevoked; + +/// Create a copy of EvmGrantsState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$EvmGrantsStateCopyWith<_EvmGrantsState> get copyWith => __$EvmGrantsStateCopyWithImpl<_EvmGrantsState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _EvmGrantsState&&const DeepCollectionEquality().equals(other._grants, _grants)&&(identical(other.showRevoked, showRevoked) || other.showRevoked == showRevoked)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_grants),showRevoked); + +@override +String toString() { + return 'EvmGrantsState(grants: $grants, showRevoked: $showRevoked)'; +} + + +} + +/// @nodoc +abstract mixin class _$EvmGrantsStateCopyWith<$Res> implements $EvmGrantsStateCopyWith<$Res> { + factory _$EvmGrantsStateCopyWith(_EvmGrantsState value, $Res Function(_EvmGrantsState) _then) = __$EvmGrantsStateCopyWithImpl; +@override @useResult +$Res call({ + List grants, bool showRevoked +}); + + + + +} +/// @nodoc +class __$EvmGrantsStateCopyWithImpl<$Res> + implements _$EvmGrantsStateCopyWith<$Res> { + __$EvmGrantsStateCopyWithImpl(this._self, this._then); + + final _EvmGrantsState _self; + final $Res Function(_EvmGrantsState) _then; + +/// Create a copy of EvmGrantsState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? grants = null,Object? showRevoked = null,}) { + return _then(_EvmGrantsState( +grants: null == grants ? _self._grants : grants // ignore: cast_nullable_to_non_nullable +as List,showRevoked: null == showRevoked ? _self.showRevoked : showRevoked // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + + +} + +// dart format on diff --git a/useragent/lib/providers/evm_grants.g.dart b/useragent/lib/providers/evm_grants.g.dart new file mode 100644 index 0000000..f0e97e8 --- /dev/null +++ b/useragent/lib/providers/evm_grants.g.dart @@ -0,0 +1,54 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'evm_grants.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(EvmGrants) +final evmGrantsProvider = EvmGrantsProvider._(); + +final class EvmGrantsProvider + extends $AsyncNotifierProvider { + EvmGrantsProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'evmGrantsProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$evmGrantsHash(); + + @$internal + @override + EvmGrants create() => EvmGrants(); +} + +String _$evmGrantsHash() => r'd71ec12bbc1b412f11fdbaae27382b289f8a3538'; + +abstract class _$EvmGrants extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref, EvmGrantsState?>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, EvmGrantsState?>, + AsyncValue, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/useragent/lib/router.dart b/useragent/lib/router.dart index 0e7fea9..977b75b 100644 --- a/useragent/lib/router.dart +++ b/useragent/lib/router.dart @@ -10,12 +10,14 @@ class Router extends RootStackRouter { AutoRoute(page: ServerInfoSetupRoute.page, path: '/server-info'), AutoRoute(page: ServerConnectionRoute.page, path: '/server-connection'), AutoRoute(page: VaultSetupRoute.page, path: '/vault'), + AutoRoute(page: CreateEvmGrantRoute.page, path: '/evm-grants/create'), AutoRoute( page: DashboardRouter.page, path: '/dashboard', children: [ AutoRoute(page: EvmRoute.page, path: 'evm'), + AutoRoute(page: EvmGrantsRoute.page, path: 'grants'), AutoRoute(page: AboutRoute.page, path: 'about'), ], ), diff --git a/useragent/lib/router.gr.dart b/useragent/lib/router.gr.dart index da281ae..82c8625 100644 --- a/useragent/lib/router.gr.dart +++ b/useragent/lib/router.gr.dart @@ -10,24 +10,26 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:arbiter/screens/bootstrap.dart' as _i2; -import 'package:arbiter/screens/dashboard.dart' as _i3; +import 'package:arbiter/screens/dashboard.dart' as _i4; import 'package:arbiter/screens/dashboard/about.dart' as _i1; -import 'package:arbiter/screens/dashboard/evm.dart' as _i4; -import 'package:arbiter/screens/server_connection.dart' as _i5; -import 'package:arbiter/screens/server_info_setup.dart' as _i6; -import 'package:arbiter/screens/vault_setup.dart' as _i7; -import 'package:auto_route/auto_route.dart' as _i8; -import 'package:flutter/material.dart' as _i9; +import 'package:arbiter/screens/dashboard/evm.dart' as _i6; +import 'package:arbiter/screens/dashboard/evm_grant_create.dart' as _i3; +import 'package:arbiter/screens/dashboard/evm_grants.dart' as _i5; +import 'package:arbiter/screens/server_connection.dart' as _i7; +import 'package:arbiter/screens/server_info_setup.dart' as _i8; +import 'package:arbiter/screens/vault_setup.dart' as _i9; +import 'package:auto_route/auto_route.dart' as _i10; +import 'package:flutter/material.dart' as _i11; /// generated route for /// [_i1.AboutScreen] -class AboutRoute extends _i8.PageRouteInfo { - const AboutRoute({List<_i8.PageRouteInfo>? children}) +class AboutRoute extends _i10.PageRouteInfo { + const AboutRoute({List<_i10.PageRouteInfo>? children}) : super(AboutRoute.name, initialChildren: children); static const String name = 'AboutRoute'; - static _i8.PageInfo page = _i8.PageInfo( + static _i10.PageInfo page = _i10.PageInfo( name, builder: (data) { return const _i1.AboutScreen(); @@ -37,13 +39,13 @@ class AboutRoute extends _i8.PageRouteInfo { /// generated route for /// [_i2.Bootstrap] -class Bootstrap extends _i8.PageRouteInfo { - const Bootstrap({List<_i8.PageRouteInfo>? children}) +class Bootstrap extends _i10.PageRouteInfo { + const Bootstrap({List<_i10.PageRouteInfo>? children}) : super(Bootstrap.name, initialChildren: children); static const String name = 'Bootstrap'; - static _i8.PageInfo page = _i8.PageInfo( + static _i10.PageInfo page = _i10.PageInfo( name, builder: (data) { return const _i2.Bootstrap(); @@ -52,45 +54,77 @@ class Bootstrap extends _i8.PageRouteInfo { } /// generated route for -/// [_i3.DashboardRouter] -class DashboardRouter extends _i8.PageRouteInfo { - const DashboardRouter({List<_i8.PageRouteInfo>? children}) +/// [_i3.CreateEvmGrantScreen] +class CreateEvmGrantRoute extends _i10.PageRouteInfo { + const CreateEvmGrantRoute({List<_i10.PageRouteInfo>? children}) + : super(CreateEvmGrantRoute.name, initialChildren: children); + + static const String name = 'CreateEvmGrantRoute'; + + static _i10.PageInfo page = _i10.PageInfo( + name, + builder: (data) { + return const _i3.CreateEvmGrantScreen(); + }, + ); +} + +/// generated route for +/// [_i4.DashboardRouter] +class DashboardRouter extends _i10.PageRouteInfo { + const DashboardRouter({List<_i10.PageRouteInfo>? children}) : super(DashboardRouter.name, initialChildren: children); static const String name = 'DashboardRouter'; - static _i8.PageInfo page = _i8.PageInfo( + static _i10.PageInfo page = _i10.PageInfo( name, builder: (data) { - return const _i3.DashboardRouter(); + return const _i4.DashboardRouter(); }, ); } /// generated route for -/// [_i4.EvmScreen] -class EvmRoute extends _i8.PageRouteInfo { - const EvmRoute({List<_i8.PageRouteInfo>? children}) +/// [_i5.EvmGrantsScreen] +class EvmGrantsRoute extends _i10.PageRouteInfo { + const EvmGrantsRoute({List<_i10.PageRouteInfo>? children}) + : super(EvmGrantsRoute.name, initialChildren: children); + + static const String name = 'EvmGrantsRoute'; + + static _i10.PageInfo page = _i10.PageInfo( + name, + builder: (data) { + return const _i5.EvmGrantsScreen(); + }, + ); +} + +/// generated route for +/// [_i6.EvmScreen] +class EvmRoute extends _i10.PageRouteInfo { + const EvmRoute({List<_i10.PageRouteInfo>? children}) : super(EvmRoute.name, initialChildren: children); static const String name = 'EvmRoute'; - static _i8.PageInfo page = _i8.PageInfo( + static _i10.PageInfo page = _i10.PageInfo( name, builder: (data) { - return const _i4.EvmScreen(); + return const _i6.EvmScreen(); }, ); } /// generated route for -/// [_i5.ServerConnectionScreen] +/// [_i7.ServerConnectionScreen] class ServerConnectionRoute - extends _i8.PageRouteInfo { + extends _i10.PageRouteInfo { ServerConnectionRoute({ - _i9.Key? key, + _i11.Key? key, String? arbiterUrl, - List<_i8.PageRouteInfo>? children, + List<_i10.PageRouteInfo>? children, }) : super( ServerConnectionRoute.name, args: ServerConnectionRouteArgs(key: key, arbiterUrl: arbiterUrl), @@ -99,13 +133,13 @@ class ServerConnectionRoute static const String name = 'ServerConnectionRoute'; - static _i8.PageInfo page = _i8.PageInfo( + static _i10.PageInfo page = _i10.PageInfo( name, builder: (data) { final args = data.argsAs( orElse: () => const ServerConnectionRouteArgs(), ); - return _i5.ServerConnectionScreen( + return _i7.ServerConnectionScreen( key: args.key, arbiterUrl: args.arbiterUrl, ); @@ -116,7 +150,7 @@ class ServerConnectionRoute class ServerConnectionRouteArgs { const ServerConnectionRouteArgs({this.key, this.arbiterUrl}); - final _i9.Key? key; + final _i11.Key? key; final String? arbiterUrl; @@ -137,33 +171,33 @@ class ServerConnectionRouteArgs { } /// generated route for -/// [_i6.ServerInfoSetupScreen] -class ServerInfoSetupRoute extends _i8.PageRouteInfo { - const ServerInfoSetupRoute({List<_i8.PageRouteInfo>? children}) +/// [_i8.ServerInfoSetupScreen] +class ServerInfoSetupRoute extends _i10.PageRouteInfo { + const ServerInfoSetupRoute({List<_i10.PageRouteInfo>? children}) : super(ServerInfoSetupRoute.name, initialChildren: children); static const String name = 'ServerInfoSetupRoute'; - static _i8.PageInfo page = _i8.PageInfo( + static _i10.PageInfo page = _i10.PageInfo( name, builder: (data) { - return const _i6.ServerInfoSetupScreen(); + return const _i8.ServerInfoSetupScreen(); }, ); } /// generated route for -/// [_i7.VaultSetupScreen] -class VaultSetupRoute extends _i8.PageRouteInfo { - const VaultSetupRoute({List<_i8.PageRouteInfo>? children}) +/// [_i9.VaultSetupScreen] +class VaultSetupRoute extends _i10.PageRouteInfo { + const VaultSetupRoute({List<_i10.PageRouteInfo>? children}) : super(VaultSetupRoute.name, initialChildren: children); static const String name = 'VaultSetupRoute'; - static _i8.PageInfo page = _i8.PageInfo( + static _i10.PageInfo page = _i10.PageInfo( name, builder: (data) { - return const _i7.VaultSetupScreen(); + return const _i9.VaultSetupScreen(); }, ); } diff --git a/useragent/lib/screens/bootstrap.dart b/useragent/lib/screens/bootstrap.dart index d2e2172..25f8657 100644 --- a/useragent/lib/screens/bootstrap.dart +++ b/useragent/lib/screens/bootstrap.dart @@ -34,6 +34,6 @@ class Bootstrap extends HookConsumerWidget { [stages], ); - return bootstrapper; + return Scaffold(body: bootstrapper); } } diff --git a/useragent/lib/screens/dashboard.dart b/useragent/lib/screens/dashboard.dart index 82acdaa..e89a8d3 100644 --- a/useragent/lib/screens/dashboard.dart +++ b/useragent/lib/screens/dashboard.dart @@ -5,7 +5,7 @@ import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; const breakpoints = MaterialAdaptiveBreakpoints(); -final routes = [EvmRoute(), AboutRoute()]; +final routes = [const EvmRoute(), const EvmGrantsRoute(), const AboutRoute()]; @RoutePage() class DashboardRouter extends StatelessWidget { @@ -30,6 +30,11 @@ class DashboardRouter extends StatelessWidget { selectedIcon: Icon(Icons.account_balance_wallet), label: "Wallets", ), + NavigationDestination( + icon: Icon(Icons.rule_folder_outlined), + selectedIcon: Icon(Icons.rule_folder), + label: "Grants", + ), NavigationDestination( icon: Icon(Icons.info_outline), selectedIcon: Icon(Icons.info), diff --git a/useragent/lib/screens/dashboard/evm_grant_create.dart b/useragent/lib/screens/dashboard/evm_grant_create.dart new file mode 100644 index 0000000..51cad20 --- /dev/null +++ b/useragent/lib/screens/dashboard/evm_grant_create.dart @@ -0,0 +1,824 @@ +import 'package:arbiter/proto/evm.pb.dart'; +import 'package:arbiter/providers/evm.dart'; +import 'package:arbiter/providers/evm_grants.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:fixnum/fixnum.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:hooks_riverpod/experimental/mutation.dart'; +import 'package:sizer/sizer.dart'; + +@RoutePage() +class CreateEvmGrantScreen extends HookConsumerWidget { + const CreateEvmGrantScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final wallets = ref.watch(evmProvider).asData?.value ?? const []; + final createMutation = ref.watch(createEvmGrantMutation); + + final selectedWalletIndex = useState(wallets.isEmpty ? null : 0); + final clientIdController = useTextEditingController(); + final chainIdController = useTextEditingController(text: '1'); + final gasFeeController = useTextEditingController(); + final priorityFeeController = useTextEditingController(); + final txCountController = useTextEditingController(); + final txWindowController = useTextEditingController(); + final recipientsController = useTextEditingController(); + final etherVolumeController = useTextEditingController(); + final etherVolumeWindowController = useTextEditingController(); + final tokenContractController = useTextEditingController(); + final tokenTargetController = useTextEditingController(); + final validFrom = useState(null); + final validUntil = useState(null); + final grantType = useState( + SpecificGrant_Grant.etherTransfer, + ); + final tokenVolumeLimits = useState>([ + const _VolumeLimitValue(), + ]); + + Future submit() async { + final selectedWallet = selectedWalletIndex.value; + if (selectedWallet == null) { + _showCreateMessage(context, 'At least one wallet is required.'); + return; + } + + try { + final clientId = int.parse(clientIdController.text.trim()); + final chainId = Int64.parseInt(chainIdController.text.trim()); + final rateLimit = _buildRateLimit( + txCountController.text, + txWindowController.text, + ); + final specific = switch (grantType.value) { + SpecificGrant_Grant.etherTransfer => SpecificGrant( + etherTransfer: EtherTransferSettings( + targets: _parseAddresses(recipientsController.text), + limit: _buildVolumeLimit( + etherVolumeController.text, + etherVolumeWindowController.text, + ), + ), + ), + SpecificGrant_Grant.tokenTransfer => SpecificGrant( + tokenTransfer: TokenTransferSettings( + tokenContract: _parseHexAddress(tokenContractController.text), + target: tokenTargetController.text.trim().isEmpty + ? null + : _parseHexAddress(tokenTargetController.text), + volumeLimits: tokenVolumeLimits.value + .where((item) => item.amount.trim().isNotEmpty) + .map( + (item) => VolumeRateLimit( + maxVolume: _parseBigIntBytes(item.amount), + windowSecs: Int64.parseInt(item.windowSeconds), + ), + ) + .toList(), + ), + ), + _ => throw Exception('Unsupported grant type.'), + }; + + await executeCreateEvmGrant( + ref, + clientId: clientId, + walletId: selectedWallet + 1, + chainId: chainId, + validFrom: validFrom.value, + validUntil: validUntil.value, + maxGasFeePerGas: _optionalBigIntBytes(gasFeeController.text), + maxPriorityFeePerGas: _optionalBigIntBytes(priorityFeeController.text), + rateLimit: rateLimit, + specific: specific, + ); + if (!context.mounted) { + return; + } + context.router.pop(); + } catch (error) { + if (!context.mounted) { + return; + } + _showCreateMessage(context, _formatCreateError(error)); + } + } + + return Scaffold( + appBar: AppBar(title: const Text('Create EVM Grant')), + body: SafeArea( + child: ListView( + padding: EdgeInsets.fromLTRB(2.4.w, 2.h, 2.4.w, 3.2.h), + children: [ + _CreateIntroCard(walletCount: wallets.length), + SizedBox(height: 1.8.h), + _CreateSection( + title: 'Shared grant options', + children: [ + _WalletPickerField( + wallets: wallets, + selectedIndex: selectedWalletIndex.value, + onChanged: (value) => selectedWalletIndex.value = value, + ), + _NumberInputField( + controller: clientIdController, + label: 'Client ID', + hint: '42', + helper: + 'Manual for now. The app does not yet expose a client picker.', + ), + _NumberInputField( + controller: chainIdController, + label: 'Chain ID', + hint: '1', + ), + _ValidityWindowField( + validFrom: validFrom.value, + validUntil: validUntil.value, + onValidFromChanged: (value) => validFrom.value = value, + onValidUntilChanged: (value) => validUntil.value = value, + ), + _GasFeeOptionsField( + gasFeeController: gasFeeController, + priorityFeeController: priorityFeeController, + ), + _TransactionRateLimitField( + txCountController: txCountController, + txWindowController: txWindowController, + ), + ], + ), + SizedBox(height: 1.8.h), + _GrantTypeSelector( + value: grantType.value, + onChanged: (value) => grantType.value = value, + ), + SizedBox(height: 1.8.h), + _CreateSection( + title: 'Grant-specific options', + children: [ + if (grantType.value == SpecificGrant_Grant.etherTransfer) ...[ + _EtherTargetsField(controller: recipientsController), + _VolumeLimitField( + amountController: etherVolumeController, + windowController: etherVolumeWindowController, + title: 'Ether volume limit', + ), + ] else ...[ + _TokenContractField(controller: tokenContractController), + _TokenRecipientField(controller: tokenTargetController), + _TokenVolumeLimitsField( + values: tokenVolumeLimits.value, + onChanged: (values) => tokenVolumeLimits.value = values, + ), + ], + ], + ), + SizedBox(height: 2.2.h), + Align( + alignment: Alignment.centerRight, + child: FilledButton.icon( + onPressed: createMutation is MutationPending ? null : submit, + icon: createMutation is MutationPending + ? SizedBox( + width: 1.8.h, + height: 1.8.h, + child: const CircularProgressIndicator(strokeWidth: 2.2), + ) + : const Icon(Icons.check_rounded), + label: Text( + createMutation is MutationPending + ? 'Creating...' + : 'Create grant', + ), + ), + ), + ], + ), + ), + ); + } +} + +class _CreateIntroCard extends StatelessWidget { + const _CreateIntroCard({required this.walletCount}); + + final int walletCount; + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(2.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + gradient: const LinearGradient( + colors: [Color(0xFFF7F9FC), Color(0xFFFDF5EA)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + border: Border.all(color: const Color(0x1A17324A)), + ), + child: Text( + 'Compose shared constraints once, then switch between Ether and token transfer rules. $walletCount wallet${walletCount == 1 ? '' : 's'} currently available.', + style: Theme.of(context).textTheme.bodyLarge?.copyWith(height: 1.5), + ), + ); + } +} + +class _CreateSection extends StatelessWidget { + const _CreateSection({required this.title, required this.children}); + + final String title; + final List children; + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(2.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + color: Colors.white, + border: Border.all(color: const Color(0x1A17324A)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w800, + ), + ), + SizedBox(height: 1.4.h), + ...children.map( + (child) => Padding( + padding: EdgeInsets.only(bottom: 1.6.h), + child: child, + ), + ), + ], + ), + ); + } +} + +class _WalletPickerField extends StatelessWidget { + const _WalletPickerField({ + required this.wallets, + required this.selectedIndex, + required this.onChanged, + }); + + final List wallets; + final int? selectedIndex; + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + return DropdownButtonFormField( + initialValue: selectedIndex, + decoration: const InputDecoration( + labelText: 'Wallet', + helperText: + 'Uses the current wallet order. The API still does not expose stable wallet IDs directly.', + border: OutlineInputBorder(), + ), + items: [ + for (var i = 0; i < wallets.length; i++) + DropdownMenuItem( + value: i, + child: Text( + 'Wallet ${(i + 1).toString().padLeft(2, '0')} · ${_shortAddress(wallets[i].address)}', + ), + ), + ], + onChanged: wallets.isEmpty ? null : onChanged, + ); + } +} + +class _NumberInputField extends StatelessWidget { + const _NumberInputField({ + required this.controller, + required this.label, + required this.hint, + this.helper, + }); + + final TextEditingController controller; + final String label; + final String hint; + final String? helper; + + @override + Widget build(BuildContext context) { + return TextField( + controller: controller, + keyboardType: TextInputType.number, + decoration: InputDecoration( + labelText: label, + hintText: hint, + helperText: helper, + border: const OutlineInputBorder(), + ), + ); + } +} + +class _ValidityWindowField extends StatelessWidget { + const _ValidityWindowField({ + required this.validFrom, + required this.validUntil, + required this.onValidFromChanged, + required this.onValidUntilChanged, + }); + + final DateTime? validFrom; + final DateTime? validUntil; + final ValueChanged onValidFromChanged; + final ValueChanged onValidUntilChanged; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: _DateButtonField( + label: 'Valid from', + value: validFrom, + onChanged: onValidFromChanged, + ), + ), + SizedBox(width: 1.w), + Expanded( + child: _DateButtonField( + label: 'Valid until', + value: validUntil, + onChanged: onValidUntilChanged, + ), + ), + ], + ); + } +} + +class _DateButtonField extends StatelessWidget { + const _DateButtonField({ + required this.label, + required this.value, + required this.onChanged, + }); + + final String label; + final DateTime? value; + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + return OutlinedButton( + onPressed: () async { + final now = DateTime.now(); + final date = await showDatePicker( + context: context, + firstDate: DateTime(now.year - 5), + lastDate: DateTime(now.year + 10), + initialDate: value ?? now, + ); + if (date == null || !context.mounted) { + return; + } + final time = await showTimePicker( + context: context, + initialTime: TimeOfDay.fromDateTime(value ?? now), + ); + if (time == null) { + return; + } + onChanged( + DateTime(date.year, date.month, date.day, time.hour, time.minute), + ); + }, + onLongPress: value == null ? null : () => onChanged(null), + child: Padding( + padding: EdgeInsets.symmetric(vertical: 1.8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label), + SizedBox(height: 0.6.h), + Text(value?.toLocal().toString() ?? 'Not set'), + ], + ), + ), + ); + } +} + +class _GasFeeOptionsField extends StatelessWidget { + const _GasFeeOptionsField({ + required this.gasFeeController, + required this.priorityFeeController, + }); + + final TextEditingController gasFeeController; + final TextEditingController priorityFeeController; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: _NumberInputField( + controller: gasFeeController, + label: 'Max gas fee / gas', + hint: '1000000000', + ), + ), + SizedBox(width: 1.w), + Expanded( + child: _NumberInputField( + controller: priorityFeeController, + label: 'Max priority fee / gas', + hint: '100000000', + ), + ), + ], + ); + } +} + +class _TransactionRateLimitField extends StatelessWidget { + const _TransactionRateLimitField({ + required this.txCountController, + required this.txWindowController, + }); + + final TextEditingController txCountController; + final TextEditingController txWindowController; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: _NumberInputField( + controller: txCountController, + label: 'Tx count limit', + hint: '10', + ), + ), + SizedBox(width: 1.w), + Expanded( + child: _NumberInputField( + controller: txWindowController, + label: 'Window (seconds)', + hint: '3600', + ), + ), + ], + ); + } +} + +class _GrantTypeSelector extends StatelessWidget { + const _GrantTypeSelector({required this.value, required this.onChanged}); + + final SpecificGrant_Grant value; + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + return SegmentedButton( + segments: const [ + ButtonSegment( + value: SpecificGrant_Grant.etherTransfer, + label: Text('Ether'), + icon: Icon(Icons.bolt_rounded), + ), + ButtonSegment( + value: SpecificGrant_Grant.tokenTransfer, + label: Text('Token'), + icon: Icon(Icons.token_rounded), + ), + ], + selected: {value}, + onSelectionChanged: (selection) => onChanged(selection.first), + ); + } +} + +class _EtherTargetsField extends StatelessWidget { + const _EtherTargetsField({required this.controller}); + + final TextEditingController controller; + + @override + Widget build(BuildContext context) { + return TextField( + controller: controller, + minLines: 3, + maxLines: 6, + decoration: const InputDecoration( + labelText: 'Ether recipients', + hintText: 'One 0x address per line. Leave empty for unrestricted targets.', + border: OutlineInputBorder(), + ), + ); + } +} + +class _VolumeLimitField extends StatelessWidget { + const _VolumeLimitField({ + required this.amountController, + required this.windowController, + required this.title, + }); + + final TextEditingController amountController; + final TextEditingController windowController; + final String title; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: Theme.of(context).textTheme.labelLarge?.copyWith( + fontWeight: FontWeight.w800, + ), + ), + SizedBox(height: 0.8.h), + Row( + children: [ + Expanded( + child: _NumberInputField( + controller: amountController, + label: 'Max volume', + hint: '1000000000000000000', + ), + ), + SizedBox(width: 1.w), + Expanded( + child: _NumberInputField( + controller: windowController, + label: 'Window (seconds)', + hint: '86400', + ), + ), + ], + ), + ], + ); + } +} + +class _TokenContractField extends StatelessWidget { + const _TokenContractField({required this.controller}); + + final TextEditingController controller; + + @override + Widget build(BuildContext context) { + return TextField( + controller: controller, + decoration: const InputDecoration( + labelText: 'Token contract', + hintText: '0x...', + border: OutlineInputBorder(), + ), + ); + } +} + +class _TokenRecipientField extends StatelessWidget { + const _TokenRecipientField({required this.controller}); + + final TextEditingController controller; + + @override + Widget build(BuildContext context) { + return TextField( + controller: controller, + decoration: const InputDecoration( + labelText: 'Token recipient', + hintText: '0x... or leave empty for any recipient', + border: OutlineInputBorder(), + ), + ); + } +} + +class _TokenVolumeLimitsField extends StatelessWidget { + const _TokenVolumeLimitsField({ + required this.values, + required this.onChanged, + }); + + final List<_VolumeLimitValue> values; + final ValueChanged> onChanged; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + 'Token volume limits', + style: Theme.of(context).textTheme.labelLarge?.copyWith( + fontWeight: FontWeight.w800, + ), + ), + ), + TextButton.icon( + onPressed: () => + onChanged([...values, const _VolumeLimitValue()]), + icon: const Icon(Icons.add_rounded), + label: const Text('Add'), + ), + ], + ), + SizedBox(height: 0.8.h), + for (var i = 0; i < values.length; i++) + Padding( + padding: EdgeInsets.only(bottom: 1.h), + child: _TokenVolumeLimitRow( + value: values[i], + onChanged: (next) { + final updated = [...values]; + updated[i] = next; + onChanged(updated); + }, + onRemove: values.length == 1 + ? null + : () { + final updated = [...values]..removeAt(i); + onChanged(updated); + }, + ), + ), + ], + ); + } +} + +class _TokenVolumeLimitRow extends StatelessWidget { + const _TokenVolumeLimitRow({ + required this.value, + required this.onChanged, + required this.onRemove, + }); + + final _VolumeLimitValue value; + final ValueChanged<_VolumeLimitValue> onChanged; + final VoidCallback? onRemove; + + @override + Widget build(BuildContext context) { + final amountController = TextEditingController(text: value.amount); + final windowController = TextEditingController(text: value.windowSeconds); + + return Row( + children: [ + Expanded( + child: TextField( + controller: amountController, + onChanged: (next) => + onChanged(value.copyWith(amount: next)), + decoration: const InputDecoration( + labelText: 'Max volume', + border: OutlineInputBorder(), + ), + ), + ), + SizedBox(width: 1.w), + Expanded( + child: TextField( + controller: windowController, + onChanged: (next) => + onChanged(value.copyWith(windowSeconds: next)), + decoration: const InputDecoration( + labelText: 'Window (seconds)', + border: OutlineInputBorder(), + ), + ), + ), + SizedBox(width: 0.4.w), + IconButton( + onPressed: onRemove, + icon: const Icon(Icons.remove_circle_outline_rounded), + ), + ], + ); + } +} + +class _VolumeLimitValue { + const _VolumeLimitValue({this.amount = '', this.windowSeconds = ''}); + + final String amount; + final String windowSeconds; + + _VolumeLimitValue copyWith({String? amount, String? windowSeconds}) { + return _VolumeLimitValue( + amount: amount ?? this.amount, + windowSeconds: windowSeconds ?? this.windowSeconds, + ); + } +} + +TransactionRateLimit? _buildRateLimit(String countText, String windowText) { + if (countText.trim().isEmpty || windowText.trim().isEmpty) { + return null; + } + return TransactionRateLimit( + count: int.parse(countText.trim()), + windowSecs: Int64.parseInt(windowText.trim()), + ); +} + +VolumeRateLimit? _buildVolumeLimit(String amountText, String windowText) { + if (amountText.trim().isEmpty || windowText.trim().isEmpty) { + return null; + } + return VolumeRateLimit( + maxVolume: _parseBigIntBytes(amountText), + windowSecs: Int64.parseInt(windowText.trim()), + ); +} + +List? _optionalBigIntBytes(String value) { + if (value.trim().isEmpty) { + return null; + } + return _parseBigIntBytes(value); +} + +List _parseBigIntBytes(String value) { + final number = BigInt.parse(value.trim()); + if (number < BigInt.zero) { + throw Exception('Numeric values must be positive.'); + } + if (number == BigInt.zero) { + return [0]; + } + + var remaining = number; + final bytes = []; + while (remaining > BigInt.zero) { + bytes.insert(0, (remaining & BigInt.from(0xff)).toInt()); + remaining >>= 8; + } + return bytes; +} + +List> _parseAddresses(String input) { + final parts = input + .split(RegExp(r'[\n,]')) + .map((part) => part.trim()) + .where((part) => part.isNotEmpty); + return parts.map(_parseHexAddress).toList(); +} + +List _parseHexAddress(String value) { + final normalized = value.trim().replaceFirst(RegExp(r'^0x'), ''); + if (normalized.length != 40) { + throw Exception('Expected a 20-byte hex address.'); + } + return [ + for (var i = 0; i < normalized.length; i += 2) + int.parse(normalized.substring(i, i + 2), radix: 16), + ]; +} + +String _shortAddress(List bytes) { + final hex = bytes + .map((byte) => byte.toRadixString(16).padLeft(2, '0')) + .join(); + return '0x${hex.substring(0, 6)}...${hex.substring(hex.length - 4)}'; +} + +void _showCreateMessage(BuildContext context, String message) { + if (!context.mounted) { + return; + } + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message), behavior: SnackBarBehavior.floating), + ); +} + +String _formatCreateError(Object error) { + final text = error.toString(); + if (text.startsWith('Exception: ')) { + return text.substring('Exception: '.length); + } + return text; +} diff --git a/useragent/lib/screens/dashboard/evm_grants.dart b/useragent/lib/screens/dashboard/evm_grants.dart new file mode 100644 index 0000000..79710c3 --- /dev/null +++ b/useragent/lib/screens/dashboard/evm_grants.dart @@ -0,0 +1,1007 @@ +import 'dart:math' as math; + +import 'package:arbiter/proto/evm.pb.dart'; +import 'package:arbiter/providers/connection/connection_manager.dart'; +import 'package:arbiter/providers/evm.dart'; +import 'package:arbiter/providers/evm_grants.dart'; +import 'package:arbiter/router.gr.dart'; +import 'package:arbiter/widgets/bottom_popup.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:hooks_riverpod/experimental/mutation.dart'; +import 'package:sizer/sizer.dart'; + +@RoutePage() +class EvmGrantsScreen extends ConsumerWidget { + const EvmGrantsScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final grantsAsync = ref.watch(evmGrantsProvider); + final grantsState = grantsAsync.asData?.value; + final wallets = ref.watch(evmProvider).asData?.value ?? const []; + final revokeMutation = ref.watch(revokeEvmGrantMutation); + final isConnected = + ref.watch(connectionManagerProvider).asData?.value != null; + + Future refresh() async { + try { + await ref.read(evmGrantsProvider.notifier).refresh(); + } catch (error) { + if (!context.mounted) { + return; + } + _showMessage(context, _formatError(error)); + } + } + + Future revokeGrant(GrantEntry grant) async { + try { + await executeRevokeEvmGrant(ref, grantId: grant.id); + if (context.mounted) { + Navigator.of(context).pop(); + _showMessage(context, 'Grant revoked.'); + } + } catch (error) { + if (!context.mounted) { + return; + } + _showMessage(context, _formatError(error)); + } + } + + Future openCreate() async { + await context.router.push(const CreateEvmGrantRoute()); + } + + final content = switch (grantsAsync) { + AsyncLoading() when grantsState == null => const _GrantStatePanel( + icon: Icons.hourglass_top, + title: 'Loading grants', + body: 'Pulling EVM grants and wallet context from Arbiter.', + busy: true, + ), + AsyncError(:final error) => _GrantStatePanel( + icon: Icons.sync_problem, + title: 'Grant registry unavailable', + body: _formatError(error), + actionLabel: 'Retry', + onAction: refresh, + ), + _ when !isConnected => _GrantStatePanel( + icon: Icons.portable_wifi_off, + title: 'No active server connection', + body: 'Reconnect to Arbiter to inspect or create EVM grants.', + actionLabel: 'Refresh', + onAction: refresh, + ), + _ when grantsState == null => const SizedBox.shrink(), + _ when grantsState.grants.isEmpty => _GrantStatePanel( + icon: Icons.rule_folder_outlined, + title: 'No grants yet', + body: + 'Create the first grant to authorize scoped transaction signing for a client.', + actionLabel: 'Create grant', + onAction: openCreate, + ), + _ => _GrantGrid( + state: grantsState, + wallets: wallets, + onGrantTap: (grant) { + return showBottomPopup( + context: context, + builder: (popupContext) => _GrantDetailSheet( + grant: grant, + wallets: wallets, + isRevoking: revokeMutation is MutationPending, + onRevoke: () => revokeGrant(grant), + ), + ); + }, + ), + }; + + return Scaffold( + body: SafeArea( + child: RefreshIndicator.adaptive( + color: _GrantPalette.ink, + backgroundColor: Colors.white, + onRefresh: refresh, + child: ListView( + physics: const BouncingScrollPhysics( + parent: AlwaysScrollableScrollPhysics(), + ), + padding: EdgeInsets.fromLTRB(2.4.w, 2.4.h, 2.4.w, 3.2.h), + children: [ + _GrantHeader( + state: grantsState, + isRefreshing: grantsAsync.isLoading, + onRefresh: refresh, + onCreate: openCreate, + onToggleShowRevoked: (value) { + ref.read(evmGrantsProvider.notifier).toggleShowRevoked(value); + if (value) { + _showMessage( + context, + 'Revoked grant history is waiting on backend support. Active grants only for now.', + ); + } + }, + ), + if (grantsState?.showRevoked == true) + Padding( + padding: EdgeInsets.only(top: 1.4.h), + child: const _RevokedSupportBanner(), + ), + SizedBox(height: 1.8.h), + content, + ], + ), + ), + ), + ); + } +} + +class _GrantPalette { + static const ink = Color(0xFF17324A); + static const sea = Color(0xFF0F766E); + static const gold = Color(0xFFE19A2A); + static const coral = Color(0xFFE46B56); + static const mist = Color(0xFFF7F8FB); + static const line = Color(0x1A17324A); +} + +class _GrantHeader extends StatelessWidget { + const _GrantHeader({ + required this.state, + required this.isRefreshing, + required this.onRefresh, + required this.onCreate, + required this.onToggleShowRevoked, + }); + + final EvmGrantsState? state; + final bool isRefreshing; + final Future Function() onRefresh; + final Future Function() onCreate; + final ValueChanged onToggleShowRevoked; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final menuValue = state?.showRevoked ?? false; + + return Container( + padding: EdgeInsets.symmetric(horizontal: 1.8.w, vertical: 1.4.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(22), + gradient: const LinearGradient( + colors: [Color(0xFFF6F8FC), Color(0xFFFDF7EF)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + border: Border.all(color: _GrantPalette.line), + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'EVM Grants', + style: theme.textTheme.titleLarge?.copyWith( + color: _GrantPalette.ink, + fontWeight: FontWeight.w800, + ), + ), + SizedBox(height: 0.4.h), + Text( + 'Browse active permissions, inspect policy details, and create new grants.', + style: theme.textTheme.bodyMedium?.copyWith( + color: _GrantPalette.ink.withValues(alpha: 0.70), + height: 1.35, + ), + ), + ], + ), + ), + IconButton.filledTonal( + tooltip: 'Filters', + onPressed: () async { + final button = context.findRenderObject() as RenderBox?; + final overlay = + Overlay.of(context).context.findRenderObject() as RenderBox?; + if (button == null || overlay == null) { + return; + } + final selected = await showMenu( + context: context, + position: RelativeRect.fromRect( + Rect.fromPoints( + button.localToGlobal(Offset.zero, ancestor: overlay), + button.localToGlobal( + button.size.bottomRight(Offset.zero), + ancestor: overlay, + ), + ), + Offset.zero & overlay.size, + ), + items: [ + CheckedPopupMenuItem( + value: !menuValue, + checked: menuValue, + child: Text( + menuValue ? 'Hide revoked grants' : 'Show revoked grants', + ), + ), + ], + ); + if (selected != null) { + onToggleShowRevoked(selected); + } + }, + icon: const Icon(Icons.filter_list_rounded), + ), + SizedBox(width: 0.8.w), + OutlinedButton.icon( + onPressed: isRefreshing ? null : () => onRefresh(), + icon: isRefreshing + ? SizedBox( + width: 1.8.h, + height: 1.8.h, + child: const CircularProgressIndicator(strokeWidth: 2.2), + ) + : const Icon(Icons.refresh_rounded, size: 18), + label: const Text('Refresh'), + ), + SizedBox(width: 0.8.w), + FilledButton.icon( + onPressed: () => onCreate(), + style: FilledButton.styleFrom( + backgroundColor: _GrantPalette.ink, + foregroundColor: Colors.white, + ), + icon: const Icon(Icons.add_rounded, size: 18), + label: const Text('Create'), + ), + ], + ), + ); + } +} + +class _RevokedSupportBanner extends StatelessWidget { + const _RevokedSupportBanner(); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(1.6.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18), + color: _GrantPalette.gold.withValues(alpha: 0.12), + border: Border.all(color: _GrantPalette.gold.withValues(alpha: 0.28)), + ), + child: Row( + children: [ + Icon(Icons.info_outline_rounded, color: _GrantPalette.gold), + SizedBox(width: 1.2.w), + Expanded( + child: Text( + 'Revoked grant history is not exposed by the current backend yet. This screen still shows active grants only.', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: _GrantPalette.ink.withValues(alpha: 0.78), + height: 1.35, + ), + ), + ), + ], + ), + ); + } +} + +class _GrantGrid extends StatelessWidget { + const _GrantGrid({ + required this.state, + required this.wallets, + required this.onGrantTap, + }); + + final EvmGrantsState state; + final List wallets; + final Future Function(GrantEntry grant) onGrantTap; + + @override + Widget build(BuildContext context) { + final grantsByWallet = >{}; + for (final grant in state.grants) { + grantsByWallet.putIfAbsent(grant.shared.walletId, () => []).add(grant); + } + + final walletIds = grantsByWallet.keys.toList()..sort(); + + return Column( + children: [ + for (final walletId in walletIds) + Padding( + padding: EdgeInsets.only(bottom: 1.8.h), + child: _WalletGrantSection( + walletId: walletId, + walletAddress: _addressForWalletId(wallets, walletId), + grants: grantsByWallet[walletId]!, + onGrantTap: onGrantTap, + ), + ), + ], + ); + } +} + +class _WalletGrantSection extends StatelessWidget { + const _WalletGrantSection({ + required this.walletId, + required this.walletAddress, + required this.grants, + required this.onGrantTap, + }); + + final int walletId; + final List? walletAddress; + final List grants; + final Future Function(GrantEntry grant) onGrantTap; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + color: _GrantPalette.mist, + border: Border.all(color: _GrantPalette.line), + ), + padding: EdgeInsets.all(2.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Wallet ${walletId.toString().padLeft(2, '0')}', + style: theme.textTheme.titleMedium?.copyWith( + color: _GrantPalette.ink, + fontWeight: FontWeight.w800, + ), + ), + SizedBox(height: 0.4.h), + Text( + walletAddress == null + ? 'Wallet address unavailable in the current API.' + : _hexAddress(walletAddress!), + style: theme.textTheme.bodySmall?.copyWith( + color: _GrantPalette.ink.withValues(alpha: 0.70), + ), + ), + SizedBox(height: 1.8.h), + LayoutBuilder( + builder: (context, constraints) { + final maxWidth = constraints.maxWidth; + final cardWidth = maxWidth >= 900 + ? (maxWidth - 2.w * 2) / 3 + : maxWidth >= 620 + ? (maxWidth - 2.w) / 2 + : maxWidth; + return Wrap( + spacing: 1.4.w, + runSpacing: 1.4.h, + children: [ + for (final grant in grants) + SizedBox( + width: math.max(280, cardWidth), + child: _GrantCardRouter( + grant: grant, + walletAddress: walletAddress, + onTap: () => onGrantTap(grant), + ), + ), + ], + ); + }, + ), + ], + ), + ); + } +} + +class _GrantCardRouter extends StatelessWidget { + const _GrantCardRouter({ + required this.grant, + required this.walletAddress, + required this.onTap, + }); + + final GrantEntry grant; + final List? walletAddress; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return switch (grant.specific.whichGrant()) { + SpecificGrant_Grant.etherTransfer => _EtherGrantCard( + grant: grant, + walletAddress: walletAddress, + onTap: onTap, + ), + SpecificGrant_Grant.tokenTransfer => _TokenGrantCard( + grant: grant, + walletAddress: walletAddress, + onTap: onTap, + ), + _ => _UnsupportedGrantCard(grant: grant, onTap: onTap), + }; + } +} + +class _GrantCardFrame extends StatelessWidget { + const _GrantCardFrame({ + required this.icon, + required this.iconColor, + required this.title, + required this.subtitle, + required this.chips, + required this.onTap, + }); + + final IconData icon; + final Color iconColor; + final String title; + final String subtitle; + final List chips; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Material( + color: Colors.white, + borderRadius: BorderRadius.circular(22), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(22), + child: Ink( + padding: EdgeInsets.all(2.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(22), + border: Border.all(color: _GrantPalette.line), + boxShadow: [ + BoxShadow( + color: _GrantPalette.ink.withValues(alpha: 0.05), + blurRadius: 24, + offset: const Offset(0, 12), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + child: Container( + width: 6.2.h, + height: 6.2.h, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: iconColor.withValues(alpha: 0.14), + ), + child: Icon(icon, color: iconColor, size: 3.h), + ), + ), + SizedBox(height: 1.6.h), + Text( + title, + style: theme.textTheme.titleMedium?.copyWith( + color: _GrantPalette.ink, + fontWeight: FontWeight.w800, + ), + ), + SizedBox(height: 0.5.h), + Text( + subtitle, + style: theme.textTheme.bodyMedium?.copyWith( + color: _GrantPalette.ink.withValues(alpha: 0.72), + height: 1.35, + ), + ), + SizedBox(height: 1.4.h), + Wrap( + spacing: 0.8.w, + runSpacing: 0.8.h, + children: [ + for (final chip in chips) _GrantChip(label: chip), + ], + ), + ], + ), + ), + ), + ); + } +} + +class _EtherGrantCard extends StatelessWidget { + const _EtherGrantCard({ + required this.grant, + required this.walletAddress, + required this.onTap, + }); + + final GrantEntry grant; + final List? walletAddress; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final settings = grant.specific.etherTransfer; + final targets = settings.targets.length; + final subtitle = targets == 0 + ? 'ETH transfers with a shared limit profile.' + : '$targets target${targets == 1 ? '' : 's'} authorized.'; + + return _GrantCardFrame( + icon: Icons.bolt_rounded, + iconColor: _GrantPalette.gold, + title: 'Ether Transfer', + subtitle: subtitle, + chips: [ + 'Client ${grant.clientId}', + 'Wallet ${grant.shared.walletId}', + if (walletAddress != null) _shortAddress(walletAddress!), + ], + onTap: onTap, + ); + } +} + +class _TokenGrantCard extends StatelessWidget { + const _TokenGrantCard({ + required this.grant, + required this.walletAddress, + required this.onTap, + }); + + final GrantEntry grant; + final List? walletAddress; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final settings = grant.specific.tokenTransfer; + + return _GrantCardFrame( + icon: Icons.token_rounded, + iconColor: _GrantPalette.sea, + title: 'Token Transfer', + subtitle: + 'Contract ${_shortAddress(settings.tokenContract)} with ${settings.volumeLimits.length} volume rule${settings.volumeLimits.length == 1 ? '' : 's'}.', + chips: [ + 'Client ${grant.clientId}', + 'Wallet ${grant.shared.walletId}', + if (walletAddress != null) _shortAddress(walletAddress!), + ], + onTap: onTap, + ); + } +} + +class _UnsupportedGrantCard extends StatelessWidget { + const _UnsupportedGrantCard({required this.grant, required this.onTap}); + + final GrantEntry grant; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return _GrantCardFrame( + icon: Icons.help_outline_rounded, + iconColor: _GrantPalette.coral, + title: 'Unsupported Grant', + subtitle: 'This grant type cannot be rendered in the current useragent.', + chips: ['Grant ${grant.id}'], + onTap: onTap, + ); + } +} + +class _GrantChip extends StatelessWidget { + const _GrantChip({required this.label}); + + final String label; + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 1.1.w, vertical: 0.7.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(999), + color: _GrantPalette.ink.withValues(alpha: 0.06), + ), + child: Text( + label, + style: Theme.of(context).textTheme.labelMedium?.copyWith( + color: _GrantPalette.ink.withValues(alpha: 0.76), + fontWeight: FontWeight.w700, + ), + ), + ); + } +} + +class _GrantDetailSheet extends StatelessWidget { + const _GrantDetailSheet({ + required this.grant, + required this.wallets, + required this.isRevoking, + required this.onRevoke, + }); + + final GrantEntry grant; + final List wallets; + final bool isRevoking; + final Future Function() onRevoke; + + @override + Widget build(BuildContext context) { + final walletAddress = _addressForWalletId(wallets, grant.shared.walletId); + + return Container( + width: 100.w, + constraints: BoxConstraints(maxWidth: 760), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(28), + color: Colors.white, + ), + padding: EdgeInsets.all(2.2.h), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + 'Grant #${grant.id}', + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + color: _GrantPalette.ink, + fontWeight: FontWeight.w800, + ), + ), + ), + IconButton( + onPressed: () => Navigator.of(context).pop(), + icon: const Icon(Icons.close_rounded), + ), + ], + ), + SizedBox(height: 1.2.h), + Wrap( + spacing: 1.w, + runSpacing: 0.8.h, + children: [ + _GrantChip(label: 'Client ${grant.clientId}'), + _GrantChip(label: 'Wallet ${grant.shared.walletId}'), + if (walletAddress != null) _GrantChip(label: _shortAddress(walletAddress)), + ], + ), + SizedBox(height: 2.h), + _SectionTitle(title: 'Shared policy'), + _FieldSummary(label: 'Chain ID', value: grant.shared.chainId.toString()), + _FieldSummary( + label: 'Validity', + value: _validitySummary(grant.shared), + ), + _FieldSummary( + label: 'Gas fee cap', + value: _optionalBigInt(grant.shared.maxGasFeePerGas), + ), + _FieldSummary( + label: 'Priority fee cap', + value: _optionalBigInt(grant.shared.maxPriorityFeePerGas), + ), + _FieldSummary( + label: 'Tx count limit', + value: grant.shared.hasRateLimit() + ? '${grant.shared.rateLimit.count} tx / ${grant.shared.rateLimit.windowSecs}s' + : 'Not set', + ), + SizedBox(height: 1.8.h), + _SectionTitle(title: 'Grant-specific settings'), + switch (grant.specific.whichGrant()) { + SpecificGrant_Grant.etherTransfer => _EtherGrantDetails( + settings: grant.specific.etherTransfer, + ), + SpecificGrant_Grant.tokenTransfer => _TokenGrantDetails( + settings: grant.specific.tokenTransfer, + ), + _ => const Text('Unsupported grant type'), + }, + SizedBox(height: 2.2.h), + Align( + alignment: Alignment.centerRight, + child: FilledButton.icon( + onPressed: isRevoking ? null : () => onRevoke(), + style: FilledButton.styleFrom( + backgroundColor: _GrantPalette.coral, + foregroundColor: Colors.white, + ), + icon: isRevoking + ? SizedBox( + width: 1.8.h, + height: 1.8.h, + child: const CircularProgressIndicator(strokeWidth: 2.2), + ) + : const Icon(Icons.block_rounded), + label: Text(isRevoking ? 'Revoking...' : 'Revoke grant'), + ), + ), + ], + ), + ); + } +} + +class _EtherGrantDetails extends StatelessWidget { + const _EtherGrantDetails({required this.settings}); + + final EtherTransferSettings settings; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _FieldSummary( + label: 'Targets', + value: settings.targets.isEmpty + ? 'No explicit target restriction' + : settings.targets.map(_hexAddress).join(', '), + ), + _FieldSummary( + label: 'Volume limit', + value: settings.hasLimit() + ? '${_optionalBigInt(settings.limit.maxVolume)} / ${settings.limit.windowSecs}s' + : 'Not set', + ), + ], + ); + } +} + +class _TokenGrantDetails extends StatelessWidget { + const _TokenGrantDetails({required this.settings}); + + final TokenTransferSettings settings; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _FieldSummary( + label: 'Token contract', + value: _hexAddress(settings.tokenContract), + ), + _FieldSummary( + label: 'Recipient', + value: settings.hasTarget() ? _hexAddress(settings.target) : 'Any recipient', + ), + _FieldSummary( + label: 'Volume rules', + value: settings.volumeLimits.isEmpty + ? 'Not set' + : settings.volumeLimits + .map( + (limit) => + '${_optionalBigInt(limit.maxVolume)} / ${limit.windowSecs}s', + ) + .join('\n'), + ), + ], + ); + } +} + +class _FieldSummary extends StatelessWidget { + const _FieldSummary({required this.label, required this.value}); + + final String label; + final String value; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only(bottom: 1.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: Theme.of(context).textTheme.labelLarge?.copyWith( + color: _GrantPalette.ink.withValues(alpha: 0.60), + fontWeight: FontWeight.w800, + ), + ), + SizedBox(height: 0.3.h), + Text( + value, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: _GrantPalette.ink, + height: 1.4, + ), + ), + ], + ), + ); + } +} + +class _SectionTitle extends StatelessWidget { + const _SectionTitle({required this.title}); + + final String title; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only(bottom: 1.1.h), + child: Text( + title, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: _GrantPalette.ink, + fontWeight: FontWeight.w800, + ), + ), + ); + } +} + +class _GrantStatePanel extends StatelessWidget { + const _GrantStatePanel({ + required this.icon, + required this.title, + required this.body, + this.actionLabel, + this.onAction, + this.busy = false, + }); + + final IconData icon; + final String title; + final String body; + final String? actionLabel; + final Future Function()? onAction; + final bool busy; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + color: _GrantPalette.mist, + border: Border.all(color: _GrantPalette.line), + ), + child: Padding( + padding: EdgeInsets.all(2.8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (busy) + SizedBox( + width: 2.8.h, + height: 2.8.h, + child: const CircularProgressIndicator(strokeWidth: 2.5), + ) + else + Icon(icon, size: 34, color: _GrantPalette.coral), + SizedBox(height: 1.8.h), + Text( + title, + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + color: _GrantPalette.ink, + fontWeight: FontWeight.w800, + ), + ), + SizedBox(height: 1.h), + Text( + body, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: _GrantPalette.ink.withValues(alpha: 0.72), + height: 1.5, + ), + ), + if (actionLabel != null && onAction != null) ...[ + SizedBox(height: 2.h), + OutlinedButton.icon( + onPressed: () => onAction!(), + icon: const Icon(Icons.refresh), + label: Text(actionLabel!), + ), + ], + ], + ), + ), + ); + } +} + +List? _addressForWalletId(List wallets, int walletId) { + final index = walletId - 1; + if (index < 0 || index >= wallets.length) { + return null; + } + return wallets[index].address; +} + +String _shortAddress(List bytes) { + final value = _hexAddress(bytes); + if (value.length <= 14) { + return value; + } + return '${value.substring(0, 8)}...${value.substring(value.length - 4)}'; +} + +String _hexAddress(List bytes) { + final hex = bytes + .map((byte) => byte.toRadixString(16).padLeft(2, '0')) + .join(); + return '0x$hex'; +} + +String _optionalBigInt(List bytes) { + if (bytes.isEmpty) { + return 'Not set'; + } + return _bytesToBigInt(bytes).toString(); +} + +String _validitySummary(SharedSettings shared) { + final from = shared.hasValidFrom() + ? DateTime.fromMillisecondsSinceEpoch( + shared.validFrom.seconds.toInt() * 1000, + isUtc: true, + ).toLocal().toString() + : 'Immediate'; + final until = shared.hasValidUntil() + ? DateTime.fromMillisecondsSinceEpoch( + shared.validUntil.seconds.toInt() * 1000, + isUtc: true, + ).toLocal().toString() + : 'Open-ended'; + return '$from -> $until'; +} + +BigInt _bytesToBigInt(List bytes) { + return bytes.fold( + BigInt.zero, + (value, byte) => (value << 8) | BigInt.from(byte), + ); +} + +void _showMessage(BuildContext context, String message) { + if (!context.mounted) { + return; + } + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message), behavior: SnackBarBehavior.floating), + ); +} + +String _formatError(Object error) { + final message = error.toString(); + if (message.startsWith('Exception: ')) { + return message.substring('Exception: '.length); + } + return message; +} diff --git a/useragent/lib/screens/vault_setup.dart b/useragent/lib/screens/vault_setup.dart index 5c1ce6e..a350c98 100644 --- a/useragent/lib/screens/vault_setup.dart +++ b/useragent/lib/screens/vault_setup.dart @@ -1,4 +1,4 @@ -import 'package:arbiter/features/connection/connection.dart'; +import 'package:arbiter/features/connection/vault.dart'; import 'package:arbiter/proto/user_agent.pbenum.dart'; import 'package:arbiter/providers/connection/connection_manager.dart'; import 'package:arbiter/providers/vault_state.dart'; From 9017ea4017529f67f6af56d7b63b85d078c81f1b Mon Sep 17 00:00:00 2001 From: hdbg Date: Mon, 16 Mar 2026 18:56:13 +0100 Subject: [PATCH 11/13] refactor(server): added `SafeCell` abstraction for easier protected memory swap --- .../arbiter-server/src/actors/evm/mod.rs | 12 +-- .../src/actors/keyholder/encryption/v1.rs | 61 +++++++------- .../src/actors/keyholder/mod.rs | 29 ++++--- .../src/actors/user_agent/session.rs | 2 - .../actors/user_agent/session/connection.rs | 15 ++-- .../arbiter-server/src/evm/safe_signer.rs | 20 ++--- .../arbiter-server/src/grpc/user_agent.rs | 2 - server/crates/arbiter-server/src/lib.rs | 2 +- server/crates/arbiter-server/src/safe_cell.rs | 79 +++++++++++++++++++ .../crates/arbiter-server/tests/common/mod.rs | 5 +- .../tests/keyholder/concurrency.rs | 10 +-- .../tests/keyholder/lifecycle.rs | 22 +++--- .../arbiter-server/tests/keyholder/storage.rs | 20 ++--- .../arbiter-server/tests/user_agent/unseal.rs | 4 +- 14 files changed, 178 insertions(+), 105 deletions(-) create mode 100644 server/crates/arbiter-server/src/safe_cell.rs diff --git a/server/crates/arbiter-server/src/actors/evm/mod.rs b/server/crates/arbiter-server/src/actors/evm/mod.rs index ce3f177..05a9d94 100644 --- a/server/crates/arbiter-server/src/actors/evm/mod.rs +++ b/server/crates/arbiter-server/src/actors/evm/mod.rs @@ -4,14 +4,13 @@ use diesel::{ }; use diesel_async::RunQueryDsl; use kameo::{Actor, actor::ActorRef, messages}; -use memsafe::MemSafe; use rand::{SeedableRng, rng, rngs::StdRng}; use crate::{ actors::keyholder::{CreateNew, Decrypt, KeyHolder}, db::{ self, DatabasePool, - models::{self, EvmBasicGrant, SqliteTimestamp}, + models::{self, SqliteTimestamp}, schema, }, evm::{ @@ -21,6 +20,7 @@ use crate::{ ether_transfer::EtherTransfer, token_transfers::TokenTransfer, }, }, + safe_cell::{SafeCell, SafeCellHandle as _}, }; pub use crate::evm::safe_signer; @@ -110,8 +110,8 @@ impl EvmActor { // Move raw key bytes into a Vec MemSafe for KeyHolder let plaintext = { - let reader = key_cell.read().expect("MemSafe read"); - MemSafe::new(reader.to_vec()).expect("MemSafe allocation") + let reader = key_cell.read(); + SafeCell::new(reader.to_vec()) }; let aead_id: i32 = self @@ -249,7 +249,7 @@ impl EvmActor { .ok_or(SignTransactionError::WalletNotFound)?; drop(conn); - let raw_key: MemSafe> = self + let raw_key: SafeCell> = self .keyholder .ask(Decrypt { aead_id: wallet.aead_encrypted_id, @@ -257,7 +257,7 @@ impl EvmActor { .await .map_err(|_| SignTransactionError::KeyholderSend)?; - let signer = safe_signer::SafeSigner::from_memsafe(raw_key)?; + let signer = safe_signer::SafeSigner::from_cell(raw_key)?; self.engine .evaluate_transaction( diff --git a/server/crates/arbiter-server/src/actors/keyholder/encryption/v1.rs b/server/crates/arbiter-server/src/actors/keyholder/encryption/v1.rs index fdc9727..9ca5a45 100644 --- a/server/crates/arbiter-server/src/actors/keyholder/encryption/v1.rs +++ b/server/crates/arbiter-server/src/actors/keyholder/encryption/v1.rs @@ -5,12 +5,13 @@ use chacha20poly1305::{ AeadInPlace, Key, KeyInit as _, XChaCha20Poly1305, XNonce, aead::{AeadMut, Error, Payload}, }; -use memsafe::MemSafe; use rand::{ Rng as _, SeedableRng, rngs::{StdRng, SysRng}, }; +use crate::safe_cell::{SafeCell, SafeCellHandle as _}; + pub const ROOT_KEY_TAG: &[u8] = "arbiter/seal/v1".as_bytes(); pub const TAG: &[u8] = "arbiter/private-key/v1".as_bytes(); @@ -47,23 +48,23 @@ impl<'a> TryFrom<&'a [u8]> for Nonce { } } -pub struct KeyCell(pub MemSafe); -impl From> for KeyCell { - fn from(value: MemSafe) -> Self { +pub struct KeyCell(pub SafeCell); +impl From> for KeyCell { + fn from(value: SafeCell) -> Self { Self(value) } } -impl TryFrom>> for KeyCell { +impl TryFrom>> for KeyCell { type Error = (); - fn try_from(mut value: MemSafe>) -> Result { - let value = value.read().unwrap(); + fn try_from(mut value: SafeCell>) -> Result { + let value = value.read(); if value.len() != size_of::() { return Err(()); } - let mut cell = MemSafe::new(Key::default()).unwrap(); + let mut cell = SafeCell::new(Key::default()); { - let mut cell_write = cell.write().unwrap(); + let mut cell_write = cell.write(); let cell_slice: &mut [u8] = cell_write.as_mut(); cell_slice.copy_from_slice(&value); } @@ -73,9 +74,9 @@ impl TryFrom>> for KeyCell { impl KeyCell { pub fn new_secure_random() -> Self { - let mut key = MemSafe::new(Key::default()).unwrap(); + let mut key = SafeCell::new(Key::default()); { - let mut key_buffer = key.write().unwrap(); + let mut key_buffer = key.write(); let key_buffer: &mut [u8] = key_buffer.as_mut(); let mut rng = StdRng::try_from_rng(&mut SysRng).unwrap(); @@ -91,7 +92,7 @@ impl KeyCell { associated_data: &[u8], mut buffer: impl AsMut>, ) -> Result<(), Error> { - let key_reader = self.0.read().unwrap(); + let key_reader = self.0.read(); let key_ref = key_reader.deref(); let cipher = XChaCha20Poly1305::new(key_ref); let nonce = XNonce::from_slice(nonce.0.as_ref()); @@ -102,13 +103,13 @@ impl KeyCell { &mut self, nonce: &Nonce, associated_data: &[u8], - buffer: &mut MemSafe>, + buffer: &mut SafeCell>, ) -> Result<(), Error> { - let key_reader = self.0.read().unwrap(); + let key_reader = self.0.read(); let key_ref = key_reader.deref(); let cipher = XChaCha20Poly1305::new(key_ref); let nonce = XNonce::from_slice(nonce.0.as_ref()); - let mut buffer = buffer.write().unwrap(); + let mut buffer = buffer.write(); let buffer: &mut Vec = buffer.as_mut(); cipher.decrypt_in_place(nonce, associated_data, buffer) } @@ -119,7 +120,7 @@ impl KeyCell { associated_data: &[u8], plaintext: impl AsRef<[u8]>, ) -> Result, Error> { - let key_reader = self.0.read().unwrap(); + let key_reader = self.0.read(); let key_ref = key_reader.deref(); let mut cipher = XChaCha20Poly1305::new(key_ref); let nonce = XNonce::from_slice(nonce.0.as_ref()); @@ -146,13 +147,13 @@ pub fn generate_salt() -> Salt { /// User password might be of different length, have not enough entropy, etc... /// Derive a fixed-length key from the password using Argon2id, which is designed for password hashing and key derivation. -pub fn derive_seal_key(mut password: MemSafe>, salt: &Salt) -> KeyCell { +pub fn derive_seal_key(mut password: SafeCell>, salt: &Salt) -> KeyCell { let params = argon2::Params::new(262_144, 3, 4, None).unwrap(); let hasher = Argon2::new(Algorithm::Argon2id, argon2::Version::V0x13, params); - let mut key = MemSafe::new(Key::default()).unwrap(); + let mut key = SafeCell::new(Key::default()); { - let password_source = password.read().unwrap(); - let mut key_buffer = key.write().unwrap(); + let password_source = password.read(); + let mut key_buffer = key.write(); let key_buffer: &mut [u8] = key_buffer.as_mut(); hasher @@ -166,20 +167,20 @@ pub fn derive_seal_key(mut password: MemSafe>, salt: &Salt) -> KeyCell { #[cfg(test)] mod tests { use super::*; - use memsafe::MemSafe; + use crate::safe_cell::SafeCell; #[test] pub fn derive_seal_key_deterministic() { static PASSWORD: &[u8] = b"password"; - let password = MemSafe::new(PASSWORD.to_vec()).unwrap(); - let password2 = MemSafe::new(PASSWORD.to_vec()).unwrap(); + let password = SafeCell::new(PASSWORD.to_vec()); + let password2 = SafeCell::new(PASSWORD.to_vec()); let salt = generate_salt(); let mut key1 = derive_seal_key(password, &salt); let mut key2 = derive_seal_key(password2, &salt); - let key1_reader = key1.0.read().unwrap(); - let key2_reader = key2.0.read().unwrap(); + let key1_reader = key1.0.read(); + let key2_reader = key2.0.read(); assert_eq!(key1_reader.deref(), key2_reader.deref()); } @@ -187,11 +188,11 @@ mod tests { #[test] pub fn successful_derive() { static PASSWORD: &[u8] = b"password"; - let password = MemSafe::new(PASSWORD.to_vec()).unwrap(); + let password = SafeCell::new(PASSWORD.to_vec()); let salt = generate_salt(); let mut key = derive_seal_key(password, &salt); - let key_reader = key.0.read().unwrap(); + let key_reader = key.0.read(); let key_ref = key_reader.deref(); assert_ne!(key_ref.as_slice(), &[0u8; 32][..]); @@ -200,7 +201,7 @@ mod tests { #[test] pub fn encrypt_decrypt() { static PASSWORD: &[u8] = b"password"; - let password = MemSafe::new(PASSWORD.to_vec()).unwrap(); + let password = SafeCell::new(PASSWORD.to_vec()); let salt = generate_salt(); let mut key = derive_seal_key(password, &salt); @@ -212,12 +213,12 @@ mod tests { .unwrap(); assert_ne!(buffer, b"secret data"); - let mut buffer = MemSafe::new(buffer).unwrap(); + let mut buffer = SafeCell::new(buffer); key.decrypt_in_place(&nonce, associated_data, &mut buffer) .unwrap(); - let buffer = buffer.read().unwrap(); + let buffer = buffer.read(); assert_eq!(*buffer, b"secret data"); } diff --git a/server/crates/arbiter-server/src/actors/keyholder/mod.rs b/server/crates/arbiter-server/src/actors/keyholder/mod.rs index f7cc8cc..e53f6a3 100644 --- a/server/crates/arbiter-server/src/actors/keyholder/mod.rs +++ b/server/crates/arbiter-server/src/actors/keyholder/mod.rs @@ -5,15 +5,15 @@ use diesel::{ }; use diesel_async::{AsyncConnection, RunQueryDsl}; use kameo::{Actor, Reply, messages}; -use memsafe::MemSafe; use strum::{EnumDiscriminants, IntoDiscriminant}; use tracing::{error, info}; -use crate::db::{ +use crate::{db::{ self, models::{self, RootKeyHistory}, schema::{self}, -}; +}, safe_cell::SafeCellHandle as _}; +use crate::safe_cell::SafeCell; use encryption::v1::{self, KeyCell, Nonce}; pub mod encryption; @@ -136,7 +136,7 @@ impl KeyHolder { } #[message] - pub async fn bootstrap(&mut self, seal_key_raw: MemSafe>) -> Result<(), Error> { + pub async fn bootstrap(&mut self, seal_key_raw: SafeCell>) -> Result<(), Error> { if !matches!(self.state, State::Unbootstrapped) { return Err(Error::AlreadyBootstrapped); } @@ -149,7 +149,7 @@ impl KeyHolder { let data_encryption_nonce = v1::Nonce::default(); let root_key_ciphertext: Vec = { - let root_key_reader = root_key.0.read().unwrap(); + let root_key_reader = root_key.0.read(); let root_key_reader = root_key_reader.as_slice(); seal_key .encrypt(&root_key_nonce, v1::ROOT_KEY_TAG, root_key_reader) @@ -199,7 +199,7 @@ impl KeyHolder { } #[message] - pub async fn try_unseal(&mut self, seal_key_raw: MemSafe>) -> Result<(), Error> { + pub async fn try_unseal(&mut self, seal_key_raw: SafeCell>) -> Result<(), Error> { let State::Sealed { root_key_history_id, } = &self.state @@ -225,7 +225,7 @@ impl KeyHolder { })?; let mut seal_key = v1::derive_seal_key(seal_key_raw, &salt); - let mut root_key = MemSafe::new(current_key.ciphertext.clone()).unwrap(); + let mut root_key = SafeCell::new(current_key.ciphertext.clone()); let nonce = v1::Nonce::try_from(current_key.root_key_encryption_nonce.as_slice()).map_err( |_| { @@ -256,7 +256,7 @@ impl KeyHolder { // Decrypts the `aead_encrypted` entry with the given ID and returns the plaintext #[message] - pub async fn decrypt(&mut self, aead_id: i32) -> Result>, Error> { + pub async fn decrypt(&mut self, aead_id: i32) -> Result>, Error> { let State::Unsealed { root_key, .. } = &mut self.state else { return Err(Error::NotBootstrapped); }; @@ -279,14 +279,14 @@ impl KeyHolder { ); Error::BrokenDatabase })?; - let mut output = MemSafe::new(row.ciphertext).unwrap(); + let mut output = SafeCell::new(row.ciphertext); root_key.decrypt_in_place(&nonce, v1::TAG, &mut output)?; Ok(output) } // Creates new `aead_encrypted` entry in the database and returns it's ID #[message] - pub async fn create_new(&mut self, mut plaintext: MemSafe>) -> Result { + pub async fn create_new(&mut self, mut plaintext: SafeCell>) -> Result { let State::Unsealed { root_key, root_key_history_id, @@ -299,7 +299,7 @@ impl KeyHolder { // Borrow checker note: &mut borrow a few lines above is disjoint from this field let nonce = Self::get_new_nonce(&self.db, *root_key_history_id).await?; - let mut ciphertext_buffer = plaintext.write().unwrap(); + let mut ciphertext_buffer = plaintext.write(); let ciphertext_buffer: &mut Vec = ciphertext_buffer.as_mut(); root_key.encrypt_in_place(&nonce, v1::TAG, &mut *ciphertext_buffer)?; @@ -348,15 +348,14 @@ mod tests { use diesel::SelectableHelper; use diesel_async::RunQueryDsl; - use memsafe::MemSafe; - use crate::db::{self}; + use crate::{db::{self}, safe_cell::SafeCell}; use super::*; async fn bootstrapped_actor(db: &db::DatabasePool) -> KeyHolder { let mut actor = KeyHolder::new(db.clone()).await.unwrap(); - let seal_key = MemSafe::new(b"test-seal-key".to_vec()).unwrap(); + let seal_key = SafeCell::new(b"test-seal-key".to_vec()); actor.bootstrap(seal_key).await.unwrap(); actor } @@ -391,7 +390,7 @@ mod tests { assert_eq!(root_row.data_encryption_nonce, n2.to_vec()); let id = actor - .create_new(MemSafe::new(b"post-interleave".to_vec()).unwrap()) + .create_new(SafeCell::new(b"post-interleave".to_vec())) .await .unwrap(); let row: models::AeadEncrypted = schema::aead_encrypted::table diff --git a/server/crates/arbiter-server/src/actors/user_agent/session.rs b/server/crates/arbiter-server/src/actors/user_agent/session.rs index 9fe8d91..d568cc5 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/session.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/session.rs @@ -1,5 +1,3 @@ - -use chacha20poly1305::aead::KeyInit; use ed25519_dalek::VerifyingKey; use kameo::{Actor, messages, prelude::Context}; use tokio::{select, sync::watch}; diff --git a/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs b/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs index e303ef6..984f464 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs @@ -2,13 +2,11 @@ use std::{ops::DerefMut, sync::Mutex}; use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit}; use kameo::error::SendError; -use memsafe::MemSafe; use tracing::{error, info}; use x25519_dalek::{EphemeralSecret, PublicKey}; -use crate::actors::{ - evm::{Generate, ListWallets, UseragentListGrants}, - evm::{UseragentCreateGrant, UseragentDeleteGrant}, +use crate::{actors::{ + evm::{Generate, ListWallets, UseragentCreateGrant, UseragentDeleteGrant, UseragentListGrants}, keyholder::{self, Bootstrap, TryUnseal}, user_agent::{ BootstrapError, Request, Response, TransportResponseError, UnsealError, VaultState, @@ -17,7 +15,8 @@ use crate::actors::{ state::{UnsealContext, UserAgentEvents, UserAgentStates}, }, }, -}; +}, safe_cell::SafeCellHandle as _}; +use crate::safe_cell::SafeCell; impl UserAgentSession { pub async fn process_transport_inbound(&mut self, req: Request) -> Output { @@ -93,16 +92,16 @@ impl UserAgentSession { nonce: &[u8], ciphertext: &[u8], associated_data: &[u8], - ) -> Result>, ()> { + ) -> Result>, ()> { let nonce = XNonce::from_slice(nonce); let shared_secret = ephemeral_secret.diffie_hellman(&client_public_key); let cipher = XChaCha20Poly1305::new(shared_secret.as_bytes().into()); - let mut key_buffer = MemSafe::new(ciphertext.to_vec()).unwrap(); + let mut key_buffer = SafeCell::new(ciphertext.to_vec()); let decryption_result = { - let mut write_handle = key_buffer.write().unwrap(); + let mut write_handle = key_buffer.write(); let write_handle = write_handle.deref_mut(); cipher.decrypt_in_place(nonce, associated_data, write_handle) }; diff --git a/server/crates/arbiter-server/src/evm/safe_signer.rs b/server/crates/arbiter-server/src/evm/safe_signer.rs index fd39189..124a248 100644 --- a/server/crates/arbiter-server/src/evm/safe_signer.rs +++ b/server/crates/arbiter-server/src/evm/safe_signer.rs @@ -8,7 +8,7 @@ use alloy::{ }; use async_trait::async_trait; use k256::ecdsa::{self, RecoveryId, SigningKey, signature::hazmat::PrehashSigner}; -use memsafe::MemSafe; +use crate::safe_cell::{SafeCell, SafeCellHandle as _}; /// An Ethereum signer that stores its secp256k1 secret key inside a /// hardware-protected [`MemSafe`] cell. @@ -20,7 +20,7 @@ use memsafe::MemSafe; /// Because [`MemSafe::read`] requires `&mut self` while the [`Signer`] trait /// requires `&self`, the cell is wrapped in a [`Mutex`]. pub struct SafeSigner { - key: Mutex>, + key: Mutex>, address: Address, chain_id: Option, } @@ -42,14 +42,14 @@ impl std::fmt::Debug for SafeSigner { /// rejection, but we retry to be correct). /// /// Returns the protected key bytes and the derived Ethereum address. -pub fn generate(rng: &mut impl rand::Rng) -> (MemSafe<[u8; 32]>, Address) { +pub fn generate(rng: &mut impl rand::Rng) -> (SafeCell<[u8; 32]>, Address) { loop { - let mut cell = MemSafe::new([0u8; 32]).expect("MemSafe allocation"); + let mut cell = SafeCell::new([0u8; 32]); { - let mut w = cell.write().expect("MemSafe write"); + let mut w = cell.write(); rng.fill_bytes(w.as_mut()); } - let reader = cell.read().expect("MemSafe read"); + let reader = cell.read(); if let Ok(sk) = SigningKey::from_slice(reader.as_ref()) { let address = secret_key_to_address(&sk); drop(reader); @@ -64,8 +64,8 @@ impl SafeSigner { /// The key bytes are read from protected memory, parsed as a secp256k1 /// scalar, and immediately moved into a new [`MemSafe`] cell. The raw /// bytes are never exposed outside this function. - pub fn from_memsafe(mut cell: MemSafe>) -> Result { - let reader = cell.read().map_err(Error::other)?; + pub fn from_cell(mut cell: SafeCell>) -> Result { + let reader = cell.read(); let sk = SigningKey::from_slice(reader.as_slice()).map_err(Error::other)?; drop(reader); Self::new(sk) @@ -75,7 +75,7 @@ impl SafeSigner { /// memory region. pub fn new(key: SigningKey) -> Result { let address = secret_key_to_address(&key); - let cell = MemSafe::new(key).map_err(Error::other)?; + let cell = SafeCell::new(key); Ok(Self { key: Mutex::new(cell), address, @@ -85,7 +85,7 @@ impl SafeSigner { fn sign_hash_inner(&self, hash: &B256) -> Result { let mut cell = self.key.lock().expect("SafeSigner mutex poisoned"); - let reader = cell.read().map_err(Error::other)?; + let reader = cell.read(); let sig: (ecdsa::Signature, RecoveryId) = reader.sign_prehash(hash.as_ref())?; Ok(sig.into()) } diff --git a/server/crates/arbiter-server/src/grpc/user_agent.rs b/server/crates/arbiter-server/src/grpc/user_agent.rs index a309b52..39c6afb 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent.rs @@ -6,7 +6,6 @@ use arbiter_proto::{ EvmGrantCreateRequest, EvmGrantCreateResponse, EvmGrantDeleteRequest, EvmGrantDeleteResponse, EvmGrantList, EvmGrantListResponse, GrantEntry, SharedSettings as ProtoSharedSettings, SpecificGrant as ProtoSpecificGrant, - SpecificGrant as ProtoGrantSpecificGrant, TokenTransferSettings as ProtoTokenTransferSettings, VolumeRateLimit as ProtoVolumeRateLimit, WalletCreateResponse, WalletEntry, WalletList, WalletListResponse, evm_grant_create_response::Result as EvmGrantCreateResult, @@ -42,7 +41,6 @@ use crate::{ TransportResponseError, UnsealError, VaultState, }, evm::{ - self, policies::{Grant, SpecificGrant}, policies::{ SharedGrantSettings, TransactionRateLimit, VolumeRateLimit, ether_transfer, diff --git a/server/crates/arbiter-server/src/lib.rs b/server/crates/arbiter-server/src/lib.rs index 70f1f80..410e499 100644 --- a/server/crates/arbiter-server/src/lib.rs +++ b/server/crates/arbiter-server/src/lib.rs @@ -12,6 +12,7 @@ pub mod context; pub mod db; pub mod evm; pub mod grpc; +pub mod safe_cell; const DEFAULT_CHANNEL_SIZE: usize = 1000; @@ -25,4 +26,3 @@ impl Server { } } - diff --git a/server/crates/arbiter-server/src/safe_cell.rs b/server/crates/arbiter-server/src/safe_cell.rs new file mode 100644 index 0000000..056e131 --- /dev/null +++ b/server/crates/arbiter-server/src/safe_cell.rs @@ -0,0 +1,79 @@ +use std::ops::{Deref, DerefMut}; +use std::{any::type_name, fmt}; + +use memsafe::MemSafe; + +pub trait SafeCellHandle { + type CellRead<'a>: Deref + where + Self: 'a, + T: 'a; + type CellWrite<'a>: Deref + DerefMut + where + Self: 'a, + T: 'a; + + fn new(value: T) -> Self + where + Self: Sized; + + fn read(&mut self) -> Self::CellRead<'_>; + fn write(&mut self) -> Self::CellWrite<'_>; +} + +pub struct MemSafeCell(MemSafe); + +impl fmt::Debug for MemSafeCell { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MemSafeCell") + .field("inner", &format_args!("", type_name::())) + .finish() + } +} + +impl SafeCellHandle for MemSafeCell { + type CellRead<'a> + = memsafe::MemSafeRead<'a, T> + where + Self: 'a, + T: 'a; + type CellWrite<'a> + = memsafe::MemSafeWrite<'a, T> + where + Self: 'a, + T: 'a; + + fn new(value: T) -> Self { + match MemSafe::new(value) { + Ok(inner) => Self(inner), + Err(err) => { + // If protected memory cannot be allocated, process integrity is compromised. + abort_memory_breach("safe cell allocation", &err) + } + } + } + + fn read(&mut self) -> Self::CellRead<'_> { + match self.0.read() { + Ok(inner) => inner, + Err(err) => abort_memory_breach("safe cell read", &err), + } + } + + fn write(&mut self) -> Self::CellWrite<'_> { + match self.0.write() { + Ok(inner) => inner, + Err(err) => { + // If protected memory becomes unwritable here, treat it as a fatal memory breach. + abort_memory_breach("safe cell write", &err) + } + } + } +} + +fn abort_memory_breach(action: &str, err: &memsafe::error::MemoryError) -> ! { + eprintln!("fatal {action}: {err}"); + std::process::abort(); +} + +pub type SafeCell = MemSafeCell; diff --git a/server/crates/arbiter-server/tests/common/mod.rs b/server/crates/arbiter-server/tests/common/mod.rs index 7fb5bac..3bc3430 100644 --- a/server/crates/arbiter-server/tests/common/mod.rs +++ b/server/crates/arbiter-server/tests/common/mod.rs @@ -1,19 +1,18 @@ use arbiter_proto::transport::{Bi, Error}; use arbiter_server::{ actors::keyholder::KeyHolder, - db::{self, schema}, + db::{self, schema}, safe_cell::{SafeCell, SafeCellHandle as _}, }; use async_trait::async_trait; use diesel::QueryDsl; use diesel_async::RunQueryDsl; -use memsafe::MemSafe; use tokio::sync::mpsc; #[allow(dead_code)] pub async fn bootstrapped_keyholder(db: &db::DatabasePool) -> KeyHolder { let mut actor = KeyHolder::new(db.clone()).await.unwrap(); actor - .bootstrap(MemSafe::new(b"test-seal-key".to_vec()).unwrap()) + .bootstrap(SafeCell::new(b"test-seal-key".to_vec())) .await .unwrap(); actor diff --git a/server/crates/arbiter-server/tests/keyholder/concurrency.rs b/server/crates/arbiter-server/tests/keyholder/concurrency.rs index d34e315..7dbe669 100644 --- a/server/crates/arbiter-server/tests/keyholder/concurrency.rs +++ b/server/crates/arbiter-server/tests/keyholder/concurrency.rs @@ -3,11 +3,11 @@ use std::collections::{HashMap, HashSet}; use arbiter_server::{ actors::keyholder::{CreateNew, Error, KeyHolder}, db::{self, models, schema}, + safe_cell::{SafeCell, SafeCellHandle as _}, }; use diesel::{ExpressionMethods as _, QueryDsl, SelectableHelper, dsl::sql_query}; use diesel_async::RunQueryDsl; use kameo::actor::{ActorRef, Spawn as _}; -use memsafe::MemSafe; use tokio::task::JoinSet; use crate::common; @@ -24,7 +24,7 @@ async fn write_concurrently( let plaintext = format!("{prefix}-{i}").into_bytes(); let id = actor .ask(CreateNew { - plaintext: MemSafe::new(plaintext.clone()).unwrap(), + plaintext: SafeCell::new(plaintext.clone()), }) .await .unwrap(); @@ -118,7 +118,7 @@ async fn insert_failure_does_not_create_partial_row() { drop(conn); let err = actor - .create_new(MemSafe::new(b"should fail".to_vec()).unwrap()) + .create_new(SafeCell::new(b"should fail".to_vec())) .await .unwrap_err(); assert!(matches!(err, Error::DatabaseTransaction(_))); @@ -162,12 +162,12 @@ async fn decrypt_roundtrip_after_high_concurrency() { let mut decryptor = KeyHolder::new(db.clone()).await.unwrap(); decryptor - .try_unseal(MemSafe::new(b"test-seal-key".to_vec()).unwrap()) + .try_unseal(SafeCell::new(b"test-seal-key".to_vec())) .await .unwrap(); for (id, plaintext) in expected { let mut decrypted = decryptor.decrypt(id).await.unwrap(); - assert_eq!(*decrypted.read().unwrap(), plaintext); + assert_eq!(*decrypted.read(), plaintext); } } diff --git a/server/crates/arbiter-server/tests/keyholder/lifecycle.rs b/server/crates/arbiter-server/tests/keyholder/lifecycle.rs index 2cf1f55..e0023d3 100644 --- a/server/crates/arbiter-server/tests/keyholder/lifecycle.rs +++ b/server/crates/arbiter-server/tests/keyholder/lifecycle.rs @@ -1,10 +1,10 @@ use arbiter_server::{ actors::keyholder::{Error, KeyHolder}, db::{self, models, schema}, + safe_cell::{SafeCell, SafeCellHandle as _}, }; use diesel::{QueryDsl, SelectableHelper}; use diesel_async::RunQueryDsl; -use memsafe::MemSafe; use crate::common; @@ -14,7 +14,7 @@ async fn test_bootstrap() { let db = db::create_test_pool().await; let mut actor = KeyHolder::new(db.clone()).await.unwrap(); - let seal_key = MemSafe::new(b"test-seal-key".to_vec()).unwrap(); + let seal_key = SafeCell::new(b"test-seal-key".to_vec()); actor.bootstrap(seal_key).await.unwrap(); let mut conn = db.get().await.unwrap(); @@ -43,7 +43,7 @@ async fn test_bootstrap_rejects_double() { let db = db::create_test_pool().await; let mut actor = common::bootstrapped_keyholder(&db).await; - let seal_key2 = MemSafe::new(b"test-seal-key".to_vec()).unwrap(); + let seal_key2 = SafeCell::new(b"test-seal-key".to_vec()); let err = actor.bootstrap(seal_key2).await.unwrap_err(); assert!(matches!(err, Error::AlreadyBootstrapped)); } @@ -55,7 +55,7 @@ async fn test_create_new_before_bootstrap_fails() { let mut actor = KeyHolder::new(db).await.unwrap(); let err = actor - .create_new(MemSafe::new(b"data".to_vec()).unwrap()) + .create_new(SafeCell::new(b"data".to_vec())) .await .unwrap_err(); assert!(matches!(err, Error::NotBootstrapped)); @@ -91,17 +91,17 @@ async fn test_unseal_correct_password() { let plaintext = b"survive a restart"; let aead_id = actor - .create_new(MemSafe::new(plaintext.to_vec()).unwrap()) + .create_new(SafeCell::new(plaintext.to_vec())) .await .unwrap(); drop(actor); let mut actor = KeyHolder::new(db.clone()).await.unwrap(); - let seal_key = MemSafe::new(b"test-seal-key".to_vec()).unwrap(); + let seal_key = SafeCell::new(b"test-seal-key".to_vec()); actor.try_unseal(seal_key).await.unwrap(); let mut decrypted = actor.decrypt(aead_id).await.unwrap(); - assert_eq!(*decrypted.read().unwrap(), plaintext); + assert_eq!(*decrypted.read(), plaintext); } #[tokio::test] @@ -112,20 +112,20 @@ async fn test_unseal_wrong_then_correct_password() { let plaintext = b"important data"; let aead_id = actor - .create_new(MemSafe::new(plaintext.to_vec()).unwrap()) + .create_new(SafeCell::new(plaintext.to_vec())) .await .unwrap(); drop(actor); let mut actor = KeyHolder::new(db.clone()).await.unwrap(); - let bad_key = MemSafe::new(b"wrong-password".to_vec()).unwrap(); + let bad_key = SafeCell::new(b"wrong-password".to_vec()); let err = actor.try_unseal(bad_key).await.unwrap_err(); assert!(matches!(err, Error::InvalidKey)); - let good_key = MemSafe::new(b"test-seal-key".to_vec()).unwrap(); + let good_key = SafeCell::new(b"test-seal-key".to_vec()); actor.try_unseal(good_key).await.unwrap(); let mut decrypted = actor.decrypt(aead_id).await.unwrap(); - assert_eq!(*decrypted.read().unwrap(), plaintext); + assert_eq!(*decrypted.read(), plaintext); } diff --git a/server/crates/arbiter-server/tests/keyholder/storage.rs b/server/crates/arbiter-server/tests/keyholder/storage.rs index e595339..74a67cc 100644 --- a/server/crates/arbiter-server/tests/keyholder/storage.rs +++ b/server/crates/arbiter-server/tests/keyholder/storage.rs @@ -3,10 +3,10 @@ use std::collections::HashSet; use arbiter_server::{ actors::keyholder::{Error, encryption::v1}, db::{self, models, schema}, + safe_cell::{SafeCell, SafeCellHandle as _}, }; use diesel::{ExpressionMethods as _, QueryDsl, SelectableHelper, dsl::update}; use diesel_async::RunQueryDsl; -use memsafe::MemSafe; use crate::common; @@ -18,12 +18,12 @@ async fn test_create_decrypt_roundtrip() { let plaintext = b"hello arbiter"; let aead_id = actor - .create_new(MemSafe::new(plaintext.to_vec()).unwrap()) + .create_new(SafeCell::new(plaintext.to_vec())) .await .unwrap(); let mut decrypted = actor.decrypt(aead_id).await.unwrap(); - assert_eq!(*decrypted.read().unwrap(), plaintext); + assert_eq!(*decrypted.read(), plaintext); } #[tokio::test] @@ -44,11 +44,11 @@ async fn test_ciphertext_differs_across_entries() { let plaintext = b"same content"; let id1 = actor - .create_new(MemSafe::new(plaintext.to_vec()).unwrap()) + .create_new(SafeCell::new(plaintext.to_vec())) .await .unwrap(); let id2 = actor - .create_new(MemSafe::new(plaintext.to_vec()).unwrap()) + .create_new(SafeCell::new(plaintext.to_vec())) .await .unwrap(); @@ -70,8 +70,8 @@ async fn test_ciphertext_differs_across_entries() { let mut d1 = actor.decrypt(id1).await.unwrap(); let mut d2 = actor.decrypt(id2).await.unwrap(); - assert_eq!(*d1.read().unwrap(), plaintext); - assert_eq!(*d2.read().unwrap(), plaintext); + assert_eq!(*d1.read(), plaintext); + assert_eq!(*d2.read(), plaintext); } #[tokio::test] @@ -83,7 +83,7 @@ async fn test_nonce_never_reused() { let n = 5; for i in 0..n { actor - .create_new(MemSafe::new(format!("secret {i}").into_bytes()).unwrap()) + .create_new(SafeCell::new(format!("secret {i}").into_bytes())) .await .unwrap(); } @@ -137,7 +137,7 @@ async fn broken_db_nonce_format_fails_closed() { drop(conn); let err = actor - .create_new(MemSafe::new(b"must fail".to_vec()).unwrap()) + .create_new(SafeCell::new(b"must fail".to_vec())) .await .unwrap_err(); assert!(matches!(err, Error::BrokenDatabase)); @@ -145,7 +145,7 @@ async fn broken_db_nonce_format_fails_closed() { let db = db::create_test_pool().await; let mut actor = common::bootstrapped_keyholder(&db).await; let id = actor - .create_new(MemSafe::new(b"decrypt target".to_vec()).unwrap()) + .create_new(SafeCell::new(b"decrypt target".to_vec())) .await .unwrap(); let mut conn = db.get().await.unwrap(); diff --git a/server/crates/arbiter-server/tests/user_agent/unseal.rs b/server/crates/arbiter-server/tests/user_agent/unseal.rs index 486cb5d..ec5de37 100644 --- a/server/crates/arbiter-server/tests/user_agent/unseal.rs +++ b/server/crates/arbiter-server/tests/user_agent/unseal.rs @@ -5,9 +5,9 @@ use arbiter_server::{ user_agent::{Request, Response, UnsealError, session::UserAgentSession}, }, db, + safe_cell::{SafeCell, SafeCellHandle as _}, }; use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit}; -use memsafe::MemSafe; use x25519_dalek::{EphemeralSecret, PublicKey}; async fn setup_sealed_user_agent(seal_key: &[u8]) -> (db::DatabasePool, UserAgentSession) { @@ -17,7 +17,7 @@ async fn setup_sealed_user_agent(seal_key: &[u8]) -> (db::DatabasePool, UserAgen actors .key_holder .ask(Bootstrap { - seal_key_raw: MemSafe::new(seal_key.to_vec()).unwrap(), + seal_key_raw: SafeCell::new(seal_key.to_vec()), }) .await .unwrap(); From c56184d30b077043e538c7e4a0de7c916241a3bb Mon Sep 17 00:00:00 2001 From: hdbg Date: Mon, 16 Mar 2026 19:41:24 +0100 Subject: [PATCH 12/13] refactor(server): rewrote cell access using new helpers and added `ast-grep` rules for it --- mise.lock | 9 ++++++ mise.toml | 3 +- .../arbiter-server/src/actors/evm/mod.rs | 6 +--- .../src/actors/keyholder/encryption/v1.rs | 22 ++++--------- .../src/actors/keyholder/mod.rs | 27 +++++++++------- .../actors/user_agent/session/connection.rs | 31 ++++++++++-------- .../arbiter-server/src/evm/safe_signer.rs | 11 +++---- server/crates/arbiter-server/src/safe_cell.rs | 32 +++++++++++++++++++ server/rules/.gitkeep | 0 server/rules/safecell/new-inline.yaml | 10 ++++++ server/rules/safecell/read-inline.yaml | 17 ++++++++++ server/rules/safecell/write-inline.yaml | 13 ++++++++ server/sgconfig.yml | 2 ++ 13 files changed, 131 insertions(+), 52 deletions(-) create mode 100644 server/rules/.gitkeep create mode 100644 server/rules/safecell/new-inline.yaml create mode 100644 server/rules/safecell/read-inline.yaml create mode 100644 server/rules/safecell/write-inline.yaml create mode 100644 server/sgconfig.yml diff --git a/mise.lock b/mise.lock index e3ad0a5..2037239 100644 --- a/mise.lock +++ b/mise.lock @@ -1,3 +1,12 @@ +[[tools.ast-grep]] +version = "0.42.0" +backend = "aqua:ast-grep/ast-grep" +"platforms.linux-arm64" = { checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836", url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip"} +"platforms.linux-x64" = { checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651", url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip"} +"platforms.macos-arm64" = { checksum = "sha256:fc300d5293b1c770a5aece03a8a193b92e71e87cec726c28096990691a582620", url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-apple-darwin.zip"} +"platforms.macos-x64" = { checksum = "sha256:979ffe611327056f4730a1ae71b0209b3b830f58b22c6ed194cda34f55400db2", url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-apple-darwin.zip"} +"platforms.windows-x64" = { checksum = "sha256:55836fa1b2c65dc7d61615a4d9368622a0d2371a76d28b9a165e5a3ab6ae32a4", url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-pc-windows-msvc.zip"} + [[tools."cargo:cargo-audit"]] version = "0.22.1" backend = "cargo:cargo-audit" diff --git a/mise.toml b/mise.toml index ea6265d..9e1e802 100644 --- a/mise.toml +++ b/mise.toml @@ -10,6 +10,7 @@ protoc = "29.6" "cargo:cargo-shear" = "latest" "cargo:cargo-insta" = "1.46.3" python = "3.14.3" +ast-grep = "0.42.0" [tasks.codegen] sources = ['protobufs/*.proto'] @@ -17,4 +18,4 @@ outputs = ['useragent/lib/proto/*'] run = ''' dart pub global activate protoc_plugin && \ protoc --dart_out=grpc:useragent/lib/proto --proto_path=protobufs/ protobufs/*.proto -''' \ No newline at end of file +''' diff --git a/server/crates/arbiter-server/src/actors/evm/mod.rs b/server/crates/arbiter-server/src/actors/evm/mod.rs index 05a9d94..16e4200 100644 --- a/server/crates/arbiter-server/src/actors/evm/mod.rs +++ b/server/crates/arbiter-server/src/actors/evm/mod.rs @@ -108,11 +108,7 @@ impl EvmActor { pub async fn generate(&mut self) -> Result { let (mut key_cell, address) = safe_signer::generate(&mut self.rng); - // Move raw key bytes into a Vec MemSafe for KeyHolder - let plaintext = { - let reader = key_cell.read(); - SafeCell::new(reader.to_vec()) - }; + let plaintext = key_cell.read_inline(|reader| SafeCell::new(reader.to_vec())); let aead_id: i32 = self .keyholder diff --git a/server/crates/arbiter-server/src/actors/keyholder/encryption/v1.rs b/server/crates/arbiter-server/src/actors/keyholder/encryption/v1.rs index 9ca5a45..99348f4 100644 --- a/server/crates/arbiter-server/src/actors/keyholder/encryption/v1.rs +++ b/server/crates/arbiter-server/src/actors/keyholder/encryption/v1.rs @@ -62,26 +62,19 @@ impl TryFrom>> for KeyCell { if value.len() != size_of::() { return Err(()); } - let mut cell = SafeCell::new(Key::default()); - { - let mut cell_write = cell.write(); - let cell_slice: &mut [u8] = cell_write.as_mut(); - cell_slice.copy_from_slice(&value); - } + let cell = SafeCell::new_inline(|cell_write: &mut Key| { + cell_write.copy_from_slice(&value); + }); Ok(Self(cell)) } } impl KeyCell { pub fn new_secure_random() -> Self { - let mut key = SafeCell::new(Key::default()); - { - let mut key_buffer = key.write(); - let key_buffer: &mut [u8] = key_buffer.as_mut(); - + let key = SafeCell::new_inline(|key_buffer: &mut Key| { let mut rng = StdRng::try_from_rng(&mut SysRng).unwrap(); rng.fill_bytes(key_buffer); - } + }); key.into() } @@ -151,15 +144,14 @@ pub fn derive_seal_key(mut password: SafeCell>, salt: &Salt) -> KeyCell let params = argon2::Params::new(262_144, 3, 4, None).unwrap(); let hasher = Argon2::new(Algorithm::Argon2id, argon2::Version::V0x13, params); let mut key = SafeCell::new(Key::default()); - { - let password_source = password.read(); + password.read_inline(|password_source| { let mut key_buffer = key.write(); let key_buffer: &mut [u8] = key_buffer.as_mut(); hasher .hash_password_into(password_source.deref(), salt, key_buffer) .unwrap(); - } + }); key.into() } diff --git a/server/crates/arbiter-server/src/actors/keyholder/mod.rs b/server/crates/arbiter-server/src/actors/keyholder/mod.rs index e53f6a3..f37284a 100644 --- a/server/crates/arbiter-server/src/actors/keyholder/mod.rs +++ b/server/crates/arbiter-server/src/actors/keyholder/mod.rs @@ -8,12 +8,15 @@ use kameo::{Actor, Reply, messages}; use strum::{EnumDiscriminants, IntoDiscriminant}; use tracing::{error, info}; -use crate::{db::{ - self, - models::{self, RootKeyHistory}, - schema::{self}, -}, safe_cell::SafeCellHandle as _}; use crate::safe_cell::SafeCell; +use crate::{ + db::{ + self, + models::{self, RootKeyHistory}, + schema::{self}, + }, + safe_cell::SafeCellHandle as _, +}; use encryption::v1::{self, KeyCell, Nonce}; pub mod encryption; @@ -148,16 +151,15 @@ impl KeyHolder { let root_key_nonce = v1::Nonce::default(); let data_encryption_nonce = v1::Nonce::default(); - let root_key_ciphertext: Vec = { - let root_key_reader = root_key.0.read(); - let root_key_reader = root_key_reader.as_slice(); + let root_key_ciphertext: Vec = root_key.0.read_inline(|reader| { + let root_key_reader = reader.as_slice(); seal_key .encrypt(&root_key_nonce, v1::ROOT_KEY_TAG, root_key_reader) .map_err(|err| { error!(?err, "Fatal bootstrap error"); Error::Encryption(err) - })? - }; + }) + })?; let mut conn = self.db.get().await?; @@ -349,7 +351,10 @@ mod tests { use diesel_async::RunQueryDsl; - use crate::{db::{self}, safe_cell::SafeCell}; + use crate::{ + db::{self}, + safe_cell::SafeCell, + }; use super::*; diff --git a/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs b/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs index 984f464..627e5f1 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs @@ -5,18 +5,23 @@ use kameo::error::SendError; use tracing::{error, info}; use x25519_dalek::{EphemeralSecret, PublicKey}; -use crate::{actors::{ - evm::{Generate, ListWallets, UseragentCreateGrant, UseragentDeleteGrant, UseragentListGrants}, - keyholder::{self, Bootstrap, TryUnseal}, - user_agent::{ - BootstrapError, Request, Response, TransportResponseError, UnsealError, VaultState, - session::{ - UserAgentSession, - state::{UnsealContext, UserAgentEvents, UserAgentStates}, +use crate::safe_cell::SafeCell; +use crate::{ + actors::{ + evm::{ + Generate, ListWallets, UseragentCreateGrant, UseragentDeleteGrant, UseragentListGrants, + }, + keyholder::{self, Bootstrap, TryUnseal}, + user_agent::{ + BootstrapError, Request, Response, TransportResponseError, UnsealError, VaultState, + session::{ + UserAgentSession, + state::{UnsealContext, UserAgentEvents, UserAgentStates}, + }, }, }, -}, safe_cell::SafeCellHandle as _}; -use crate::safe_cell::SafeCell; + safe_cell::SafeCellHandle as _, +}; impl UserAgentSession { pub async fn process_transport_inbound(&mut self, req: Request) -> Output { @@ -100,11 +105,9 @@ impl UserAgentSession { let mut key_buffer = SafeCell::new(ciphertext.to_vec()); - let decryption_result = { - let mut write_handle = key_buffer.write(); - let write_handle = write_handle.deref_mut(); + let decryption_result = key_buffer.write_inline(|write_handle| { cipher.decrypt_in_place(nonce, associated_data, write_handle) - }; + }); match decryption_result { Ok(_) => Ok(key_buffer), diff --git a/server/crates/arbiter-server/src/evm/safe_signer.rs b/server/crates/arbiter-server/src/evm/safe_signer.rs index 124a248..f1f5bcd 100644 --- a/server/crates/arbiter-server/src/evm/safe_signer.rs +++ b/server/crates/arbiter-server/src/evm/safe_signer.rs @@ -1,5 +1,6 @@ use std::sync::Mutex; +use crate::safe_cell::{SafeCell, SafeCellHandle as _}; use alloy::{ consensus::SignableTransaction, network::{TxSigner, TxSignerSync}, @@ -8,7 +9,6 @@ use alloy::{ }; use async_trait::async_trait; use k256::ecdsa::{self, RecoveryId, SigningKey, signature::hazmat::PrehashSigner}; -use crate::safe_cell::{SafeCell, SafeCellHandle as _}; /// An Ethereum signer that stores its secp256k1 secret key inside a /// hardware-protected [`MemSafe`] cell. @@ -44,11 +44,10 @@ impl std::fmt::Debug for SafeSigner { /// Returns the protected key bytes and the derived Ethereum address. pub fn generate(rng: &mut impl rand::Rng) -> (SafeCell<[u8; 32]>, Address) { loop { - let mut cell = SafeCell::new([0u8; 32]); - { - let mut w = cell.write(); - rng.fill_bytes(w.as_mut()); - } + let mut cell = SafeCell::new_inline(|w: &mut [u8; 32]| { + rng.fill_bytes(w); + }); + let reader = cell.read(); if let Ok(sk) = SigningKey::from_slice(reader.as_ref()) { let address = secret_key_to_address(&sk); diff --git a/server/crates/arbiter-server/src/safe_cell.rs b/server/crates/arbiter-server/src/safe_cell.rs index 056e131..dc44065 100644 --- a/server/crates/arbiter-server/src/safe_cell.rs +++ b/server/crates/arbiter-server/src/safe_cell.rs @@ -19,6 +19,36 @@ pub trait SafeCellHandle { fn read(&mut self) -> Self::CellRead<'_>; fn write(&mut self) -> Self::CellWrite<'_>; + + fn new_inline(f: F) -> Self + where + Self: Sized, + T: Default, + F: for<'a> FnOnce(&'a mut T), + { + let mut cell = Self::new(T::default()); + { + let mut handle = cell.write(); + f(handle.deref_mut()); + } + cell + } + + #[inline(always)] + fn read_inline(&mut self, f: F) -> R + where + F: FnOnce(&T) -> R, + { + f(&*self.read()) + } + + #[inline(always)] + fn write_inline(&mut self, f: F) -> R + where + F: FnOnce(&mut T) -> R, + { + f(&mut *self.write()) + } } pub struct MemSafeCell(MemSafe); @@ -53,6 +83,7 @@ impl SafeCellHandle for MemSafeCell { } } + #[inline(always)] fn read(&mut self) -> Self::CellRead<'_> { match self.0.read() { Ok(inner) => inner, @@ -60,6 +91,7 @@ impl SafeCellHandle for MemSafeCell { } } + #[inline(always)] fn write(&mut self) -> Self::CellWrite<'_> { match self.0.write() { Ok(inner) => inner, diff --git a/server/rules/.gitkeep b/server/rules/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/server/rules/safecell/new-inline.yaml b/server/rules/safecell/new-inline.yaml new file mode 100644 index 0000000..48c2e4e --- /dev/null +++ b/server/rules/safecell/new-inline.yaml @@ -0,0 +1,10 @@ +id: safecell-new-inline +language: Rust +rule: + pattern: $CELL.write_inline(|$W| $BODY); + follows: + pattern: let mut $CELL = SafeCell::new($INIT); +fix: + template: let mut $CELL = SafeCell::new_inline(|$W| $BODY); + expandStart: + pattern: let mut $CELL = SafeCell::new($INIT) \ No newline at end of file diff --git a/server/rules/safecell/read-inline.yaml b/server/rules/safecell/read-inline.yaml new file mode 100644 index 0000000..e5bbee6 --- /dev/null +++ b/server/rules/safecell/read-inline.yaml @@ -0,0 +1,17 @@ +id: safecell-read-inline +language: Rust +rule: + pattern: + context: | + { + let $READ = $CELL.read(); + $$$BODY + } + selector: block + inside: + kind: block +fix: + template: | + $CELL.read_inline(|$READ| { + $$$BODY + }); \ No newline at end of file diff --git a/server/rules/safecell/write-inline.yaml b/server/rules/safecell/write-inline.yaml new file mode 100644 index 0000000..cfa13fe --- /dev/null +++ b/server/rules/safecell/write-inline.yaml @@ -0,0 +1,13 @@ +id: safecell-write-inline +language: Rust +rule: + pattern: | + { + let mut $WRITE = $CELL.write(); + $$$BODY + } +fix: + template: | + $CELL.write_inline(|$WRITE| { + $$$BODY + }); \ No newline at end of file diff --git a/server/sgconfig.yml b/server/sgconfig.yml new file mode 100644 index 0000000..b0f5823 --- /dev/null +++ b/server/sgconfig.yml @@ -0,0 +1,2 @@ +ruleDirs: +- ./rules From 712f114763c91c418d3a79fdf840e28a1fb894c4 Mon Sep 17 00:00:00 2001 From: hdbg Date: Tue, 17 Mar 2026 11:30:06 +0100 Subject: [PATCH 13/13] style(encryption): suppress clippy unwrap lints with justifications --- .../src/actors/keyholder/encryption/v1.rs | 13 +++++++++++++ .../arbiter-server/src/actors/router/mod.rs | 2 +- .../src/actors/user_agent/mod.rs | 18 +++++++++++++----- .../actors/user_agent/session/connection.rs | 6 +++++- .../crates/arbiter-server/src/context/tls.rs | 4 ++++ server/crates/arbiter-server/src/db/mod.rs | 3 +++ .../arbiter-server/src/evm/safe_signer.rs | 4 +++- 7 files changed, 42 insertions(+), 8 deletions(-) diff --git a/server/crates/arbiter-server/src/actors/keyholder/encryption/v1.rs b/server/crates/arbiter-server/src/actors/keyholder/encryption/v1.rs index 99348f4..8befeb1 100644 --- a/server/crates/arbiter-server/src/actors/keyholder/encryption/v1.rs +++ b/server/crates/arbiter-server/src/actors/keyholder/encryption/v1.rs @@ -72,6 +72,10 @@ impl TryFrom>> for KeyCell { impl KeyCell { pub fn new_secure_random() -> Self { let key = SafeCell::new_inline(|key_buffer: &mut Key| { + #[allow( + clippy::unwrap_used, + reason = "Rng failure is unrecoverable and should panic" + )] let mut rng = StdRng::try_from_rng(&mut SysRng).unwrap(); rng.fill_bytes(key_buffer); }); @@ -133,6 +137,10 @@ pub type Salt = [u8; ArgonSalt::RECOMMENDED_LENGTH]; pub fn generate_salt() -> Salt { let mut salt = Salt::default(); + #[allow( + clippy::unwrap_used, + reason = "Rng failure is unrecoverable and should panic" + )] let mut rng = StdRng::try_from_rng(&mut SysRng).unwrap(); rng.fill_bytes(&mut salt); salt @@ -141,6 +149,7 @@ pub fn generate_salt() -> Salt { /// User password might be of different length, have not enough entropy, etc... /// Derive a fixed-length key from the password using Argon2id, which is designed for password hashing and key derivation. pub fn derive_seal_key(mut password: SafeCell>, salt: &Salt) -> KeyCell { + #[allow(clippy::unwrap_used)] let params = argon2::Params::new(262_144, 3, 4, None).unwrap(); let hasher = Argon2::new(Algorithm::Argon2id, argon2::Version::V0x13, params); let mut key = SafeCell::new(Key::default()); @@ -148,6 +157,10 @@ pub fn derive_seal_key(mut password: SafeCell>, salt: &Salt) -> KeyCell let mut key_buffer = key.write(); let key_buffer: &mut [u8] = key_buffer.as_mut(); + #[allow( + clippy::unwrap_used, + reason = "Better fail completely than return a weak key" + )] hasher .hash_password_into(password_source.deref(), salt, key_buffer) .unwrap(); diff --git a/server/crates/arbiter-server/src/actors/router/mod.rs b/server/crates/arbiter-server/src/actors/router/mod.rs index a0a75b8..f1654b2 100644 --- a/server/crates/arbiter-server/src/actors/router/mod.rs +++ b/server/crates/arbiter-server/src/actors/router/mod.rs @@ -154,7 +154,7 @@ impl MessageRouter { ctx: &mut Context>>, ) -> DelegatedReply> { let (reply, Some(reply_sender)) = ctx.reply_sender() else { - panic!("Exptected `request_client_approval` to have callback channel"); + unreachable!("Expected `request_client_approval` to have callback channel"); }; let weak_refs = self diff --git a/server/crates/arbiter-server/src/actors/user_agent/mod.rs b/server/crates/arbiter-server/src/actors/user_agent/mod.rs index b4e048b..6b4a7d6 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/mod.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/mod.rs @@ -1,12 +1,13 @@ use alloy::primitives::Address; -use arbiter_proto::{transport::Bi}; +use arbiter_proto::transport::Bi; use kameo::actor::Spawn as _; use tracing::{error, info}; use crate::{ actors::{GlobalActors, evm, user_agent::session::UserAgentSession}, - db::{self, models::KeyType}, evm::policies::{Grant, SpecificGrant}, + db::{self, models::KeyType}, evm::policies::SharedGrantSettings, + evm::policies::{Grant, SpecificGrant}, }; #[derive(Debug, thiserror::Error, PartialEq)] @@ -47,6 +48,7 @@ impl AuthPublicKey { AuthPublicKey::EcdsaSecp256k1(k) => k.to_encoded_point(true).as_bytes().to_vec(), AuthPublicKey::Rsa(k) => { use rsa::pkcs8::EncodePublicKey as _; + #[allow(clippy::expect_used)] k.to_public_key_der() .expect("rsa SPKI encoding is infallible") .to_vec() @@ -124,13 +126,19 @@ pub enum Request { #[derive(Debug)] pub enum Response { - AuthChallenge { nonce: i32 }, + AuthChallenge { + nonce: i32, + }, AuthOk, - UnsealStartResponse { server_pubkey: x25519_dalek::PublicKey }, + UnsealStartResponse { + server_pubkey: x25519_dalek::PublicKey, + }, UnsealResult(Result<(), UnsealError>), BootstrapResult(Result<(), BootstrapError>), VaultState(VaultState), - ClientConnectionRequest { pubkey: ed25519_dalek::VerifyingKey }, + ClientConnectionRequest { + pubkey: ed25519_dalek::VerifyingKey, + }, ClientConnectionCancel, EvmWalletCreate(Result<(), evm::Error>), EvmWalletList(Vec
), diff --git a/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs b/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs index 627e5f1..f7cf2be 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs @@ -1,4 +1,4 @@ -use std::{ops::DerefMut, sync::Mutex}; +use std::sync::Mutex; use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit}; use kameo::error::SendError; @@ -76,6 +76,10 @@ impl UserAgentSession { }; let ephemeral_secret = { + #[allow( + clippy::unwrap_used, + reason = "Mutex poison is unrecoverable and should panic" + )] let mut secret_lock = unseal_context.secret.lock().unwrap(); let secret = secret_lock.take(); match secret { diff --git a/server/crates/arbiter-server/src/context/tls.rs b/server/crates/arbiter-server/src/context/tls.rs index 85196ec..0798dc8 100644 --- a/server/crates/arbiter-server/src/context/tls.rs +++ b/server/crates/arbiter-server/src/context/tls.rs @@ -91,6 +91,10 @@ impl TlsCa { let cert_key_pem = certified_issuer.key().serialize_pem(); + #[allow( + clippy::unwrap_used, + reason = "Broken cert couldn't bootstrap server anyway" + )] let issuer = Issuer::from_ca_cert_pem( &certified_issuer.pem(), KeyPair::from_pem(cert_key_pem.as_ref()).unwrap(), diff --git a/server/crates/arbiter-server/src/db/mod.rs b/server/crates/arbiter-server/src/db/mod.rs index d8771dc..616bd92 100644 --- a/server/crates/arbiter-server/src/db/mod.rs +++ b/server/crates/arbiter-server/src/db/mod.rs @@ -92,6 +92,7 @@ fn initialize_database(url: &str) -> Result<(), DatabaseSetupError> { #[tracing::instrument(level = "info")] pub async fn create_pool(url: Option<&str>) -> Result { let database_url = url.map(String::from).unwrap_or( + #[allow(clippy::expect_used)] database_path()? .to_str() .expect("database path is not valid UTF-8") @@ -135,11 +136,13 @@ pub async fn create_test_pool() -> DatabasePool { let tempfile_name = Alphanumeric.sample_string(&mut rand::rng(), 16); let file = std::env::temp_dir().join(tempfile_name); + #[allow(clippy::expect_used)] let url = file .to_str() .expect("temp file path is not valid UTF-8") .to_string(); + #[allow(clippy::expect_used)] create_pool(Some(&url)) .await .expect("Failed to create test database pool") diff --git a/server/crates/arbiter-server/src/evm/safe_signer.rs b/server/crates/arbiter-server/src/evm/safe_signer.rs index f1f5bcd..3d15a05 100644 --- a/server/crates/arbiter-server/src/evm/safe_signer.rs +++ b/server/crates/arbiter-server/src/evm/safe_signer.rs @@ -83,6 +83,7 @@ impl SafeSigner { } fn sign_hash_inner(&self, hash: &B256) -> Result { + #[allow(clippy::expect_used)] let mut cell = self.key.lock().expect("SafeSigner mutex poisoned"); let reader = cell.read(); let sig: (ecdsa::Signature, RecoveryId) = reader.sign_prehash(hash.as_ref())?; @@ -95,7 +96,8 @@ impl SafeSigner { { return Err(Error::TransactionChainIdMismatch { signer: chain_id, - tx: tx.chain_id().unwrap(), + #[allow(clippy::expect_used)] + tx: tx.chain_id().expect("Chain ID is guaranteed to be set"), }); } self.sign_hash_inner(&tx.signature_hash())