Compare commits
4 Commits
feat-auto-
...
security-b
| Author | SHA1 | Date | |
|---|---|---|---|
| 075d33219e | |||
| 8cb6f4abe0 | |||
|
|
4bac70a6e9 | ||
|
|
54a41743be |
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
name: Widget decomposition and provider subscriptions
|
|
||||||
description: Prefer splitting screens into multiple focused files/widgets; each widget subscribes to its own relevant providers
|
|
||||||
type: feedback
|
|
||||||
---
|
|
||||||
|
|
||||||
Split screens into multiple smaller widgets across multiple files. Each widget should subscribe only to the providers it needs (`ref.watch` at lowest possible level), rather than having one large screen widget that watches everything and passes data down as parameters.
|
|
||||||
|
|
||||||
**Why:** Reduces unnecessary rebuilds; improves readability; each file has one clear responsibility.
|
|
||||||
|
|
||||||
**How to apply:** When building a new screen, identify which sub-widgets need their own provider subscriptions and extract them into separate files (e.g., `widgets/grant_card.dart` watches enrichment providers itself, rather than the screen doing it and passing resolved strings down).
|
|
||||||
7
.gitignore
vendored
@@ -1,6 +1 @@
|
|||||||
target/
|
target/
|
||||||
scripts/__pycache__/
|
|
||||||
.DS_Store
|
|
||||||
.cargo/config.toml
|
|
||||||
.vscode/
|
|
||||||
docs/superpowers
|
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
when:
|
when:
|
||||||
- event: pull_request
|
- event: pull_request
|
||||||
path:
|
path:
|
||||||
include: ['.woodpecker/server-*.yaml', 'server/**']
|
include: ['.woodpecker/server-*.yaml', 'server/**']
|
||||||
- event: push
|
- event: push
|
||||||
branch: main
|
branch: main
|
||||||
path:
|
path:
|
||||||
include: ['.woodpecker/server-*.yaml', 'server/**']
|
include: ['.woodpecker/server-*.yaml', 'server/**']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: audit
|
- name: test
|
||||||
image: jdxcode/mise:latest
|
image: jdxcode/mise:latest
|
||||||
directory: server
|
directory: server
|
||||||
environment:
|
environment:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
CARGO_TARGET_DIR: /usr/local/cargo/target
|
CARGO_TARGET_DIR: /usr/local/cargo/target
|
||||||
CARGO_HOME: /usr/local/cargo/registry
|
CARGO_HOME: /usr/local/cargo/registry
|
||||||
volumes:
|
volumes:
|
||||||
- cargo-target:/usr/local/cargo/target
|
- cargo-target:/usr/local/cargo/target
|
||||||
- cargo-registry:/usr/local/cargo/registry
|
- cargo-registry:/usr/local/cargo/registry
|
||||||
commands:
|
commands:
|
||||||
- apt-get update && apt-get install -y pkg-config
|
- apt-get update && apt-get install -y pkg-config
|
||||||
# Install only the necessary Rust toolchain and test runner to speed up the CI
|
# Install only the necessary Rust toolchain and test runner to speed up the CI
|
||||||
- mise install rust
|
- mise install rust
|
||||||
- mise install cargo:cargo-audit
|
- mise install cargo:cargo-audit
|
||||||
- mise exec cargo:cargo-audit -- cargo audit
|
- mise exec cargo:cargo-audit -- cargo audit
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
when:
|
|
||||||
- event: push
|
|
||||||
branch: main
|
|
||||||
path:
|
|
||||||
include: ['.woodpecker/server-*.yaml', 'server/**']
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: clippy-fix
|
|
||||||
image: jdxcode/mise:latest
|
|
||||||
directory: server
|
|
||||||
environment:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
CARGO_TARGET_DIR: /usr/local/cargo/target
|
|
||||||
CARGO_HOME: /usr/local/cargo/registry
|
|
||||||
GITEA_TOKEN:
|
|
||||||
from_secret: GITEA_TOKEN
|
|
||||||
GITEA_URL:
|
|
||||||
from_secret: GITEA_URL
|
|
||||||
volumes:
|
|
||||||
- cargo-target:/usr/local/cargo/target
|
|
||||||
- cargo-registry:/usr/local/cargo/registry
|
|
||||||
commands:
|
|
||||||
- apt-get update && apt-get install -y pkg-config curl
|
|
||||||
- mise install rust
|
|
||||||
- mise install protoc
|
|
||||||
- mise exec rust -- cargo clippy --fix --allow-dirty --all
|
|
||||||
- |
|
|
||||||
cd ..
|
|
||||||
if git diff --quiet HEAD; then
|
|
||||||
echo "No machine-applicable clippy fixes found, nothing to do."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
git config user.email "bot@arbiter"
|
|
||||||
git config user.name "Clippy Bot"
|
|
||||||
git add -A
|
|
||||||
git commit -m "fix(clippy): apply auto-fixable linting suggestions"
|
|
||||||
git config http.extraHeader "Authorization: token ${GITEA_TOKEN}"
|
|
||||||
git push --force origin HEAD:refs/heads/bot/clippy-fixes
|
|
||||||
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
||||||
-X POST "${GITEA_URL}/api/v1/repos/${CI_REPO}/pulls" \
|
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{\"title\":\"fix(clippy): apply auto-fixable linting suggestions\",\"head\":\"bot/clippy-fixes\",\"base\":\"main\",\"body\":\"Automated clippy fixes generated by CI bot.\"}")
|
|
||||||
echo "Gitea API response: ${HTTP_STATUS}"
|
|
||||||
[ "$HTTP_STATUS" = "201" ] || [ "$HTTP_STATUS" = "409" ] || [ "$HTTP_STATUS" = "422" ] || exit 1
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
when:
|
|
||||||
- event: pull_request
|
|
||||||
path:
|
|
||||||
include: ['.woodpecker/server-*.yaml', 'server/**']
|
|
||||||
- event: push
|
|
||||||
branch: main
|
|
||||||
path:
|
|
||||||
include: ['.woodpecker/server-*.yaml', 'server/**']
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: lint
|
|
||||||
image: jdxcode/mise:latest
|
|
||||||
directory: server
|
|
||||||
environment:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
CARGO_TARGET_DIR: /usr/local/cargo/target
|
|
||||||
CARGO_HOME: /usr/local/cargo/registry
|
|
||||||
volumes:
|
|
||||||
- cargo-target:/usr/local/cargo/target
|
|
||||||
- cargo-registry:/usr/local/cargo/registry
|
|
||||||
commands:
|
|
||||||
- apt-get update && apt-get install -y pkg-config
|
|
||||||
- mise install rust
|
|
||||||
- mise install protoc
|
|
||||||
- mise exec rust -- cargo clippy --all -- -D warnings
|
|
||||||
@@ -1,27 +1,27 @@
|
|||||||
when:
|
when:
|
||||||
- event: pull_request
|
- event: pull_request
|
||||||
path:
|
path:
|
||||||
include: ['.woodpecker/server-*.yaml', 'server/**']
|
include: ['.woodpecker/server-*.yaml', 'server/**']
|
||||||
- event: push
|
- event: push
|
||||||
branch: main
|
branch: main
|
||||||
path:
|
path:
|
||||||
include: ['.woodpecker/server-*.yaml', 'server/**']
|
include: ['.woodpecker/server-*.yaml', 'server/**']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: test
|
- name: test
|
||||||
image: jdxcode/mise:latest
|
image: jdxcode/mise:latest
|
||||||
directory: server
|
directory: server
|
||||||
environment:
|
environment:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
CARGO_TARGET_DIR: /usr/local/cargo/target
|
CARGO_TARGET_DIR: /usr/local/cargo/target
|
||||||
CARGO_HOME: /usr/local/cargo/registry
|
CARGO_HOME: /usr/local/cargo/registry
|
||||||
volumes:
|
volumes:
|
||||||
- cargo-target:/usr/local/cargo/target
|
- cargo-target:/usr/local/cargo/target
|
||||||
- cargo-registry:/usr/local/cargo/registry
|
- cargo-registry:/usr/local/cargo/registry
|
||||||
commands:
|
commands:
|
||||||
- apt-get update && apt-get install -y pkg-config
|
- apt-get update && apt-get install -y pkg-config
|
||||||
# Install only the necessary Rust toolchain and test runner to speed up the CI
|
# Install only the necessary Rust toolchain and test runner to speed up the CI
|
||||||
- mise install rust
|
- mise install rust
|
||||||
- mise install protoc
|
- mise install protoc
|
||||||
- mise install cargo:cargo-nextest
|
- mise install cargo:cargo-nextest
|
||||||
- mise exec cargo:cargo-nextest -- cargo nextest run --no-fail-fast --all-features
|
- mise exec cargo:cargo-nextest -- cargo nextest run --no-fail-fast
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
when:
|
when:
|
||||||
- event: pull_request
|
- event: pull_request
|
||||||
path:
|
path:
|
||||||
include: ['.woodpecker/server-*.yaml', 'server/**']
|
include: ['.woodpecker/server-*.yaml', 'server/**']
|
||||||
- event: push
|
- event: push
|
||||||
branch: main
|
branch: main
|
||||||
path:
|
path:
|
||||||
include: ['.woodpecker/server-*.yaml', 'server/**']
|
include: ['.woodpecker/server-*.yaml', 'server/**']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: vet
|
- name: test
|
||||||
image: jdxcode/mise:latest
|
image: jdxcode/mise:latest
|
||||||
directory: server
|
directory: server
|
||||||
environment:
|
environment:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
CARGO_TARGET_DIR: /usr/local/cargo/target
|
CARGO_TARGET_DIR: /usr/local/cargo/target
|
||||||
CARGO_HOME: /usr/local/cargo/registry
|
CARGO_HOME: /usr/local/cargo/registry
|
||||||
volumes:
|
volumes:
|
||||||
- cargo-target:/usr/local/cargo/target
|
- cargo-target:/usr/local/cargo/target
|
||||||
- cargo-registry:/usr/local/cargo/registry
|
- cargo-registry:/usr/local/cargo/registry
|
||||||
commands:
|
commands:
|
||||||
- apt-get update && apt-get install -y pkg-config
|
- apt-get update && apt-get install -y pkg-config
|
||||||
# Install only the necessary Rust toolchain and test runner to speed up the CI
|
# Install only the necessary Rust toolchain and test runner to speed up the CI
|
||||||
- mise install rust
|
- mise install rust
|
||||||
- mise install cargo:cargo-vet
|
- mise install cargo:cargo-vet
|
||||||
- mise exec cargo:cargo-vet -- cargo vet
|
- mise exec cargo:cargo-vet -- cargo vet
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
when:
|
|
||||||
- event: pull_request
|
|
||||||
path:
|
|
||||||
include: ['.woodpecker/useragent-*.yaml', 'useragent/**']
|
|
||||||
- event: push
|
|
||||||
branch: main
|
|
||||||
path:
|
|
||||||
include: ['.woodpecker/useragent-*.yaml', 'useragent/**']
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: analyze
|
|
||||||
image: jdxcode/mise:latest
|
|
||||||
commands:
|
|
||||||
- mise install flutter
|
|
||||||
- mise install protoc
|
|
||||||
# Reruns codegen to catch protocol drift
|
|
||||||
- mise codegen
|
|
||||||
- cd useragent/ && flutter analyze
|
|
||||||
149
AGENTS.md
@@ -1,149 +0,0 @@
|
|||||||
# 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
|
|
||||||
- **`operator/`** — 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-operator` | Rust client library for the operator 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 <test_name>
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
- **`Vault`** — 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.
|
|
||||||
- **`FlowCoordinator`** — Coordinates cross-connection flow between operators and SDK clients.
|
|
||||||
- **`EvmActor`** — Handles EVM transaction policy enforcement and signing.
|
|
||||||
|
|
||||||
Per-connection actors live under `actors/operator/` 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 <name> --migration-dir crates/arbiter-server/migrations
|
|
||||||
|
|
||||||
# Run migrations manually (server also runs them on startup)
|
|
||||||
diesel migration run --migration-dir crates/arbiter-server/migrations
|
|
||||||
```
|
|
||||||
|
|
||||||
### Code Conventions
|
|
||||||
|
|
||||||
**`#[must_use]` Attribute:**
|
|
||||||
Apply the `#[must_use]` attribute to return types of functions where the return value is critical and should not be accidentally ignored. This is commonly used for:
|
|
||||||
|
|
||||||
- Methods that return `bool` indicating success/failure or validation state
|
|
||||||
- Any function where ignoring the return value indicates a logic error
|
|
||||||
|
|
||||||
Do not apply `#[must_use]` redundantly to items (types or functions) that are already annotated with `#[must_use]`.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[must_use]
|
|
||||||
pub fn verify(&self, nonce: i32, context: &[u8], signature: &Signature) -> bool {
|
|
||||||
// verification logic
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This forces callers to either use the return value or explicitly ignore it with `let _ = ...;`, preventing silent failures.
|
|
||||||
|
|
||||||
## Operator (Flutter + Rinf at `operator/`)
|
|
||||||
|
|
||||||
The Flutter app uses [Rinf](https://rinf.cunarist.org) to call Rust code. The Rust logic lives in `operator/native/hub/` as a separate crate that uses `arbiter-operator` for the gRPC client.
|
|
||||||
|
|
||||||
Communication between Dart and Rust uses typed **signals** defined in `operator/native/hub/src/signals/`. After modifying signal structs, regenerate Dart bindings:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd operator && rinf gen
|
|
||||||
```
|
|
||||||
|
|
||||||
### Common Commands
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd operator
|
|
||||||
|
|
||||||
# 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 `operator/native/hub/src/lib.rs`. It spawns actors defined in `operator/native/hub/src/actors/` which handle Dart↔server communication via signals.
|
|
||||||
156
ARCHITECTURE.md
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
# Arbiter
|
||||||
|
|
||||||
|
Arbiter is a permissioned signing service for cryptocurrency wallets. It runs as a background service on the user's machine with an optional client application for vault management.
|
||||||
|
|
||||||
|
**Core principle:** The vault NEVER exposes key material. It only produces signatures when a request satisfies the configured policies.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Peer Types
|
||||||
|
|
||||||
|
Arbiter distinguishes two kinds of peers:
|
||||||
|
|
||||||
|
- **User Agent** — A client application used by the owner to manage the vault (create wallets, approve SDK clients, configure policies).
|
||||||
|
- **SDK Client** — A consumer of signing capabilities, typically an automation tool. In the future, this could include a browser-based wallet.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Authentication
|
||||||
|
|
||||||
|
### 2.1 Challenge-Response
|
||||||
|
|
||||||
|
All peers authenticate via public-key cryptography using a challenge-response protocol:
|
||||||
|
|
||||||
|
1. The peer sends its public key and requests a challenge.
|
||||||
|
2. The server looks up the key in its database. If found, it increments the nonce and returns a challenge (replay-attack protection).
|
||||||
|
3. The peer signs the challenge with its private key and sends the signature back.
|
||||||
|
4. The server verifies the signature:
|
||||||
|
- **Pass:** The connection is considered authenticated.
|
||||||
|
- **Fail:** The server closes the connection.
|
||||||
|
|
||||||
|
### 2.2 User Agent Bootstrap
|
||||||
|
|
||||||
|
On first run — when no User Agents are registered — the server generates a one-time bootstrap token. It is made available in two ways:
|
||||||
|
|
||||||
|
- **Local setup:** Written to `~/.arbiter/bootstrap_token` for automatic discovery by a co-located User Agent.
|
||||||
|
- **Remote setup:** Printed to the server's console output.
|
||||||
|
|
||||||
|
The first User Agent must present this token alongside the standard challenge-response to complete registration.
|
||||||
|
|
||||||
|
### 2.3 SDK Client Registration
|
||||||
|
|
||||||
|
There is no bootstrap mechanism for SDK clients. They must be explicitly approved by an already-registered User Agent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Server Identity
|
||||||
|
|
||||||
|
The server proves its identity using TLS with a self-signed certificate. The TLS private key is generated on first run and is long-term; no rotation mechanism exists yet due to the complexity of multi-peer coordination.
|
||||||
|
|
||||||
|
Peers verify the server by its **public key fingerprint**:
|
||||||
|
|
||||||
|
- **User Agent (local):** Receives the fingerprint automatically through the bootstrap token.
|
||||||
|
- **User Agent (remote) / SDK Client:** Must receive the fingerprint out-of-band.
|
||||||
|
|
||||||
|
> A streamlined setup mechanism using a single connection string is planned but not yet implemented.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Key Management
|
||||||
|
|
||||||
|
### 4.1 Key Hierarchy
|
||||||
|
|
||||||
|
There are three layers of keys:
|
||||||
|
|
||||||
|
| Key | Encrypts | Encrypted by |
|
||||||
|
|---|---|---|
|
||||||
|
| **User key** (password) | Root key | — (derived from user input) |
|
||||||
|
| **Root key** | Wallet keys | User key |
|
||||||
|
| **Wallet keys** | — (used for signing) | Root key |
|
||||||
|
|
||||||
|
This layered design enables:
|
||||||
|
|
||||||
|
- **Password rotation** without re-encrypting every wallet key (only the root key is re-encrypted).
|
||||||
|
- **Root key rotation** without requiring the user to change their password.
|
||||||
|
|
||||||
|
### 4.2 Encryption at Rest
|
||||||
|
|
||||||
|
The database stores everything in encrypted form using symmetric AEAD. The encryption scheme is versioned to support transparent migration — when the vault unseals, Arbiter automatically re-encrypts any entries that are behind the current scheme version. See [IMPLEMENTATION.md](IMPLEMENTATION.md) for the specific scheme and versioning mechanism.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Vault Lifecycle
|
||||||
|
|
||||||
|
### 5.1 Sealed State
|
||||||
|
|
||||||
|
On boot, the root key is encrypted and the server cannot perform any signing operations. This state is called **Sealed**.
|
||||||
|
|
||||||
|
### 5.2 Unseal Flow
|
||||||
|
|
||||||
|
To transition to the **Unsealed** state, a User Agent must provide the password:
|
||||||
|
|
||||||
|
1. The User Agent initiates an unseal request.
|
||||||
|
2. The server generates a one-time key pair and returns the public key.
|
||||||
|
3. The User Agent encrypts the user's password with this one-time public key and sends the ciphertext to the server.
|
||||||
|
4. The server decrypts and verifies the password:
|
||||||
|
- **Success:** The root key is decrypted and placed into a hardened memory cell. The server transitions to `Unsealed`. Any entries pending encryption scheme migration are re-encrypted.
|
||||||
|
- **Failure:** The server returns an error indicating the password is incorrect.
|
||||||
|
|
||||||
|
### 5.3 Memory Protection
|
||||||
|
|
||||||
|
Once unsealed, the root key must be protected in memory against:
|
||||||
|
|
||||||
|
- Memory dumps
|
||||||
|
- Page swaps to disk
|
||||||
|
- Hibernation files
|
||||||
|
|
||||||
|
See [IMPLEMENTATION.md](IMPLEMENTATION.md) for the current and planned memory protection approaches.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Permission Engine
|
||||||
|
|
||||||
|
### 6.1 Fundamental Rules
|
||||||
|
|
||||||
|
- SDK clients have **no access by default**.
|
||||||
|
- Access is granted **explicitly** by a User Agent.
|
||||||
|
- Grants are scoped to **specific wallets** and governed by **policies**.
|
||||||
|
|
||||||
|
Each blockchain requires its own policy system due to differences in static transaction analysis. Currently, only EVM is supported; Solana support is planned.
|
||||||
|
|
||||||
|
Arbiter is also responsible for ensuring that **transaction nonces are never reused**.
|
||||||
|
|
||||||
|
### 6.2 EVM Policies
|
||||||
|
|
||||||
|
Every EVM grant is scoped to a specific **wallet** and **chain ID**.
|
||||||
|
|
||||||
|
#### 6.2.1 Transaction Sub-Grants
|
||||||
|
|
||||||
|
Arbiter maintains an ever-expanding database of known contracts and their ABIs. Based on contract knowledge, transaction requests fall into three categories:
|
||||||
|
|
||||||
|
**1. Known contract (ABI available)**
|
||||||
|
|
||||||
|
The transaction can be decoded and presented with semantic meaning. For example: *"Client X wants to transfer Y USDT to address Z."*
|
||||||
|
|
||||||
|
Available restrictions:
|
||||||
|
- Volume limits (e.g., "no more than 10,000 tokens ever")
|
||||||
|
- Rate limits (e.g., "no more than 100 tokens per hour")
|
||||||
|
|
||||||
|
**2. Unknown contract (no ABI)**
|
||||||
|
|
||||||
|
The transaction cannot be decoded, so its effects are opaque — it could do anything, including draining all tokens. The user is warned, and if approved, access is granted to all interactions with the contract (matched by the `to` field).
|
||||||
|
|
||||||
|
Available restrictions:
|
||||||
|
- Transaction count limits (e.g., "no more than 100 transactions ever")
|
||||||
|
- Rate limits (e.g., "no more than 5 transactions per hour")
|
||||||
|
|
||||||
|
**3. Plain ether transfer (no calldata)**
|
||||||
|
|
||||||
|
These transactions have no `calldata` and therefore cannot interact with contracts. They can be subject to the same volume and rate restrictions as above.
|
||||||
|
|
||||||
|
#### 6.2.2 Global Limits
|
||||||
|
|
||||||
|
In addition to sub-grant-specific restrictions, the following limits can be applied across all grant types:
|
||||||
|
|
||||||
|
- **Gas limit** — Maximum gas per transaction.
|
||||||
|
- **Time-window restrictions** — e.g., signing allowed only 08:00–20:00 on Mondays and Thursdays.
|
||||||
35
IMPLEMENTATION.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Implementation Details
|
||||||
|
|
||||||
|
This document covers concrete technology choices and dependencies. For the architectural design, see [ARCHITECTURE.md](ARCHITECTURE.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cryptography
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
- **Signature scheme:** ed25519
|
||||||
|
|
||||||
|
### Encryption at Rest
|
||||||
|
- **Scheme:** Symmetric AEAD — currently **XChaCha20-Poly1305**
|
||||||
|
- **Version tracking:** Each `aead_encrypted` database entry carries a `scheme` field denoting the version, enabling transparent migration on unseal
|
||||||
|
|
||||||
|
### Server Identity
|
||||||
|
- **Transport:** TLS with a self-signed certificate
|
||||||
|
- **Key type:** Generated on first run; long-term (no rotation mechanism yet)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Communication
|
||||||
|
|
||||||
|
- **Protocol:** gRPC with Protocol Buffers
|
||||||
|
- **Server identity distribution:** `ServerInfo` protobuf struct containing the TLS public key fingerprint
|
||||||
|
- **Future consideration:** grpc-web lacks bidirectional stream support, so a browser-based wallet may require protojson over WebSocket
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Memory Protection
|
||||||
|
|
||||||
|
The unsealed root key must be held in a hardened memory cell resistant to dumps, page swaps, and hibernation.
|
||||||
|
|
||||||
|
- **Current:** Using the `memsafe` crate as an interim solution
|
||||||
|
- **Planned:** Custom implementation based on `mlock` (Unix) and `VirtualProtect` (Windows)
|
||||||
190
LICENSE
@@ -1,190 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
Copyright 2026 MarketTakers
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
13
README.md
@@ -1,13 +0,0 @@
|
|||||||
# Arbiter
|
|
||||||
> Policy-first multi-client wallet daemon, allowing permissioned transactions across blockchains
|
|
||||||
|
|
||||||
## Security warning
|
|
||||||
Arbiter can't meaningfully protect against host compromise. Potential attack flow:
|
|
||||||
- Attacker steals TLS keys from database
|
|
||||||
- Pretends to be server; just accepts operator challenge solutions
|
|
||||||
- Pretend to be in sealed state and performing DH with client
|
|
||||||
- Steals user password and derives seal key
|
|
||||||
|
|
||||||
While this attack is highly targetive, it's still possible.
|
|
||||||
|
|
||||||
> This software is experimental. Do not use with funds you cannot afford to lose.
|
|
||||||
@@ -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.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":2,"entries":[{"package":"app","rootUri":"../","packageUri":"lib/"}]}
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
{
|
|
||||||
"configVersion": 2,
|
|
||||||
"packages": [
|
|
||||||
{
|
|
||||||
"name": "async",
|
|
||||||
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/async-2.13.0",
|
|
||||||
"packageUri": "lib/",
|
|
||||||
"languageVersion": "3.4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "boolean_selector",
|
|
||||||
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2",
|
|
||||||
"packageUri": "lib/",
|
|
||||||
"languageVersion": "3.1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "characters",
|
|
||||||
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/characters-1.4.0",
|
|
||||||
"packageUri": "lib/",
|
|
||||||
"languageVersion": "3.4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "clock",
|
|
||||||
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/clock-1.1.2",
|
|
||||||
"packageUri": "lib/",
|
|
||||||
"languageVersion": "3.4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "collection",
|
|
||||||
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/collection-1.19.1",
|
|
||||||
"packageUri": "lib/",
|
|
||||||
"languageVersion": "3.4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "cupertino_icons",
|
|
||||||
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cupertino_icons-1.0.8",
|
|
||||||
"packageUri": "lib/",
|
|
||||||
"languageVersion": "3.1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "fake_async",
|
|
||||||
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/fake_async-1.3.3",
|
|
||||||
"packageUri": "lib/",
|
|
||||||
"languageVersion": "3.3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "flutter",
|
|
||||||
"rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/packages/flutter",
|
|
||||||
"packageUri": "lib/",
|
|
||||||
"languageVersion": "3.8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "flutter_lints",
|
|
||||||
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_lints-6.0.0",
|
|
||||||
"packageUri": "lib/",
|
|
||||||
"languageVersion": "3.8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "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": "lints",
|
|
||||||
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/lints-6.1.0",
|
|
||||||
"packageUri": "lib/",
|
|
||||||
"languageVersion": "3.8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "path",
|
|
||||||
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path-1.9.1",
|
|
||||||
"packageUri": "lib/",
|
|
||||||
"languageVersion": "3.4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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_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": "stream_channel",
|
|
||||||
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/stream_channel-2.1.4",
|
|
||||||
"packageUri": "lib/",
|
|
||||||
"languageVersion": "3.3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "string_scanner",
|
|
||||||
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/string_scanner-1.4.1",
|
|
||||||
"packageUri": "lib/",
|
|
||||||
"languageVersion": "3.1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "term_glyph",
|
|
||||||
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/term_glyph-1.2.2",
|
|
||||||
"packageUri": "lib/",
|
|
||||||
"languageVersion": "3.1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "test_api",
|
|
||||||
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/test_api-0.7.7",
|
|
||||||
"packageUri": "lib/",
|
|
||||||
"languageVersion": "3.5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "app",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
{
|
|
||||||
"roots": [
|
|
||||||
"app"
|
|
||||||
],
|
|
||||||
"packages": [
|
|
||||||
{
|
|
||||||
"name": "app",
|
|
||||||
"version": "1.0.0+1",
|
|
||||||
"dependencies": [
|
|
||||||
"cupertino_icons",
|
|
||||||
"flutter"
|
|
||||||
],
|
|
||||||
"devDependencies": [
|
|
||||||
"flutter_lints",
|
|
||||||
"flutter_test"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "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": "lints",
|
|
||||||
"version": "6.1.0",
|
|
||||||
"dependencies": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "stream_channel",
|
|
||||||
"version": "2.1.4",
|
|
||||||
"dependencies": [
|
|
||||||
"async"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "meta",
|
|
||||||
"version": "1.17.0",
|
|
||||||
"dependencies": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "collection",
|
|
||||||
"version": "1.19.1",
|
|
||||||
"dependencies": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "leak_tracker_flutter_testing",
|
|
||||||
"version": "3.0.10",
|
|
||||||
"dependencies": [
|
|
||||||
"flutter",
|
|
||||||
"leak_tracker",
|
|
||||||
"leak_tracker_testing",
|
|
||||||
"matcher",
|
|
||||||
"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",
|
|
||||||
"dependencies": [
|
|
||||||
"clock",
|
|
||||||
"collection"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "path",
|
|
||||||
"version": "1.9.1",
|
|
||||||
"dependencies": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "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": "async",
|
|
||||||
"version": "2.13.0",
|
|
||||||
"dependencies": [
|
|
||||||
"collection",
|
|
||||||
"meta"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "term_glyph",
|
|
||||||
"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",
|
|
||||||
"dependencies": [
|
|
||||||
"source_span",
|
|
||||||
"string_scanner"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "vm_service",
|
|
||||||
"version": "15.0.2",
|
|
||||||
"dependencies": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"configVersion": 1
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
3.38.9
|
|
||||||
90
useragent/.gitignore → app/.gitignore
vendored
@@ -1,45 +1,45 @@
|
|||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
*.class
|
*.class
|
||||||
*.log
|
*.log
|
||||||
*.pyc
|
*.pyc
|
||||||
*.swp
|
*.swp
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.atom/
|
.atom/
|
||||||
.build/
|
.build/
|
||||||
.buildlog/
|
.buildlog/
|
||||||
.history
|
.history
|
||||||
.svn/
|
.svn/
|
||||||
.swiftpm/
|
.swiftpm/
|
||||||
migrate_working_dir/
|
migrate_working_dir/
|
||||||
|
|
||||||
# IntelliJ related
|
# IntelliJ related
|
||||||
*.iml
|
*.iml
|
||||||
*.ipr
|
*.ipr
|
||||||
*.iws
|
*.iws
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
# The .vscode folder contains launch configuration and tasks you configure in
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
# VS Code which you may wish to be included in version control, so this line
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
# is commented out by default.
|
# is commented out by default.
|
||||||
#.vscode/
|
#.vscode/
|
||||||
|
|
||||||
# Flutter/Dart/Pub related
|
# Flutter/Dart/Pub related
|
||||||
**/doc/api/
|
**/doc/api/
|
||||||
**/ios/Flutter/.last_build_id
|
**/ios/Flutter/.last_build_id
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
.flutter-plugins-dependencies
|
.flutter-plugins-dependencies
|
||||||
.pub-cache/
|
.pub-cache/
|
||||||
.pub/
|
.pub/
|
||||||
/build/
|
/build/
|
||||||
/coverage/
|
/coverage/
|
||||||
|
|
||||||
# Symbolication related
|
# Symbolication related
|
||||||
app.*.symbols
|
app.*.symbols
|
||||||
|
|
||||||
# Obfuscation related
|
# Obfuscation related
|
||||||
app.*.map.json
|
app.*.map.json
|
||||||
|
|
||||||
# Android Studio will place build artifacts here
|
# Android Studio will place build artifacts here
|
||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
@@ -1,39 +1,39 @@
|
|||||||
# This file tracks properties of this Flutter project.
|
# This file tracks properties of this Flutter project.
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
#
|
#
|
||||||
# This file should be version controlled and should not be manually edited.
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: "67323de285b00232883f53b84095eb72be97d35c"
|
revision: "67323de285b00232883f53b84095eb72be97d35c"
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
|
||||||
# Tracks metadata for the flutter migrate command
|
# Tracks metadata for the flutter migrate command
|
||||||
migration:
|
migration:
|
||||||
platforms:
|
platforms:
|
||||||
- platform: root
|
- platform: root
|
||||||
create_revision: 67323de285b00232883f53b84095eb72be97d35c
|
create_revision: 67323de285b00232883f53b84095eb72be97d35c
|
||||||
base_revision: 67323de285b00232883f53b84095eb72be97d35c
|
base_revision: 67323de285b00232883f53b84095eb72be97d35c
|
||||||
- platform: android
|
- platform: android
|
||||||
create_revision: 67323de285b00232883f53b84095eb72be97d35c
|
create_revision: 67323de285b00232883f53b84095eb72be97d35c
|
||||||
base_revision: 67323de285b00232883f53b84095eb72be97d35c
|
base_revision: 67323de285b00232883f53b84095eb72be97d35c
|
||||||
- platform: macos
|
- platform: macos
|
||||||
create_revision: 67323de285b00232883f53b84095eb72be97d35c
|
create_revision: 67323de285b00232883f53b84095eb72be97d35c
|
||||||
base_revision: 67323de285b00232883f53b84095eb72be97d35c
|
base_revision: 67323de285b00232883f53b84095eb72be97d35c
|
||||||
- platform: web
|
- platform: web
|
||||||
create_revision: 67323de285b00232883f53b84095eb72be97d35c
|
create_revision: 67323de285b00232883f53b84095eb72be97d35c
|
||||||
base_revision: 67323de285b00232883f53b84095eb72be97d35c
|
base_revision: 67323de285b00232883f53b84095eb72be97d35c
|
||||||
- platform: windows
|
- platform: windows
|
||||||
create_revision: 67323de285b00232883f53b84095eb72be97d35c
|
create_revision: 67323de285b00232883f53b84095eb72be97d35c
|
||||||
base_revision: 67323de285b00232883f53b84095eb72be97d35c
|
base_revision: 67323de285b00232883f53b84095eb72be97d35c
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
# List of Local paths (relative to this file) that should be
|
# List of Local paths (relative to this file) that should be
|
||||||
# ignored by the migrate tool.
|
# ignored by the migrate tool.
|
||||||
#
|
#
|
||||||
# Files that are not part of the templates will be ignored by default.
|
# Files that are not part of the templates will be ignored by default.
|
||||||
unmanaged_files:
|
unmanaged_files:
|
||||||
- 'lib/main.dart'
|
- 'lib/main.dart'
|
||||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
# useragent
|
# app
|
||||||
|
|
||||||
A new Flutter project.
|
A new Flutter project.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
This project is a starting point for a Flutter application.
|
This project is a starting point for a Flutter application.
|
||||||
|
|
||||||
A few resources to get you started if this is your first Flutter project:
|
A few resources to get you started if this is your first Flutter project:
|
||||||
|
|
||||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||||
|
|
||||||
For help getting started with Flutter development, view the
|
For help getting started with Flutter development, view the
|
||||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||||
samples, guidance on mobile development, and a full API reference.
|
samples, guidance on mobile development, and a full API reference.
|
||||||
@@ -1,28 +1,28 @@
|
|||||||
# This file configures the analyzer, which statically analyzes Dart code to
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
# check for errors, warnings, and lints.
|
# check for errors, warnings, and lints.
|
||||||
#
|
#
|
||||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
# invoked from the command line by running `flutter analyze`.
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
# The following line activates a set of recommended lints for Flutter apps,
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
# packages, and plugins designed to encourage good coding practices.
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
# The lint rules applied to this project can be customized in the
|
# The lint rules applied to this project can be customized in the
|
||||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
# included above or to enable additional rules. A list of all available lints
|
# included above or to enable additional rules. A list of all available lints
|
||||||
# and their documentation is published at https://dart.dev/lints.
|
# and their documentation is published at https://dart.dev/lints.
|
||||||
#
|
#
|
||||||
# Instead of disabling a lint rule for the entire project in the
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
# section below, it can also be suppressed for a single line of code
|
# section below, it can also be suppressed for a single line of code
|
||||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
# producing the lint.
|
# producing the lint.
|
||||||
rules:
|
rules:
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Additional information about this file can be found at
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
122
app/lib/main.dart
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import 'package:flutter/material.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<MyHomePage> createState() => _MyHomePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyHomePageState extends State<MyHomePage> {
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Flutter-related
|
# Flutter-related
|
||||||
**/Flutter/ephemeral/
|
**/Flutter/ephemeral/
|
||||||
**/Pods/
|
**/Pods/
|
||||||
|
|
||||||
# Xcode-related
|
# Xcode-related
|
||||||
**/dgph
|
**/dgph
|
||||||
**/xcuserdata/
|
**/xcuserdata/
|
||||||
1
app/macos/Flutter/Flutter-Debug.xcconfig
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||||
1
app/macos/Flutter/Flutter-Release.xcconfig
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||||
10
app/macos/Flutter/GeneratedPluginRegistrant.swift
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
import FlutterMacOS
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
}
|
||||||
@@ -1,11 +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/app
|
|
||||||
COCOAPODS_PARALLEL_CODE_SIGN=true
|
|
||||||
FLUTTER_BUILD_DIR=build
|
|
||||||
FLUTTER_BUILD_NAME=1.0.0
|
|
||||||
FLUTTER_BUILD_NUMBER=1
|
|
||||||
DART_OBFUSCATION=false
|
|
||||||
TRACK_WIDGET_CREATION=true
|
|
||||||
TREE_SHAKE_ICONS=false
|
|
||||||
PACKAGE_CONFIG=.dart_tool/package_config.json
|
|
||||||
@@ -1,12 +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/app"
|
|
||||||
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
|
|
||||||
export "FLUTTER_BUILD_DIR=build"
|
|
||||||
export "FLUTTER_BUILD_NAME=1.0.0"
|
|
||||||
export "FLUTTER_BUILD_NUMBER=1"
|
|
||||||
export "DART_OBFUSCATION=false"
|
|
||||||
export "TRACK_WIDGET_CREATION=true"
|
|
||||||
export "TREE_SHAKE_ICONS=false"
|
|
||||||
export "PACKAGE_CONFIG=.dart_tool/package_config.json"
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>IDEDidComputeMac32BitWarning</key>
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -1,99 +1,99 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1510"
|
LastUpgradeVersion = "1510"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
buildForRunning = "YES"
|
buildForRunning = "YES"
|
||||||
buildForProfiling = "YES"
|
buildForProfiling = "YES"
|
||||||
buildForArchiving = "YES"
|
buildForArchiving = "YES"
|
||||||
buildForAnalyzing = "YES">
|
buildForAnalyzing = "YES">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
BuildableName = "useragent.app"
|
BuildableName = "app.app"
|
||||||
BlueprintName = "Runner"
|
BlueprintName = "Runner"
|
||||||
ReferencedContainer = "container:Runner.xcodeproj">
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildActionEntry>
|
</BuildActionEntry>
|
||||||
</BuildActionEntries>
|
</BuildActionEntries>
|
||||||
</BuildAction>
|
</BuildAction>
|
||||||
<TestAction
|
<TestAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
<MacroExpansion>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
BuildableName = "useragent.app"
|
BuildableName = "app.app"
|
||||||
BlueprintName = "Runner"
|
BlueprintName = "Runner"
|
||||||
ReferencedContainer = "container:Runner.xcodeproj">
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</MacroExpansion>
|
</MacroExpansion>
|
||||||
<Testables>
|
<Testables>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "NO"
|
skipped = "NO"
|
||||||
parallelizable = "YES">
|
parallelizable = "YES">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "331C80D4294CF70F00263BE5"
|
BlueprintIdentifier = "331C80D4294CF70F00263BE5"
|
||||||
BuildableName = "RunnerTests.xctest"
|
BuildableName = "RunnerTests.xctest"
|
||||||
BlueprintName = "RunnerTests"
|
BlueprintName = "RunnerTests"
|
||||||
ReferencedContainer = "container:Runner.xcodeproj">
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</TestableReference>
|
</TestableReference>
|
||||||
</Testables>
|
</Testables>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
enableGPUValidationMode = "1"
|
enableGPUValidationMode = "1"
|
||||||
allowLocationSimulation = "YES">
|
allowLocationSimulation = "YES">
|
||||||
<BuildableProductRunnable
|
<BuildableProductRunnable
|
||||||
runnableDebuggingMode = "0">
|
runnableDebuggingMode = "0">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
BuildableName = "useragent.app"
|
BuildableName = "app.app"
|
||||||
BlueprintName = "Runner"
|
BlueprintName = "Runner"
|
||||||
ReferencedContainer = "container:Runner.xcodeproj">
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "Profile"
|
buildConfiguration = "Profile"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
savedToolIdentifier = ""
|
savedToolIdentifier = ""
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
debugDocumentVersioning = "YES">
|
debugDocumentVersioning = "YES">
|
||||||
<BuildableProductRunnable
|
<BuildableProductRunnable
|
||||||
runnableDebuggingMode = "0">
|
runnableDebuggingMode = "0">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
BuildableName = "useragent.app"
|
BuildableName = "app.app"
|
||||||
BlueprintName = "Runner"
|
BlueprintName = "Runner"
|
||||||
ReferencedContainer = "container:Runner.xcodeproj">
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
</ProfileAction>
|
</ProfileAction>
|
||||||
<AnalyzeAction
|
<AnalyzeAction
|
||||||
buildConfiguration = "Debug">
|
buildConfiguration = "Debug">
|
||||||
</AnalyzeAction>
|
</AnalyzeAction>
|
||||||
<ArchiveAction
|
<ArchiveAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
revealArchiveInOrganizer = "YES">
|
revealArchiveInOrganizer = "YES">
|
||||||
</ArchiveAction>
|
</ArchiveAction>
|
||||||
</Scheme>
|
</Scheme>
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Workspace
|
<Workspace
|
||||||
version = "1.0">
|
version = "1.0">
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "group:Runner.xcodeproj">
|
location = "group:Runner.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
<FileRef
|
</Workspace>
|
||||||
location = "group:Pods/Pods.xcodeproj">
|
|
||||||
</FileRef>
|
|
||||||
</Workspace>
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>IDEDidComputeMac32BitWarning</key>
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@main
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,68 +1,68 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"size" : "16x16",
|
"size" : "16x16",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "app_icon_16.png",
|
"filename" : "app_icon_16.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "16x16",
|
"size" : "16x16",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "app_icon_32.png",
|
"filename" : "app_icon_32.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "32x32",
|
"size" : "32x32",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "app_icon_32.png",
|
"filename" : "app_icon_32.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "32x32",
|
"size" : "32x32",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "app_icon_64.png",
|
"filename" : "app_icon_64.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "128x128",
|
"size" : "128x128",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "app_icon_128.png",
|
"filename" : "app_icon_128.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "128x128",
|
"size" : "128x128",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "app_icon_256.png",
|
"filename" : "app_icon_256.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "256x256",
|
"size" : "256x256",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "app_icon_256.png",
|
"filename" : "app_icon_256.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "256x256",
|
"size" : "256x256",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "app_icon_512.png",
|
"filename" : "app_icon_512.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "512x512",
|
"size" : "512x512",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "app_icon_512.png",
|
"filename" : "app_icon_512.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "512x512",
|
"size" : "512x512",
|
||||||
"idiom" : "mac",
|
"idiom" : "mac",
|
||||||
"filename" : "app_icon_1024.png",
|
"filename" : "app_icon_1024.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
"version" : 1,
|
"version" : 1,
|
||||||
"author" : "xcode"
|
"author" : "xcode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 520 B |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -1,343 +1,343 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="macosx"/>
|
<deployment identifier="macosx"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
|
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
|
||||||
</connections>
|
</connections>
|
||||||
</customObject>
|
</customObject>
|
||||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
|
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
|
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
|
||||||
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
|
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
|
||||||
</connections>
|
</connections>
|
||||||
</customObject>
|
</customObject>
|
||||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||||
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="APP_NAME" id="1Xt-HY-uBw">
|
<menuItem title="APP_NAME" id="1Xt-HY-uBw">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
|
<menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="About APP_NAME" id="5kV-Vb-QxS">
|
<menuItem title="About APP_NAME" id="5kV-Vb-QxS">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
|
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||||
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
|
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
|
||||||
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
|
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
|
||||||
<menuItem title="Services" id="NMo-om-nkz">
|
<menuItem title="Services" id="NMo-om-nkz">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
||||||
<menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
|
<menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
|
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
|
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
|
||||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
|
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Show All" id="Kd2-mp-pUS">
|
<menuItem title="Show All" id="Kd2-mp-pUS">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
|
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
||||||
<menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
|
<menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
|
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Edit" id="5QF-Oa-p0T">
|
<menuItem title="Edit" id="5QF-Oa-p0T">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
|
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
|
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
|
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
|
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
|
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
|
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
|
||||||
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
|
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
|
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
|
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
|
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
|
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
|
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
|
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
|
||||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
|
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Delete" id="pa3-QI-u2k">
|
<menuItem title="Delete" id="pa3-QI-u2k">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
|
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
|
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
|
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
|
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
|
||||||
<menuItem title="Find" id="4EN-yA-p0u">
|
<menuItem title="Find" id="4EN-yA-p0u">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Find" id="1b7-l0-nxx">
|
<menu key="submenu" title="Find" id="1b7-l0-nxx">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
|
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
|
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
|
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
|
||||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
|
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
|
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
|
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
|
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
|
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
|
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
|
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
|
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
|
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
|
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
|
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
|
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
|
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
|
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
|
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
|
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
|
||||||
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
|
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
|
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
|
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
|
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
|
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
|
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Substitutions" id="9ic-FL-obx">
|
<menuItem title="Substitutions" id="9ic-FL-obx">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
|
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
|
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
|
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
|
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
|
||||||
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
|
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
|
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
|
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
|
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
|
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
|
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Smart Links" id="cwL-P1-jid">
|
<menuItem title="Smart Links" id="cwL-P1-jid">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
|
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Data Detectors" id="tRr-pd-1PS">
|
<menuItem title="Data Detectors" id="tRr-pd-1PS">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
|
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
|
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
|
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Transformations" id="2oI-Rn-ZJC">
|
<menuItem title="Transformations" id="2oI-Rn-ZJC">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
|
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
|
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
|
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
|
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
|
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
|
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
|
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Speech" id="xrE-MZ-jX0">
|
<menuItem title="Speech" id="xrE-MZ-jX0">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
|
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
|
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
|
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
|
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
|
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="View" id="H8h-7b-M4v">
|
<menuItem title="View" id="H8h-7b-M4v">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="View" id="HyV-fh-RgO">
|
<menu key="submenu" title="View" id="HyV-fh-RgO">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
|
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
|
||||||
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
|
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Window" id="aUF-d1-5bR">
|
<menuItem title="Window" id="aUF-d1-5bR">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
|
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
|
||||||
<items>
|
<items>
|
||||||
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
|
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
|
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Zoom" id="R4o-n2-Eq4">
|
<menuItem title="Zoom" id="R4o-n2-Eq4">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
|
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
|
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
|
||||||
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
|
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
|
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Help" id="EPT-qC-fAb">
|
<menuItem title="Help" id="EPT-qC-fAb">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Help" systemMenu="help" id="rJ0-wn-3NY"/>
|
<menu key="submenu" title="Help" systemMenu="help" id="rJ0-wn-3NY"/>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
</items>
|
</items>
|
||||||
<point key="canvasLocation" x="142" y="-258"/>
|
<point key="canvasLocation" x="142" y="-258"/>
|
||||||
</menu>
|
</menu>
|
||||||
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
|
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
|
||||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||||
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
|
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
|
||||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
|
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
|
||||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
|
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</view>
|
</view>
|
||||||
</window>
|
</window>
|
||||||
</objects>
|
</objects>
|
||||||
</document>
|
</document>
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
// Application-level settings for the Runner target.
|
// Application-level settings for the Runner target.
|
||||||
//
|
//
|
||||||
// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
|
// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
|
||||||
// future. If not, the values below would default to using the project name when this becomes a
|
// future. If not, the values below would default to using the project name when this becomes a
|
||||||
// 'flutter create' template.
|
// 'flutter create' template.
|
||||||
|
|
||||||
// The application's name. By default this is also the title of the Flutter window.
|
// The application's name. By default this is also the title of the Flutter window.
|
||||||
PRODUCT_NAME = useragent
|
PRODUCT_NAME = app
|
||||||
|
|
||||||
// The application's bundle identifier
|
// The application's bundle identifier
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.useragent
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.app
|
||||||
|
|
||||||
// The copyright displayed in application information
|
// The copyright displayed in application information
|
||||||
PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved.
|
PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved.
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
#include "../../Flutter/Flutter-Debug.xcconfig"
|
#include "../../Flutter/Flutter-Debug.xcconfig"
|
||||||
#include "Warnings.xcconfig"
|
#include "Warnings.xcconfig"
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
#include "../../Flutter/Flutter-Release.xcconfig"
|
#include "../../Flutter/Flutter-Release.xcconfig"
|
||||||
#include "Warnings.xcconfig"
|
#include "Warnings.xcconfig"
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
|
WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES
|
GCC_WARN_UNDECLARED_SELECTOR = YES
|
||||||
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
|
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
|
||||||
CLANG_WARN_PRAGMA_PACK = YES
|
CLANG_WARN_PRAGMA_PACK = YES
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES
|
CLANG_WARN_STRICT_PROTOTYPES = YES
|
||||||
CLANG_WARN_COMMA = YES
|
CLANG_WARN_COMMA = YES
|
||||||
GCC_WARN_STRICT_SELECTOR_MATCH = YES
|
GCC_WARN_STRICT_SELECTOR_MATCH = YES
|
||||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
|
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
|
||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
|
||||||
GCC_WARN_SHADOW = YES
|
GCC_WARN_SHADOW = YES
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES
|
CLANG_WARN_UNREACHABLE_CODE = YES
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>keychain-access-groups</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<array/>
|
<true/>
|
||||||
</dict>
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
</plist>
|
<true/>
|
||||||
|
<key>com.apple.security.network.server</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -1,32 +1,32 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>$(PRODUCT_NAME)</string>
|
<string>$(PRODUCT_NAME)</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>$(PRODUCT_COPYRIGHT)</string>
|
<string>$(PRODUCT_COPYRIGHT)</string>
|
||||||
<key>NSMainNibFile</key>
|
<key>NSMainNibFile</key>
|
||||||
<string>MainMenu</string>
|
<string>MainMenu</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
class MainFlutterWindow: NSWindow {
|
class MainFlutterWindow: NSWindow {
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
let flutterViewController = FlutterViewController()
|
let flutterViewController = FlutterViewController()
|
||||||
let windowFrame = self.frame
|
let windowFrame = self.frame
|
||||||
self.contentViewController = flutterViewController
|
self.contentViewController = flutterViewController
|
||||||
self.setFrame(windowFrame, display: true)
|
self.setFrame(windowFrame, display: true)
|
||||||
|
|
||||||
RegisterGeneratedPlugins(registry: flutterViewController)
|
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||||
|
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>keychain-access-groups</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<array/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
class RunnerTests: XCTestCase {
|
class RunnerTests: XCTestCase {
|
||||||
|
|
||||||
func testExample() {
|
func testExample() {
|
||||||
// If you add code to the Runner application, consider adding tests here.
|
// If you add code to the Runner application, consider adding tests here.
|
||||||
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
213
app/pubspec.lock
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.13.0"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
clock:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: clock
|
||||||
|
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
cupertino_icons:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: cupertino_icons
|
||||||
|
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.8"
|
||||||
|
fake_async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fake_async
|
||||||
|
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.3"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_lints
|
||||||
|
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
|
flutter_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
leak_tracker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker
|
||||||
|
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "11.0.2"
|
||||||
|
leak_tracker_flutter_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_flutter_testing
|
||||||
|
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.10"
|
||||||
|
leak_tracker_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_testing
|
||||||
|
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
lints:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.0"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.17"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.11.1"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.17.0"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.2"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.7"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.0.2"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.10.8 <4.0.0"
|
||||||
|
flutter: ">=3.18.0-18.0.pre.54"
|
||||||
89
app/pubspec.yaml
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
name: app
|
||||||
|
description: "A new Flutter project."
|
||||||
|
# The following line prevents the package from being accidentally published to
|
||||||
|
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||||
|
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
|
|
||||||
|
# The following defines the version and build number for your application.
|
||||||
|
# A version number is three numbers separated by dots, like 1.2.43
|
||||||
|
# followed by an optional build number separated by a +.
|
||||||
|
# Both the version and the builder number may be overridden in flutter
|
||||||
|
# build by specifying --build-name and --build-number, respectively.
|
||||||
|
# In Android, build-name is used as versionName while build-number used as versionCode.
|
||||||
|
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
||||||
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
|
||||||
|
# Read more about iOS versioning at
|
||||||
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
|
version: 1.0.0+1
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ^3.10.8
|
||||||
|
|
||||||
|
# Dependencies specify other packages that your package needs in order to work.
|
||||||
|
# To automatically upgrade your package dependencies to the latest versions
|
||||||
|
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
||||||
|
# dependencies can be manually updated by changing the version numbers below to
|
||||||
|
# the latest version available on pub.dev. To see which dependencies have newer
|
||||||
|
# versions available, run `flutter pub outdated`.
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
# The following adds the Cupertino Icons font to your application.
|
||||||
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
|
cupertino_icons: ^1.0.8
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
# The "flutter_lints" package below contains a set of recommended lints to
|
||||||
|
# encourage good coding practices. The lint set provided by the package is
|
||||||
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
|
# package. See that file for information about deactivating specific lint
|
||||||
|
# rules and activating additional ones.
|
||||||
|
flutter_lints: ^6.0.0
|
||||||
|
|
||||||
|
# For information on the generic Dart part of this file, see the
|
||||||
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
||||||
|
# The following section is specific to Flutter packages.
|
||||||
|
flutter:
|
||||||
|
|
||||||
|
# The following line ensures that the Material Icons font is
|
||||||
|
# included with your application, so that you can use the icons in
|
||||||
|
# the material Icons class.
|
||||||
|
uses-material-design: true
|
||||||
|
|
||||||
|
# To add assets to your application, add an assets section, like this:
|
||||||
|
# assets:
|
||||||
|
# - images/a_dot_burr.jpeg
|
||||||
|
# - images/a_dot_ham.jpeg
|
||||||
|
|
||||||
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
|
# https://flutter.dev/to/resolution-aware-images
|
||||||
|
|
||||||
|
# For details regarding adding assets from package dependencies, see
|
||||||
|
# https://flutter.dev/to/asset-from-package
|
||||||
|
|
||||||
|
# To add custom fonts to your application, add a fonts section here,
|
||||||
|
# in this "flutter" section. Each entry in this list should have a
|
||||||
|
# "family" key with the font family name, and a "fonts" key with a
|
||||||
|
# list giving the asset and other descriptors for the font. For
|
||||||
|
# example:
|
||||||
|
# fonts:
|
||||||
|
# - family: Schyler
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/Schyler-Regular.ttf
|
||||||
|
# - asset: fonts/Schyler-Italic.ttf
|
||||||
|
# style: italic
|
||||||
|
# - family: Trajan Pro
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/TrajanPro.ttf
|
||||||
|
# - asset: fonts/TrajanPro_Bold.ttf
|
||||||
|
# weight: 700
|
||||||
|
#
|
||||||
|
# For details regarding fonts from package dependencies,
|
||||||
|
# see https://flutter.dev/to/font-from-package
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
flutter/ephemeral/
|
flutter/ephemeral/
|
||||||
|
|
||||||
# Visual Studio user-specific files.
|
# Visual Studio user-specific files.
|
||||||
*.suo
|
*.suo
|
||||||
*.user
|
*.user
|
||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
|
|
||||||
# Visual Studio build-related files.
|
# Visual Studio build-related files.
|
||||||
x64/
|
x64/
|
||||||
x86/
|
x86/
|
||||||
|
|
||||||
# Visual Studio cache files
|
# Visual Studio cache files
|
||||||
# files ending in .cache can be ignored
|
# files ending in .cache can be ignored
|
||||||
*.[Cc]ache
|
*.[Cc]ache
|
||||||
# but keep track of directories ending in .cache
|
# but keep track of directories ending in .cache
|
||||||
!*.[Cc]ache/
|
!*.[Cc]ache/
|
||||||
@@ -1,108 +1,108 @@
|
|||||||
# Project-level configuration.
|
# Project-level configuration.
|
||||||
cmake_minimum_required(VERSION 3.14)
|
cmake_minimum_required(VERSION 3.14)
|
||||||
project(useragent LANGUAGES CXX)
|
project(app LANGUAGES CXX)
|
||||||
|
|
||||||
# The name of the executable created for the application. Change this to change
|
# The name of the executable created for the application. Change this to change
|
||||||
# the on-disk name of your application.
|
# the on-disk name of your application.
|
||||||
set(BINARY_NAME "useragent")
|
set(BINARY_NAME "app")
|
||||||
|
|
||||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||||
# versions of CMake.
|
# versions of CMake.
|
||||||
cmake_policy(VERSION 3.14...3.25)
|
cmake_policy(VERSION 3.14...3.25)
|
||||||
|
|
||||||
# Define build configuration option.
|
# Define build configuration option.
|
||||||
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||||
if(IS_MULTICONFIG)
|
if(IS_MULTICONFIG)
|
||||||
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
|
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
|
||||||
CACHE STRING "" FORCE)
|
CACHE STRING "" FORCE)
|
||||||
else()
|
else()
|
||||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||||
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||||
STRING "Flutter build mode" FORCE)
|
STRING "Flutter build mode" FORCE)
|
||||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||||
"Debug" "Profile" "Release")
|
"Debug" "Profile" "Release")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
# Define settings for the Profile build mode.
|
# Define settings for the Profile build mode.
|
||||||
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
|
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
|
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
|
||||||
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
|
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
|
||||||
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
|
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
|
||||||
|
|
||||||
# Use Unicode for all projects.
|
# Use Unicode for all projects.
|
||||||
add_definitions(-DUNICODE -D_UNICODE)
|
add_definitions(-DUNICODE -D_UNICODE)
|
||||||
|
|
||||||
# Compilation settings that should be applied to most targets.
|
# Compilation settings that should be applied to most targets.
|
||||||
#
|
#
|
||||||
# Be cautious about adding new options here, as plugins use this function by
|
# Be cautious about adding new options here, as plugins use this function by
|
||||||
# default. In most cases, you should add new options to specific targets instead
|
# default. In most cases, you should add new options to specific targets instead
|
||||||
# of modifying this function.
|
# of modifying this function.
|
||||||
function(APPLY_STANDARD_SETTINGS TARGET)
|
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||||
target_compile_features(${TARGET} PUBLIC cxx_std_17)
|
target_compile_features(${TARGET} PUBLIC cxx_std_17)
|
||||||
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
|
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
|
||||||
target_compile_options(${TARGET} PRIVATE /EHsc)
|
target_compile_options(${TARGET} PRIVATE /EHsc)
|
||||||
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
|
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
|
||||||
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
|
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
# Flutter library and tool build rules.
|
# Flutter library and tool build rules.
|
||||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||||
|
|
||||||
# Application build; see runner/CMakeLists.txt.
|
# Application build; see runner/CMakeLists.txt.
|
||||||
add_subdirectory("runner")
|
add_subdirectory("runner")
|
||||||
|
|
||||||
|
|
||||||
# Generated plugin build rules, which manage building the plugins and adding
|
# Generated plugin build rules, which manage building the plugins and adding
|
||||||
# them to the application.
|
# them to the application.
|
||||||
include(flutter/generated_plugins.cmake)
|
include(flutter/generated_plugins.cmake)
|
||||||
|
|
||||||
|
|
||||||
# === Installation ===
|
# === Installation ===
|
||||||
# Support files are copied into place next to the executable, so that it can
|
# Support files are copied into place next to the executable, so that it can
|
||||||
# run in place. This is done instead of making a separate bundle (as on Linux)
|
# run in place. This is done instead of making a separate bundle (as on Linux)
|
||||||
# so that building and running from within Visual Studio will work.
|
# so that building and running from within Visual Studio will work.
|
||||||
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
|
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
|
||||||
# Make the "install" step default, as it's required to run.
|
# Make the "install" step default, as it's required to run.
|
||||||
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
|
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
|
||||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||||
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||||
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
|
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
|
||||||
|
|
||||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||||
COMPONENT Runtime)
|
COMPONENT Runtime)
|
||||||
|
|
||||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||||
COMPONENT Runtime)
|
COMPONENT Runtime)
|
||||||
|
|
||||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
COMPONENT Runtime)
|
COMPONENT Runtime)
|
||||||
|
|
||||||
if(PLUGIN_BUNDLED_LIBRARIES)
|
if(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
|
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
|
||||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
COMPONENT Runtime)
|
COMPONENT Runtime)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Copy the native assets provided by the build.dart from all packages.
|
# Copy the native assets provided by the build.dart from all packages.
|
||||||
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/")
|
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/")
|
||||||
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
|
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
|
||||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
COMPONENT Runtime)
|
COMPONENT Runtime)
|
||||||
|
|
||||||
# Fully re-copy the assets directory on each build to avoid having stale files
|
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||||
# from a previous install.
|
# from a previous install.
|
||||||
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||||
install(CODE "
|
install(CODE "
|
||||||
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||||
" COMPONENT Runtime)
|
" COMPONENT Runtime)
|
||||||
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||||
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||||
|
|
||||||
# Install the AOT library on non-Debug builds only.
|
# Install the AOT library on non-Debug builds only.
|
||||||
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||||
CONFIGURATIONS Profile;Release
|
CONFIGURATIONS Profile;Release
|
||||||
COMPONENT Runtime)
|
COMPONENT Runtime)
|
||||||
@@ -1,109 +1,109 @@
|
|||||||
# This file controls Flutter-level build steps. It should not be edited.
|
# This file controls Flutter-level build steps. It should not be edited.
|
||||||
cmake_minimum_required(VERSION 3.14)
|
cmake_minimum_required(VERSION 3.14)
|
||||||
|
|
||||||
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||||
|
|
||||||
# Configuration provided via flutter tool.
|
# Configuration provided via flutter tool.
|
||||||
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||||
|
|
||||||
# TODO: Move the rest of this into files in ephemeral. See
|
# TODO: Move the rest of this into files in ephemeral. See
|
||||||
# https://github.com/flutter/flutter/issues/57146.
|
# https://github.com/flutter/flutter/issues/57146.
|
||||||
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
|
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
|
||||||
|
|
||||||
# Set fallback configurations for older versions of the flutter tool.
|
# Set fallback configurations for older versions of the flutter tool.
|
||||||
if (NOT DEFINED FLUTTER_TARGET_PLATFORM)
|
if (NOT DEFINED FLUTTER_TARGET_PLATFORM)
|
||||||
set(FLUTTER_TARGET_PLATFORM "windows-x64")
|
set(FLUTTER_TARGET_PLATFORM "windows-x64")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# === Flutter Library ===
|
# === Flutter Library ===
|
||||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
|
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
|
||||||
|
|
||||||
# Published to parent scope for install step.
|
# Published to parent scope for install step.
|
||||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
|
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
|
||||||
|
|
||||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||||
"flutter_export.h"
|
"flutter_export.h"
|
||||||
"flutter_windows.h"
|
"flutter_windows.h"
|
||||||
"flutter_messenger.h"
|
"flutter_messenger.h"
|
||||||
"flutter_plugin_registrar.h"
|
"flutter_plugin_registrar.h"
|
||||||
"flutter_texture_registrar.h"
|
"flutter_texture_registrar.h"
|
||||||
)
|
)
|
||||||
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
|
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
|
||||||
add_library(flutter INTERFACE)
|
add_library(flutter INTERFACE)
|
||||||
target_include_directories(flutter INTERFACE
|
target_include_directories(flutter INTERFACE
|
||||||
"${EPHEMERAL_DIR}"
|
"${EPHEMERAL_DIR}"
|
||||||
)
|
)
|
||||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
|
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
|
||||||
add_dependencies(flutter flutter_assemble)
|
add_dependencies(flutter flutter_assemble)
|
||||||
|
|
||||||
# === Wrapper ===
|
# === Wrapper ===
|
||||||
list(APPEND CPP_WRAPPER_SOURCES_CORE
|
list(APPEND CPP_WRAPPER_SOURCES_CORE
|
||||||
"core_implementations.cc"
|
"core_implementations.cc"
|
||||||
"standard_codec.cc"
|
"standard_codec.cc"
|
||||||
)
|
)
|
||||||
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
|
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
|
||||||
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
|
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
|
||||||
"plugin_registrar.cc"
|
"plugin_registrar.cc"
|
||||||
)
|
)
|
||||||
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
|
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
|
||||||
list(APPEND CPP_WRAPPER_SOURCES_APP
|
list(APPEND CPP_WRAPPER_SOURCES_APP
|
||||||
"flutter_engine.cc"
|
"flutter_engine.cc"
|
||||||
"flutter_view_controller.cc"
|
"flutter_view_controller.cc"
|
||||||
)
|
)
|
||||||
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
|
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
|
||||||
|
|
||||||
# Wrapper sources needed for a plugin.
|
# Wrapper sources needed for a plugin.
|
||||||
add_library(flutter_wrapper_plugin STATIC
|
add_library(flutter_wrapper_plugin STATIC
|
||||||
${CPP_WRAPPER_SOURCES_CORE}
|
${CPP_WRAPPER_SOURCES_CORE}
|
||||||
${CPP_WRAPPER_SOURCES_PLUGIN}
|
${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||||
)
|
)
|
||||||
apply_standard_settings(flutter_wrapper_plugin)
|
apply_standard_settings(flutter_wrapper_plugin)
|
||||||
set_target_properties(flutter_wrapper_plugin PROPERTIES
|
set_target_properties(flutter_wrapper_plugin PROPERTIES
|
||||||
POSITION_INDEPENDENT_CODE ON)
|
POSITION_INDEPENDENT_CODE ON)
|
||||||
set_target_properties(flutter_wrapper_plugin PROPERTIES
|
set_target_properties(flutter_wrapper_plugin PROPERTIES
|
||||||
CXX_VISIBILITY_PRESET hidden)
|
CXX_VISIBILITY_PRESET hidden)
|
||||||
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
|
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
|
||||||
target_include_directories(flutter_wrapper_plugin PUBLIC
|
target_include_directories(flutter_wrapper_plugin PUBLIC
|
||||||
"${WRAPPER_ROOT}/include"
|
"${WRAPPER_ROOT}/include"
|
||||||
)
|
)
|
||||||
add_dependencies(flutter_wrapper_plugin flutter_assemble)
|
add_dependencies(flutter_wrapper_plugin flutter_assemble)
|
||||||
|
|
||||||
# Wrapper sources needed for the runner.
|
# Wrapper sources needed for the runner.
|
||||||
add_library(flutter_wrapper_app STATIC
|
add_library(flutter_wrapper_app STATIC
|
||||||
${CPP_WRAPPER_SOURCES_CORE}
|
${CPP_WRAPPER_SOURCES_CORE}
|
||||||
${CPP_WRAPPER_SOURCES_APP}
|
${CPP_WRAPPER_SOURCES_APP}
|
||||||
)
|
)
|
||||||
apply_standard_settings(flutter_wrapper_app)
|
apply_standard_settings(flutter_wrapper_app)
|
||||||
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
|
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
|
||||||
target_include_directories(flutter_wrapper_app PUBLIC
|
target_include_directories(flutter_wrapper_app PUBLIC
|
||||||
"${WRAPPER_ROOT}/include"
|
"${WRAPPER_ROOT}/include"
|
||||||
)
|
)
|
||||||
add_dependencies(flutter_wrapper_app flutter_assemble)
|
add_dependencies(flutter_wrapper_app flutter_assemble)
|
||||||
|
|
||||||
# === Flutter tool backend ===
|
# === Flutter tool backend ===
|
||||||
# _phony_ is a non-existent file to force this command to run every time,
|
# _phony_ is a non-existent file to force this command to run every time,
|
||||||
# since currently there's no way to get a full input/output list from the
|
# since currently there's no way to get a full input/output list from the
|
||||||
# flutter tool.
|
# flutter tool.
|
||||||
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
|
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
|
||||||
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
|
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||||
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
|
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||||
${CPP_WRAPPER_SOURCES_APP}
|
${CPP_WRAPPER_SOURCES_APP}
|
||||||
${PHONY_OUTPUT}
|
${PHONY_OUTPUT}
|
||||||
COMMAND ${CMAKE_COMMAND} -E env
|
COMMAND ${CMAKE_COMMAND} -E env
|
||||||
${FLUTTER_TOOL_ENVIRONMENT}
|
${FLUTTER_TOOL_ENVIRONMENT}
|
||||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
|
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
|
||||||
${FLUTTER_TARGET_PLATFORM} $<CONFIG>
|
${FLUTTER_TARGET_PLATFORM} $<CONFIG>
|
||||||
VERBATIM
|
VERBATIM
|
||||||
)
|
)
|
||||||
add_custom_target(flutter_assemble DEPENDS
|
add_custom_target(flutter_assemble DEPENDS
|
||||||
"${FLUTTER_LIBRARY}"
|
"${FLUTTER_LIBRARY}"
|
||||||
${FLUTTER_LIBRARY_HEADERS}
|
${FLUTTER_LIBRARY_HEADERS}
|
||||||
${CPP_WRAPPER_SOURCES_CORE}
|
${CPP_WRAPPER_SOURCES_CORE}
|
||||||
${CPP_WRAPPER_SOURCES_PLUGIN}
|
${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||||
${CPP_WRAPPER_SOURCES_APP}
|
${CPP_WRAPPER_SOURCES_APP}
|
||||||
)
|
)
|
||||||
11
app/windows/flutter/generated_plugin_registrant.cc
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
|
||||||
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
//
|
//
|
||||||
// Generated file. Do not edit.
|
// Generated file. Do not edit.
|
||||||
//
|
//
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
|
||||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||||
#define GENERATED_PLUGIN_REGISTRANT_
|
#define GENERATED_PLUGIN_REGISTRANT_
|
||||||
|
|
||||||
#include <flutter/plugin_registry.h>
|
#include <flutter/plugin_registry.h>
|
||||||
|
|
||||||
// Registers Flutter plugins.
|
// Registers Flutter plugins.
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry);
|
void RegisterPlugins(flutter::PluginRegistry* registry);
|
||||||
|
|
||||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
#endif // GENERATED_PLUGIN_REGISTRANT_
|
||||||
@@ -1,29 +1,23 @@
|
|||||||
#
|
#
|
||||||
# Generated file, do not edit.
|
# Generated file, do not edit.
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
biometric_signature
|
)
|
||||||
flutter_secure_storage_windows
|
|
||||||
rive_native
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
share_plus
|
)
|
||||||
url_launcher_windows
|
|
||||||
)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||||
rust_lib_arbiter
|
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
|
||||||
)
|
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||||
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||||
|
endforeach(plugin)
|
||||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
|
||||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
|
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
|
||||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
endforeach(ffi_plugin)
|
||||||
endforeach(plugin)
|
|
||||||
|
|
||||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
|
||||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
|
|
||||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
|
||||||
endforeach(ffi_plugin)
|
|
||||||
@@ -1,40 +1,40 @@
|
|||||||
cmake_minimum_required(VERSION 3.14)
|
cmake_minimum_required(VERSION 3.14)
|
||||||
project(runner LANGUAGES CXX)
|
project(runner LANGUAGES CXX)
|
||||||
|
|
||||||
# Define the application target. To change its name, change BINARY_NAME in the
|
# Define the application target. To change its name, change BINARY_NAME in the
|
||||||
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
|
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
|
||||||
# work.
|
# work.
|
||||||
#
|
#
|
||||||
# Any new source files that you add to the application should be added here.
|
# Any new source files that you add to the application should be added here.
|
||||||
add_executable(${BINARY_NAME} WIN32
|
add_executable(${BINARY_NAME} WIN32
|
||||||
"flutter_window.cpp"
|
"flutter_window.cpp"
|
||||||
"main.cpp"
|
"main.cpp"
|
||||||
"utils.cpp"
|
"utils.cpp"
|
||||||
"win32_window.cpp"
|
"win32_window.cpp"
|
||||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||||
"Runner.rc"
|
"Runner.rc"
|
||||||
"runner.exe.manifest"
|
"runner.exe.manifest"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply the standard set of build settings. This can be removed for applications
|
# Apply the standard set of build settings. This can be removed for applications
|
||||||
# that need different build settings.
|
# that need different build settings.
|
||||||
apply_standard_settings(${BINARY_NAME})
|
apply_standard_settings(${BINARY_NAME})
|
||||||
|
|
||||||
# Add preprocessor definitions for the build version.
|
# Add preprocessor definitions for the build version.
|
||||||
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
|
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
|
||||||
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
|
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
|
||||||
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
|
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
|
||||||
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
|
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
|
||||||
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
|
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
|
||||||
|
|
||||||
# Disable Windows macros that collide with C++ standard library functions.
|
# Disable Windows macros that collide with C++ standard library functions.
|
||||||
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
|
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
|
||||||
|
|
||||||
# Add dependency libraries and include directories. Add any application-specific
|
# Add dependency libraries and include directories. Add any application-specific
|
||||||
# dependencies here.
|
# dependencies here.
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
|
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
|
target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
|
||||||
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||||
|
|
||||||
# Run the Flutter tool portions of the build. This must not be removed.
|
# Run the Flutter tool portions of the build. This must not be removed.
|
||||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||||
@@ -1,121 +1,121 @@
|
|||||||
// Microsoft Visual C++ generated resource script.
|
// Microsoft Visual C++ generated resource script.
|
||||||
//
|
//
|
||||||
#pragma code_page(65001)
|
#pragma code_page(65001)
|
||||||
#include "resource.h"
|
#include "resource.h"
|
||||||
|
|
||||||
#define APSTUDIO_READONLY_SYMBOLS
|
#define APSTUDIO_READONLY_SYMBOLS
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Generated from the TEXTINCLUDE 2 resource.
|
// Generated from the TEXTINCLUDE 2 resource.
|
||||||
//
|
//
|
||||||
#include "winres.h"
|
#include "winres.h"
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
#undef APSTUDIO_READONLY_SYMBOLS
|
#undef APSTUDIO_READONLY_SYMBOLS
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
// English (United States) resources
|
// English (United States) resources
|
||||||
|
|
||||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||||
|
|
||||||
#ifdef APSTUDIO_INVOKED
|
#ifdef APSTUDIO_INVOKED
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// TEXTINCLUDE
|
// TEXTINCLUDE
|
||||||
//
|
//
|
||||||
|
|
||||||
1 TEXTINCLUDE
|
1 TEXTINCLUDE
|
||||||
BEGIN
|
BEGIN
|
||||||
"resource.h\0"
|
"resource.h\0"
|
||||||
END
|
END
|
||||||
|
|
||||||
2 TEXTINCLUDE
|
2 TEXTINCLUDE
|
||||||
BEGIN
|
BEGIN
|
||||||
"#include ""winres.h""\r\n"
|
"#include ""winres.h""\r\n"
|
||||||
"\0"
|
"\0"
|
||||||
END
|
END
|
||||||
|
|
||||||
3 TEXTINCLUDE
|
3 TEXTINCLUDE
|
||||||
BEGIN
|
BEGIN
|
||||||
"\r\n"
|
"\r\n"
|
||||||
"\0"
|
"\0"
|
||||||
END
|
END
|
||||||
|
|
||||||
#endif // APSTUDIO_INVOKED
|
#endif // APSTUDIO_INVOKED
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Icon
|
// Icon
|
||||||
//
|
//
|
||||||
|
|
||||||
// Icon with lowest ID value placed first to ensure application icon
|
// Icon with lowest ID value placed first to ensure application icon
|
||||||
// remains consistent on all systems.
|
// remains consistent on all systems.
|
||||||
IDI_APP_ICON ICON "resources\\app_icon.ico"
|
IDI_APP_ICON ICON "resources\\app_icon.ico"
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Version
|
// Version
|
||||||
//
|
//
|
||||||
|
|
||||||
#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
|
#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
|
||||||
#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
|
#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
|
||||||
#else
|
#else
|
||||||
#define VERSION_AS_NUMBER 1,0,0,0
|
#define VERSION_AS_NUMBER 1,0,0,0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(FLUTTER_VERSION)
|
#if defined(FLUTTER_VERSION)
|
||||||
#define VERSION_AS_STRING FLUTTER_VERSION
|
#define VERSION_AS_STRING FLUTTER_VERSION
|
||||||
#else
|
#else
|
||||||
#define VERSION_AS_STRING "1.0.0"
|
#define VERSION_AS_STRING "1.0.0"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION VERSION_AS_NUMBER
|
FILEVERSION VERSION_AS_NUMBER
|
||||||
PRODUCTVERSION VERSION_AS_NUMBER
|
PRODUCTVERSION VERSION_AS_NUMBER
|
||||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS VS_FF_DEBUG
|
FILEFLAGS VS_FF_DEBUG
|
||||||
#else
|
#else
|
||||||
FILEFLAGS 0x0L
|
FILEFLAGS 0x0L
|
||||||
#endif
|
#endif
|
||||||
FILEOS VOS__WINDOWS32
|
FILEOS VOS__WINDOWS32
|
||||||
FILETYPE VFT_APP
|
FILETYPE VFT_APP
|
||||||
FILESUBTYPE 0x0L
|
FILESUBTYPE 0x0L
|
||||||
BEGIN
|
BEGIN
|
||||||
BLOCK "StringFileInfo"
|
BLOCK "StringFileInfo"
|
||||||
BEGIN
|
BEGIN
|
||||||
BLOCK "040904e4"
|
BLOCK "040904e4"
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "com.example" "\0"
|
VALUE "CompanyName", "com.example" "\0"
|
||||||
VALUE "FileDescription", "useragent" "\0"
|
VALUE "FileDescription", "app" "\0"
|
||||||
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||||
VALUE "InternalName", "useragent" "\0"
|
VALUE "InternalName", "app" "\0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0"
|
VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0"
|
||||||
VALUE "OriginalFilename", "useragent.exe" "\0"
|
VALUE "OriginalFilename", "app.exe" "\0"
|
||||||
VALUE "ProductName", "useragent" "\0"
|
VALUE "ProductName", "app" "\0"
|
||||||
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "Translation", 0x409, 1252
|
VALUE "Translation", 0x409, 1252
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
|
|
||||||
#endif // English (United States) resources
|
#endif // English (United States) resources
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef APSTUDIO_INVOKED
|
#ifndef APSTUDIO_INVOKED
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Generated from the TEXTINCLUDE 3 resource.
|
// Generated from the TEXTINCLUDE 3 resource.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
#endif // not APSTUDIO_INVOKED
|
#endif // not APSTUDIO_INVOKED
|
||||||
@@ -1,71 +1,71 @@
|
|||||||
#include "flutter_window.h"
|
#include "flutter_window.h"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "flutter/generated_plugin_registrant.h"
|
#include "flutter/generated_plugin_registrant.h"
|
||||||
|
|
||||||
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
||||||
: project_(project) {}
|
: project_(project) {}
|
||||||
|
|
||||||
FlutterWindow::~FlutterWindow() {}
|
FlutterWindow::~FlutterWindow() {}
|
||||||
|
|
||||||
bool FlutterWindow::OnCreate() {
|
bool FlutterWindow::OnCreate() {
|
||||||
if (!Win32Window::OnCreate()) {
|
if (!Win32Window::OnCreate()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RECT frame = GetClientArea();
|
RECT frame = GetClientArea();
|
||||||
|
|
||||||
// The size here must match the window dimensions to avoid unnecessary surface
|
// The size here must match the window dimensions to avoid unnecessary surface
|
||||||
// creation / destruction in the startup path.
|
// creation / destruction in the startup path.
|
||||||
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
|
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
|
||||||
frame.right - frame.left, frame.bottom - frame.top, project_);
|
frame.right - frame.left, frame.bottom - frame.top, project_);
|
||||||
// Ensure that basic setup of the controller was successful.
|
// Ensure that basic setup of the controller was successful.
|
||||||
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
|
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
RegisterPlugins(flutter_controller_->engine());
|
RegisterPlugins(flutter_controller_->engine());
|
||||||
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
||||||
|
|
||||||
flutter_controller_->engine()->SetNextFrameCallback([&]() {
|
flutter_controller_->engine()->SetNextFrameCallback([&]() {
|
||||||
this->Show();
|
this->Show();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Flutter can complete the first frame before the "show window" callback is
|
// Flutter can complete the first frame before the "show window" callback is
|
||||||
// registered. The following call ensures a frame is pending to ensure the
|
// registered. The following call ensures a frame is pending to ensure the
|
||||||
// window is shown. It is a no-op if the first frame hasn't completed yet.
|
// window is shown. It is a no-op if the first frame hasn't completed yet.
|
||||||
flutter_controller_->ForceRedraw();
|
flutter_controller_->ForceRedraw();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlutterWindow::OnDestroy() {
|
void FlutterWindow::OnDestroy() {
|
||||||
if (flutter_controller_) {
|
if (flutter_controller_) {
|
||||||
flutter_controller_ = nullptr;
|
flutter_controller_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Win32Window::OnDestroy();
|
Win32Window::OnDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
LRESULT
|
LRESULT
|
||||||
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
|
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
|
||||||
WPARAM const wparam,
|
WPARAM const wparam,
|
||||||
LPARAM const lparam) noexcept {
|
LPARAM const lparam) noexcept {
|
||||||
// Give Flutter, including plugins, an opportunity to handle window messages.
|
// Give Flutter, including plugins, an opportunity to handle window messages.
|
||||||
if (flutter_controller_) {
|
if (flutter_controller_) {
|
||||||
std::optional<LRESULT> result =
|
std::optional<LRESULT> result =
|
||||||
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
|
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
|
||||||
lparam);
|
lparam);
|
||||||
if (result) {
|
if (result) {
|
||||||
return *result;
|
return *result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (message) {
|
switch (message) {
|
||||||
case WM_FONTCHANGE:
|
case WM_FONTCHANGE:
|
||||||
flutter_controller_->engine()->ReloadSystemFonts();
|
flutter_controller_->engine()->ReloadSystemFonts();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
|
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
|
||||||
}
|
}
|
||||||
@@ -1,33 +1,33 @@
|
|||||||
#ifndef RUNNER_FLUTTER_WINDOW_H_
|
#ifndef RUNNER_FLUTTER_WINDOW_H_
|
||||||
#define RUNNER_FLUTTER_WINDOW_H_
|
#define RUNNER_FLUTTER_WINDOW_H_
|
||||||
|
|
||||||
#include <flutter/dart_project.h>
|
#include <flutter/dart_project.h>
|
||||||
#include <flutter/flutter_view_controller.h>
|
#include <flutter/flutter_view_controller.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "win32_window.h"
|
#include "win32_window.h"
|
||||||
|
|
||||||
// A window that does nothing but host a Flutter view.
|
// A window that does nothing but host a Flutter view.
|
||||||
class FlutterWindow : public Win32Window {
|
class FlutterWindow : public Win32Window {
|
||||||
public:
|
public:
|
||||||
// Creates a new FlutterWindow hosting a Flutter view running |project|.
|
// Creates a new FlutterWindow hosting a Flutter view running |project|.
|
||||||
explicit FlutterWindow(const flutter::DartProject& project);
|
explicit FlutterWindow(const flutter::DartProject& project);
|
||||||
virtual ~FlutterWindow();
|
virtual ~FlutterWindow();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Win32Window:
|
// Win32Window:
|
||||||
bool OnCreate() override;
|
bool OnCreate() override;
|
||||||
void OnDestroy() override;
|
void OnDestroy() override;
|
||||||
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
|
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
|
||||||
LPARAM const lparam) noexcept override;
|
LPARAM const lparam) noexcept override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// The project to run.
|
// The project to run.
|
||||||
flutter::DartProject project_;
|
flutter::DartProject project_;
|
||||||
|
|
||||||
// The Flutter instance hosted by this window.
|
// The Flutter instance hosted by this window.
|
||||||
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
|
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // RUNNER_FLUTTER_WINDOW_H_
|
#endif // RUNNER_FLUTTER_WINDOW_H_
|
||||||
@@ -1,43 +1,43 @@
|
|||||||
#include <flutter/dart_project.h>
|
#include <flutter/dart_project.h>
|
||||||
#include <flutter/flutter_view_controller.h>
|
#include <flutter/flutter_view_controller.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#include "flutter_window.h"
|
#include "flutter_window.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||||
_In_ wchar_t *command_line, _In_ int show_command) {
|
_In_ wchar_t *command_line, _In_ int show_command) {
|
||||||
// Attach to console when present (e.g., 'flutter run') or create a
|
// Attach to console when present (e.g., 'flutter run') or create a
|
||||||
// new console when running with a debugger.
|
// new console when running with a debugger.
|
||||||
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
||||||
CreateAndAttachConsole();
|
CreateAndAttachConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize COM, so that it is available for use in the library and/or
|
// Initialize COM, so that it is available for use in the library and/or
|
||||||
// plugins.
|
// plugins.
|
||||||
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||||
|
|
||||||
flutter::DartProject project(L"data");
|
flutter::DartProject project(L"data");
|
||||||
|
|
||||||
std::vector<std::string> command_line_arguments =
|
std::vector<std::string> command_line_arguments =
|
||||||
GetCommandLineArguments();
|
GetCommandLineArguments();
|
||||||
|
|
||||||
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
|
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
|
||||||
|
|
||||||
FlutterWindow window(project);
|
FlutterWindow window(project);
|
||||||
Win32Window::Point origin(10, 10);
|
Win32Window::Point origin(10, 10);
|
||||||
Win32Window::Size size(1280, 720);
|
Win32Window::Size size(1280, 720);
|
||||||
if (!window.Create(L"useragent", origin, size)) {
|
if (!window.Create(L"app", origin, size)) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
window.SetQuitOnClose(true);
|
window.SetQuitOnClose(true);
|
||||||
|
|
||||||
::MSG msg;
|
::MSG msg;
|
||||||
while (::GetMessage(&msg, nullptr, 0, 0)) {
|
while (::GetMessage(&msg, nullptr, 0, 0)) {
|
||||||
::TranslateMessage(&msg);
|
::TranslateMessage(&msg);
|
||||||
::DispatchMessage(&msg);
|
::DispatchMessage(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
::CoUninitialize();
|
::CoUninitialize();
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
//{{NO_DEPENDENCIES}}
|
//{{NO_DEPENDENCIES}}
|
||||||
// Microsoft Visual C++ generated include file.
|
// Microsoft Visual C++ generated include file.
|
||||||
// Used by Runner.rc
|
// Used by Runner.rc
|
||||||
//
|
//
|
||||||
#define IDI_APP_ICON 101
|
#define IDI_APP_ICON 101
|
||||||
|
|
||||||
// Next default values for new objects
|
// Next default values for new objects
|
||||||
//
|
//
|
||||||
#ifdef APSTUDIO_INVOKED
|
#ifdef APSTUDIO_INVOKED
|
||||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||||
#define _APS_NEXT_RESOURCE_VALUE 102
|
#define _APS_NEXT_RESOURCE_VALUE 102
|
||||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||||
#define _APS_NEXT_CONTROL_VALUE 1001
|
#define _APS_NEXT_CONTROL_VALUE 1001
|
||||||
#define _APS_NEXT_SYMED_VALUE 101
|
#define _APS_NEXT_SYMED_VALUE 101
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
@@ -1,14 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
<windowsSettings>
|
<windowsSettings>
|
||||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
</windowsSettings>
|
</windowsSettings>
|
||||||
</application>
|
</application>
|
||||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
<application>
|
<application>
|
||||||
<!-- Windows 10 and Windows 11 -->
|
<!-- Windows 10 and Windows 11 -->
|
||||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||||
</application>
|
</application>
|
||||||
</compatibility>
|
</compatibility>
|
||||||
</assembly>
|
</assembly>
|
||||||
@@ -1,65 +1,65 @@
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
#include <flutter_windows.h>
|
#include <flutter_windows.h>
|
||||||
#include <io.h>
|
#include <io.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
void CreateAndAttachConsole() {
|
void CreateAndAttachConsole() {
|
||||||
if (::AllocConsole()) {
|
if (::AllocConsole()) {
|
||||||
FILE *unused;
|
FILE *unused;
|
||||||
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
|
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
|
||||||
_dup2(_fileno(stdout), 1);
|
_dup2(_fileno(stdout), 1);
|
||||||
}
|
}
|
||||||
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
|
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
|
||||||
_dup2(_fileno(stdout), 2);
|
_dup2(_fileno(stdout), 2);
|
||||||
}
|
}
|
||||||
std::ios::sync_with_stdio();
|
std::ios::sync_with_stdio();
|
||||||
FlutterDesktopResyncOutputStreams();
|
FlutterDesktopResyncOutputStreams();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> GetCommandLineArguments() {
|
std::vector<std::string> GetCommandLineArguments() {
|
||||||
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
|
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
|
||||||
int argc;
|
int argc;
|
||||||
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
|
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
|
||||||
if (argv == nullptr) {
|
if (argv == nullptr) {
|
||||||
return std::vector<std::string>();
|
return std::vector<std::string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> command_line_arguments;
|
std::vector<std::string> command_line_arguments;
|
||||||
|
|
||||||
// Skip the first argument as it's the binary name.
|
// Skip the first argument as it's the binary name.
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
|
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
::LocalFree(argv);
|
::LocalFree(argv);
|
||||||
|
|
||||||
return command_line_arguments;
|
return command_line_arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
|
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
|
||||||
if (utf16_string == nullptr) {
|
if (utf16_string == nullptr) {
|
||||||
return std::string();
|
return std::string();
|
||||||
}
|
}
|
||||||
unsigned int target_length = ::WideCharToMultiByte(
|
unsigned int target_length = ::WideCharToMultiByte(
|
||||||
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
||||||
-1, nullptr, 0, nullptr, nullptr)
|
-1, nullptr, 0, nullptr, nullptr)
|
||||||
-1; // remove the trailing null character
|
-1; // remove the trailing null character
|
||||||
int input_length = (int)wcslen(utf16_string);
|
int input_length = (int)wcslen(utf16_string);
|
||||||
std::string utf8_string;
|
std::string utf8_string;
|
||||||
if (target_length == 0 || target_length > utf8_string.max_size()) {
|
if (target_length == 0 || target_length > utf8_string.max_size()) {
|
||||||
return utf8_string;
|
return utf8_string;
|
||||||
}
|
}
|
||||||
utf8_string.resize(target_length);
|
utf8_string.resize(target_length);
|
||||||
int converted_length = ::WideCharToMultiByte(
|
int converted_length = ::WideCharToMultiByte(
|
||||||
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
||||||
input_length, utf8_string.data(), target_length, nullptr, nullptr);
|
input_length, utf8_string.data(), target_length, nullptr, nullptr);
|
||||||
if (converted_length == 0) {
|
if (converted_length == 0) {
|
||||||
return std::string();
|
return std::string();
|
||||||
}
|
}
|
||||||
return utf8_string;
|
return utf8_string;
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
#ifndef RUNNER_UTILS_H_
|
#ifndef RUNNER_UTILS_H_
|
||||||
#define RUNNER_UTILS_H_
|
#define RUNNER_UTILS_H_
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// Creates a console for the process, and redirects stdout and stderr to
|
// Creates a console for the process, and redirects stdout and stderr to
|
||||||
// it for both the runner and the Flutter library.
|
// it for both the runner and the Flutter library.
|
||||||
void CreateAndAttachConsole();
|
void CreateAndAttachConsole();
|
||||||
|
|
||||||
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
|
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
|
||||||
// encoded in UTF-8. Returns an empty std::string on failure.
|
// encoded in UTF-8. Returns an empty std::string on failure.
|
||||||
std::string Utf8FromUtf16(const wchar_t* utf16_string);
|
std::string Utf8FromUtf16(const wchar_t* utf16_string);
|
||||||
|
|
||||||
// Gets the command line arguments passed in as a std::vector<std::string>,
|
// Gets the command line arguments passed in as a std::vector<std::string>,
|
||||||
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
|
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
|
||||||
std::vector<std::string> GetCommandLineArguments();
|
std::vector<std::string> GetCommandLineArguments();
|
||||||
|
|
||||||
#endif // RUNNER_UTILS_H_
|
#endif // RUNNER_UTILS_H_
|
||||||
@@ -1,288 +1,288 @@
|
|||||||
#include "win32_window.h"
|
#include "win32_window.h"
|
||||||
|
|
||||||
#include <dwmapi.h>
|
#include <dwmapi.h>
|
||||||
#include <flutter_windows.h>
|
#include <flutter_windows.h>
|
||||||
|
|
||||||
#include "resource.h"
|
#include "resource.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
/// Window attribute that enables dark mode window decorations.
|
/// Window attribute that enables dark mode window decorations.
|
||||||
///
|
///
|
||||||
/// Redefined in case the developer's machine has a Windows SDK older than
|
/// Redefined in case the developer's machine has a Windows SDK older than
|
||||||
/// version 10.0.22000.0.
|
/// version 10.0.22000.0.
|
||||||
/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
|
/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
|
||||||
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
|
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
|
||||||
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
|
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
|
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
|
||||||
|
|
||||||
/// Registry key for app theme preference.
|
/// Registry key for app theme preference.
|
||||||
///
|
///
|
||||||
/// A value of 0 indicates apps should use dark mode. A non-zero or missing
|
/// A value of 0 indicates apps should use dark mode. A non-zero or missing
|
||||||
/// value indicates apps should use light mode.
|
/// value indicates apps should use light mode.
|
||||||
constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
|
constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
|
||||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
|
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
|
||||||
constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
|
constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
|
||||||
|
|
||||||
// The number of Win32Window objects that currently exist.
|
// The number of Win32Window objects that currently exist.
|
||||||
static int g_active_window_count = 0;
|
static int g_active_window_count = 0;
|
||||||
|
|
||||||
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
|
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
|
||||||
|
|
||||||
// Scale helper to convert logical scaler values to physical using passed in
|
// Scale helper to convert logical scaler values to physical using passed in
|
||||||
// scale factor
|
// scale factor
|
||||||
int Scale(int source, double scale_factor) {
|
int Scale(int source, double scale_factor) {
|
||||||
return static_cast<int>(source * scale_factor);
|
return static_cast<int>(source * scale_factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
|
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
|
||||||
// This API is only needed for PerMonitor V1 awareness mode.
|
// This API is only needed for PerMonitor V1 awareness mode.
|
||||||
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
|
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
|
||||||
HMODULE user32_module = LoadLibraryA("User32.dll");
|
HMODULE user32_module = LoadLibraryA("User32.dll");
|
||||||
if (!user32_module) {
|
if (!user32_module) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto enable_non_client_dpi_scaling =
|
auto enable_non_client_dpi_scaling =
|
||||||
reinterpret_cast<EnableNonClientDpiScaling*>(
|
reinterpret_cast<EnableNonClientDpiScaling*>(
|
||||||
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
|
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
|
||||||
if (enable_non_client_dpi_scaling != nullptr) {
|
if (enable_non_client_dpi_scaling != nullptr) {
|
||||||
enable_non_client_dpi_scaling(hwnd);
|
enable_non_client_dpi_scaling(hwnd);
|
||||||
}
|
}
|
||||||
FreeLibrary(user32_module);
|
FreeLibrary(user32_module);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// Manages the Win32Window's window class registration.
|
// Manages the Win32Window's window class registration.
|
||||||
class WindowClassRegistrar {
|
class WindowClassRegistrar {
|
||||||
public:
|
public:
|
||||||
~WindowClassRegistrar() = default;
|
~WindowClassRegistrar() = default;
|
||||||
|
|
||||||
// Returns the singleton registrar instance.
|
// Returns the singleton registrar instance.
|
||||||
static WindowClassRegistrar* GetInstance() {
|
static WindowClassRegistrar* GetInstance() {
|
||||||
if (!instance_) {
|
if (!instance_) {
|
||||||
instance_ = new WindowClassRegistrar();
|
instance_ = new WindowClassRegistrar();
|
||||||
}
|
}
|
||||||
return instance_;
|
return instance_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the name of the window class, registering the class if it hasn't
|
// Returns the name of the window class, registering the class if it hasn't
|
||||||
// previously been registered.
|
// previously been registered.
|
||||||
const wchar_t* GetWindowClass();
|
const wchar_t* GetWindowClass();
|
||||||
|
|
||||||
// Unregisters the window class. Should only be called if there are no
|
// Unregisters the window class. Should only be called if there are no
|
||||||
// instances of the window.
|
// instances of the window.
|
||||||
void UnregisterWindowClass();
|
void UnregisterWindowClass();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WindowClassRegistrar() = default;
|
WindowClassRegistrar() = default;
|
||||||
|
|
||||||
static WindowClassRegistrar* instance_;
|
static WindowClassRegistrar* instance_;
|
||||||
|
|
||||||
bool class_registered_ = false;
|
bool class_registered_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
|
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
|
||||||
|
|
||||||
const wchar_t* WindowClassRegistrar::GetWindowClass() {
|
const wchar_t* WindowClassRegistrar::GetWindowClass() {
|
||||||
if (!class_registered_) {
|
if (!class_registered_) {
|
||||||
WNDCLASS window_class{};
|
WNDCLASS window_class{};
|
||||||
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||||
window_class.lpszClassName = kWindowClassName;
|
window_class.lpszClassName = kWindowClassName;
|
||||||
window_class.style = CS_HREDRAW | CS_VREDRAW;
|
window_class.style = CS_HREDRAW | CS_VREDRAW;
|
||||||
window_class.cbClsExtra = 0;
|
window_class.cbClsExtra = 0;
|
||||||
window_class.cbWndExtra = 0;
|
window_class.cbWndExtra = 0;
|
||||||
window_class.hInstance = GetModuleHandle(nullptr);
|
window_class.hInstance = GetModuleHandle(nullptr);
|
||||||
window_class.hIcon =
|
window_class.hIcon =
|
||||||
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
|
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
|
||||||
window_class.hbrBackground = 0;
|
window_class.hbrBackground = 0;
|
||||||
window_class.lpszMenuName = nullptr;
|
window_class.lpszMenuName = nullptr;
|
||||||
window_class.lpfnWndProc = Win32Window::WndProc;
|
window_class.lpfnWndProc = Win32Window::WndProc;
|
||||||
RegisterClass(&window_class);
|
RegisterClass(&window_class);
|
||||||
class_registered_ = true;
|
class_registered_ = true;
|
||||||
}
|
}
|
||||||
return kWindowClassName;
|
return kWindowClassName;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowClassRegistrar::UnregisterWindowClass() {
|
void WindowClassRegistrar::UnregisterWindowClass() {
|
||||||
UnregisterClass(kWindowClassName, nullptr);
|
UnregisterClass(kWindowClassName, nullptr);
|
||||||
class_registered_ = false;
|
class_registered_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Win32Window::Win32Window() {
|
Win32Window::Win32Window() {
|
||||||
++g_active_window_count;
|
++g_active_window_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
Win32Window::~Win32Window() {
|
Win32Window::~Win32Window() {
|
||||||
--g_active_window_count;
|
--g_active_window_count;
|
||||||
Destroy();
|
Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Win32Window::Create(const std::wstring& title,
|
bool Win32Window::Create(const std::wstring& title,
|
||||||
const Point& origin,
|
const Point& origin,
|
||||||
const Size& size) {
|
const Size& size) {
|
||||||
Destroy();
|
Destroy();
|
||||||
|
|
||||||
const wchar_t* window_class =
|
const wchar_t* window_class =
|
||||||
WindowClassRegistrar::GetInstance()->GetWindowClass();
|
WindowClassRegistrar::GetInstance()->GetWindowClass();
|
||||||
|
|
||||||
const POINT target_point = {static_cast<LONG>(origin.x),
|
const POINT target_point = {static_cast<LONG>(origin.x),
|
||||||
static_cast<LONG>(origin.y)};
|
static_cast<LONG>(origin.y)};
|
||||||
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
|
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
|
||||||
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
|
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
|
||||||
double scale_factor = dpi / 96.0;
|
double scale_factor = dpi / 96.0;
|
||||||
|
|
||||||
HWND window = CreateWindow(
|
HWND window = CreateWindow(
|
||||||
window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
|
window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
|
||||||
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
|
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
|
||||||
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
|
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
|
||||||
nullptr, nullptr, GetModuleHandle(nullptr), this);
|
nullptr, nullptr, GetModuleHandle(nullptr), this);
|
||||||
|
|
||||||
if (!window) {
|
if (!window) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateTheme(window);
|
UpdateTheme(window);
|
||||||
|
|
||||||
return OnCreate();
|
return OnCreate();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Win32Window::Show() {
|
bool Win32Window::Show() {
|
||||||
return ShowWindow(window_handle_, SW_SHOWNORMAL);
|
return ShowWindow(window_handle_, SW_SHOWNORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
|
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
|
||||||
UINT const message,
|
UINT const message,
|
||||||
WPARAM const wparam,
|
WPARAM const wparam,
|
||||||
LPARAM const lparam) noexcept {
|
LPARAM const lparam) noexcept {
|
||||||
if (message == WM_NCCREATE) {
|
if (message == WM_NCCREATE) {
|
||||||
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
|
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
|
||||||
SetWindowLongPtr(window, GWLP_USERDATA,
|
SetWindowLongPtr(window, GWLP_USERDATA,
|
||||||
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
|
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
|
||||||
|
|
||||||
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
|
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
|
||||||
EnableFullDpiSupportIfAvailable(window);
|
EnableFullDpiSupportIfAvailable(window);
|
||||||
that->window_handle_ = window;
|
that->window_handle_ = window;
|
||||||
} else if (Win32Window* that = GetThisFromHandle(window)) {
|
} else if (Win32Window* that = GetThisFromHandle(window)) {
|
||||||
return that->MessageHandler(window, message, wparam, lparam);
|
return that->MessageHandler(window, message, wparam, lparam);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DefWindowProc(window, message, wparam, lparam);
|
return DefWindowProc(window, message, wparam, lparam);
|
||||||
}
|
}
|
||||||
|
|
||||||
LRESULT
|
LRESULT
|
||||||
Win32Window::MessageHandler(HWND hwnd,
|
Win32Window::MessageHandler(HWND hwnd,
|
||||||
UINT const message,
|
UINT const message,
|
||||||
WPARAM const wparam,
|
WPARAM const wparam,
|
||||||
LPARAM const lparam) noexcept {
|
LPARAM const lparam) noexcept {
|
||||||
switch (message) {
|
switch (message) {
|
||||||
case WM_DESTROY:
|
case WM_DESTROY:
|
||||||
window_handle_ = nullptr;
|
window_handle_ = nullptr;
|
||||||
Destroy();
|
Destroy();
|
||||||
if (quit_on_close_) {
|
if (quit_on_close_) {
|
||||||
PostQuitMessage(0);
|
PostQuitMessage(0);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
case WM_DPICHANGED: {
|
case WM_DPICHANGED: {
|
||||||
auto newRectSize = reinterpret_cast<RECT*>(lparam);
|
auto newRectSize = reinterpret_cast<RECT*>(lparam);
|
||||||
LONG newWidth = newRectSize->right - newRectSize->left;
|
LONG newWidth = newRectSize->right - newRectSize->left;
|
||||||
LONG newHeight = newRectSize->bottom - newRectSize->top;
|
LONG newHeight = newRectSize->bottom - newRectSize->top;
|
||||||
|
|
||||||
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
|
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
|
||||||
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
|
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
case WM_SIZE: {
|
case WM_SIZE: {
|
||||||
RECT rect = GetClientArea();
|
RECT rect = GetClientArea();
|
||||||
if (child_content_ != nullptr) {
|
if (child_content_ != nullptr) {
|
||||||
// Size and position the child window.
|
// Size and position the child window.
|
||||||
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
|
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
|
||||||
rect.bottom - rect.top, TRUE);
|
rect.bottom - rect.top, TRUE);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
case WM_ACTIVATE:
|
case WM_ACTIVATE:
|
||||||
if (child_content_ != nullptr) {
|
if (child_content_ != nullptr) {
|
||||||
SetFocus(child_content_);
|
SetFocus(child_content_);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
case WM_DWMCOLORIZATIONCOLORCHANGED:
|
case WM_DWMCOLORIZATIONCOLORCHANGED:
|
||||||
UpdateTheme(hwnd);
|
UpdateTheme(hwnd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return DefWindowProc(window_handle_, message, wparam, lparam);
|
return DefWindowProc(window_handle_, message, wparam, lparam);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Win32Window::Destroy() {
|
void Win32Window::Destroy() {
|
||||||
OnDestroy();
|
OnDestroy();
|
||||||
|
|
||||||
if (window_handle_) {
|
if (window_handle_) {
|
||||||
DestroyWindow(window_handle_);
|
DestroyWindow(window_handle_);
|
||||||
window_handle_ = nullptr;
|
window_handle_ = nullptr;
|
||||||
}
|
}
|
||||||
if (g_active_window_count == 0) {
|
if (g_active_window_count == 0) {
|
||||||
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
|
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
|
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
|
||||||
return reinterpret_cast<Win32Window*>(
|
return reinterpret_cast<Win32Window*>(
|
||||||
GetWindowLongPtr(window, GWLP_USERDATA));
|
GetWindowLongPtr(window, GWLP_USERDATA));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Win32Window::SetChildContent(HWND content) {
|
void Win32Window::SetChildContent(HWND content) {
|
||||||
child_content_ = content;
|
child_content_ = content;
|
||||||
SetParent(content, window_handle_);
|
SetParent(content, window_handle_);
|
||||||
RECT frame = GetClientArea();
|
RECT frame = GetClientArea();
|
||||||
|
|
||||||
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
|
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
|
||||||
frame.bottom - frame.top, true);
|
frame.bottom - frame.top, true);
|
||||||
|
|
||||||
SetFocus(child_content_);
|
SetFocus(child_content_);
|
||||||
}
|
}
|
||||||
|
|
||||||
RECT Win32Window::GetClientArea() {
|
RECT Win32Window::GetClientArea() {
|
||||||
RECT frame;
|
RECT frame;
|
||||||
GetClientRect(window_handle_, &frame);
|
GetClientRect(window_handle_, &frame);
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
HWND Win32Window::GetHandle() {
|
HWND Win32Window::GetHandle() {
|
||||||
return window_handle_;
|
return window_handle_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Win32Window::SetQuitOnClose(bool quit_on_close) {
|
void Win32Window::SetQuitOnClose(bool quit_on_close) {
|
||||||
quit_on_close_ = quit_on_close;
|
quit_on_close_ = quit_on_close;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Win32Window::OnCreate() {
|
bool Win32Window::OnCreate() {
|
||||||
// No-op; provided for subclasses.
|
// No-op; provided for subclasses.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Win32Window::OnDestroy() {
|
void Win32Window::OnDestroy() {
|
||||||
// No-op; provided for subclasses.
|
// No-op; provided for subclasses.
|
||||||
}
|
}
|
||||||
|
|
||||||
void Win32Window::UpdateTheme(HWND const window) {
|
void Win32Window::UpdateTheme(HWND const window) {
|
||||||
DWORD light_mode;
|
DWORD light_mode;
|
||||||
DWORD light_mode_size = sizeof(light_mode);
|
DWORD light_mode_size = sizeof(light_mode);
|
||||||
LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
|
LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
|
||||||
kGetPreferredBrightnessRegValue,
|
kGetPreferredBrightnessRegValue,
|
||||||
RRF_RT_REG_DWORD, nullptr, &light_mode,
|
RRF_RT_REG_DWORD, nullptr, &light_mode,
|
||||||
&light_mode_size);
|
&light_mode_size);
|
||||||
|
|
||||||
if (result == ERROR_SUCCESS) {
|
if (result == ERROR_SUCCESS) {
|
||||||
BOOL enable_dark_mode = light_mode == 0;
|
BOOL enable_dark_mode = light_mode == 0;
|
||||||
DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
|
DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||||||
&enable_dark_mode, sizeof(enable_dark_mode));
|
&enable_dark_mode, sizeof(enable_dark_mode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,102 +1,102 @@
|
|||||||
#ifndef RUNNER_WIN32_WINDOW_H_
|
#ifndef RUNNER_WIN32_WINDOW_H_
|
||||||
#define RUNNER_WIN32_WINDOW_H_
|
#define RUNNER_WIN32_WINDOW_H_
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
|
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
|
||||||
// inherited from by classes that wish to specialize with custom
|
// inherited from by classes that wish to specialize with custom
|
||||||
// rendering and input handling
|
// rendering and input handling
|
||||||
class Win32Window {
|
class Win32Window {
|
||||||
public:
|
public:
|
||||||
struct Point {
|
struct Point {
|
||||||
unsigned int x;
|
unsigned int x;
|
||||||
unsigned int y;
|
unsigned int y;
|
||||||
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
|
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Size {
|
struct Size {
|
||||||
unsigned int width;
|
unsigned int width;
|
||||||
unsigned int height;
|
unsigned int height;
|
||||||
Size(unsigned int width, unsigned int height)
|
Size(unsigned int width, unsigned int height)
|
||||||
: width(width), height(height) {}
|
: width(width), height(height) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
Win32Window();
|
Win32Window();
|
||||||
virtual ~Win32Window();
|
virtual ~Win32Window();
|
||||||
|
|
||||||
// Creates a win32 window with |title| that is positioned and sized using
|
// Creates a win32 window with |title| that is positioned and sized using
|
||||||
// |origin| and |size|. New windows are created on the default monitor. Window
|
// |origin| and |size|. New windows are created on the default monitor. Window
|
||||||
// sizes are specified to the OS in physical pixels, hence to ensure a
|
// sizes are specified to the OS in physical pixels, hence to ensure a
|
||||||
// consistent size this function will scale the inputted width and height as
|
// consistent size this function will scale the inputted width and height as
|
||||||
// as appropriate for the default monitor. The window is invisible until
|
// as appropriate for the default monitor. The window is invisible until
|
||||||
// |Show| is called. Returns true if the window was created successfully.
|
// |Show| is called. Returns true if the window was created successfully.
|
||||||
bool Create(const std::wstring& title, const Point& origin, const Size& size);
|
bool Create(const std::wstring& title, const Point& origin, const Size& size);
|
||||||
|
|
||||||
// Show the current window. Returns true if the window was successfully shown.
|
// Show the current window. Returns true if the window was successfully shown.
|
||||||
bool Show();
|
bool Show();
|
||||||
|
|
||||||
// Release OS resources associated with window.
|
// Release OS resources associated with window.
|
||||||
void Destroy();
|
void Destroy();
|
||||||
|
|
||||||
// Inserts |content| into the window tree.
|
// Inserts |content| into the window tree.
|
||||||
void SetChildContent(HWND content);
|
void SetChildContent(HWND content);
|
||||||
|
|
||||||
// Returns the backing Window handle to enable clients to set icon and other
|
// Returns the backing Window handle to enable clients to set icon and other
|
||||||
// window properties. Returns nullptr if the window has been destroyed.
|
// window properties. Returns nullptr if the window has been destroyed.
|
||||||
HWND GetHandle();
|
HWND GetHandle();
|
||||||
|
|
||||||
// If true, closing this window will quit the application.
|
// If true, closing this window will quit the application.
|
||||||
void SetQuitOnClose(bool quit_on_close);
|
void SetQuitOnClose(bool quit_on_close);
|
||||||
|
|
||||||
// Return a RECT representing the bounds of the current client area.
|
// Return a RECT representing the bounds of the current client area.
|
||||||
RECT GetClientArea();
|
RECT GetClientArea();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Processes and route salient window messages for mouse handling,
|
// Processes and route salient window messages for mouse handling,
|
||||||
// size change and DPI. Delegates handling of these to member overloads that
|
// size change and DPI. Delegates handling of these to member overloads that
|
||||||
// inheriting classes can handle.
|
// inheriting classes can handle.
|
||||||
virtual LRESULT MessageHandler(HWND window,
|
virtual LRESULT MessageHandler(HWND window,
|
||||||
UINT const message,
|
UINT const message,
|
||||||
WPARAM const wparam,
|
WPARAM const wparam,
|
||||||
LPARAM const lparam) noexcept;
|
LPARAM const lparam) noexcept;
|
||||||
|
|
||||||
// Called when CreateAndShow is called, allowing subclass window-related
|
// Called when CreateAndShow is called, allowing subclass window-related
|
||||||
// setup. Subclasses should return false if setup fails.
|
// setup. Subclasses should return false if setup fails.
|
||||||
virtual bool OnCreate();
|
virtual bool OnCreate();
|
||||||
|
|
||||||
// Called when Destroy is called.
|
// Called when Destroy is called.
|
||||||
virtual void OnDestroy();
|
virtual void OnDestroy();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class WindowClassRegistrar;
|
friend class WindowClassRegistrar;
|
||||||
|
|
||||||
// OS callback called by message pump. Handles the WM_NCCREATE message which
|
// OS callback called by message pump. Handles the WM_NCCREATE message which
|
||||||
// is passed when the non-client area is being created and enables automatic
|
// is passed when the non-client area is being created and enables automatic
|
||||||
// non-client DPI scaling so that the non-client area automatically
|
// non-client DPI scaling so that the non-client area automatically
|
||||||
// responds to changes in DPI. All other messages are handled by
|
// responds to changes in DPI. All other messages are handled by
|
||||||
// MessageHandler.
|
// MessageHandler.
|
||||||
static LRESULT CALLBACK WndProc(HWND const window,
|
static LRESULT CALLBACK WndProc(HWND const window,
|
||||||
UINT const message,
|
UINT const message,
|
||||||
WPARAM const wparam,
|
WPARAM const wparam,
|
||||||
LPARAM const lparam) noexcept;
|
LPARAM const lparam) noexcept;
|
||||||
|
|
||||||
// Retrieves a class instance pointer for |window|
|
// Retrieves a class instance pointer for |window|
|
||||||
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
|
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
|
||||||
|
|
||||||
// Update the window frame's theme to match the system theme.
|
// Update the window frame's theme to match the system theme.
|
||||||
static void UpdateTheme(HWND const window);
|
static void UpdateTheme(HWND const window);
|
||||||
|
|
||||||
bool quit_on_close_ = false;
|
bool quit_on_close_ = false;
|
||||||
|
|
||||||
// window handle for top level window.
|
// window handle for top level window.
|
||||||
HWND window_handle_ = nullptr;
|
HWND window_handle_ = nullptr;
|
||||||
|
|
||||||
// window handle for hosted content.
|
// window handle for hosted content.
|
||||||
HWND child_content_ = nullptr;
|
HWND child_content_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // RUNNER_WIN32_WINDOW_H_
|
#endif // RUNNER_WIN32_WINDOW_H_
|
||||||
@@ -1,334 +0,0 @@
|
|||||||
# Arbiter
|
|
||||||
|
|
||||||
Arbiter is a permissioned signing service for cryptocurrency wallets. It runs as a background service on the user's machine with an optional client application for vault management.
|
|
||||||
|
|
||||||
**Core principle:** The vault NEVER exposes key material. It only produces signatures when a request satisfies the configured policies.
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Peer Types
|
|
||||||
|
|
||||||
Arbiter distinguishes two kinds of peers:
|
|
||||||
|
|
||||||
- **Operator** — A client application used by the owner to manage the vault (create wallets, approve SDK clients, configure policies).
|
|
||||||
- **SDK Client** — A consumer of signing capabilities, typically an automation tool. In the future, this could include a browser-based wallet.
|
|
||||||
- **Recovery Operator** — A dormant recovery participant with narrowly scoped authority used only for custody recovery and operator replacement.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Authentication
|
|
||||||
|
|
||||||
### 2.1 Challenge-Response
|
|
||||||
|
|
||||||
All peers authenticate via public-key cryptography using a challenge-response protocol:
|
|
||||||
|
|
||||||
1. The peer sends its public key and requests a challenge.
|
|
||||||
2. The server looks up the key in its database. If found, it generates a fresh challenge from random bytes plus the current timestamp.
|
|
||||||
3. The peer signs the canonical challenge payload with its private key and sends the signature back.
|
|
||||||
4. The server verifies the signature:
|
|
||||||
- **Pass:** The connection is considered authenticated.
|
|
||||||
- **Fail:** The server closes the connection.
|
|
||||||
|
|
||||||
Authentication challenges are per-connection, ephemeral values. They are not persisted in the peer tables, and peer records store no challenge state.
|
|
||||||
|
|
||||||
### 2.2 Operator Bootstrap
|
|
||||||
|
|
||||||
On first run — when no Operators are registered — the server generates a one-time bootstrap token. It is made available in two ways:
|
|
||||||
|
|
||||||
- **Local setup:** Written to `~/.arbiter/bootstrap_token` for automatic discovery by a co-located Operator.
|
|
||||||
- **Remote setup:** Printed to the server's console output.
|
|
||||||
|
|
||||||
The first Operator must present this token alongside the standard challenge-response to complete registration.
|
|
||||||
|
|
||||||
### 2.3 SDK Client Registration
|
|
||||||
|
|
||||||
There is no bootstrap mechanism for SDK clients. They must be explicitly approved by an already-registered Operator.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Multi-Operator Governance
|
|
||||||
|
|
||||||
When more than one Operator is registered, the vault is treated as having multiple operators. In that mode, sensitive actions are governed by voting rather than by a single operator decision.
|
|
||||||
|
|
||||||
### 3.1 Voting Rules
|
|
||||||
|
|
||||||
Voting is based on the total number of registered operators:
|
|
||||||
|
|
||||||
- **1 operator:** no vote is needed; the single operator decides directly.
|
|
||||||
- **2 operators:** full consensus is required; both operators must approve.
|
|
||||||
- **3 or more operators:** quorum is `floor(N / 2) + 1`.
|
|
||||||
|
|
||||||
For a decision to count, the operator's approval or rejection must be signed by that operator's associated key. Unsigned votes, or votes that fail signature verification, are ignored.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- **3 operators:** 2 approvals required
|
|
||||||
- **4 operators:** 3 approvals required
|
|
||||||
|
|
||||||
### 3.2 Actions Requiring a Vote
|
|
||||||
|
|
||||||
In multi-operator mode, a successful vote is required for:
|
|
||||||
|
|
||||||
- approving new SDK clients
|
|
||||||
- granting an SDK client visibility to a wallet
|
|
||||||
- approving a one-off transaction
|
|
||||||
- approving creation of a persistent grant
|
|
||||||
- approving operator replacement
|
|
||||||
- approving server updates
|
|
||||||
- updating Shamir secret-sharing parameters
|
|
||||||
|
|
||||||
### 3.3 Special Rule for Key Rotation
|
|
||||||
|
|
||||||
Key rotation always requires full quorum, regardless of the normal voting threshold.
|
|
||||||
|
|
||||||
This is stricter than ordinary governance actions because rotating the root key requires every operator to participate in coordinated share refresh/update steps. The root key itself is not redistributed directly, but each operator's share material must be changed consistently.
|
|
||||||
|
|
||||||
### 3.4 Root Key Custody
|
|
||||||
|
|
||||||
When the vault has multiple operators, the vault root key is protected using Shamir secret sharing.
|
|
||||||
|
|
||||||
The vault root key is encrypted in a way that requires reconstruction from user-held shares rather than from a single shared password.
|
|
||||||
|
|
||||||
For ordinary operators, the Shamir threshold matches the ordinary governance quorum. For example:
|
|
||||||
|
|
||||||
- **2 operators:** `2-of-2`
|
|
||||||
- **3 operators:** `2-of-3`
|
|
||||||
- **4 operators:** `3-of-4`
|
|
||||||
|
|
||||||
In practice, the Shamir share set also includes Recovery Operator shares. This means the effective Shamir parameters are computed over the combined share pool while keeping the same threshold. For example:
|
|
||||||
|
|
||||||
- **3 ordinary operators + 2 recovery shares:** `2-of-5`
|
|
||||||
|
|
||||||
This ensures that the normal custody threshold follows the ordinary operator quorum, while still allowing dormant recovery shares to exist for break-glass recovery flows.
|
|
||||||
|
|
||||||
### 3.5 Recovery Operators
|
|
||||||
|
|
||||||
Recovery Operators are a separate peer type from ordinary vault operators.
|
|
||||||
|
|
||||||
Their role is intentionally narrow. They can only:
|
|
||||||
|
|
||||||
- participate in unsealing the vault
|
|
||||||
- vote for operator replacement
|
|
||||||
|
|
||||||
Recovery Operators do not participate in routine governance such as approving SDK clients, granting wallet visibility, approving transactions, creating grants, approving server updates, or changing Shamir parameters.
|
|
||||||
|
|
||||||
### 3.6 Sleeping and Waking Recovery Operators
|
|
||||||
|
|
||||||
By default, Recovery Operators are **sleeping** and do not participate in any active flow.
|
|
||||||
|
|
||||||
Any ordinary operator may request that Recovery Operators **wake up**.
|
|
||||||
|
|
||||||
Any ordinary operator may also cancel a pending wake-up request.
|
|
||||||
|
|
||||||
This creates a dispute window before recovery powers become active. The default wake-up delay is **14 days**.
|
|
||||||
|
|
||||||
Recovery Operators are therefore part of the break-glass recovery path rather than the normal operating quorum.
|
|
||||||
|
|
||||||
The high-level recovery flow is:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
autonumber
|
|
||||||
actor Op as Ordinary Operator
|
|
||||||
participant Server
|
|
||||||
actor Other as Other Operator
|
|
||||||
actor Rec as Recovery Operator
|
|
||||||
|
|
||||||
Op->>Server: Request recovery wake-up
|
|
||||||
Server-->>Op: Wake-up pending
|
|
||||||
Note over Server: Default dispute window: 14 days
|
|
||||||
|
|
||||||
alt Wake-up cancelled during dispute window
|
|
||||||
Other->>Server: Cancel wake-up
|
|
||||||
Server-->>Op: Recovery cancelled
|
|
||||||
Server-->>Rec: Stay sleeping
|
|
||||||
else No cancellation for 14 days
|
|
||||||
Server-->>Rec: Wake up
|
|
||||||
Rec->>Server: Join recovery flow
|
|
||||||
critical Recovery authority
|
|
||||||
Rec->>Server: Participate in unseal
|
|
||||||
Rec->>Server: Vote on operator replacement
|
|
||||||
end
|
|
||||||
Server-->>Op: Recovery mode active
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.7 Committee Formation
|
|
||||||
|
|
||||||
There are two ways to form a multi-operator committee:
|
|
||||||
|
|
||||||
- convert an existing single-operator vault by adding new operators
|
|
||||||
- bootstrap an unbootstrapped vault directly into multi-operator mode
|
|
||||||
|
|
||||||
In both cases, committee formation is a coordinated process. Arbiter does not allow multi-operator custody to emerge implicitly from unrelated registrations.
|
|
||||||
|
|
||||||
### 3.8 Bootstrapping an Unbootstrapped Vault into Multi-Operator Mode
|
|
||||||
|
|
||||||
When an unbootstrapped vault is initialized as a multi-operator vault, the setup proceeds as follows:
|
|
||||||
|
|
||||||
1. An operator connects to the unbootstrapped vault using an Operator and the bootstrap token.
|
|
||||||
2. During bootstrap setup, that operator declares:
|
|
||||||
- the total number of ordinary operators
|
|
||||||
- the total number of Recovery Operators
|
|
||||||
3. The vault enters **multi-bootstrap mode**.
|
|
||||||
4. While in multi-bootstrap mode:
|
|
||||||
- every ordinary operator must connect with an Operator using the bootstrap token
|
|
||||||
- every Recovery Operator must also connect using the bootstrap token
|
|
||||||
- each participant is registered individually
|
|
||||||
- each participant's share is created and protected with that participant's credentials
|
|
||||||
5. The vault is considered fully bootstrapped only after all declared operator and recovery-share registrations have completed successfully.
|
|
||||||
|
|
||||||
This means the operator and recovery set is fixed at bootstrap completion time, based on the counts declared when multi-bootstrap mode was entered.
|
|
||||||
|
|
||||||
### 3.9 Special Bootstrap Constraint for Two-Operator Vaults
|
|
||||||
|
|
||||||
If a vault is declared with exactly **2 ordinary operators**, Arbiter requires at least **1 Recovery Operator** to be configured during bootstrap.
|
|
||||||
|
|
||||||
This prevents the worst-case custody failure in which a `2-of-2` operator set becomes permanently unrecoverable after loss of a single operator.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Server Identity
|
|
||||||
|
|
||||||
The server proves its identity using TLS with a self-signed certificate. The TLS private key is generated on first run and is long-term; no rotation mechanism exists yet due to the complexity of multi-peer coordination.
|
|
||||||
|
|
||||||
Peers verify the server by its **public key fingerprint**:
|
|
||||||
|
|
||||||
- **Operator (local):** Receives the fingerprint automatically through the bootstrap token.
|
|
||||||
- **Operator (remote) / SDK Client:** Must receive the fingerprint out-of-band.
|
|
||||||
|
|
||||||
> A streamlined setup mechanism using a single connection string is planned but not yet implemented.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Key Management
|
|
||||||
|
|
||||||
### 5.1 Key Hierarchy
|
|
||||||
|
|
||||||
There are three layers of keys:
|
|
||||||
|
|
||||||
| Key | Encrypts | Encrypted by |
|
|
||||||
|---|---|---|
|
|
||||||
| **User key** (password) | Root key | — (derived from user input) |
|
|
||||||
| **Root key** | Wallet keys | User key |
|
|
||||||
| **Wallet keys** | — (used for signing) | Root key |
|
|
||||||
|
|
||||||
This layered design enables:
|
|
||||||
|
|
||||||
- **Password rotation** without re-encrypting every wallet key (only the root key is re-encrypted).
|
|
||||||
- **Root key rotation** without requiring the user to change their password.
|
|
||||||
|
|
||||||
### 5.2 Encryption at Rest
|
|
||||||
|
|
||||||
The database stores everything in encrypted form using symmetric AEAD. The encryption scheme is versioned to support transparent migration — when the vault unseals, Arbiter automatically re-encrypts any entries that are behind the current scheme version. See [IMPLEMENTATION.md](IMPLEMENTATION.md) for the specific scheme and versioning mechanism.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Vault Lifecycle
|
|
||||||
|
|
||||||
### 6.1 Sealed State
|
|
||||||
|
|
||||||
On boot, the root key is encrypted and the server cannot perform any signing operations. This state is called **Sealed**.
|
|
||||||
|
|
||||||
### 6.2 Unseal Flow
|
|
||||||
|
|
||||||
To transition to the **Unsealed** state, an Operator must provide the password:
|
|
||||||
|
|
||||||
1. The Operator initiates an unseal request.
|
|
||||||
2. The server generates a one-time key pair and returns the public key.
|
|
||||||
3. The Operator encrypts the user's password with this one-time public key and sends the ciphertext to the server.
|
|
||||||
4. The server decrypts and verifies the password:
|
|
||||||
- **Success:** The root key is decrypted and placed into a hardened memory cell. The server transitions to `Unsealed`. Any entries pending encryption scheme migration are re-encrypted.
|
|
||||||
- **Failure:** The server returns an error indicating the password is incorrect.
|
|
||||||
|
|
||||||
### 6.3 Memory Protection
|
|
||||||
|
|
||||||
Once unsealed, the root key must be protected in memory against:
|
|
||||||
|
|
||||||
- Memory dumps
|
|
||||||
- Page swaps to disk
|
|
||||||
- Hibernation files
|
|
||||||
|
|
||||||
See [IMPLEMENTATION.md](IMPLEMENTATION.md) for the current and planned memory protection approaches.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Permission Engine
|
|
||||||
|
|
||||||
### 7.1 Fundamental Rules
|
|
||||||
|
|
||||||
- SDK clients have **no access by default**.
|
|
||||||
- Access is granted **explicitly** by an Operator.
|
|
||||||
- Grants are scoped to **specific wallets** and governed by **policies**.
|
|
||||||
|
|
||||||
Each blockchain requires its own policy system due to differences in static transaction analysis. Currently, only EVM is supported; Solana support is planned.
|
|
||||||
|
|
||||||
Arbiter is also responsible for ensuring that **transaction nonces are never reused**.
|
|
||||||
|
|
||||||
### 7.2 EVM Policies
|
|
||||||
|
|
||||||
Every EVM grant is scoped to a specific **wallet** and **chain ID**.
|
|
||||||
|
|
||||||
#### 7.2.0 Transaction Signing Sequence
|
|
||||||
|
|
||||||
The high-level interaction order is:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
autonumber
|
|
||||||
actor SDK as SDK Client
|
|
||||||
participant Server
|
|
||||||
participant operator as Operator
|
|
||||||
|
|
||||||
SDK->>Server: SignTransactionRequest
|
|
||||||
Server->>Server: Resolve wallet and wallet visibility
|
|
||||||
alt Visibility approval required
|
|
||||||
Server->>operator: Ask for wallet visibility approval
|
|
||||||
operator-->>Server: Vote result
|
|
||||||
end
|
|
||||||
Server->>Server: Evaluate transaction
|
|
||||||
Server->>Server: Load grant and limits context
|
|
||||||
alt Grant approval required
|
|
||||||
Server->>operator: Ask for execution / grant approval
|
|
||||||
operator-->>Server: Vote result
|
|
||||||
opt Create persistent grant
|
|
||||||
Server->>Server: Create and store grant
|
|
||||||
end
|
|
||||||
Server->>Server: Retry evaluation
|
|
||||||
end
|
|
||||||
critical Final authorization path
|
|
||||||
Server->>Server: Check limits and record execution
|
|
||||||
Server-->>Server: Signature or evaluation error
|
|
||||||
end
|
|
||||||
Server-->>SDK: Signature or error
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 7.2.1 Transaction Sub-Grants
|
|
||||||
|
|
||||||
Arbiter maintains an ever-expanding database of known contracts and their ABIs. Based on contract knowledge, transaction requests fall into three categories:
|
|
||||||
|
|
||||||
**1. Known contract (ABI available)**
|
|
||||||
|
|
||||||
The transaction can be decoded and presented with semantic meaning. For example: *"Client X wants to transfer Y USDT to address Z."*
|
|
||||||
|
|
||||||
Available restrictions:
|
|
||||||
- Volume limits (e.g., "no more than 10,000 tokens ever")
|
|
||||||
- Rate limits (e.g., "no more than 100 tokens per hour")
|
|
||||||
|
|
||||||
**2. Unknown contract (no ABI)**
|
|
||||||
|
|
||||||
The transaction cannot be decoded, so its effects are opaque — it could do anything, including draining all tokens. The user is warned, and if approved, access is granted to all interactions with the contract (matched by the `to` field).
|
|
||||||
|
|
||||||
Available restrictions:
|
|
||||||
- Transaction count limits (e.g., "no more than 100 transactions ever")
|
|
||||||
- Rate limits (e.g., "no more than 5 transactions per hour")
|
|
||||||
|
|
||||||
**3. Plain ether transfer (no calldata)**
|
|
||||||
|
|
||||||
These transactions have no `calldata` and therefore cannot interact with contracts. They can be subject to the same volume and rate restrictions as above.
|
|
||||||
|
|
||||||
#### 7.2.2 Global Limits
|
|
||||||
|
|
||||||
In addition to sub-grant-specific restrictions, the following limits can be applied across all grant types:
|
|
||||||
|
|
||||||
- **Gas limit** — Maximum gas per transaction.
|
|
||||||
- **Time-window restrictions** — e.g., signing allowed only 08:00–20:00 on Mondays and Thursdays.
|
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
# Implementation Details
|
|
||||||
|
|
||||||
This document covers concrete technology choices and dependencies. For the architectural design, see [ARCHITECTURE.md](ARCHITECTURE.md).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Client Connection Flow
|
|
||||||
|
|
||||||
### Authentication Result Semantics
|
|
||||||
|
|
||||||
Authentication no longer uses an implicit success-only response shape. Both `client` and `operator` return explicit auth status enums over the wire.
|
|
||||||
|
|
||||||
- **Client:** `AuthResult` may return `SUCCESS`, `INVALID_KEY`, `INVALID_SIGNATURE`, `APPROVAL_DENIED`, `NO_OPERATORS_ONLINE`, or `INTERNAL`
|
|
||||||
- **Operator:** `AuthResult` may return `SUCCESS`, `INVALID_KEY`, `INVALID_SIGNATURE`, `BOOTSTRAP_REQUIRED`, `TOKEN_INVALID`, or `INTERNAL`
|
|
||||||
|
|
||||||
This makes transport-level failures and actor/domain-level auth failures distinct:
|
|
||||||
|
|
||||||
- **Transport/protocol failures** are surfaced as stream/status errors
|
|
||||||
- **Authentication failures** are surfaced as successful protocol responses carrying an explicit auth status
|
|
||||||
|
|
||||||
Clients are expected to handle these status codes directly and present the concrete failure reason to the user.
|
|
||||||
|
|
||||||
### New Client Approval
|
|
||||||
|
|
||||||
When a client whose public key is not yet in the database connects, all connected operators are asked to approve the connection. The first operator to respond determines the outcome; remaining requests are cancelled via a watch channel.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TD
|
|
||||||
A([Client connects]) --> B[Receive AuthChallengeRequest]
|
|
||||||
B --> C{pubkey in DB?}
|
|
||||||
|
|
||||||
C -- yes --> G[Generate AuthChallenge]
|
|
||||||
|
|
||||||
C -- no --> E[Ask all Operators:\nClientConnectionRequest]
|
|
||||||
E --> F{First response}
|
|
||||||
F -- denied --> Z([Reject connection])
|
|
||||||
F -- approved --> F2[Cancel remaining\nOperator requests]
|
|
||||||
F2 --> F3[INSERT client]
|
|
||||||
F3 --> G
|
|
||||||
|
|
||||||
G --> H[Send AuthChallenge\ntimestamp + random bytes]
|
|
||||||
H --> I[Receive AuthChallengeSolution]
|
|
||||||
I --> K{Signature valid?}
|
|
||||||
K -- no --> Z
|
|
||||||
K -- yes --> J([Session started])
|
|
||||||
```
|
|
||||||
|
|
||||||
Auth challenges are generated from fresh random bytes plus a nanosecond timestamp. The server keeps the issued challenge only in the in-flight authentication state for that connection, then verifies the signature against the same canonical challenge payload.
|
|
||||||
|
|
||||||
The authentication schema stores peer identity, not replay counters:
|
|
||||||
|
|
||||||
- `program_client` stores the SDK client's public key, metadata binding, and timestamps.
|
|
||||||
- `operator_client` stores the Operator public key and timestamps.
|
|
||||||
- Neither table stores an authentication nonce, and challenge generation does not update either table.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Cryptography
|
|
||||||
|
|
||||||
### Authentication
|
|
||||||
- **Client protocol:** ML-DSA
|
|
||||||
|
|
||||||
### User-Agent Authentication
|
|
||||||
|
|
||||||
Operator authentication supports multiple signature schemes because platform-provided "hardware-bound" keys do not expose a uniform algorithm across operating systems and hardware.
|
|
||||||
|
|
||||||
- **Supported schemes:** ML-DSA
|
|
||||||
- **Why:** Secure Enclave (MacOS) support them natively, on other platforms we could emulate while they roll-out
|
|
||||||
|
|
||||||
### Encryption at Rest
|
|
||||||
- **Scheme:** Symmetric AEAD — currently **XChaCha20-Poly1305**
|
|
||||||
- **Version tracking:** Each `aead_encrypted` database entry carries a `scheme` field denoting the version, enabling transparent migration on unseal
|
|
||||||
|
|
||||||
### Server Identity
|
|
||||||
- **Transport:** TLS with a self-signed certificate
|
|
||||||
- **Key type:** Generated on first run; long-term (no rotation mechanism yet)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Communication
|
|
||||||
|
|
||||||
- **Protocol:** gRPC with Protocol Buffers
|
|
||||||
- **Request/response matching:** multiplexed over a single bidirectional stream using per-connection request IDs
|
|
||||||
- **Server identity distribution:** `ServerInfo` protobuf struct containing the TLS public key fingerprint
|
|
||||||
- **Future consideration:** grpc-web lacks bidirectional stream support, so a browser-based wallet may require protojson over WebSocket
|
|
||||||
|
|
||||||
### Request Multiplexing
|
|
||||||
|
|
||||||
Both `client` and `operator` connections support multiple in-flight requests over one gRPC bidi stream.
|
|
||||||
|
|
||||||
- Every request carries a monotonically increasing request ID
|
|
||||||
- Every normal response echoes the request ID it corresponds to
|
|
||||||
- Out-of-band server messages omit the response ID entirely
|
|
||||||
- The server rejects already-seen request IDs at the transport adapter boundary before business logic sees the message
|
|
||||||
|
|
||||||
This keeps request correlation entirely in transport/client connection code while leaving actor and domain handlers unaware of request IDs.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## EVM Policy Engine
|
|
||||||
|
|
||||||
### Overview
|
|
||||||
|
|
||||||
The EVM engine classifies incoming transactions, enforces grant constraints, and records executions. It is the sole path through which a wallet key is used for signing.
|
|
||||||
|
|
||||||
The central abstraction is the `Policy` trait. Each implementation handles one semantic transaction category and owns its own database tables for grant storage and transaction logging.
|
|
||||||
|
|
||||||
### Transaction Evaluation Flow
|
|
||||||
|
|
||||||
`Engine::evaluate_transaction` runs the following steps in order:
|
|
||||||
|
|
||||||
1. **Classify** — Each registered policy's `analyze(context)` inspects the transaction fields (`chain`, `to`, `value`, `calldata`). The first one returning `Some(meaning)` wins. If none match, the transaction is rejected as `UnsupportedTransactionType`.
|
|
||||||
2. **Find grant** — `Policy::try_find_grant` queries for a non-revoked grant covering this wallet, client, chain, and target address.
|
|
||||||
3. **Check shared constraints** — `check_shared_constraints` runs in the engine before any policy-specific logic. It enforces the validity window, gas fee caps, and transaction count rate limit (see below).
|
|
||||||
4. **Evaluate** — `Policy::evaluate` checks the decoded meaning against the grant's policy-specific constraints and returns any violations.
|
|
||||||
5. **Record** — If `RunKind::Execution` and there are no violations, the engine writes to `evm_transaction_log` and calls `Policy::record_transaction` for any policy-specific logging (e.g., token transfer volume).
|
|
||||||
|
|
||||||
The detailed branch structure is shown below:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TD
|
|
||||||
A[SDK Client sends sign transaction request] --> B[Server resolves wallet]
|
|
||||||
B --> C{Wallet exists?}
|
|
||||||
|
|
||||||
C -- No --> Z1[Return wallet not found error]
|
|
||||||
C -- Yes --> D[Check SDK client wallet visibility]
|
|
||||||
|
|
||||||
D --> E{Wallet visible to SDK client?}
|
|
||||||
E -- No --> F[Start wallet visibility voting flow]
|
|
||||||
F --> G{Vote approved?}
|
|
||||||
G -- No --> Z2[Return wallet access denied error]
|
|
||||||
G -- Yes --> H[Persist wallet visibility]
|
|
||||||
E -- Yes --> I[Classify transaction meaning]
|
|
||||||
H --> I
|
|
||||||
|
|
||||||
I --> J{Meaning supported?}
|
|
||||||
J -- No --> Z3[Return unsupported transaction error]
|
|
||||||
J -- Yes --> K[Find matching grant]
|
|
||||||
|
|
||||||
K --> L{Grant exists?}
|
|
||||||
L -- Yes --> M[Check grant limits]
|
|
||||||
L -- No --> N[Start execution or grant voting flow]
|
|
||||||
|
|
||||||
N --> O{Operator decision}
|
|
||||||
O -- Reject --> Z4[Return no matching grant error]
|
|
||||||
O -- Allow once --> M
|
|
||||||
O -- Create grant --> P[Create grant with user-selected limits]
|
|
||||||
P --> Q[Persist grant]
|
|
||||||
Q --> M
|
|
||||||
|
|
||||||
M --> R{Limits exceeded?}
|
|
||||||
R -- Yes --> Z5[Return evaluation error]
|
|
||||||
R -- No --> S[Record transaction in logs]
|
|
||||||
S --> T[Produce signature]
|
|
||||||
T --> U[Return signature to SDK client]
|
|
||||||
|
|
||||||
note1[Limit checks include volume, count, and gas constraints.]
|
|
||||||
note2[Grant lookup depends on classified meaning, such as ether transfer or token transfer.]
|
|
||||||
|
|
||||||
K -. uses .-> note2
|
|
||||||
M -. checks .-> note1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Policy Trait
|
|
||||||
|
|
||||||
| Method | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| `analyze` | Pure — classifies a transaction into a typed `Meaning`, or `None` if this policy doesn't apply |
|
|
||||||
| `evaluate` | Checks the `Meaning` against a `Grant`; returns a list of `EvalViolation`s |
|
|
||||||
| `create_grant` | Inserts policy-specific rows; returns the specific grant ID |
|
|
||||||
| `try_find_grant` | Finds a matching non-revoked grant for the given `EvalContext` |
|
|
||||||
| `find_all_grants` | Returns all non-revoked grants (used for listing) |
|
|
||||||
| `record_transaction` | Persists policy-specific data after execution |
|
|
||||||
|
|
||||||
`analyze` and `evaluate` are intentionally separate: classification is pure and cheap, while evaluation may involve DB queries (e.g., fetching past transfer volume).
|
|
||||||
|
|
||||||
### Registered Policies
|
|
||||||
|
|
||||||
**EtherTransfer** — plain ETH transfers (empty calldata)
|
|
||||||
|
|
||||||
- Grant requires: allowlist of recipient addresses + one volumetric rate limit (max ETH over a time window)
|
|
||||||
- Violations: recipient not in allowlist, cumulative ETH volume exceeded
|
|
||||||
|
|
||||||
**TokenTransfer** — ERC-20 `transfer(address,uint256)` calls
|
|
||||||
|
|
||||||
- Recognised by ABI-decoding the `transfer(address,uint256)` selector against a static registry of known token contracts (`arbiter_tokens_registry`)
|
|
||||||
- Grant requires: token contract address, optional recipient restriction, zero or more volumetric rate limits
|
|
||||||
- Violations: recipient mismatch, any volumetric limit exceeded
|
|
||||||
|
|
||||||
### Grant Model
|
|
||||||
|
|
||||||
Every grant has two layers:
|
|
||||||
|
|
||||||
- **Shared (`evm_basic_grant`)** — wallet, chain, validity period, gas fee caps, transaction count rate limit. One row per grant regardless of type.
|
|
||||||
- **Specific** — policy-owned tables (`evm_ether_transfer_grant`, `evm_token_transfer_grant`) holding type-specific configuration.
|
|
||||||
|
|
||||||
`find_all_grants` uses a `#[diesel::auto_type]` base join between the specific and shared tables, then batch-loads related rows (targets, volume limits) in two additional queries to avoid N+1.
|
|
||||||
|
|
||||||
The engine exposes `list_all_grants` which collects across all policy types into `Vec<Grant<SpecificGrant>>` via a blanket `From<Grant<S>> for Grant<SpecificGrant>` conversion.
|
|
||||||
|
|
||||||
### Shared Constraints (enforced by the engine)
|
|
||||||
|
|
||||||
These are checked centrally in `check_shared_constraints` before policy evaluation:
|
|
||||||
|
|
||||||
| Constraint | Fields | Behaviour |
|
|
||||||
|---|---|---|
|
|
||||||
| Validity window | `valid_from`, `valid_until` | Emits `InvalidTime` if current time is outside the range |
|
|
||||||
| Gas fee cap | `max_gas_fee_per_gas`, `max_priority_fee_per_gas` | Emits `GasLimitExceeded` if either cap is breached |
|
|
||||||
| Tx count rate limit | `rate_limit` (`count` + `window`) | Counts rows in `evm_transaction_log` within the window; emits `RateLimitExceeded` if at or above the limit |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Known Limitations
|
|
||||||
|
|
||||||
- **Only EIP-1559 transactions are supported.** Legacy and EIP-2930 types are rejected outright.
|
|
||||||
- **No opaque-calldata (unknown contract) grant type.** The architecture describes a category for unrecognised contracts, but no policy implements it yet. Any transaction that is not a plain ETH transfer or a known ERC-20 transfer is unconditionally rejected.
|
|
||||||
- **Token registry is static.** Tokens are recognised only if they appear in the hard-coded `arbiter_tokens_registry` crate. There is no mechanism to register additional contracts at runtime.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Memory Protection
|
|
||||||
|
|
||||||
The unsealed root key must be held in a hardened memory cell resistant to dumps, page swaps, and hibernation.
|
|
||||||
|
|
||||||
- **Current:** A dedicated memory-protection abstraction is in place, with `memsafe` used behind that abstraction today
|
|
||||||
- **Planned:** Additional backends can be introduced behind the same abstraction, including a custom implementation based on `mlock` (Unix) and `VirtualProtect` (Windows)
|
|
||||||
@@ -1,821 +0,0 @@
|
|||||||
# Grant Grid View Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Add an "EVM Grants" dashboard tab that displays all grants as enriched cards (type, chain, wallet address, client name) with per-card revoke support.
|
|
||||||
|
|
||||||
**Architecture:** A new `walletAccessListProvider` fetches wallet accesses with their DB row IDs. The screen (`grants.dart`) watches only `evmGrantsProvider` for top-level state. Each `GrantCard` widget (its own file) watches enrichment providers (`walletAccessListProvider`, `evmProvider`, `sdkClientsProvider`) and the revoke mutation directly — keeping rebuilds scoped to the card. The screen is registered as a dashboard tab in `AdaptiveScaffold`.
|
|
||||||
|
|
||||||
**Tech Stack:** Flutter, Riverpod (`riverpod_annotation` + `build_runner` codegen), `sizer` (adaptive sizing), `auto_route`, Protocol Buffers (Dart), `Palette` design tokens.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## File Map
|
|
||||||
|
|
||||||
| File | Action | Responsibility |
|
|
||||||
|---|---|---|
|
|
||||||
| `operator/lib/theme/palette.dart` | Modify | Add `Palette.token` (indigo accent for token-transfer cards) |
|
|
||||||
| `operator/lib/features/connection/evm/wallet_access.dart` | Modify | Add `listAllWalletAccesses()` function |
|
|
||||||
| `operator/lib/providers/sdk_clients/wallet_access_list.dart` | Create | `WalletAccessListProvider` — fetches full wallet access list with IDs |
|
|
||||||
| `operator/lib/screens/dashboard/evm/grants/widgets/grant_card.dart` | Create | `GrantCard` widget — watches enrichment providers + revoke mutation; one card per grant |
|
|
||||||
| `operator/lib/screens/dashboard/evm/grants/grants.dart` | Create | `EvmGrantsScreen` — watches `evmGrantsProvider`; handles loading/error/empty/data states; renders `GrantCard` list |
|
|
||||||
| `operator/lib/router.dart` | Modify | Register `EvmGrantsRoute` in dashboard children |
|
|
||||||
| `operator/lib/screens/dashboard.dart` | Modify | Add Grants entry to `routes` list and `NavigationDestination` list |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 1: Add `Palette.token`
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `operator/lib/theme/palette.dart`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Add the color**
|
|
||||||
|
|
||||||
Replace the contents of `operator/lib/theme/palette.dart` with:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class Palette {
|
|
||||||
static const ink = Color(0xFF15263C);
|
|
||||||
static const coral = Color(0xFFE26254);
|
|
||||||
static const cream = Color(0xFFFFFAF4);
|
|
||||||
static const line = Color(0x1A15263C);
|
|
||||||
static const token = Color(0xFF5C6BC0);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd operator && flutter analyze lib/theme/palette.dart
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: no issues.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
jj describe -m "feat(theme): add Palette.token for token-transfer grant cards"
|
|
||||||
jj new
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 2: Add `listAllWalletAccesses` feature function
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `operator/lib/features/connection/evm/wallet_access.dart`
|
|
||||||
|
|
||||||
`readClientWalletAccess` (existing) filters the list to one client's wallet IDs and returns `Set<int>`. This new function returns the complete unfiltered list with row IDs so the grant cards can resolve wallet_access_id → wallet + client.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Append function**
|
|
||||||
|
|
||||||
Add at the bottom of `operator/lib/features/connection/evm/wallet_access.dart`:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
Future<List<SdkClientWalletAccess>> listAllWalletAccesses(
|
|
||||||
Connection connection,
|
|
||||||
) async {
|
|
||||||
final response = await connection.ask(
|
|
||||||
OperatorRequest(listWalletAccess: Empty()),
|
|
||||||
);
|
|
||||||
if (!response.hasListWalletAccessResponse()) {
|
|
||||||
throw Exception(
|
|
||||||
'Expected list wallet access response, got ${response.whichPayload()}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return response.listWalletAccessResponse.accesses.toList(growable: false);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Each returned `SdkClientWalletAccess` has:
|
|
||||||
- `.id` — the `evm_wallet_access` row ID (same value as `wallet_access_id` in a `GrantEntry`)
|
|
||||||
- `.access.walletId` — the EVM wallet DB ID
|
|
||||||
- `.access.sdkClientId` — the SDK client DB ID
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd operator && flutter analyze lib/features/connection/evm/wallet_access.dart
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: no issues.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
jj describe -m "feat(evm): add listAllWalletAccesses feature function"
|
|
||||||
jj new
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 3: Create `WalletAccessListProvider`
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `operator/lib/providers/sdk_clients/wallet_access_list.dart`
|
|
||||||
- Generated: `operator/lib/providers/sdk_clients/wallet_access_list.g.dart`
|
|
||||||
|
|
||||||
Mirrors the structure of `EvmGrants` in `providers/evm/evm_grants.dart` — class-based `@riverpod` with a `refresh()` method.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write the provider**
|
|
||||||
|
|
||||||
Create `operator/lib/providers/sdk_clients/wallet_access_list.dart`:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'package:arbiter/features/connection/evm/wallet_access.dart';
|
|
||||||
import 'package:arbiter/proto/operator.pb.dart';
|
|
||||||
import 'package:arbiter/providers/connection/connection_manager.dart';
|
|
||||||
import 'package:mtcore/markettakers.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
|
|
||||||
part 'wallet_access_list.g.dart';
|
|
||||||
|
|
||||||
@riverpod
|
|
||||||
class WalletAccessList extends _$WalletAccessList {
|
|
||||||
@override
|
|
||||||
Future<List<SdkClientWalletAccess>?> build() async {
|
|
||||||
final connection = await ref.watch(connectionManagerProvider.future);
|
|
||||||
if (connection == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await listAllWalletAccesses(connection);
|
|
||||||
} catch (e, st) {
|
|
||||||
talker.handle(e, st);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> refresh() async {
|
|
||||||
final connection = await ref.read(connectionManagerProvider.future);
|
|
||||||
if (connection == null) {
|
|
||||||
state = const AsyncData(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = const AsyncLoading();
|
|
||||||
state = await AsyncValue.guard(() => listAllWalletAccesses(connection));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run code generation**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd operator && dart run build_runner build --delete-conflicting-outputs
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: `operator/lib/providers/sdk_clients/wallet_access_list.g.dart` created. No errors.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Verify**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd operator && flutter analyze lib/providers/sdk_clients/
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: no issues.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Commit**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
jj describe -m "feat(providers): add WalletAccessListProvider"
|
|
||||||
jj new
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 4: Create `GrantCard` widget
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `operator/lib/screens/dashboard/evm/grants/widgets/grant_card.dart`
|
|
||||||
|
|
||||||
This widget owns all per-card logic: enrichment lookups, revoke action, and rebuild scope. The screen only passes it a `GrantEntry` — the card fetches everything else itself.
|
|
||||||
|
|
||||||
**Key types:**
|
|
||||||
- `GrantEntry` (from `proto/evm.pb.dart`): `.id`, `.shared.walletAccessId`, `.shared.chainId`, `.specific.whichGrant()`
|
|
||||||
- `SpecificGrant_Grant.etherTransfer` / `.tokenTransfer` — enum values for the oneof
|
|
||||||
- `SdkClientWalletAccess` (from `proto/operator.pb.dart`): `.id`, `.access.walletId`, `.access.sdkClientId`
|
|
||||||
- `WalletEntry` (from `proto/evm.pb.dart`): `.id`, `.address` (List<int>)
|
|
||||||
- `SdkClientEntry` (from `proto/operator.pb.dart`): `.id`, `.info.name`
|
|
||||||
- `revokeEvmGrantMutation` — `Mutation<void>` (global; all revoke buttons disable together while any revoke is in flight)
|
|
||||||
- `executeRevokeEvmGrant(ref, grantId: int)` — `Future<void>`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write the widget**
|
|
||||||
|
|
||||||
Create `operator/lib/screens/dashboard/evm/grants/widgets/grant_card.dart`:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'package:arbiter/proto/evm.pb.dart';
|
|
||||||
import 'package:arbiter/proto/operator.pb.dart';
|
|
||||||
import 'package:arbiter/providers/evm/evm.dart';
|
|
||||||
import 'package:arbiter/providers/evm/evm_grants.dart';
|
|
||||||
import 'package:arbiter/providers/sdk_clients/list.dart';
|
|
||||||
import 'package:arbiter/providers/sdk_clients/wallet_access_list.dart';
|
|
||||||
import 'package:arbiter/theme/palette.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:hooks_riverpod/experimental/mutation.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:sizer/sizer.dart';
|
|
||||||
|
|
||||||
String _shortAddress(List<int> bytes) {
|
|
||||||
final hex = bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
|
||||||
return '0x${hex.substring(0, 6)}...${hex.substring(hex.length - 4)}';
|
|
||||||
}
|
|
||||||
|
|
||||||
String _formatError(Object error) {
|
|
||||||
final message = error.toString();
|
|
||||||
if (message.startsWith('Exception: ')) {
|
|
||||||
return message.substring('Exception: '.length);
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GrantCard extends ConsumerWidget {
|
|
||||||
const GrantCard({super.key, required this.grant});
|
|
||||||
|
|
||||||
final GrantEntry grant;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
// Enrichment lookups — each watch scopes rebuilds to this card only
|
|
||||||
final walletAccesses =
|
|
||||||
ref.watch(walletAccessListProvider).asData?.value ?? const [];
|
|
||||||
final wallets = ref.watch(evmProvider).asData?.value ?? const [];
|
|
||||||
final clients = ref.watch(sdkClientsProvider).asData?.value ?? const [];
|
|
||||||
final revoking = ref.watch(revokeEvmGrantMutation) is MutationPending;
|
|
||||||
|
|
||||||
final isEther =
|
|
||||||
grant.specific.whichGrant() == SpecificGrant_Grant.etherTransfer;
|
|
||||||
final accent = isEther ? Palette.coral : Palette.token;
|
|
||||||
final typeLabel = isEther ? 'Ether' : 'Token';
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
final muted = Palette.ink.withValues(alpha: 0.62);
|
|
||||||
|
|
||||||
// Resolve wallet_access_id → wallet address + client name
|
|
||||||
final accessById = <int, SdkClientWalletAccess>{
|
|
||||||
for (final a in walletAccesses) a.id: a,
|
|
||||||
};
|
|
||||||
final walletById = <int, WalletEntry>{
|
|
||||||
for (final w in wallets) w.id: w,
|
|
||||||
};
|
|
||||||
final clientNameById = <int, String>{
|
|
||||||
for (final c in clients) c.id: c.info.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
final accessId = grant.shared.walletAccessId;
|
|
||||||
final access = accessById[accessId];
|
|
||||||
final wallet = access != null ? walletById[access.access.walletId] : null;
|
|
||||||
|
|
||||||
final walletLabel = wallet != null
|
|
||||||
? _shortAddress(wallet.address)
|
|
||||||
: 'Access #$accessId';
|
|
||||||
|
|
||||||
final clientLabel = () {
|
|
||||||
if (access == null) return '';
|
|
||||||
final name = clientNameById[access.access.sdkClientId] ?? '';
|
|
||||||
return name.isEmpty ? 'Client #${access.access.sdkClientId}' : name;
|
|
||||||
}();
|
|
||||||
|
|
||||||
void showError(String message) {
|
|
||||||
if (!context.mounted) return;
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text(message), behavior: SnackBarBehavior.floating),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> revoke() async {
|
|
||||||
try {
|
|
||||||
await executeRevokeEvmGrant(ref, grantId: grant.id);
|
|
||||||
} catch (e) {
|
|
||||||
showError(_formatError(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
color: Palette.cream.withValues(alpha: 0.92),
|
|
||||||
border: Border.all(color: Palette.line),
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
// Accent strip
|
|
||||||
Container(
|
|
||||||
width: 0.8.w,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: accent,
|
|
||||||
borderRadius: const BorderRadius.horizontal(
|
|
||||||
left: Radius.circular(24),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Card body
|
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 1.6.w,
|
|
||||||
vertical: 1.4.h,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Row 1: type badge · chain · spacer · revoke button
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 1.w,
|
|
||||||
vertical: 0.4.h,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: accent.withValues(alpha: 0.15),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
typeLabel,
|
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
|
||||||
color: accent,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 1.w),
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 1.w,
|
|
||||||
vertical: 0.4.h,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Palette.ink.withValues(alpha: 0.06),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'Chain ${grant.shared.chainId}',
|
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
|
||||||
color: muted,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
if (revoking)
|
|
||||||
SizedBox(
|
|
||||||
width: 1.8.h,
|
|
||||||
height: 1.8.h,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
strokeWidth: 2,
|
|
||||||
color: Palette.coral,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
OutlinedButton.icon(
|
|
||||||
onPressed: revoke,
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
foregroundColor: Palette.coral,
|
|
||||||
side: BorderSide(
|
|
||||||
color: Palette.coral.withValues(alpha: 0.4),
|
|
||||||
),
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 1.w,
|
|
||||||
vertical: 0.6.h,
|
|
||||||
),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
icon: const Icon(Icons.block_rounded, size: 16),
|
|
||||||
label: const Text('Revoke'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 0.8.h),
|
|
||||||
// Row 2: wallet address · client name
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
walletLabel,
|
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
|
||||||
color: Palette.ink,
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 0.8.w),
|
|
||||||
child: Text(
|
|
||||||
'·',
|
|
||||||
style: theme.textTheme.bodySmall
|
|
||||||
?.copyWith(color: muted),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
clientLabel,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: theme.textTheme.bodySmall
|
|
||||||
?.copyWith(color: muted),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd operator && flutter analyze lib/screens/dashboard/evm/grants/widgets/grant_card.dart
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: no issues.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
jj describe -m "feat(grants): add GrantCard widget with self-contained enrichment"
|
|
||||||
jj new
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 5: Create `EvmGrantsScreen`
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `operator/lib/screens/dashboard/evm/grants/grants.dart`
|
|
||||||
|
|
||||||
The screen watches only `evmGrantsProvider` for top-level state (loading / error / no connection / empty / data). When there is data it renders a list of `GrantCard` widgets — each card manages its own enrichment subscriptions.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write the screen**
|
|
||||||
|
|
||||||
Create `operator/lib/screens/dashboard/evm/grants/grants.dart`:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'package:arbiter/proto/evm.pb.dart';
|
|
||||||
import 'package:arbiter/providers/evm/evm_grants.dart';
|
|
||||||
import 'package:arbiter/providers/sdk_clients/wallet_access_list.dart';
|
|
||||||
import 'package:arbiter/router.gr.dart';
|
|
||||||
import 'package:arbiter/screens/dashboard/evm/grants/widgets/grant_card.dart';
|
|
||||||
import 'package:arbiter/theme/palette.dart';
|
|
||||||
import 'package:arbiter/widgets/page_header.dart';
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:sizer/sizer.dart';
|
|
||||||
|
|
||||||
String _formatError(Object error) {
|
|
||||||
final message = error.toString();
|
|
||||||
if (message.startsWith('Exception: ')) {
|
|
||||||
return message.substring('Exception: '.length);
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── State panel ──────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
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<void> 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: const 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!),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Grant list ───────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
class _GrantList extends StatelessWidget {
|
|
||||||
const _GrantList({required this.grants});
|
|
||||||
|
|
||||||
final List<GrantEntry> grants;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
for (var i = 0; i < grants.length; i++)
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
bottom: i == grants.length - 1 ? 0 : 1.8.h,
|
|
||||||
),
|
|
||||||
child: GrantCard(grant: grants[i]),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Screen ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class EvmGrantsScreen extends ConsumerWidget {
|
|
||||||
const EvmGrantsScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
// Screen watches only the grant list for top-level state decisions
|
|
||||||
final grantsAsync = ref.watch(evmGrantsProvider);
|
|
||||||
|
|
||||||
Future<void> refresh() async {
|
|
||||||
await Future.wait([
|
|
||||||
ref.read(evmGrantsProvider.notifier).refresh(),
|
|
||||||
ref.read(walletAccessListProvider.notifier).refresh(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void showMessage(String message) {
|
|
||||||
if (!context.mounted) return;
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text(message), behavior: SnackBarBehavior.floating),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> safeRefresh() async {
|
|
||||||
try {
|
|
||||||
await refresh();
|
|
||||||
} catch (e) {
|
|
||||||
showMessage(_formatError(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final grantsState = grantsAsync.asData?.value;
|
|
||||||
final grants = grantsState?.grants;
|
|
||||||
|
|
||||||
final content = switch (grantsAsync) {
|
|
||||||
AsyncLoading() when grantsState == null => const _StatePanel(
|
|
||||||
icon: Icons.hourglass_top,
|
|
||||||
title: 'Loading grants',
|
|
||||||
body: 'Pulling grant registry from Arbiter.',
|
|
||||||
busy: true,
|
|
||||||
),
|
|
||||||
AsyncError(:final error) => _StatePanel(
|
|
||||||
icon: Icons.sync_problem,
|
|
||||||
title: 'Grant registry unavailable',
|
|
||||||
body: _formatError(error),
|
|
||||||
actionLabel: 'Retry',
|
|
||||||
onAction: safeRefresh,
|
|
||||||
),
|
|
||||||
AsyncData(:final value) when value == null => _StatePanel(
|
|
||||||
icon: Icons.portable_wifi_off,
|
|
||||||
title: 'No active server connection',
|
|
||||||
body: 'Reconnect to Arbiter to list EVM grants.',
|
|
||||||
actionLabel: 'Refresh',
|
|
||||||
onAction: safeRefresh,
|
|
||||||
),
|
|
||||||
_ when grants != null && grants.isEmpty => _StatePanel(
|
|
||||||
icon: Icons.policy_outlined,
|
|
||||||
title: 'No grants yet',
|
|
||||||
body: 'Create a grant to allow SDK clients to sign transactions.',
|
|
||||||
actionLabel: 'Create grant',
|
|
||||||
onAction: () => context.router.push(const CreateEvmGrantRoute()),
|
|
||||||
),
|
|
||||||
_ => _GrantList(grants: grants ?? const []),
|
|
||||||
};
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
body: SafeArea(
|
|
||||||
child: RefreshIndicator.adaptive(
|
|
||||||
color: Palette.ink,
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
onRefresh: safeRefresh,
|
|
||||||
child: ListView(
|
|
||||||
physics: const BouncingScrollPhysics(
|
|
||||||
parent: AlwaysScrollableScrollPhysics(),
|
|
||||||
),
|
|
||||||
padding: EdgeInsets.fromLTRB(2.4.w, 2.4.h, 2.4.w, 3.2.h),
|
|
||||||
children: [
|
|
||||||
PageHeader(
|
|
||||||
title: 'EVM Grants',
|
|
||||||
isBusy: grantsAsync.isLoading,
|
|
||||||
actions: [
|
|
||||||
FilledButton.icon(
|
|
||||||
onPressed: () =>
|
|
||||||
context.router.push(const CreateEvmGrantRoute()),
|
|
||||||
icon: const Icon(Icons.add_rounded),
|
|
||||||
label: const Text('Create grant'),
|
|
||||||
),
|
|
||||||
SizedBox(width: 1.w),
|
|
||||||
OutlinedButton.icon(
|
|
||||||
onPressed: safeRefresh,
|
|
||||||
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'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: 1.8.h),
|
|
||||||
content,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd operator && flutter analyze lib/screens/dashboard/evm/grants/
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: no issues.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
jj describe -m "feat(grants): add EvmGrantsScreen"
|
|
||||||
jj new
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 6: Wire router and dashboard tab
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `operator/lib/router.dart`
|
|
||||||
- Modify: `operator/lib/screens/dashboard.dart`
|
|
||||||
- Regenerated: `operator/lib/router.gr.dart`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Add route to `router.dart`**
|
|
||||||
|
|
||||||
Replace the contents of `operator/lib/router.dart` with:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
|
|
||||||
import 'router.gr.dart';
|
|
||||||
|
|
||||||
@AutoRouterConfig(generateForDir: ['lib/screens'])
|
|
||||||
class Router extends RootStackRouter {
|
|
||||||
@override
|
|
||||||
List<AutoRoute> 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: VaultSetupRoute.page, path: '/vault'),
|
|
||||||
AutoRoute(page: ClientDetailsRoute.page, path: '/clients/:clientId'),
|
|
||||||
AutoRoute(page: CreateEvmGrantRoute.page, path: '/evm-grants/create'),
|
|
||||||
|
|
||||||
AutoRoute(
|
|
||||||
page: DashboardRouter.page,
|
|
||||||
path: '/dashboard',
|
|
||||||
children: [
|
|
||||||
AutoRoute(page: EvmRoute.page, path: 'evm'),
|
|
||||||
AutoRoute(page: ClientsRoute.page, path: 'clients'),
|
|
||||||
AutoRoute(page: EvmGrantsRoute.page, path: 'grants'),
|
|
||||||
AutoRoute(page: AboutRoute.page, path: 'about'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Update `dashboard.dart`**
|
|
||||||
|
|
||||||
In `operator/lib/screens/dashboard.dart`, replace the `routes` constant:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
final routes = [
|
|
||||||
const EvmRoute(),
|
|
||||||
const ClientsRoute(),
|
|
||||||
const EvmGrantsRoute(),
|
|
||||||
const AboutRoute(),
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
And replace the `destinations` list inside `AdaptiveScaffold`:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
destinations: const [
|
|
||||||
NavigationDestination(
|
|
||||||
icon: Icon(Icons.account_balance_wallet_outlined),
|
|
||||||
selectedIcon: Icon(Icons.account_balance_wallet),
|
|
||||||
label: 'Wallets',
|
|
||||||
),
|
|
||||||
NavigationDestination(
|
|
||||||
icon: Icon(Icons.devices_other_outlined),
|
|
||||||
selectedIcon: Icon(Icons.devices_other),
|
|
||||||
label: 'Clients',
|
|
||||||
),
|
|
||||||
NavigationDestination(
|
|
||||||
icon: Icon(Icons.policy_outlined),
|
|
||||||
selectedIcon: Icon(Icons.policy),
|
|
||||||
label: 'Grants',
|
|
||||||
),
|
|
||||||
NavigationDestination(
|
|
||||||
icon: Icon(Icons.info_outline),
|
|
||||||
selectedIcon: Icon(Icons.info),
|
|
||||||
label: 'About',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Regenerate router**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd operator && dart run build_runner build --delete-conflicting-outputs
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: `lib/router.gr.dart` updated, `EvmGrantsRoute` now available, no errors.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Full project verify**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd operator && flutter analyze
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: no issues.
|
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
jj describe -m "feat(nav): add Grants dashboard tab"
|
|
||||||
jj new
|
|
||||||
```
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
# Grant Grid View — Design Spec
|
|
||||||
|
|
||||||
**Date:** 2026-03-28
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Add a "Grants" dashboard tab to the Flutter operator app that displays all EVM grants as a card-based grid. Each card shows a compact summary (type, chain, wallet address, client name) with a revoke action. The tab integrates into the existing `AdaptiveScaffold` navigation alongside Wallets, Clients, and About.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
- New `walletAccessListProvider` for fetching wallet access entries with their DB row IDs
|
|
||||||
- New `EvmGrantsScreen` as a dashboard tab
|
|
||||||
- Grant card widget with enriched display (type, chain, wallet, client)
|
|
||||||
- Revoke action wired to existing `executeRevokeEvmGrant` mutation
|
|
||||||
- Dashboard tab bar and router updated
|
|
||||||
- New token-transfer accent color added to `Palette`
|
|
||||||
|
|
||||||
**Out of scope:** Fixing grant creation (separate task).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Data Layer
|
|
||||||
|
|
||||||
### `walletAccessListProvider`
|
|
||||||
|
|
||||||
**File:** `operator/lib/providers/sdk_clients/wallet_access_list.dart`
|
|
||||||
|
|
||||||
- `@riverpod` class, watches `connectionManagerProvider.future`
|
|
||||||
- Returns `List<SdkClientWalletAccess>?` (null when not connected)
|
|
||||||
- Each entry: `.id` (wallet_access_id), `.access.walletId`, `.access.sdkClientId`
|
|
||||||
- Exposes a `refresh()` method following the same pattern as `EvmGrants.refresh()`
|
|
||||||
|
|
||||||
### Enrichment at render time (Approach A)
|
|
||||||
|
|
||||||
The `EvmGrantsScreen` watches four providers:
|
|
||||||
1. `evmGrantsProvider` — the grant list
|
|
||||||
2. `walletAccessListProvider` — to resolve wallet_access_id → (wallet_id, sdk_client_id)
|
|
||||||
3. `evmProvider` — to resolve wallet_id → wallet address
|
|
||||||
4. `sdkClientsProvider` — to resolve sdk_client_id → client name
|
|
||||||
|
|
||||||
All lookups are in-memory Maps built inside the build method; no extra model class needed.
|
|
||||||
|
|
||||||
Fallbacks:
|
|
||||||
- Wallet address not found → `"Access #N"` where N is the wallet_access_id
|
|
||||||
- Client name not found → `"Client #N"` where N is the sdk_client_id
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Route Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
/dashboard
|
|
||||||
/evm ← existing (Wallets tab)
|
|
||||||
/clients ← existing (Clients tab)
|
|
||||||
/grants ← NEW (Grants tab)
|
|
||||||
/about ← existing
|
|
||||||
|
|
||||||
/evm-grants/create ← existing push route (unchanged)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Changes to `router.dart`
|
|
||||||
|
|
||||||
Add inside dashboard children:
|
|
||||||
```dart
|
|
||||||
AutoRoute(page: EvmGrantsRoute.page, path: 'grants'),
|
|
||||||
```
|
|
||||||
|
|
||||||
### Changes to `dashboard.dart`
|
|
||||||
|
|
||||||
Add to `routes` list:
|
|
||||||
```dart
|
|
||||||
const EvmGrantsRoute()
|
|
||||||
```
|
|
||||||
|
|
||||||
Add `NavigationDestination`:
|
|
||||||
```dart
|
|
||||||
NavigationDestination(
|
|
||||||
icon: Icon(Icons.policy_outlined),
|
|
||||||
selectedIcon: Icon(Icons.policy),
|
|
||||||
label: 'Grants',
|
|
||||||
),
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Screen: `EvmGrantsScreen`
|
|
||||||
|
|
||||||
**File:** `operator/lib/screens/dashboard/evm/grants/grants.dart`
|
|
||||||
|
|
||||||
```
|
|
||||||
Scaffold
|
|
||||||
└─ SafeArea
|
|
||||||
└─ RefreshIndicator.adaptive (refreshes evmGrantsProvider + walletAccessListProvider)
|
|
||||||
└─ ListView (BouncingScrollPhysics + AlwaysScrollableScrollPhysics)
|
|
||||||
├─ PageHeader
|
|
||||||
│ title: 'EVM Grants'
|
|
||||||
│ isBusy: evmGrantsProvider.isLoading
|
|
||||||
│ actions: [CreateGrantButton, RefreshButton]
|
|
||||||
├─ SizedBox(height: 1.8.h)
|
|
||||||
└─ <content>
|
|
||||||
```
|
|
||||||
|
|
||||||
### State handling
|
|
||||||
|
|
||||||
Matches the pattern from `EvmScreen` and `ClientsScreen`:
|
|
||||||
|
|
||||||
| State | Display |
|
|
||||||
|---|---|
|
|
||||||
| Loading (no data yet) | `_StatePanel` with spinner, "Loading grants" |
|
|
||||||
| Error | `_StatePanel` with coral icon, error message, Retry button |
|
|
||||||
| No connection | `_StatePanel`, "No active server connection" |
|
|
||||||
| Empty list | `_StatePanel`, "No grants yet", with Create Grant shortcut |
|
|
||||||
| Data | Column of `_GrantCard` widgets |
|
|
||||||
|
|
||||||
### Header actions
|
|
||||||
|
|
||||||
**CreateGrantButton:** `FilledButton.icon` with `Icons.add_rounded`, pushes `CreateEvmGrantRoute()` via `context.router.push(...)`.
|
|
||||||
|
|
||||||
**RefreshButton:** `OutlinedButton.icon` with `Icons.refresh`, calls `ref.read(evmGrantsProvider.notifier).refresh()`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Grant Card: `_GrantCard`
|
|
||||||
|
|
||||||
**Layout:**
|
|
||||||
|
|
||||||
```
|
|
||||||
Container (rounded 24, Palette.cream bg, Palette.line border)
|
|
||||||
└─ IntrinsicHeight > Row
|
|
||||||
├─ Accent strip (0.8.w wide, full height, rounded left)
|
|
||||||
└─ Padding > Column
|
|
||||||
├─ Row 1: TypeBadge + ChainChip + Spacer + RevokeButton
|
|
||||||
└─ Row 2: WalletText + "·" + ClientText
|
|
||||||
```
|
|
||||||
|
|
||||||
**Accent color by grant type:**
|
|
||||||
- Ether transfer → `Palette.coral`
|
|
||||||
- Token transfer → `Palette.token` (new entry in `Palette` — indigo, e.g. `Color(0xFF5C6BC0)`)
|
|
||||||
|
|
||||||
**TypeBadge:** Small pill container with accent color background at 15% opacity, accent-colored text. Label: `'Ether'` or `'Token'`.
|
|
||||||
|
|
||||||
**ChainChip:** Small container: `'Chain ${grant.shared.chainId}'`, muted ink color.
|
|
||||||
|
|
||||||
**WalletText:** Short hex address (`0xabc...def`) from wallet lookup, `bodySmall`, monospace font family.
|
|
||||||
|
|
||||||
**ClientText:** Client name from `sdkClientsProvider` lookup, or fallback string. `bodySmall`, muted ink.
|
|
||||||
|
|
||||||
**RevokeButton:**
|
|
||||||
- `OutlinedButton` with `Icons.block_rounded` icon, label `'Revoke'`
|
|
||||||
- `foregroundColor: Palette.coral`, `side: BorderSide(color: Palette.coral.withValues(alpha: 0.4))`
|
|
||||||
- Disabled (replaced with `CircularProgressIndicator`) while `revokeEvmGrantMutation` is pending — note: this is a single global mutation, so all revoke buttons disable while any revoke is in flight
|
|
||||||
- On press: calls `executeRevokeEvmGrant(ref, grantId: grant.id)`; shows `SnackBar` on error
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Adaptive Sizing
|
|
||||||
|
|
||||||
All sizing uses `sizer` units (`1.h`, `1.w`, etc.). No hardcoded pixel values.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files to Create / Modify
|
|
||||||
|
|
||||||
| File | Action |
|
|
||||||
|---|---|
|
|
||||||
| `lib/theme/palette.dart` | Modify — add `Palette.token` color |
|
|
||||||
| `lib/providers/sdk_clients/wallet_access_list.dart` | Create |
|
|
||||||
| `lib/screens/dashboard/evm/grants/grants.dart` | Create |
|
|
||||||
| `lib/router.dart` | Modify — add grants route to dashboard children |
|
|
||||||
| `lib/screens/dashboard.dart` | Modify — add tab to routes list and NavigationDestinations |
|
|
||||||
144
mise.lock
@@ -1,156 +1,56 @@
|
|||||||
# @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html
|
|
||||||
|
|
||||||
[[tools.ast-grep]]
|
|
||||||
version = "0.42.1"
|
|
||||||
backend = "aqua:ast-grep/ast-grep"
|
|
||||||
|
|
||||||
[tools.ast-grep."platforms.linux-arm64"]
|
|
||||||
checksum = "sha256:3ba383839044cf9817929435f5ce0027f91d06931e8efb32d942e58d73d92be5"
|
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-aarch64-unknown-linux-gnu.zip"
|
|
||||||
|
|
||||||
[tools.ast-grep."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:3ba383839044cf9817929435f5ce0027f91d06931e8efb32d942e58d73d92be5"
|
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-aarch64-unknown-linux-gnu.zip"
|
|
||||||
|
|
||||||
[tools.ast-grep."platforms.linux-x64"]
|
|
||||||
checksum = "sha256:5de8b87cba67fc8dc3e239d54b6484802ad745a7ae3de76be4fe89661dc52657"
|
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-x86_64-unknown-linux-gnu.zip"
|
|
||||||
|
|
||||||
[tools.ast-grep."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:5de8b87cba67fc8dc3e239d54b6484802ad745a7ae3de76be4fe89661dc52657"
|
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-x86_64-unknown-linux-gnu.zip"
|
|
||||||
|
|
||||||
[tools.ast-grep."platforms.macos-arm64"]
|
|
||||||
checksum = "sha256:c3961d8e8a4ee0ce2d0d98c7beeb168bb331cdc766b53630118a7b6c4fd39015"
|
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-aarch64-apple-darwin.zip"
|
|
||||||
|
|
||||||
[tools.ast-grep."platforms.macos-x64"]
|
|
||||||
checksum = "sha256:a038965bfd7fe44257c771cdf8918dc3467dd8ec0eef673b8b14f639b144cdbd"
|
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-x86_64-apple-darwin.zip"
|
|
||||||
|
|
||||||
[tools.ast-grep."platforms.windows-x64"]
|
|
||||||
checksum = "sha256:fe34f631bb24c08ad146f92ca2a92971a53d179461b509fd8d32dc863bff9f83"
|
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-x86_64-pc-windows-msvc.zip"
|
|
||||||
|
|
||||||
[[tools."cargo:cargo-audit"]]
|
[[tools."cargo:cargo-audit"]]
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
backend = "cargo:cargo-audit"
|
backend = "cargo:cargo-audit"
|
||||||
|
|
||||||
[[tools."cargo:cargo-edit"]]
|
[[tools."cargo:cargo-features"]]
|
||||||
version = "0.13.10"
|
version = "1.0.0"
|
||||||
backend = "cargo:cargo-edit"
|
backend = "cargo:cargo-features"
|
||||||
|
|
||||||
[[tools."cargo:cargo-features-manager"]]
|
[[tools."cargo:cargo-features-manager"]]
|
||||||
version = "0.12.0"
|
version = "0.11.1"
|
||||||
backend = "cargo:cargo-features-manager"
|
backend = "cargo:cargo-features-manager"
|
||||||
|
|
||||||
[[tools."cargo:cargo-insta"]]
|
|
||||||
version = "1.47.2"
|
|
||||||
backend = "cargo:cargo-insta"
|
|
||||||
|
|
||||||
[[tools."cargo:cargo-mutants"]]
|
|
||||||
version = "27.0.0"
|
|
||||||
backend = "cargo:cargo-mutants"
|
|
||||||
|
|
||||||
[[tools."cargo:cargo-nextest"]]
|
[[tools."cargo:cargo-nextest"]]
|
||||||
version = "0.9.133"
|
version = "0.9.126"
|
||||||
backend = "cargo:cargo-nextest"
|
backend = "cargo:cargo-nextest"
|
||||||
|
|
||||||
[[tools."cargo:cargo-shear"]]
|
[[tools."cargo:cargo-shear"]]
|
||||||
version = "1.11.2"
|
version = "1.9.1"
|
||||||
backend = "cargo:cargo-shear"
|
backend = "cargo:cargo-shear"
|
||||||
|
|
||||||
[[tools."cargo:cargo-vet"]]
|
[[tools."cargo:cargo-vet"]]
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
backend = "cargo:cargo-vet"
|
backend = "cargo:cargo-vet"
|
||||||
|
|
||||||
|
[[tools."cargo:diesel-cli"]]
|
||||||
|
version = "2.3.6"
|
||||||
|
backend = "cargo:diesel-cli"
|
||||||
|
|
||||||
|
[tools."cargo:diesel-cli".options]
|
||||||
|
default-features = "false"
|
||||||
|
features = "sqlite,sqlite-bundled"
|
||||||
|
|
||||||
[[tools."cargo:diesel_cli"]]
|
[[tools."cargo:diesel_cli"]]
|
||||||
version = "2.3.7"
|
version = "2.3.6"
|
||||||
backend = "cargo:diesel_cli"
|
backend = "cargo:diesel_cli"
|
||||||
|
|
||||||
[tools."cargo:diesel_cli".options]
|
[tools."cargo:diesel_cli".options]
|
||||||
default-features = "false"
|
default-features = "false"
|
||||||
features = "sqlite,sqlite-bundled"
|
features = "sqlite,sqlite-bundled"
|
||||||
|
|
||||||
[[tools."cargo:flutter_rust_bridge_codegen"]]
|
|
||||||
version = "2.12.0"
|
|
||||||
backend = "cargo:flutter_rust_bridge_codegen"
|
|
||||||
|
|
||||||
[[tools.flutter]]
|
[[tools.flutter]]
|
||||||
version = "3.41.7-stable"
|
version = "3.38.9-stable"
|
||||||
backend = "asdf:flutter"
|
backend = "asdf:flutter"
|
||||||
|
|
||||||
[[tools.protoc]]
|
[[tools.protoc]]
|
||||||
version = "29.6"
|
version = "29.6"
|
||||||
backend = "aqua:protocolbuffers/protobuf/protoc"
|
backend = "aqua:protocolbuffers/protobuf/protoc"
|
||||||
|
"platforms.linux-arm64" = { checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79", url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip"}
|
||||||
[tools.protoc."platforms.linux-arm64"]
|
"platforms.linux-x64" = { checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323", url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip"}
|
||||||
checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79"
|
"platforms.macos-arm64" = { checksum = "sha256:b9576b5fa1a1ef3fe13a8c91d9d8204b46545759bea5ae155cd6ba2ea4cdaeed", url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-osx-aarch_64.zip"}
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip"
|
"platforms.macos-x64" = { checksum = "sha256:312f04713946921cc0187ef34df80241ddca1bab6f564c636885fd2cc90d3f88", url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-osx-x86_64.zip"}
|
||||||
|
"platforms.windows-x64" = { checksum = "sha256:1ebd7c87baffb9f1c47169b640872bf5fb1e4408079c691af527be9561d8f6f7", url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-win64.zip"}
|
||||||
[tools.protoc."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79"
|
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip"
|
|
||||||
|
|
||||||
[tools.protoc."platforms.linux-x64"]
|
|
||||||
checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323"
|
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip"
|
|
||||||
|
|
||||||
[tools.protoc."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323"
|
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip"
|
|
||||||
|
|
||||||
[tools.protoc."platforms.macos-arm64"]
|
|
||||||
checksum = "sha256:b9576b5fa1a1ef3fe13a8c91d9d8204b46545759bea5ae155cd6ba2ea4cdaeed"
|
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-osx-aarch_64.zip"
|
|
||||||
|
|
||||||
[tools.protoc."platforms.macos-x64"]
|
|
||||||
checksum = "sha256:312f04713946921cc0187ef34df80241ddca1bab6f564c636885fd2cc90d3f88"
|
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-osx-x86_64.zip"
|
|
||||||
|
|
||||||
[tools.protoc."platforms.windows-x64"]
|
|
||||||
checksum = "sha256:1ebd7c87baffb9f1c47169b640872bf5fb1e4408079c691af527be9561d8f6f7"
|
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-win64.zip"
|
|
||||||
|
|
||||||
[[tools.python]]
|
|
||||||
version = "3.14.4"
|
|
||||||
backend = "core:python"
|
|
||||||
|
|
||||||
[tools.python."platforms.linux-arm64"]
|
|
||||||
checksum = "sha256:b8b597fdb2f8dccdc502c11947b60a4b65eb6bce79cfa60c7ccf9b6e8352c60a"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:b8b597fdb2f8dccdc502c11947b60a4b65eb6bce79cfa60c7ccf9b6e8352c60a"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.linux-x64"]
|
|
||||||
checksum = "sha256:fe9a9c32d13870af632cbac3dfc7528ae53597e94472aa4c7d6a42e8166136cd"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:fe9a9c32d13870af632cbac3dfc7528ae53597e94472aa4c7d6a42e8166136cd"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.macos-arm64"]
|
|
||||||
checksum = "blake3:0314ec66e0f33ec04959583b5900bc8edae371a396aa96b8874e750d1fe936e6"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-aarch64-apple-darwin-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.macos-x64"]
|
|
||||||
checksum = "sha256:d51250a32fa5d9f0799c7bcb71720c27b10a3afd4a7de288120f96085d508a5a"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-x86_64-apple-darwin-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.windows-x64"]
|
|
||||||
checksum = "sha256:a976991dcd085c1bb5d9a8084823a6bc8b7f9b079d8c432574a6ddd68c3a6fe1"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[[tools.rust]]
|
[[tools.rust]]
|
||||||
version = "1.95.0"
|
version = "1.93.0"
|
||||||
backend = "core:rust"
|
backend = "core:rust"
|
||||||
|
|||||||
35
mise.toml
@@ -1,24 +1,11 @@
|
|||||||
[tools]
|
[tools]
|
||||||
"cargo:diesel_cli" = { version = "2.3.7", features = "sqlite,sqlite-bundled", default-features = "false" }
|
"cargo:diesel_cli" = { version = "2.3.6", features = "sqlite,sqlite-bundled", default-features = false }
|
||||||
"cargo:cargo-audit" = "0.22.1"
|
"cargo:cargo-audit" = "0.22.1"
|
||||||
"cargo:cargo-vet" = "0.10.2"
|
"cargo:cargo-vet" = "0.10.2"
|
||||||
flutter = "3.41.7-stable"
|
|
||||||
protoc = "29.6"
|
flutter = "3.38.9-stable"
|
||||||
rust = { version = "1.95.0", components = "clippy,rust-analyzer" }
|
protoc = "29.6"
|
||||||
"cargo:cargo-features-manager" = "0.12.0"
|
rust = "1.93.1"
|
||||||
"cargo:cargo-nextest" = "0.9.133"
|
"cargo:cargo-features-manager" = "0.11.1"
|
||||||
"cargo:cargo-shear" = "latest"
|
"cargo:cargo-nextest" = "0.9.126"
|
||||||
"cargo:cargo-insta" = "1.47.2"
|
"cargo:cargo-shear" = "latest"
|
||||||
python = "3.14.4"
|
|
||||||
ast-grep = "0.42.1"
|
|
||||||
"cargo:cargo-edit" = "0.13.10"
|
|
||||||
"cargo:cargo-mutants" = "27.0.0"
|
|
||||||
"cargo:flutter_rust_bridge_codegen" = "2.12.0"
|
|
||||||
|
|
||||||
[tasks.codegen]
|
|
||||||
sources = ['protobufs/*.proto', 'protobufs/**/*.proto']
|
|
||||||
outputs = ['useragent/lib/proto/**']
|
|
||||||
run = '''
|
|
||||||
dart pub global activate protoc_plugin && \
|
|
||||||
protoc --dart_out=grpc:useragent/lib/proto --proto_path=protobufs/ $(find protobufs -name '*.proto' | sort)
|
|
||||||
'''
|
|
||||||
|
|||||||
@@ -1,16 +1,68 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package arbiter;
|
package arbiter;
|
||||||
|
|
||||||
import "client.proto";
|
import "auth.proto";
|
||||||
import "operator.proto";
|
|
||||||
|
message ClientRequest {
|
||||||
message ServerInfo {
|
oneof payload {
|
||||||
string version = 1;
|
arbiter.auth.ClientMessage auth_message = 1;
|
||||||
bytes cert_public_key = 2;
|
CertRotationAck cert_rotation_ack = 2;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
service ArbiterService {
|
|
||||||
rpc Client(stream arbiter.client.ClientRequest) returns (stream arbiter.client.ClientResponse);
|
message ClientResponse {
|
||||||
rpc Operator(stream arbiter.operator.OperatorRequest) returns (stream arbiter.operator.OperatorResponse);
|
oneof payload {
|
||||||
}
|
arbiter.auth.ServerMessage auth_message = 1;
|
||||||
|
CertRotationNotification cert_rotation_notification = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message UserAgentRequest {
|
||||||
|
oneof payload {
|
||||||
|
arbiter.auth.ClientMessage auth_message = 1;
|
||||||
|
CertRotationAck cert_rotation_ack = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message UserAgentResponse {
|
||||||
|
oneof payload {
|
||||||
|
arbiter.auth.ServerMessage auth_message = 1;
|
||||||
|
CertRotationNotification cert_rotation_notification = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ServerInfo {
|
||||||
|
string version = 1;
|
||||||
|
bytes cert_public_key = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLS Certificate Rotation Protocol
|
||||||
|
message CertRotationNotification {
|
||||||
|
// New public certificate (DER-encoded)
|
||||||
|
bytes new_cert = 1;
|
||||||
|
|
||||||
|
// Unix timestamp when rotation will be executed (if all ACKs received)
|
||||||
|
int64 rotation_scheduled_at = 2;
|
||||||
|
|
||||||
|
// Unix timestamp deadline for ACK (7 days from now)
|
||||||
|
int64 ack_deadline = 3;
|
||||||
|
|
||||||
|
// Rotation ID for tracking
|
||||||
|
int32 rotation_id = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CertRotationAck {
|
||||||
|
// Rotation ID (from CertRotationNotification)
|
||||||
|
int32 rotation_id = 1;
|
||||||
|
|
||||||
|
// Client public key for identification
|
||||||
|
bytes client_public_key = 2;
|
||||||
|
|
||||||
|
// Confirmation that client saved the new certificate
|
||||||
|
bool cert_saved = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
service ArbiterService {
|
||||||
|
rpc Client(stream ClientRequest) returns (stream ClientResponse);
|
||||||
|
rpc UserAgent(stream UserAgentRequest) returns (stream UserAgentResponse);
|
||||||
|
}
|
||||||
|
|||||||
35
protobufs/auth.proto
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package arbiter.auth;
|
||||||
|
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
message AuthChallengeRequest {
|
||||||
|
bytes pubkey = 1;
|
||||||
|
optional string bootstrap_token = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AuthChallenge {
|
||||||
|
bytes pubkey = 1;
|
||||||
|
int32 nonce = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AuthChallengeSolution {
|
||||||
|
bytes signature = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AuthOk {}
|
||||||
|
|
||||||
|
message ClientMessage {
|
||||||
|
oneof payload {
|
||||||
|
AuthChallengeRequest auth_challenge_request = 1;
|
||||||
|
AuthChallengeSolution auth_challenge_solution = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ServerMessage {
|
||||||
|
oneof payload {
|
||||||
|
AuthChallenge auth_challenge = 1;
|
||||||
|
AuthOk auth_ok = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package arbiter.client;
|
|
||||||
|
|
||||||
import "client/auth.proto";
|
|
||||||
import "client/evm.proto";
|
|
||||||
import "client/vault.proto";
|
|
||||||
|
|
||||||
message ClientRequest {
|
|
||||||
int32 request_id = 4;
|
|
||||||
oneof payload {
|
|
||||||
auth.Request auth = 1;
|
|
||||||
vault.Request vault = 2;
|
|
||||||
evm.Request evm = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message ClientResponse {
|
|
||||||
optional int32 request_id = 7;
|
|
||||||
oneof payload {
|
|
||||||
auth.Response auth = 1;
|
|
||||||
vault.Response vault = 2;
|
|
||||||
evm.Response evm = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package arbiter.client.auth;
|
|
||||||
|
|
||||||
import "shared/client.proto";
|
|
||||||
|
|
||||||
message AuthChallengeRequest {
|
|
||||||
bytes pubkey = 1;
|
|
||||||
arbiter.shared.ClientInfo client_info = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AuthChallenge {
|
|
||||||
uint64 timestamp_nanos = 1;
|
|
||||||
bytes random = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AuthChallengeSolution {
|
|
||||||
bytes signature = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AuthResult {
|
|
||||||
AUTH_RESULT_UNSPECIFIED = 0;
|
|
||||||
AUTH_RESULT_SUCCESS = 1;
|
|
||||||
AUTH_RESULT_INVALID_KEY = 2;
|
|
||||||
AUTH_RESULT_INVALID_SIGNATURE = 3;
|
|
||||||
AUTH_RESULT_APPROVAL_DENIED = 4;
|
|
||||||
AUTH_RESULT_NO_OPERATORS_ONLINE = 5;
|
|
||||||
AUTH_RESULT_INTERNAL = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Request {
|
|
||||||
oneof payload {
|
|
||||||
AuthChallengeRequest challenge_request = 1;
|
|
||||||
AuthChallengeSolution challenge_solution = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Response {
|
|
||||||
oneof payload {
|
|
||||||
AuthChallenge challenge = 1;
|
|
||||||
AuthResult result = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package arbiter.client.evm;
|
|
||||||
|
|
||||||
import "evm.proto";
|
|
||||||
|
|
||||||
message Request {
|
|
||||||
oneof payload {
|
|
||||||
arbiter.evm.EvmSignTransactionRequest sign_transaction = 1;
|
|
||||||
arbiter.evm.EvmAnalyzeTransactionRequest analyze_transaction = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Response {
|
|
||||||
oneof payload {
|
|
||||||
arbiter.evm.EvmSignTransactionResponse sign_transaction = 1;
|
|
||||||
arbiter.evm.EvmAnalyzeTransactionResponse analyze_transaction = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package arbiter.client.vault;
|
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
|
||||||
import "shared/vault.proto";
|
|
||||||
|
|
||||||
message Request {
|
|
||||||
oneof payload {
|
|
||||||
google.protobuf.Empty query_state = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Response {
|
|
||||||
oneof payload {
|
|
||||||
arbiter.shared.VaultState state = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package arbiter.evm;
|
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
|
||||||
import "google/protobuf/timestamp.proto";
|
|
||||||
import "shared/evm.proto";
|
|
||||||
|
|
||||||
enum EvmError {
|
|
||||||
EVM_ERROR_UNSPECIFIED = 0;
|
|
||||||
EVM_ERROR_VAULT_SEALED = 1;
|
|
||||||
EVM_ERROR_INTERNAL = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WalletEntry {
|
|
||||||
int32 id = 1;
|
|
||||||
bytes address = 2; // 20-byte Ethereum address
|
|
||||||
}
|
|
||||||
|
|
||||||
message WalletList {
|
|
||||||
repeated WalletEntry wallets = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WalletCreateResponse {
|
|
||||||
oneof result {
|
|
||||||
WalletEntry wallet = 1;
|
|
||||||
EvmError error = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message WalletListResponse {
|
|
||||||
oneof result {
|
|
||||||
WalletList wallets = 1;
|
|
||||||
EvmError error = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Grant types ---
|
|
||||||
|
|
||||||
message TransactionRateLimit {
|
|
||||||
uint32 count = 1;
|
|
||||||
int64 window_secs = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message VolumeRateLimit {
|
|
||||||
bytes max_volume = 1; // U256 as big-endian bytes
|
|
||||||
int64 window_secs = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SharedSettings {
|
|
||||||
int32 wallet_access_id = 1;
|
|
||||||
uint64 chain_id = 2;
|
|
||||||
optional google.protobuf.Timestamp valid_from = 3;
|
|
||||||
optional google.protobuf.Timestamp valid_until = 4;
|
|
||||||
optional bytes max_gas_fee_per_gas = 5; // U256 as big-endian bytes
|
|
||||||
optional bytes max_priority_fee_per_gas = 6; // U256 as big-endian bytes
|
|
||||||
optional TransactionRateLimit rate_limit = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EtherTransferSettings {
|
|
||||||
repeated bytes targets = 1; // list of 20-byte Ethereum addresses
|
|
||||||
VolumeRateLimit limit = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TokenTransferSettings {
|
|
||||||
bytes token_contract = 1; // 20-byte Ethereum address
|
|
||||||
optional bytes target = 2; // 20-byte Ethereum address; absent means any recipient allowed
|
|
||||||
repeated VolumeRateLimit volume_limits = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SpecificGrant {
|
|
||||||
oneof grant {
|
|
||||||
EtherTransferSettings ether_transfer = 1;
|
|
||||||
TokenTransferSettings token_transfer = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Operator grant management ---
|
|
||||||
message EvmGrantCreateRequest {
|
|
||||||
SharedSettings shared = 1;
|
|
||||||
SpecificGrant specific = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmGrantCreateResponse {
|
|
||||||
oneof result {
|
|
||||||
int32 grant_id = 1;
|
|
||||||
EvmError error = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmGrantDeleteRequest {
|
|
||||||
int32 grant_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmGrantDeleteResponse {
|
|
||||||
oneof result {
|
|
||||||
google.protobuf.Empty ok = 1;
|
|
||||||
EvmError error = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basic grant info returned in grant listings
|
|
||||||
message GrantEntry {
|
|
||||||
int32 id = 1;
|
|
||||||
int32 wallet_access_id = 2;
|
|
||||||
SharedSettings shared = 3;
|
|
||||||
SpecificGrant specific = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmGrantListRequest {
|
|
||||||
optional int32 wallet_access_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmGrantListResponse {
|
|
||||||
oneof result {
|
|
||||||
EvmGrantList grants = 1;
|
|
||||||
EvmError error = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmGrantList {
|
|
||||||
repeated GrantEntry grants = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Client transaction operations ---
|
|
||||||
|
|
||||||
message EvmSignTransactionRequest {
|
|
||||||
bytes wallet_address = 1; // 20-byte Ethereum address
|
|
||||||
bytes rlp_transaction = 2; // RLP-encoded EIP-1559 transaction (unsigned)
|
|
||||||
}
|
|
||||||
|
|
||||||
// oneof because signing and evaluation happen atomically — a signing failure
|
|
||||||
// is always either an eval error or an internal error, never a partial success
|
|
||||||
message EvmSignTransactionResponse {
|
|
||||||
oneof result {
|
|
||||||
bytes signature = 1; // 65-byte signature: r[32] || s[32] || v[1]
|
|
||||||
arbiter.shared.evm.TransactionEvalError eval_error = 2;
|
|
||||||
EvmError error = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmAnalyzeTransactionRequest {
|
|
||||||
bytes wallet_address = 1; // 20-byte Ethereum address
|
|
||||||
bytes rlp_transaction = 2; // RLP-encoded EIP-1559 transaction
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvmAnalyzeTransactionResponse {
|
|
||||||
oneof result {
|
|
||||||
arbiter.shared.evm.SpecificMeaning meaning = 1;
|
|
||||||
arbiter.shared.evm.TransactionEvalError eval_error = 2;
|
|
||||||
EvmError error = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
46
protobufs/google/protobuf/timestamp.proto
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// Protocol Buffers - Google's data interchange format
|
||||||
|
// Copyright 2008 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package google.protobuf;
|
||||||
|
|
||||||
|
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
|
||||||
|
option cc_enable_arenas = true;
|
||||||
|
option go_package = "google.golang.org/protobuf/types/known/timestamppb";
|
||||||
|
option java_package = "com.google.protobuf";
|
||||||
|
option java_outer_classname = "TimestampProto";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option objc_class_prefix = "GPB";
|
||||||
|
|
||||||
|
// A Timestamp represents a point in time independent of any time zone or local
|
||||||
|
// calendar, encoded as a count of seconds and fractions of seconds at
|
||||||
|
// nanosecond resolution. The count is relative to an epoch at UTC midnight on
|
||||||
|
// January 1, 1970, in the proleptic Gregorian calendar which extends the
|
||||||
|
// Gregorian calendar backwards to year one.
|
||||||
|
message Timestamp {
|
||||||
|
// Represents seconds of UTC time since Unix epoch
|
||||||
|
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
|
||||||
|
// 9999-12-31T23:59:59Z inclusive.
|
||||||
|
int64 seconds = 1;
|
||||||
|
|
||||||
|
// Non-negative fractions of a second at nanosecond resolution. Negative
|
||||||
|
// second values with fractions must still have non-negative nanos values
|
||||||
|
// that count forward in time. Must be from 0 to 999,999,999
|
||||||
|
// inclusive.
|
||||||
|
int32 nanos = 2;
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package arbiter.operator;
|
|
||||||
|
|
||||||
import "operator/auth.proto";
|
|
||||||
import "operator/evm.proto";
|
|
||||||
import "operator/sdk_client.proto";
|
|
||||||
import "operator/vault/vault.proto";
|
|
||||||
|
|
||||||
message OperatorRequest {
|
|
||||||
int32 id = 16;
|
|
||||||
oneof payload {
|
|
||||||
auth.Request auth = 1;
|
|
||||||
vault.Request vault = 2;
|
|
||||||
evm.Request evm = 3;
|
|
||||||
sdk_client.Request sdk_client = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message OperatorResponse {
|
|
||||||
optional int32 id = 16;
|
|
||||||
oneof payload {
|
|
||||||
auth.Response auth = 1;
|
|
||||||
vault.Response vault = 2;
|
|
||||||
evm.Response evm = 3;
|
|
||||||
sdk_client.Response sdk_client = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package arbiter.operator.auth;
|
|
||||||
|
|
||||||
message AuthChallengeRequest {
|
|
||||||
bytes pubkey = 1;
|
|
||||||
optional string bootstrap_token = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AuthChallenge {
|
|
||||||
uint64 timestamp_nanos = 1;
|
|
||||||
bytes random = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AuthChallengeSolution {
|
|
||||||
bytes signature = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AuthResult {
|
|
||||||
AUTH_RESULT_UNSPECIFIED = 0;
|
|
||||||
AUTH_RESULT_SUCCESS = 1;
|
|
||||||
AUTH_RESULT_INVALID_KEY = 2;
|
|
||||||
AUTH_RESULT_INVALID_SIGNATURE = 3;
|
|
||||||
AUTH_RESULT_BOOTSTRAP_REQUIRED = 4;
|
|
||||||
AUTH_RESULT_TOKEN_INVALID = 5;
|
|
||||||
AUTH_RESULT_INTERNAL = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Request {
|
|
||||||
oneof payload {
|
|
||||||
AuthChallengeRequest challenge_request = 1;
|
|
||||||
AuthChallengeSolution challenge_solution = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Response {
|
|
||||||
oneof payload {
|
|
||||||
AuthChallenge challenge = 1;
|
|
||||||
AuthResult result = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package arbiter.operator.evm;
|
|
||||||
|
|
||||||
import "evm.proto";
|
|
||||||
import "google/protobuf/empty.proto";
|
|
||||||
|
|
||||||
message SignTransactionRequest {
|
|
||||||
int32 client_id = 1;
|
|
||||||
arbiter.evm.EvmSignTransactionRequest request = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Request {
|
|
||||||
oneof payload {
|
|
||||||
google.protobuf.Empty wallet_create = 1;
|
|
||||||
google.protobuf.Empty wallet_list = 2;
|
|
||||||
arbiter.evm.EvmGrantCreateRequest grant_create = 3;
|
|
||||||
arbiter.evm.EvmGrantDeleteRequest grant_delete = 4;
|
|
||||||
arbiter.evm.EvmGrantListRequest grant_list = 5;
|
|
||||||
SignTransactionRequest sign_transaction = 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Response {
|
|
||||||
oneof payload {
|
|
||||||
arbiter.evm.WalletCreateResponse wallet_create = 1;
|
|
||||||
arbiter.evm.WalletListResponse wallet_list = 2;
|
|
||||||
arbiter.evm.EvmGrantCreateResponse grant_create = 3;
|
|
||||||
arbiter.evm.EvmGrantDeleteResponse grant_delete = 4;
|
|
||||||
arbiter.evm.EvmGrantListResponse grant_list = 5;
|
|
||||||
arbiter.evm.EvmSignTransactionResponse sign_transaction = 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package arbiter.operator.sdk_client;
|
|
||||||
|
|
||||||
import "shared/client.proto";
|
|
||||||
import "google/protobuf/empty.proto";
|
|
||||||
|
|
||||||
enum Error {
|
|
||||||
ERROR_UNSPECIFIED = 0;
|
|
||||||
ERROR_ALREADY_EXISTS = 1;
|
|
||||||
ERROR_NOT_FOUND = 2;
|
|
||||||
ERROR_HAS_RELATED_DATA = 3; // hard-delete blocked by FK (client has grants or transaction logs)
|
|
||||||
ERROR_INTERNAL = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RevokeRequest {
|
|
||||||
int32 client_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Entry {
|
|
||||||
int32 id = 1;
|
|
||||||
bytes pubkey = 2;
|
|
||||||
arbiter.shared.ClientInfo info = 3;
|
|
||||||
int32 created_at = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message List {
|
|
||||||
repeated Entry clients = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RevokeResponse {
|
|
||||||
oneof result {
|
|
||||||
google.protobuf.Empty ok = 1;
|
|
||||||
Error error = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListResponse {
|
|
||||||
oneof result {
|
|
||||||
List clients = 1;
|
|
||||||
Error error = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message ConnectionRequest {
|
|
||||||
bytes pubkey = 1;
|
|
||||||
arbiter.shared.ClientInfo info = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ConnectionResponse {
|
|
||||||
bool approved = 1;
|
|
||||||
bytes pubkey = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ConnectionCancel {
|
|
||||||
bytes pubkey = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WalletAccess {
|
|
||||||
int32 wallet_id = 1;
|
|
||||||
int32 sdk_client_id = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WalletAccessEntry {
|
|
||||||
int32 id = 1;
|
|
||||||
WalletAccess access = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GrantWalletAccess {
|
|
||||||
repeated WalletAccess accesses = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RevokeWalletAccess {
|
|
||||||
repeated int32 accesses = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListWalletAccessResponse {
|
|
||||||
repeated WalletAccessEntry accesses = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Request {
|
|
||||||
oneof payload {
|
|
||||||
ConnectionResponse connection_response = 1;
|
|
||||||
RevokeRequest revoke = 2;
|
|
||||||
google.protobuf.Empty list = 3;
|
|
||||||
GrantWalletAccess grant_wallet_access = 4;
|
|
||||||
RevokeWalletAccess revoke_wallet_access = 5;
|
|
||||||
google.protobuf.Empty list_wallet_access = 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Response {
|
|
||||||
oneof payload {
|
|
||||||
ConnectionRequest connection_request = 1;
|
|
||||||
ConnectionCancel connection_cancel = 2;
|
|
||||||
RevokeResponse revoke = 3;
|
|
||||||
ListResponse list = 4;
|
|
||||||
ListWalletAccessResponse list_wallet_access = 5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package arbiter.operator.vault.bootstrap;
|
|
||||||
|
|
||||||
message BootstrapEncryptedKey {
|
|
||||||
bytes nonce = 1;
|
|
||||||
bytes ciphertext = 2;
|
|
||||||
bytes associated_data = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum BootstrapResult {
|
|
||||||
BOOTSTRAP_RESULT_UNSPECIFIED = 0;
|
|
||||||
BOOTSTRAP_RESULT_SUCCESS = 1;
|
|
||||||
BOOTSTRAP_RESULT_ALREADY_BOOTSTRAPPED = 2;
|
|
||||||
BOOTSTRAP_RESULT_INVALID_KEY = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Request {
|
|
||||||
BootstrapEncryptedKey encrypted_key = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Response {
|
|
||||||
BootstrapResult result = 1;
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package arbiter.operator.vault.unseal;
|
|
||||||
|
|
||||||
message UnsealStart {
|
|
||||||
bytes client_pubkey = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UnsealStartResponse {
|
|
||||||
bytes server_pubkey = 1;
|
|
||||||
}
|
|
||||||
message UnsealEncryptedKey {
|
|
||||||
bytes nonce = 1;
|
|
||||||
bytes ciphertext = 2;
|
|
||||||
bytes associated_data = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum UnsealResult {
|
|
||||||
UNSEAL_RESULT_UNSPECIFIED = 0;
|
|
||||||
UNSEAL_RESULT_SUCCESS = 1;
|
|
||||||
UNSEAL_RESULT_INVALID_KEY = 2;
|
|
||||||
UNSEAL_RESULT_UNBOOTSTRAPPED = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Request {
|
|
||||||
oneof payload {
|
|
||||||
UnsealStart start = 1;
|
|
||||||
UnsealEncryptedKey encrypted_key = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Response {
|
|
||||||
oneof payload {
|
|
||||||
UnsealStartResponse start = 1;
|
|
||||||
UnsealResult result = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package arbiter.operator.vault;
|
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
|
||||||
import "shared/vault.proto";
|
|
||||||
import "operator/vault/bootstrap.proto";
|
|
||||||
import "operator/vault/unseal.proto";
|
|
||||||
|
|
||||||
message Request {
|
|
||||||
oneof payload {
|
|
||||||
google.protobuf.Empty query_state = 1;
|
|
||||||
unseal.Request unseal = 2;
|
|
||||||
bootstrap.Request bootstrap = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Response {
|
|
||||||
oneof payload {
|
|
||||||
arbiter.shared.VaultState state = 1;
|
|
||||||
unseal.Response unseal = 2;
|
|
||||||
bootstrap.Response bootstrap = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package arbiter.shared;
|
|
||||||
|
|
||||||
message ClientInfo {
|
|
||||||
string name = 1;
|
|
||||||
optional string description = 2;
|
|
||||||
optional string version = 3;
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package arbiter.shared.evm;
|
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
|
||||||
|
|
||||||
message EtherTransferMeaning {
|
|
||||||
bytes to = 1; // 20-byte Ethereum address
|
|
||||||
bytes value = 2; // U256 as big-endian bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
message TokenInfo {
|
|
||||||
string symbol = 1;
|
|
||||||
bytes address = 2; // 20-byte Ethereum address
|
|
||||||
uint64 chain_id = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mirror of token_transfers::Meaning
|
|
||||||
message TokenTransferMeaning {
|
|
||||||
TokenInfo token = 1;
|
|
||||||
bytes to = 2; // 20-byte Ethereum address
|
|
||||||
bytes value = 3; // U256 as big-endian bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mirror of policies::SpecificMeaning
|
|
||||||
message SpecificMeaning {
|
|
||||||
oneof meaning {
|
|
||||||
EtherTransferMeaning ether_transfer = 1;
|
|
||||||
TokenTransferMeaning token_transfer = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message GasLimitExceededViolation {
|
|
||||||
optional bytes max_gas_fee_per_gas = 1; // U256 as big-endian bytes
|
|
||||||
optional bytes max_priority_fee_per_gas = 2; // U256 as big-endian bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvalViolation {
|
|
||||||
message ChainIdMismatch {
|
|
||||||
uint64 expected = 1;
|
|
||||||
uint64 actual = 2;
|
|
||||||
}
|
|
||||||
oneof kind {
|
|
||||||
bytes invalid_target = 1; // 20-byte Ethereum address
|
|
||||||
GasLimitExceededViolation gas_limit_exceeded = 2;
|
|
||||||
google.protobuf.Empty rate_limit_exceeded = 3;
|
|
||||||
google.protobuf.Empty volumetric_limit_exceeded = 4;
|
|
||||||
google.protobuf.Empty invalid_time = 5;
|
|
||||||
google.protobuf.Empty invalid_transaction_type = 6;
|
|
||||||
|
|
||||||
ChainIdMismatch chain_id_mismatch = 7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transaction was classified but no grant covers it
|
|
||||||
message NoMatchingGrantError {
|
|
||||||
SpecificMeaning meaning = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transaction was classified and a grant was found, but constraints were violated
|
|
||||||
message PolicyViolationsError {
|
|
||||||
SpecificMeaning meaning = 1;
|
|
||||||
repeated EvalViolation violations = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// top-level error returned when transaction evaluation fails
|
|
||||||
message TransactionEvalError {
|
|
||||||
oneof kind {
|
|
||||||
google.protobuf.Empty contract_creation_not_supported = 1;
|
|
||||||
google.protobuf.Empty unsupported_transaction_type = 2;
|
|
||||||
NoMatchingGrantError no_matching_grant = 3;
|
|
||||||
PolicyViolationsError policy_violations = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package arbiter.shared;
|
|
||||||
|
|
||||||
enum VaultState {
|
|
||||||
VAULT_STATE_UNSPECIFIED = 0;
|
|
||||||
VAULT_STATE_UNBOOTSTRAPPED = 1;
|
|
||||||
VAULT_STATE_SEALED = 2;
|
|
||||||
VAULT_STATE_UNSEALED = 3;
|
|
||||||
VAULT_STATE_ERROR = 4;
|
|
||||||
}
|
|
||||||
14
protobufs/unseal.proto
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package arbiter.unseal;
|
||||||
|
|
||||||
|
message UserAgentKeyRequest {}
|
||||||
|
|
||||||
|
message ServerKeyResponse {
|
||||||
|
bytes pubkey = 1;
|
||||||
|
}
|
||||||
|
message UserAgentSealedKey {
|
||||||
|
bytes sealed_key = 1;
|
||||||
|
bytes pubkey = 2;
|
||||||
|
bytes nonce = 3;
|
||||||
|
}
|
||||||