From 37bb4cdbc3bd1c02aeddf54dc0925f03a3512700 Mon Sep 17 00:00:00 2001 From: FlowMemory HQ Agent Date: Thu, 14 May 2026 13:42:25 -0500 Subject: [PATCH 01/19] Add production L1 hq implementation snapshot --- .../data/flowchain-bridge-test-deposit.json | 4 +- ...lowchain-local-devnet-dashboard-state.json | 34 +- .../data/flowchain-local-devnet-state.json | 34 +- .../public/data/flowmemory-dashboard-v0.json | 14 +- apps/dashboard/src/App.tsx | 2 + apps/dashboard/src/components/AppShell.tsx | 7 + apps/dashboard/src/data/workbench.ts | 317 +- apps/dashboard/src/styles.css | 3216 +++++++--- apps/dashboard/src/test/dashboardData.test.ts | 163 + apps/dashboard/src/views/BridgePilotView.tsx | 754 +++ apps/dashboard/src/views/WorkbenchView.tsx | 42 +- contracts/bridge/BaseBridgeLockbox.sol | 60 +- crates/flowmemory-devnet/src/cli.rs | 234 +- crates/flowmemory-devnet/src/lib.rs | 27 +- crates/flowmemory-devnet/src/model.rs | 328 +- crates/flowmemory-devnet/src/storage.rs | 88 +- .../flowmemory-devnet/tests/devnet_tests.rs | 612 +- crypto/.gitignore | 1 + .../product-testnet-transactions.json | 28 +- crypto/fixtures/production-l1-vectors.json | 1166 ++++ crypto/package.json | 29 +- crypto/src/constants.js | 64 +- crypto/src/identity.js | 100 + crypto/src/index.js | 5 + crypto/src/no-secret-scan.js | 69 + crypto/src/objects.js | 36 +- crypto/src/operator-bridge-cli.js | 279 + crypto/src/production-l1-vector-cli.js | 33 + crypto/src/production-l1-vectors.js | 582 ++ crypto/src/production-l1.js | 221 + crypto/src/runtime-validation.d.ts | 27 + crypto/src/runtime-validation.js | 124 + crypto/src/transactions.js | 301 +- crypto/src/validate-production-l1-crypto.js | 175 + crypto/src/wallet-cli.js | 513 +- crypto/src/wallet-documents.js | 341 + crypto/src/wallet-e2e.js | 359 ++ crypto/src/wallet-envelope.js | 211 + crypto/src/wallet.js | 190 +- docs/DASHBOARD_MVP.md | 20 +- ...owchain-private-local-protocol-contract.md | 45 + docs/FLOWCHAIN_CONTROL_PLANE_API.md | 79 +- docs/FLOWCHAIN_LIVE_L1_BRIDGE_GO_NO_GO.md | 83 + docs/FLOWCHAIN_PRODUCTION_L1_GO_NO_GO.md | 79 + docs/FLOWCHAIN_REAL_VALUE_PILOT.md | 66 + .../REAL_VALUE_PILOT_WALLET_OPERATOR.md | 42 + .../production-l1-bridge/ASSET_DECISION.md | 28 + .../production-l1-bridge/CHECKLIST.md | 88 + .../production-l1-bridge/CONTRACT_PROOF.md | 56 + .../CREDIT_APPLICATION_PROOF.md | 44 + .../DEPLOYMENT_READINESS_PROOF.md | 51 + .../DEPOSIT_EVENT_PROOF.md | 47 + .../production-l1-bridge/EXPERIMENTS.md | 18 + .../FULL_MOCK_PILOT_PROOF.md | 46 + .../production-l1-bridge/HANDOFF.md | 113 + .../LIVE_OBSERVATION_GATE_PROOF.md | 51 + .../LIVE_READINESS_PROOF.md | 42 + .../MOCK_PILOT_E2E_PROOF.md | 49 + docs/agent-runs/production-l1-bridge/NOTES.md | 15 + .../OWNER_LIVE_TEST_COMMANDS.md | 113 + docs/agent-runs/production-l1-bridge/PLAN.md | 31 + .../REAL_FUNDS_PILOT_RUNBOOK.md | 140 + .../production-l1-bridge/RELAYER_PROOF.md | 51 + .../production-l1-bridge/REPLAY_PROOF.md | 35 + .../WITHDRAW_RELEASE_PROOF.md | 66 + .../production-l1-hq/BASE8453_PILOT_LEDGER.md | 39 + docs/agent-runs/production-l1-hq/CHECKLIST.md | 27 + .../production-l1-hq/COMMAND_MATRIX.md | 40 + .../production-l1-hq/COMPLETION_AUDIT.md | 35 + docs/agent-runs/production-l1-hq/EVIDENCE.md | 61 + .../production-l1-hq/FOLLOWUP_PROMPTS.md | 162 + .../production-l1-hq/INTEGRATION_LOG.md | 40 + .../production-l1-hq/INTEGRATION_PLAN.md | 29 + .../LIVE_L1_BRIDGE_EVIDENCE.md | 78 + .../production-l1-hq/OWNER_RUNBOOK.md | 45 + docs/agent-runs/production-l1-hq/PLAN.md | 33 + .../control-dashboard-handoff.md | 31 + .../bridge-local-credit-smoke.result.json | 13 + .../bridge-local-credit-smoke.stderr.log | 0 .../bridge-local-credit-smoke.stdout.log | 16 + .../flowchain-bridge-live-check.result.json | 13 + .../flowchain-bridge-live-check.stderr.log | 7 + .../flowchain-bridge-live-check.stdout.log | 7 + .../flowchain-bridge-mock-e2e.result.json | 13 + .../flowchain-bridge-mock-e2e.stderr.log | 0 .../flowchain-bridge-mock-e2e.stdout.log | 20 + .../flowchain-no-secret-scan.result.json | 13 + .../flowchain-no-secret-scan.stderr.log | 0 .../flowchain-no-secret-scan.stdout.log | 6 + .../flowchain-production-l1-e2e.result.json | 13 + ...wchain-production-l1-e2e.runner.stderr.log | 0 ...wchain-production-l1-e2e.runner.stdout.log | 1 + .../flowchain-production-l1-e2e.stderr.log | 0 .../flowchain-production-l1-e2e.stdout.log | 355 ++ ...-value-pilot-control-dashboard.result.json | 13 + ...l-value-pilot-control-dashboard.stderr.log | 0 ...l-value-pilot-control-dashboard.stdout.log | 36 + ...flowchain-real-value-pilot-e2e.result.json | 13 + ...ain-real-value-pilot-e2e.runner.stderr.log | 0 ...ain-real-value-pilot-e2e.runner.stdout.log | 1 + .../flowchain-real-value-pilot-e2e.stderr.log | 220 + .../flowchain-real-value-pilot-e2e.stdout.log | 2651 ++++++++ ...chain-real-value-pilot-runtime.result.json | 13 + ...wchain-real-value-pilot-runtime.stderr.log | 0 ...wchain-real-value-pilot-runtime.stdout.log | 37 + .../git-diff-check.result.json | 13 + .../git-diff-check.stderr.log | 70 + .../git-diff-check.stdout.log | 0 .../git-diff-stat.result.json | 13 + .../git-diff-stat.stderr.log | 70 + .../git-diff-stat.stdout.log | 67 + .../no-secret-scan-report.json | 23 + .../runner-self-test.result.json | 13 + .../runner-self-test.runner.stderr.log | 0 .../runner-self-test.runner.stdout.log | 1 + .../runner-self-test.stderr.log | 0 .../runner-self-test.stdout.log | 1 + .../production-l1-hq/no-secret-handoff.md | 33 + .../production-l1-hq/run-safe-check.ps1 | 62 + .../BRIDGE_PROTOCOL_SPEC.md | 46 + .../production-l1-protocol/CHECKLIST.md | 44 + .../production-l1-protocol/EXPERIMENTS.md | 31 + .../production-l1-protocol/GENESIS_PROOF.md | 29 + .../production-l1-protocol/HANDOFF.md | 75 + .../production-l1-protocol/NOTES.md | 18 + .../agent-runs/production-l1-protocol/PLAN.md | 37 + .../production-l1-protocol/PROFILE_MATRIX.md | 21 + .../PROTOCOL_INVENTORY.md | 24 + .../RECEIPT_EVENT_CATALOG.md | 37 + .../STATE_TRANSITION_SPEC.md | 33 + .../production-l1-protocol/TX_CATALOG.md | 28 + docs/bridge/FLOWCHAIN_BASE_BRIDGE_POC.md | 69 +- .../bridge/base-sepolia-mock-deposit.json | 4 +- ...ase8453-pilot-duplicate-mock-deposits.json | 8 +- .../bridge/base8453-pilot-mock-deposit.json | 4 +- .../base8453-runtime-bridge-handoff.json | 189 + .../bridge/local-runtime-bridge-handoff.json | 65 +- .../dashboard/flowmemory-dashboard-v0.json | 14 +- .../devnet/control-plane-handoff.json | 50 +- .../generated/devnet/dashboard-state.json | 34 +- .../generated/devnet/indexer-handoff.json | 30 +- .../launch-core/generated/devnet/state.json | 34 +- .../generated/devnet/verifier-handoff.json | 20 +- fixtures/production-l1/block.valid.json | 3506 ++++++++++ .../production-l1/bridge-evidence.valid.json | 62 + fixtures/production-l1/events.valid.json | 580 ++ .../production-l1/export-snapshot.valid.json | 909 +++ .../production-l1/finality-receipt.valid.json | 17 + fixtures/production-l1/genesis.input.json | 269 + fixtures/production-l1/genesis.json | 283 + fixtures/production-l1/negative-fixtures.json | 3166 +++++++++ .../production-l1/production-l1-tools.mjs | 2484 ++++++++ fixtures/production-l1/profiles.json | 196 + fixtures/production-l1/receipts.valid.json | 446 ++ .../state-root-manifest.valid.json | 508 ++ .../production-l1/transactions.valid.json | 1901 ++++++ .../bridge-base-mainnet-pilot-observe.ps1 | 131 +- infra/scripts/bridge-base8453-control.ps1 | 137 + infra/scripts/bridge-base8453-deploy.ps1 | 233 + infra/scripts/bridge-evidence-export.ps1 | 91 + .../flowchain-bridge-command-matrix.ps1 | 56 + infra/scripts/flowchain-bridge-live-check.ps1 | 301 + .../flowchain-bridge-no-secret-audit.ps1 | 72 + .../flowchain-bridge-release-evidence.ps1 | 63 + infra/scripts/flowchain-common.ps1 | 53 +- infra/scripts/flowchain-dex-e2e.ps1 | 75 + infra/scripts/flowchain-doctor.ps1 | 122 + .../flowchain-emergency-print-recovery.ps1 | 43 + .../flowchain-emergency-stop-local.ps1 | 51 + infra/scripts/flowchain-evidence-export.ps1 | 185 + infra/scripts/flowchain-export.ps1 | 11 +- infra/scripts/flowchain-full-smoke.ps1 | 5 +- infra/scripts/flowchain-import.ps1 | 8 +- .../scripts/flowchain-live-l1-bridge-e2e.ps1 | 926 +++ infra/scripts/flowchain-multi-node-smoke.ps1 | 5 +- infra/scripts/flowchain-no-secret-scan.ps1 | 198 + infra/scripts/flowchain-node-logs.ps1 | 30 + infra/scripts/flowchain-node-restart.ps1 | 65 + infra/scripts/flowchain-node-smoke.ps1 | 5 +- infra/scripts/flowchain-node-start.ps1 | 169 + infra/scripts/flowchain-production-l1-e2e.ps1 | 656 ++ .../flowchain-real-value-pilot-e2e.ps1 | 5 +- .../flowchain-real-value-pilot-runtime.ps1 | 584 +- .../flowchain-second-computer-bundle.ps1 | 110 + .../flowchain-second-computer-verify.ps1 | 57 + infra/scripts/flowchain-smoke.ps1 | 5 +- infra/scripts/flowchain-wallet-e2e.ps1 | 44 + infra/scripts/flowchain-wallet-operator.ps1 | 38 + .../scripts/flowchain-wallet-transfer-e2e.ps1 | 153 + package.json | 38 + schemas/flowmemory/bridge-credit.schema.json | 19 +- .../bridge-local-usage-proof.schema.json | 115 + .../flowmemory/bridge-observation.schema.json | 26 +- .../bridge-pilot-evidence.schema.json | 28 +- .../bridge-release-evidence.schema.json | 19 +- ...dge-runtime-credit-application.schema.json | 20 +- .../bridge-runtime-handoff.schema.json | 4 +- ...ridge-withdrawal-authorization.schema.json | 105 + .../bridge-withdrawal-intent.schema.json | 12 + .../local-signature-envelope.schema.json | 14 +- .../local-transaction-envelope.schema.json | 276 +- .../local-wallet-public-metadata.schema.json | 63 +- ...uction-account-public-metadata.schema.json | 253 + .../production-block-body.schema.json | 154 + .../production-block-header.schema.json | 202 + .../production-bridge-evidence.schema.json | 332 + .../flowmemory/production-event.schema.json | 244 + .../production-export-snapshot.schema.json | 248 + .../production-finality-receipt.schema.json | 204 + .../flowmemory/production-genesis.schema.json | 409 ++ .../production-network-profile.schema.json | 286 + .../flowmemory/production-receipt.schema.json | 260 + ...production-state-root-manifest.schema.json | 246 + ...roduction-transaction-envelope.schema.json | 326 + ...production-transaction-payload.schema.json | 5671 +++++++++++++++++ ...production-validator-authority.schema.json | 175 + .../wallet-signed-envelope.schema.json | 136 + services/bridge-relayer/README.md | 30 +- .../src/bridge-live-readiness-check.ts | 224 + .../bridge-relayer/src/bridge-pilot-e2e.ts | 88 +- .../src/diagnose-base8453-tx.ts | 407 ++ .../src/observe-base-lockbox.ts | 395 +- .../test/bridge-relayer.test.ts | 550 +- services/control-plane/src/fixture-state.ts | 211 +- services/control-plane/src/methods.ts | 250 +- services/control-plane/src/pilot.ts | 643 +- .../control-plane/src/real-value-pilot-e2e.ts | 13 + services/control-plane/src/server.ts | 90 +- services/control-plane/src/smoke.ts | 4 + services/control-plane/src/types.ts | 12 +- .../control-plane/test/control-plane.test.ts | 441 +- tests/bridge/BaseBridgeLockbox.t.sol | 61 +- 232 files changed, 48182 insertions(+), 1815 deletions(-) create mode 100644 apps/dashboard/src/views/BridgePilotView.tsx create mode 100644 crypto/fixtures/production-l1-vectors.json create mode 100644 crypto/src/identity.js create mode 100644 crypto/src/no-secret-scan.js create mode 100644 crypto/src/operator-bridge-cli.js create mode 100644 crypto/src/production-l1-vector-cli.js create mode 100644 crypto/src/production-l1-vectors.js create mode 100644 crypto/src/production-l1.js create mode 100644 crypto/src/runtime-validation.d.ts create mode 100644 crypto/src/runtime-validation.js create mode 100644 crypto/src/validate-production-l1-crypto.js create mode 100644 crypto/src/wallet-documents.js create mode 100644 crypto/src/wallet-e2e.js create mode 100644 crypto/src/wallet-envelope.js create mode 100644 docs/DECISIONS/2026-05-13-flowchain-private-local-protocol-contract.md create mode 100644 docs/FLOWCHAIN_LIVE_L1_BRIDGE_GO_NO_GO.md create mode 100644 docs/FLOWCHAIN_PRODUCTION_L1_GO_NO_GO.md create mode 100644 docs/agent-runs/production-l1-bridge/ASSET_DECISION.md create mode 100644 docs/agent-runs/production-l1-bridge/CHECKLIST.md create mode 100644 docs/agent-runs/production-l1-bridge/CONTRACT_PROOF.md create mode 100644 docs/agent-runs/production-l1-bridge/CREDIT_APPLICATION_PROOF.md create mode 100644 docs/agent-runs/production-l1-bridge/DEPLOYMENT_READINESS_PROOF.md create mode 100644 docs/agent-runs/production-l1-bridge/DEPOSIT_EVENT_PROOF.md create mode 100644 docs/agent-runs/production-l1-bridge/EXPERIMENTS.md create mode 100644 docs/agent-runs/production-l1-bridge/FULL_MOCK_PILOT_PROOF.md create mode 100644 docs/agent-runs/production-l1-bridge/HANDOFF.md create mode 100644 docs/agent-runs/production-l1-bridge/LIVE_OBSERVATION_GATE_PROOF.md create mode 100644 docs/agent-runs/production-l1-bridge/LIVE_READINESS_PROOF.md create mode 100644 docs/agent-runs/production-l1-bridge/MOCK_PILOT_E2E_PROOF.md create mode 100644 docs/agent-runs/production-l1-bridge/NOTES.md create mode 100644 docs/agent-runs/production-l1-bridge/OWNER_LIVE_TEST_COMMANDS.md create mode 100644 docs/agent-runs/production-l1-bridge/PLAN.md create mode 100644 docs/agent-runs/production-l1-bridge/REAL_FUNDS_PILOT_RUNBOOK.md create mode 100644 docs/agent-runs/production-l1-bridge/RELAYER_PROOF.md create mode 100644 docs/agent-runs/production-l1-bridge/REPLAY_PROOF.md create mode 100644 docs/agent-runs/production-l1-bridge/WITHDRAW_RELEASE_PROOF.md create mode 100644 docs/agent-runs/production-l1-hq/BASE8453_PILOT_LEDGER.md create mode 100644 docs/agent-runs/production-l1-hq/CHECKLIST.md create mode 100644 docs/agent-runs/production-l1-hq/COMMAND_MATRIX.md create mode 100644 docs/agent-runs/production-l1-hq/COMPLETION_AUDIT.md create mode 100644 docs/agent-runs/production-l1-hq/EVIDENCE.md create mode 100644 docs/agent-runs/production-l1-hq/FOLLOWUP_PROMPTS.md create mode 100644 docs/agent-runs/production-l1-hq/INTEGRATION_LOG.md create mode 100644 docs/agent-runs/production-l1-hq/INTEGRATION_PLAN.md create mode 100644 docs/agent-runs/production-l1-hq/LIVE_L1_BRIDGE_EVIDENCE.md create mode 100644 docs/agent-runs/production-l1-hq/OWNER_RUNBOOK.md create mode 100644 docs/agent-runs/production-l1-hq/PLAN.md create mode 100644 docs/agent-runs/production-l1-hq/control-dashboard-handoff.md create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/bridge-local-credit-smoke.result.json create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/bridge-local-credit-smoke.stderr.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/bridge-local-credit-smoke.stdout.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-bridge-live-check.result.json create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-bridge-live-check.stderr.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-bridge-live-check.stdout.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-bridge-mock-e2e.result.json create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-bridge-mock-e2e.stderr.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-bridge-mock-e2e.stdout.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-no-secret-scan.result.json create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-no-secret-scan.stderr.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-no-secret-scan.stdout.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-production-l1-e2e.result.json create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-production-l1-e2e.runner.stderr.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-production-l1-e2e.runner.stdout.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-production-l1-e2e.stderr.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-production-l1-e2e.stdout.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-real-value-pilot-control-dashboard.result.json create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-real-value-pilot-control-dashboard.stderr.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-real-value-pilot-control-dashboard.stdout.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-real-value-pilot-e2e.result.json create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-real-value-pilot-e2e.runner.stderr.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-real-value-pilot-e2e.runner.stdout.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-real-value-pilot-e2e.stderr.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-real-value-pilot-e2e.stdout.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-real-value-pilot-runtime.result.json create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-real-value-pilot-runtime.stderr.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/flowchain-real-value-pilot-runtime.stdout.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/git-diff-check.result.json create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/git-diff-check.stderr.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/git-diff-check.stdout.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/git-diff-stat.result.json create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/git-diff-stat.stderr.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/git-diff-stat.stdout.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/no-secret-scan-report.json create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/runner-self-test.result.json create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/runner-self-test.runner.stderr.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/runner-self-test.runner.stdout.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/runner-self-test.stderr.log create mode 100644 docs/agent-runs/production-l1-hq/final-live-pilot-command-logs/runner-self-test.stdout.log create mode 100644 docs/agent-runs/production-l1-hq/no-secret-handoff.md create mode 100644 docs/agent-runs/production-l1-hq/run-safe-check.ps1 create mode 100644 docs/agent-runs/production-l1-protocol/BRIDGE_PROTOCOL_SPEC.md create mode 100644 docs/agent-runs/production-l1-protocol/CHECKLIST.md create mode 100644 docs/agent-runs/production-l1-protocol/EXPERIMENTS.md create mode 100644 docs/agent-runs/production-l1-protocol/GENESIS_PROOF.md create mode 100644 docs/agent-runs/production-l1-protocol/HANDOFF.md create mode 100644 docs/agent-runs/production-l1-protocol/NOTES.md create mode 100644 docs/agent-runs/production-l1-protocol/PLAN.md create mode 100644 docs/agent-runs/production-l1-protocol/PROFILE_MATRIX.md create mode 100644 docs/agent-runs/production-l1-protocol/PROTOCOL_INVENTORY.md create mode 100644 docs/agent-runs/production-l1-protocol/RECEIPT_EVENT_CATALOG.md create mode 100644 docs/agent-runs/production-l1-protocol/STATE_TRANSITION_SPEC.md create mode 100644 docs/agent-runs/production-l1-protocol/TX_CATALOG.md create mode 100644 fixtures/bridge/base8453-runtime-bridge-handoff.json create mode 100644 fixtures/production-l1/block.valid.json create mode 100644 fixtures/production-l1/bridge-evidence.valid.json create mode 100644 fixtures/production-l1/events.valid.json create mode 100644 fixtures/production-l1/export-snapshot.valid.json create mode 100644 fixtures/production-l1/finality-receipt.valid.json create mode 100644 fixtures/production-l1/genesis.input.json create mode 100644 fixtures/production-l1/genesis.json create mode 100644 fixtures/production-l1/negative-fixtures.json create mode 100644 fixtures/production-l1/production-l1-tools.mjs create mode 100644 fixtures/production-l1/profiles.json create mode 100644 fixtures/production-l1/receipts.valid.json create mode 100644 fixtures/production-l1/state-root-manifest.valid.json create mode 100644 fixtures/production-l1/transactions.valid.json create mode 100644 infra/scripts/bridge-base8453-control.ps1 create mode 100644 infra/scripts/bridge-base8453-deploy.ps1 create mode 100644 infra/scripts/bridge-evidence-export.ps1 create mode 100644 infra/scripts/flowchain-bridge-command-matrix.ps1 create mode 100644 infra/scripts/flowchain-bridge-live-check.ps1 create mode 100644 infra/scripts/flowchain-bridge-no-secret-audit.ps1 create mode 100644 infra/scripts/flowchain-bridge-release-evidence.ps1 create mode 100644 infra/scripts/flowchain-dex-e2e.ps1 create mode 100644 infra/scripts/flowchain-doctor.ps1 create mode 100644 infra/scripts/flowchain-emergency-print-recovery.ps1 create mode 100644 infra/scripts/flowchain-emergency-stop-local.ps1 create mode 100644 infra/scripts/flowchain-evidence-export.ps1 create mode 100644 infra/scripts/flowchain-live-l1-bridge-e2e.ps1 create mode 100644 infra/scripts/flowchain-no-secret-scan.ps1 create mode 100644 infra/scripts/flowchain-node-logs.ps1 create mode 100644 infra/scripts/flowchain-node-restart.ps1 create mode 100644 infra/scripts/flowchain-node-start.ps1 create mode 100644 infra/scripts/flowchain-production-l1-e2e.ps1 create mode 100644 infra/scripts/flowchain-second-computer-bundle.ps1 create mode 100644 infra/scripts/flowchain-second-computer-verify.ps1 create mode 100644 infra/scripts/flowchain-wallet-e2e.ps1 create mode 100644 infra/scripts/flowchain-wallet-operator.ps1 create mode 100644 infra/scripts/flowchain-wallet-transfer-e2e.ps1 create mode 100644 schemas/flowmemory/bridge-local-usage-proof.schema.json create mode 100644 schemas/flowmemory/bridge-withdrawal-authorization.schema.json create mode 100644 schemas/flowmemory/production-account-public-metadata.schema.json create mode 100644 schemas/flowmemory/production-block-body.schema.json create mode 100644 schemas/flowmemory/production-block-header.schema.json create mode 100644 schemas/flowmemory/production-bridge-evidence.schema.json create mode 100644 schemas/flowmemory/production-event.schema.json create mode 100644 schemas/flowmemory/production-export-snapshot.schema.json create mode 100644 schemas/flowmemory/production-finality-receipt.schema.json create mode 100644 schemas/flowmemory/production-genesis.schema.json create mode 100644 schemas/flowmemory/production-network-profile.schema.json create mode 100644 schemas/flowmemory/production-receipt.schema.json create mode 100644 schemas/flowmemory/production-state-root-manifest.schema.json create mode 100644 schemas/flowmemory/production-transaction-envelope.schema.json create mode 100644 schemas/flowmemory/production-transaction-payload.schema.json create mode 100644 schemas/flowmemory/production-validator-authority.schema.json create mode 100644 schemas/flowmemory/wallet-signed-envelope.schema.json create mode 100644 services/bridge-relayer/src/bridge-live-readiness-check.ts create mode 100644 services/bridge-relayer/src/diagnose-base8453-tx.ts diff --git a/apps/dashboard/public/data/flowchain-bridge-test-deposit.json b/apps/dashboard/public/data/flowchain-bridge-test-deposit.json index f43813a2..19772f29 100644 --- a/apps/dashboard/public/data/flowchain-bridge-test-deposit.json +++ b/apps/dashboard/public/data/flowchain-bridge-test-deposit.json @@ -8,8 +8,8 @@ "token": "0x3333333333333333333333333333333333333333", "amount": "20000000", "sender": "0x4444444444444444444444444444444444444444", - "flowchainRecipient": "0x5555555555555555555555555555555555555555555555555555555555555555", + "flowchainRecipient": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "nonce": "1", - "metadataHash": "0x6666666666666666666666666666666666666666666666666666666666666666", + "metadataHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "status": "observed" } diff --git a/apps/dashboard/public/data/flowchain-local-devnet-dashboard-state.json b/apps/dashboard/public/data/flowchain-local-devnet-dashboard-state.json index 2ecd1722..7d49bdb2 100644 --- a/apps/dashboard/public/data/flowchain-local-devnet-dashboard-state.json +++ b/apps/dashboard/public/data/flowchain-local-devnet-dashboard-state.json @@ -31,19 +31,25 @@ }, "balanceTransfers": {}, "baseAnchors": { - "0xf57ab7d2c1459c03cf01bfddd56b046be685d8eaa4597e6bb54b5015aeaf003f": { + "0x6908eac7dbf828ab8d295d2ae86f0b0db9295a27b1fe4999c26a3ed97244ef8d": { "agentAccountRoot": "0xcf31230bfff347f79e19a55f4d1ff5fa486b0b1ad4754ce22b93de4b259a3ca7", - "anchorId": "0xf57ab7d2c1459c03cf01bfddd56b046be685d8eaa4597e6bb54b5015aeaf003f", + "anchorId": "0x6908eac7dbf828ab8d295d2ae86f0b0db9295a27b1fe4999c26a3ed97244ef8d", "appchainChainId": "flowmemory-local-devnet-v0", "artifactAvailabilityProofRoot": "0xfb4b693c45014aae0947f35696e9d864e7b26ac6fd39c1df5edb3e0dcf9bd928", "artifactCommitmentRoot": "0xb772a9f7273032fd3ba2da8b6476d4715bbbafbd2a7eed21ecd0d558bde3beab", "balanceTransferRoot": "0x9b6e249f769a93bc9f34a90156e028d1a830badcd8ccdc5b1487d512cdbf0a6d", "blockRangeEnd": 1, "blockRangeStart": 1, + "bridgeAccountMappingRoot": "0x46285ec5782d3e6cc1e557fd5dd779e979d679e5cc723832cb189d27a5c552c8", + "bridgeAssetMappingRoot": "0xbd283f6190cba76038f7364e207557dd20f7883d78e5f89a96579af5455a89aa", + "bridgeCreditReceiptRoot": "0x0a86ce98161cb40ca4c7719468c0ed8f122677d12641c13305277094b8f3a509", + "bridgeCreditRoot": "0x613ab06f110cc7a5eb06a0f44b7a0f04b0b3cfb0556d30d42842f33563596248", + "bridgeEventReceiptIndexRoot": "0xcaad305c45f57c516d0fdfdef15c5d88b399b976df173f005e9d7dc30fbdbdb2", + "bridgeReplayIndexRoot": "0x9344f8651651d2d5042d2853205154a5777cd57aa9e892e2ac41b9b9fe04c00f", "challengeRoot": "0x16da3d2bf2dcd801bc5deb3987dc01342cb957031ad01408ea77bf5d1583656f", "dexPoolRoot": "0x0e5f034494a2deb6a4f20c04f01e678795c587df4869ad3f189f107fcc447dea", "faucetRecordRoot": "0x2277503a52fab3f9e49b40debfb7d641abee75cf268aa56da403fdcf4fad6cee", - "finalityReceiptRoot": "0xdf352e20fd1ddfd2855202335e03cfec21d87e99bf8717d161fe8648998e16cf", + "finalityReceiptRoot": "0x1c8536e507c5ba2242a5875c9537043645828825286ca256cc1171f1eb960c3f", "finalityStatus": "local-placeholder", "liquidityReceiptRoot": "0x98bf15cc6859994038744612e125236b1f777895a051c41702c2004134327738", "localTestUnitBalanceRoot": "0x167041ef195b5dde2d2cade6ecb26c9a0a596e9ed21ff7bfb02d33c9d2be8d15", @@ -53,7 +59,7 @@ "operatorKeyReferenceRoot": "0x8457aa3ed0f4238834a8f3925f25ccca805828d8427c3ef67590a45659b22a40", "previousAnchorId": "0x0000000000000000000000000000000000000000000000000000000000000000", "rootfieldStateRoot": "0xb72a851dca1103410484e3272945bae5e87fc39b8f32f77d2991959b60d3bfbf", - "stateRoot": "0x8c7c1e7a078b60a809d17a51c44e275059afb8d7535769430c3fc9e9320c7e23", + "stateRoot": "0x7e50021bc0dd2537f9ecfd7973f9799b6ffdffcfd427e3bb059f4dfd1253b947", "swapReceiptRoot": "0xc9f3ee93962f36ed10421ec9ad736079a3b13ef6504336495af243b718cefee1", "tokenBalanceRoot": "0xbaf3b150fe41a0f3a2d9fe3dd9a664f9c5934bfef37218d9c3bf1c682be5f8c6", "tokenDefinitionRoot": "0xbbbad9681e8756403940e4333111706a4fcee1f30534ba14deea9ba148056be0", @@ -64,6 +70,12 @@ } }, "blockHeight": 2, + "bridgeAccountMappings": {}, + "bridgeAssetMappings": {}, + "bridgeCreditReceipts": {}, + "bridgeCredits": {}, + "bridgeEventReceiptIndex": {}, + "bridgeReplayIndex": {}, "challenges": { "challenge:demo:001": { "challengeId": "challenge:demo:001", @@ -98,7 +110,7 @@ "finalizedBy": "operator:local-demo", "receiptId": "receipt:demo:001", "rootfieldId": "rootfield:demo:alpha", - "stateRoot": "0x4e7ee5e7a8cab9b4ddda183842b9e9c1e1e000afea820b577ecc90fa4d9517e2" + "stateRoot": "0xf47b94f6d4be36bc5ade63085c9c3c6174d5b25eb5bfaae9a57e3946f5637352" } }, "genesisConfig": { @@ -134,11 +146,17 @@ "artifactAvailabilityProofRoot": "0xfb4b693c45014aae0947f35696e9d864e7b26ac6fd39c1df5edb3e0dcf9bd928", "artifactCommitmentRoot": "0xb772a9f7273032fd3ba2da8b6476d4715bbbafbd2a7eed21ecd0d558bde3beab", "balanceTransferRoot": "0x9b6e249f769a93bc9f34a90156e028d1a830badcd8ccdc5b1487d512cdbf0a6d", - "baseAnchorRoot": "0xa10b087464d8e6098696295a2a4b26a4396974c9ed10dd0bba429f22284cd573", + "baseAnchorRoot": "0x8b7c3423523f44b9a470a1281b088c72537a42e26f88fc8cd4c3e8360324c7ca", + "bridgeAccountMappingRoot": "0x46285ec5782d3e6cc1e557fd5dd779e979d679e5cc723832cb189d27a5c552c8", + "bridgeAssetMappingRoot": "0xbd283f6190cba76038f7364e207557dd20f7883d78e5f89a96579af5455a89aa", + "bridgeCreditReceiptRoot": "0x0a86ce98161cb40ca4c7719468c0ed8f122677d12641c13305277094b8f3a509", + "bridgeCreditRoot": "0x613ab06f110cc7a5eb06a0f44b7a0f04b0b3cfb0556d30d42842f33563596248", + "bridgeEventReceiptIndexRoot": "0xcaad305c45f57c516d0fdfdef15c5d88b399b976df173f005e9d7dc30fbdbdb2", + "bridgeReplayIndexRoot": "0x9344f8651651d2d5042d2853205154a5777cd57aa9e892e2ac41b9b9fe04c00f", "challengeRoot": "0x16da3d2bf2dcd801bc5deb3987dc01342cb957031ad01408ea77bf5d1583656f", "dexPoolRoot": "0x0e5f034494a2deb6a4f20c04f01e678795c587df4869ad3f189f107fcc447dea", "faucetRecordRoot": "0x2277503a52fab3f9e49b40debfb7d641abee75cf268aa56da403fdcf4fad6cee", - "finalityReceiptRoot": "0xdf352e20fd1ddfd2855202335e03cfec21d87e99bf8717d161fe8648998e16cf", + "finalityReceiptRoot": "0x1c8536e507c5ba2242a5875c9537043645828825286ca256cc1171f1eb960c3f", "importedObservationRoot": "0x99cb1b939d5a09f800f72e4c5a2b92988571126e1f6f93549f4893b3f7de7880", "importedVerifierReportRoot": "0x6070b1015f000dd509c7b276d2ad68d8a9d188ef1a961c2f573346eb75ea5ad7", "liquidityReceiptRoot": "0x98bf15cc6859994038744612e125236b1f777895a051c41702c2004134327738", @@ -210,7 +228,7 @@ } }, "schema": "flowmemory.dashboard_state.local_devnet.v0", - "stateRoot": "0x3074ef2e5311d94e8f9a2660a6cc016c7b7f9a08c56ee07f9e841c1489726e68", + "stateRoot": "0xb3c4344483c7be8c0660d49d465d033a88ae26d6a7c1cbe096a2447d32c5648c", "swapReceipts": {}, "tokenBalances": {}, "tokenDefinitions": {}, diff --git a/apps/dashboard/public/data/flowchain-local-devnet-state.json b/apps/dashboard/public/data/flowchain-local-devnet-state.json index 080bdd3f..d8fc300e 100644 --- a/apps/dashboard/public/data/flowchain-local-devnet-state.json +++ b/apps/dashboard/public/data/flowchain-local-devnet-state.json @@ -19,7 +19,7 @@ "genesisHash": "0x0f23c892cbd2d00c10839d97ddab833698a83f8df8d6df27ceac03cfdd4b7bc9", "nextBlockNumber": 3, "logicalTime": 1778688002, - "parentHash": "0x7ddb184c69f798f25f27a254f1f530c6cdc31c9656ac19d1b8c114f7a3a650c6", + "parentHash": "0x64e3d605fb1bba40b7de6a6a9813f77619282c07b9a33c7b41df1f6e3d71958b", "operatorKeyReferences": { "operator-key:local-devnet:alpha": { "schema": "flowmemory.local_devnet.operator_key_reference.v0", @@ -89,6 +89,12 @@ "lpPositions": {}, "liquidityReceipts": {}, "swapReceipts": {}, + "bridgeAssetMappings": {}, + "bridgeAccountMappings": {}, + "bridgeCredits": {}, + "bridgeCreditReceipts": {}, + "bridgeReplayIndex": {}, + "bridgeEventReceiptIndex": {}, "modelPassports": { "model:demo:local-alpha": { "modelPassportId": "model:demo:local-alpha", @@ -135,7 +141,7 @@ "finalityStatus": "finalized", "challengeCount": 1, "finalizedAtBlock": 1, - "stateRoot": "0x4e7ee5e7a8cab9b4ddda183842b9e9c1e1e000afea820b577ecc90fa4d9517e2" + "stateRoot": "0xf47b94f6d4be36bc5ade63085c9c3c6174d5b25eb5bfaae9a57e3946f5637352" } }, "artifactCommitments": { @@ -193,12 +199,12 @@ "importedObservations": {}, "importedVerifierReports": {}, "baseAnchors": { - "0xf57ab7d2c1459c03cf01bfddd56b046be685d8eaa4597e6bb54b5015aeaf003f": { - "anchorId": "0xf57ab7d2c1459c03cf01bfddd56b046be685d8eaa4597e6bb54b5015aeaf003f", + "0x6908eac7dbf828ab8d295d2ae86f0b0db9295a27b1fe4999c26a3ed97244ef8d": { + "anchorId": "0x6908eac7dbf828ab8d295d2ae86f0b0db9295a27b1fe4999c26a3ed97244ef8d", "appchainChainId": "flowmemory-local-devnet-v0", "blockRangeStart": 1, "blockRangeEnd": 1, - "stateRoot": "0x8c7c1e7a078b60a809d17a51c44e275059afb8d7535769430c3fc9e9320c7e23", + "stateRoot": "0x7e50021bc0dd2537f9ecfd7973f9799b6ffdffcfd427e3bb059f4dfd1253b947", "workReceiptRoot": "0x8b3ef5650c9eea2f608ad9c7cb73df3c289fc0ac72ed04f46e6ae4bce0a1f023", "verifierReportRoot": "0x4facd21e55423e182eba87355482a35daa93f53190fbd3a8d2969f9d55bc5373", "rootfieldStateRoot": "0xb72a851dca1103410484e3272945bae5e87fc39b8f32f77d2991959b60d3bfbf", @@ -215,10 +221,16 @@ "lpPositionRoot": "0xe67dd98259afb06ca93620b4a8742b924ec2e8f3e6e72934eef5b8e60829d46f", "liquidityReceiptRoot": "0x98bf15cc6859994038744612e125236b1f777895a051c41702c2004134327738", "swapReceiptRoot": "0xc9f3ee93962f36ed10421ec9ad736079a3b13ef6504336495af243b718cefee1", + "bridgeAssetMappingRoot": "0xbd283f6190cba76038f7364e207557dd20f7883d78e5f89a96579af5455a89aa", + "bridgeAccountMappingRoot": "0x46285ec5782d3e6cc1e557fd5dd779e979d679e5cc723832cb189d27a5c552c8", + "bridgeCreditRoot": "0x613ab06f110cc7a5eb06a0f44b7a0f04b0b3cfb0556d30d42842f33563596248", + "bridgeCreditReceiptRoot": "0x0a86ce98161cb40ca4c7719468c0ed8f122677d12641c13305277094b8f3a509", + "bridgeReplayIndexRoot": "0x9344f8651651d2d5042d2853205154a5777cd57aa9e892e2ac41b9b9fe04c00f", + "bridgeEventReceiptIndexRoot": "0xcaad305c45f57c516d0fdfdef15c5d88b399b976df173f005e9d7dc30fbdbdb2", "modelPassportRoot": "0x326aa6b0b372d29d24d747fe0879adfd7aaea206373b24ae2ab77d56357e9529", "memoryCellRoot": "0x1b4e91099dd8d867201bd880437197ae6c031e538341aaa3cd2046e5706a2c25", "challengeRoot": "0x16da3d2bf2dcd801bc5deb3987dc01342cb957031ad01408ea77bf5d1583656f", - "finalityReceiptRoot": "0xdf352e20fd1ddfd2855202335e03cfec21d87e99bf8717d161fe8648998e16cf", + "finalityReceiptRoot": "0x1c8536e507c5ba2242a5875c9537043645828825286ca256cc1171f1eb960c3f", "artifactAvailabilityProofRoot": "0xfb4b693c45014aae0947f35696e9d864e7b26ac6fd39c1df5edb3e0dcf9bd928", "verifierModuleRoot": "0xd6ddd8a2d0f5812d64679656c69983a2e0aecd36bd36199d900245658ae4626c", "previousAnchorId": "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -325,13 +337,13 @@ "error": null } ], - "stateRoot": "0x8c7c1e7a078b60a809d17a51c44e275059afb8d7535769430c3fc9e9320c7e23", - "blockHash": "0x61e9f90b982f13988e85a382fc39da82c9114ecceea9001ab454c744e0801a9b" + "stateRoot": "0x7e50021bc0dd2537f9ecfd7973f9799b6ffdffcfd427e3bb059f4dfd1253b947", + "blockHash": "0xd890758bdeaa4ab7a658d7813b2b9bdd0850824aaef830b9f2f4ff455aaf3811" }, { "schema": "flowmemory.local_devnet.block.v0", "blockNumber": 2, - "parentHash": "0x61e9f90b982f13988e85a382fc39da82c9114ecceea9001ab454c744e0801a9b", + "parentHash": "0xd890758bdeaa4ab7a658d7813b2b9bdd0850824aaef830b9f2f4ff455aaf3811", "logicalTime": 1778688001, "txIds": [ "0x8f719c880f17b5d4fb6d9efd54ac276d0dd8050d11c2c7870c36a79b66bc49d7" @@ -343,8 +355,8 @@ "error": null } ], - "stateRoot": "0x3074ef2e5311d94e8f9a2660a6cc016c7b7f9a08c56ee07f9e841c1489726e68", - "blockHash": "0x7ddb184c69f798f25f27a254f1f530c6cdc31c9656ac19d1b8c114f7a3a650c6" + "stateRoot": "0xb3c4344483c7be8c0660d49d465d033a88ae26d6a7c1cbe096a2447d32c5648c", + "blockHash": "0x64e3d605fb1bba40b7de6a6a9813f77619282c07b9a33c7b41df1f6e3d71958b" } ], "pendingTxs": [] diff --git a/apps/dashboard/public/data/flowmemory-dashboard-v0.json b/apps/dashboard/public/data/flowmemory-dashboard-v0.json index b226d982..01343d34 100644 --- a/apps/dashboard/public/data/flowmemory-dashboard-v0.json +++ b/apps/dashboard/public/data/flowmemory-dashboard-v0.json @@ -1993,11 +1993,11 @@ ], "devnetBlocks": [ { - "id": "0x61e9f90b982f13988e85a382fc39da82c9114ecceea9001ab454c744e0801a9b", + "id": "0xd890758bdeaa4ab7a658d7813b2b9bdd0850824aaef830b9f2f4ff455aaf3811", "blockNumber": 1, - "blockHash": "0x61e9f90b982f13988e85a382fc39da82c9114ecceea9001ab454c744e0801a9b", + "blockHash": "0xd890758bdeaa4ab7a658d7813b2b9bdd0850824aaef830b9f2f4ff455aaf3811", "parentHash": "0x0f23c892cbd2d00c10839d97ddab833698a83f8df8d6df27ceac03cfdd4b7bc9", - "stateRoot": "0x8c7c1e7a078b60a809d17a51c44e275059afb8d7535769430c3fc9e9320c7e23", + "stateRoot": "0x7e50021bc0dd2537f9ecfd7973f9799b6ffdffcfd427e3bb059f4dfd1253b947", "receiptsRoot": "0x2f98caf4b28b2209cdf1f9beb1c23f8732c538657cc7a1d8855878b5400efabd", "timestamp": "2026-05-13T16:00:00.000Z", "observationCount": 8, @@ -2015,11 +2015,11 @@ } }, { - "id": "0x7ddb184c69f798f25f27a254f1f530c6cdc31c9656ac19d1b8c114f7a3a650c6", + "id": "0x64e3d605fb1bba40b7de6a6a9813f77619282c07b9a33c7b41df1f6e3d71958b", "blockNumber": 2, - "blockHash": "0x7ddb184c69f798f25f27a254f1f530c6cdc31c9656ac19d1b8c114f7a3a650c6", - "parentHash": "0x61e9f90b982f13988e85a382fc39da82c9114ecceea9001ab454c744e0801a9b", - "stateRoot": "0x3074ef2e5311d94e8f9a2660a6cc016c7b7f9a08c56ee07f9e841c1489726e68", + "blockHash": "0x64e3d605fb1bba40b7de6a6a9813f77619282c07b9a33c7b41df1f6e3d71958b", + "parentHash": "0xd890758bdeaa4ab7a658d7813b2b9bdd0850824aaef830b9f2f4ff455aaf3811", + "stateRoot": "0xb3c4344483c7be8c0660d49d465d033a88ae26d6a7c1cbe096a2447d32c5648c", "receiptsRoot": "0xa0407b9a8a55106d549e0f19b92fceaa7f7a25697e94ebf8a1fa74af7b9168f4", "timestamp": "2026-05-13T16:00:01.000Z", "observationCount": 8, diff --git a/apps/dashboard/src/App.tsx b/apps/dashboard/src/App.tsx index 750ab52e..9d67d2eb 100644 --- a/apps/dashboard/src/App.tsx +++ b/apps/dashboard/src/App.tsx @@ -6,6 +6,7 @@ import { DEFAULT_CANARY_DASHBOARD_DATA_PATH, fetchDashboardData } from "./data/l import type { DashboardData } from "./data/types"; import { DEFAULT_CONTROL_PLANE_URL, buildWorkbenchSnapshot, fetchWorkbenchSnapshot, type WorkbenchSnapshot } from "./data/workbench"; import { AlertsView } from "./views/AlertsView"; +import { BridgePilotView } from "./views/BridgePilotView"; import { CanaryDeploymentView } from "./views/CanaryDeploymentView"; import { DevnetBlocksView } from "./views/DevnetBlocksView"; import { FlowMemoryView } from "./views/FlowMemoryView"; @@ -116,6 +117,7 @@ export default function App() { setVersion((current) => current + 1)} />} /> + } /> } /> } /> } /> diff --git a/apps/dashboard/src/components/AppShell.tsx b/apps/dashboard/src/components/AppShell.tsx index 765d1308..2745ebc6 100644 --- a/apps/dashboard/src/components/AppShell.tsx +++ b/apps/dashboard/src/components/AppShell.tsx @@ -8,6 +8,7 @@ import { BrainCircuit, Boxes, ClipboardCheck, + ArrowRightLeft, RadioReceiver, LayoutDashboard, Monitor, @@ -28,6 +29,7 @@ interface AppShellProps { const NAV_ITEMS = [ { to: "/", label: "Workbench", icon: Monitor }, + { to: "/bridge", label: "Bridge pilot", icon: ArrowRightLeft }, { to: "/overview", label: "Overview", icon: LayoutDashboard }, { to: "/canary", label: "Base canary", icon: RadioReceiver }, { to: "/flowmemory", label: "Flow Memory", icon: BrainCircuit }, @@ -43,6 +45,11 @@ const NAV_ITEMS = [ export function AppShell({ data, canaryData, workbench, children }: AppShellProps) { const location = useLocation(); + const isBridgeRoute = location.pathname.startsWith("/bridge"); + if (isBridgeRoute) { + return <>{children}; + } + const isCanaryRoute = location.pathname.startsWith("/canary"); const activeData = isCanaryRoute && canaryData ? canaryData : data; const bannerMode = isCanaryRoute diff --git a/apps/dashboard/src/data/workbench.ts b/apps/dashboard/src/data/workbench.ts index c33e48d8..93afd5af 100644 --- a/apps/dashboard/src/data/workbench.ts +++ b/apps/dashboard/src/data/workbench.ts @@ -77,6 +77,10 @@ export interface ControlPlaneProbe { health?: unknown; state?: unknown; pilotStatus?: unknown; + bridgeLiveReadiness?: unknown; + pilotLifecycle?: unknown; + walletBalances?: unknown; + walletTransfers?: unknown; } export interface WorkbenchNodeStatus { @@ -115,6 +119,10 @@ export interface WorkbenchSnapshot { devnetDashboardState: unknown | null; bridgeTestDeposit: unknown | null; controlPlanePilotStatus: unknown | null; + controlPlaneBridgeReadiness: unknown | null; + controlPlanePilotLifecycle: unknown | null; + controlPlaneWalletBalances: unknown | null; + controlPlaneWalletTransfers: unknown | null; controlPlaneHealth: unknown | null; controlPlaneState: unknown | null; }; @@ -246,8 +254,8 @@ export const WORKBENCH_SECTIONS: WorkbenchSectionDefinition[] = [ { key: "realValuePilot", label: "Real-Value Pilot", - detail: "Capped owner-testing lifecycle for Base deposit observation, local credit, replay/retry status, withdrawal intent, release evidence, caps, pause, and emergency state.", - expectedEndpoint: "GET /pilot/status + POST /rpc pilot_status", + detail: "Capped owner-testing lifecycle for Base deposit observation, exact local credit, wallet transferability, withdrawal/release evidence, readiness blockers, caps, pause, and emergency state.", + expectedEndpoint: "GET /bridge/live-readiness + GET /pilot/lifecycle + GET /pilot/status", missingCommand: "npm run control-plane:serve", missingService: "FlowChain real-value pilot control-plane /pilot/status", }, @@ -494,16 +502,23 @@ function stringArray(value: unknown): string[] { function statusFrom(value: unknown, fallback: DashboardStatus = "observed"): DashboardStatus { const normalized = text(value, fallback).toLowerCase(); - if (normalized === "applied" || normalized === "success" || normalized === "active" || normalized === "live") { + if ( + normalized === "applied" || + normalized === "success" || + normalized === "active" || + normalized === "live" || + normalized === "ready_for_operator_live_pilot" || + normalized === "ready" + ) { return "verified"; } if (normalized === "finalized") { return "finalized"; } - if (normalized === "failed" || normalized === "invalid" || normalized === "reverted") { + if (normalized === "failed" || normalized === "invalid" || normalized === "reverted" || normalized === "failure") { return "failed"; } - if (normalized === "pending" || normalized === "local-placeholder" || normalized === "degraded") { + if (normalized === "pending" || normalized === "local-placeholder" || normalized === "degraded" || normalized === "blocked") { return "pending"; } if (normalized === "error") { @@ -698,12 +713,24 @@ async function fetchOptionalJson(path: string): Promise<{ value: unknown | null; async function probeControlPlane(): Promise { const url = getControlPlaneUrl(); const checkedAt = new Date().toISOString(); - const defaultEndpoints = ["GET /health", "GET /state", "GET /pilot/status"]; + const defaultEndpoints = [ + "GET /health", + "GET /state", + "GET /pilot/status", + "GET /bridge/live-readiness", + "GET /pilot/lifecycle", + "GET /wallets/balances", + "GET /wallets/transfers", + ]; try { const health = await fetchJsonWithTimeout(`${url}/health`, CONTROL_PLANE_TIMEOUT_MS); let state: unknown | undefined; let pilotStatus: unknown | undefined; + let bridgeLiveReadiness: unknown | undefined; + let pilotLifecycle: unknown | undefined; + let walletBalances: unknown | undefined; + let walletTransfers: unknown | undefined; try { pilotStatus = await fetchJsonWithTimeout(`${url}/pilot/status`, CONTROL_PLANE_TIMEOUT_MS); @@ -711,6 +738,30 @@ async function probeControlPlane(): Promise { pilotStatus = undefined; } + try { + bridgeLiveReadiness = await fetchJsonWithTimeout(`${url}/bridge/live-readiness`, CONTROL_PLANE_TIMEOUT_MS); + } catch { + bridgeLiveReadiness = undefined; + } + + try { + pilotLifecycle = await fetchJsonWithTimeout(`${url}/pilot/lifecycle`, CONTROL_PLANE_TIMEOUT_MS); + } catch { + pilotLifecycle = undefined; + } + + try { + walletBalances = await fetchJsonWithTimeout(`${url}/wallets/balances`, CONTROL_PLANE_TIMEOUT_MS); + } catch { + walletBalances = undefined; + } + + try { + walletTransfers = await fetchJsonWithTimeout(`${url}/wallets/transfers`, CONTROL_PLANE_TIMEOUT_MS); + } catch { + walletTransfers = undefined; + } + try { state = await fetchJsonWithTimeout(`${url}/state`, CONTROL_PLANE_TIMEOUT_MS); } catch (error) { @@ -721,6 +772,10 @@ async function probeControlPlane(): Promise { endpoints: uniqueEndpoints(defaultEndpoints, collectEndpointHints(health)), health, pilotStatus, + bridgeLiveReadiness, + pilotLifecycle, + walletBalances, + walletTransfers, error: `Health endpoint responded, but state endpoint was not loaded: ${ error instanceof Error ? error.message : "unknown state error" }`, @@ -735,6 +790,10 @@ async function probeControlPlane(): Promise { health, state, pilotStatus, + bridgeLiveReadiness, + pilotLifecycle, + walletBalances, + walletTransfers, }; } catch (error) { return { @@ -1106,8 +1165,232 @@ function commandFromStep(step: unknown): string { return isRecord(step) ? text(step.command, "npm run flowchain:real-value-pilot:e2e") : "npm run flowchain:real-value-pilot:e2e"; } +function readinessStatus(value: unknown): DashboardStatus { + const normalized = text(value, "BLOCKED").toUpperCase(); + if (normalized === "READY_FOR_OPERATOR_LIVE_PILOT") { + return "verified"; + } + if (normalized === "FAILED") { + return "failed"; + } + return "pending"; +} + +function readinessPayload(controlPlane: ControlPlaneProbe, pilot: UnknownRecord | null): UnknownRecord | null { + if (isRecord(controlPlane.bridgeLiveReadiness)) { + return controlPlane.bridgeLiveReadiness; + } + if (isRecord(pilot?.bridgeLiveReadiness)) { + return pilot.bridgeLiveReadiness as UnknownRecord; + } + return null; +} + +function bridgeReadinessRecord(controlPlane: ControlPlaneProbe, readiness: UnknownRecord | null): WorkbenchRecord { + if (!readiness) { + return makeLocalRecord( + "devnet", + controlPlane.url, + { + id: "bridge-live-readiness", + kind: "Bridge live readiness", + title: "Bridge live readiness BLOCKED", + summary: "The live readiness endpoint is unavailable; operator live pilot remains fail-closed until the control-plane returns readiness details.", + status: controlPlane.status === "available" ? "pending" : "offline", + facts: [ + { label: "fail-closed status", value: "BLOCKED" }, + { label: "base chain", value: "8453" }, + { label: "missing env names", value: "endpoint unavailable" }, + { label: "env values printed", value: "false" }, + { label: "mock presented as live", value: "false" }, + { label: "owner verified lockbox", value: "false" }, + ], + raw: { endpoint: "/bridge/live-readiness", status: "unavailable" }, + }, + controlPlane.checkedAt, + ); + } + + const node = isRecord(readiness.node) ? readiness.node : {}; + const lockbox = isRecord(readiness.lockbox) ? readiness.lockbox : {}; + const confirmationDepth = isRecord(readiness.confirmationDepth) ? readiness.confirmationDepth : {}; + const artifacts = isRecord(readiness.currentArtifacts) ? readiness.currentArtifacts : {}; + const missingEnvNames = stringArray(readiness.missingEnvNames); + const failClosedStatus = text(readiness.failClosedStatus, "BLOCKED"); + + return makeLocalRecord( + "devnet", + controlPlane.url, + { + id: "bridge-live-readiness", + kind: "Bridge live readiness", + title: `Bridge live readiness ${failClosedStatus}`, + summary: + missingEnvNames.length > 0 + ? `Fail-closed with missing env names: ${missingEnvNames.join(", ")}.` + : text(readiness.machineStatus, "Live readiness is available from the control-plane."), + status: readinessStatus(failClosedStatus), + facts: [ + { label: "fail-closed status", value: failClosedStatus }, + { label: "base chain", value: `${text(readiness.baseChainName, "Base")} ${text(readiness.baseChainId, "8453")}` }, + { label: "node running", value: text(node.running, "false") }, + { label: "lockbox configured", value: text(lockbox.configured, "false") }, + { label: "confirmation configured", value: text(confirmationDepth.configured, "false") }, + { label: "missing env names", value: missingEnvNames.join(", ") || "none" }, + { label: "env values printed", value: text(readiness.envValuesPrinted, "false") }, + { label: "mock presented as live", value: text(artifacts.mockPresentedAsLive, "false") }, + ], + raw: readiness, + }, + controlPlane.checkedAt, + ); +} + +function bridgeReadinessIssueRecords(controlPlane: ControlPlaneProbe, readiness: UnknownRecord | null): WorkbenchRecord[] { + if (!readiness) { + return []; + } + + return collectionFrom(readiness, ["issues"]).map((issue, index) => + makeLocalRecord( + "devnet", + controlPlane.url, + { + id: `bridge-readiness-issue:${text(issue.reasonCode, String(index + 1))}`, + kind: "Operational empty/error state", + title: text(issue.title, "Bridge readiness issue"), + summary: text(issue.summary, "A live-pilot readiness issue is visible in the control-plane response."), + status: statusFrom(issue.status, "pending"), + facts: [ + { label: "reason code", value: text(issue.reasonCode) }, + { label: "status", value: text(issue.status, "blocked") }, + { label: "env names", value: stringArray(issue.envNames).join(", ") || "none" }, + { label: "machine readable", value: "true" }, + ], + raw: issue, + }, + controlPlane.checkedAt, + ), + ); +} + +function lifecycleRows(controlPlane: ControlPlaneProbe, pilot: UnknownRecord | null): UnknownRecord[] { + if (isRecord(controlPlane.pilotLifecycle)) { + return collectionFrom(controlPlane.pilotLifecycle, ["lifecycleRecords"]); + } + if (pilot) { + return collectionFrom(pilot, ["lifecycleRecords"]); + } + return []; +} + +function bridgeLifecycleRecord(controlPlane: ControlPlaneProbe, row: UnknownRecord, index: number): WorkbenchRecord { + const equality = isRecord(row.equality) ? row.equality : {}; + const equalities = isRecord(equality.equalities) ? equality.equalities : {}; + const depositObservation = isRecord(row.depositObservation) ? row.depositObservation : {}; + const withdrawalIntent = isRecord(row.withdrawalIntent) ? row.withdrawalIntent : {}; + const releaseEvidence = isRecord(row.releaseEvidence) ? row.releaseEvidence : {}; + const liveArtifact = row.liveArtifact === true; + const artifactClass = text(row.artifactClass, liveArtifact ? "live-base8453" : "local-or-mock"); + const baseTxHash = text(row.baseTxHash ?? row.txHash, `lifecycle:${index + 1}`); + const creditId = text(row.creditId, "credit pending"); + const amount = text(row.amountSmallestUnits ?? equality.depositAmount, "0"); + const replayKey = text(row.replayKey ?? depositObservation.replayKey); + const withdrawalIntentId = text(row.withdrawalIntentId ?? withdrawalIntent.withdrawalIntentId); + const releaseEvidenceId = text(row.releaseEvidenceId ?? releaseEvidence.releaseEvidenceId); + + return makeLocalRecord( + "devnet", + controlPlane.url, + { + id: text(row.lifecycleRecordId, `${baseTxHash}:${text(row.logIndex, String(index))}`), + kind: "Bridge exact lifecycle", + title: `${baseTxHash} / ${creditId}`, + summary: `${artifactClass} record with deposit, observed, credited, wallet delta, transferable, withdrawal, and release amount equality ${text(equality.allEqual, "false")}.`, + status: statusFrom(row.status, "pending"), + facts: [ + { label: "base tx hash", value: baseTxHash }, + { label: "log index", value: text(row.logIndex) }, + { label: "deposit id", value: text(row.depositId ?? depositObservation.depositId) }, + { label: "replay key", value: replayKey }, + { label: "replay status", value: text(row.replayStatus ?? depositObservation.replayStatus) }, + { label: "credit id", value: creditId }, + { label: "recipient wallet", value: text(row.recipientWallet) }, + { label: "withdrawal intent", value: withdrawalIntentId }, + { label: "withdrawal status", value: text(row.withdrawalStatus ?? withdrawalIntent.status) }, + { label: "release evidence", value: releaseEvidenceId }, + { label: "release status", value: text(row.releaseStatus ?? releaseEvidence.status) }, + { label: "asset", value: text(row.asset) }, + { label: "amount smallest units", value: amount }, + { label: "deposit amount", value: text(equality.depositAmount) }, + { label: "credited amount", value: text(equality.creditedAmount) }, + { label: "withdrawal amount", value: text(equality.withdrawalAmount) }, + { label: "release amount", value: text(equality.releaseAmount) }, + { label: "all values equal", value: text(equality.allEqual, "false") }, + { label: "wallet delta equal", value: text(equalities.walletDelta, "false") }, + { label: "evidence path", value: text(row.evidenceFilePath) }, + ], + raw: row, + }, + controlPlane.checkedAt, + ); +} + +function buildControlPlaneWalletBalanceRecords(controlPlane: ControlPlaneProbe): WorkbenchRecord[] { + return collectionFrom(controlPlane.walletBalances, ["balances"]).map((balance, index) => + makeLocalRecord( + "devnet", + controlPlane.url, + { + id: text(balance.balanceId, `wallet-balance:${index + 1}`), + kind: "Wallet balance", + title: text(balance.walletAddress, `wallet:${index + 1}`), + summary: `Wallet balance ${text(balance.status, "observed")} for ${text(balance.asset, "asset")} is ${text(balance.amount, "0")} smallest units.`, + status: statusFrom(balance.status, "observed"), + facts: [ + { label: "wallet", value: text(balance.walletAddress) }, + { label: "asset", value: text(balance.asset) }, + { label: "amount", value: text(balance.amount, "0") }, + { label: "previous amount", value: text(balance.previousAmount) }, + { label: "credit id", value: text(balance.creditId) }, + { label: "transfer id", value: text(balance.transferId) }, + ], + raw: balance, + }, + controlPlane.checkedAt, + ), + ); +} + +function buildControlPlaneWalletTransferRecords(controlPlane: ControlPlaneProbe): WorkbenchRecord[] { + return collectionFrom(controlPlane.walletTransfers, ["transfers"]).map((transfer, index) => + makeLocalRecord( + "devnet", + controlPlane.url, + { + id: text(transfer.transferId ?? transfer.txId, `wallet-transfer:${index + 1}`), + kind: "Wallet transfer history", + title: text(transfer.txId ?? transfer.transferId, `transfer:${index + 1}`), + summary: `${text(transfer.amount, "0")} ${text(transfer.assetId, "asset")} transferred from ${text(transfer.fromAccountId)} to ${text(transfer.toAccountId)}.`, + status: statusFrom(transfer.status, "observed"), + facts: [ + { label: "from wallet", value: text(transfer.fromAccountId) }, + { label: "to wallet", value: text(transfer.toAccountId) }, + { label: "asset", value: text(transfer.assetId) }, + { label: "amount", value: text(transfer.amount, "0") }, + { label: "status", value: text(transfer.status) }, + { label: "evidence path", value: text(transfer.evidenceFilePath) }, + ], + raw: transfer, + }, + controlPlane.checkedAt, + ), + ); +} + function buildPilotRecords(controlPlane: ControlPlaneProbe): WorkbenchRecord[] { const pilot = isRecord(controlPlane.pilotStatus) ? controlPlane.pilotStatus : null; + const readiness = readinessPayload(controlPlane, pilot); const records: WorkbenchRecord[] = []; if (!pilot) { @@ -1132,6 +1415,10 @@ function buildPilotRecords(controlPlane: ControlPlaneProbe): WorkbenchRecord[] { controlPlane.checkedAt, ), ); + records.push(bridgeReadinessRecord(controlPlane, readiness)); + records.push(...bridgeReadinessIssueRecords(controlPlane, readiness)); + records.push(...buildControlPlaneWalletBalanceRecords(controlPlane)); + records.push(...buildControlPlaneWalletTransferRecords(controlPlane)); return records; } @@ -1167,6 +1454,14 @@ function buildPilotRecords(controlPlane: ControlPlaneProbe): WorkbenchRecord[] { ), ); + records.push(bridgeReadinessRecord(controlPlane, readiness)); + records.push(...bridgeReadinessIssueRecords(controlPlane, readiness)); + lifecycleRows(controlPlane, pilot).forEach((row, index) => { + records.push(bridgeLifecycleRecord(controlPlane, row, index)); + }); + records.push(...buildControlPlaneWalletBalanceRecords(controlPlane)); + records.push(...buildControlPlaneWalletTransferRecords(controlPlane)); + lifecycle.forEach((step, index) => { records.push( makeLocalRecord( @@ -1902,6 +2197,10 @@ function buildRawJsonRecords( raw: { health: controlPlane.health ?? null, state: controlPlane.state ?? null, + bridgeLiveReadiness: controlPlane.bridgeLiveReadiness ?? null, + pilotLifecycle: controlPlane.pilotLifecycle ?? null, + walletBalances: controlPlane.walletBalances ?? null, + walletTransfers: controlPlane.walletTransfers ?? null, error: controlPlane.error ?? null, }, }, @@ -2093,7 +2392,7 @@ export function buildWorkbenchSnapshot( transactions: buildTransactionRecords(data, activeDevnetState), mempool: buildMempoolRecords(activeDevnetState), accounts: buildAccountRecords(activeDevnetState), - balances: buildBalanceRecords(activeDevnetState), + balances: [...buildBalanceRecords(activeDevnetState), ...buildControlPlaneWalletBalanceRecords(controlPlane)], faucetEvents: buildFaucetEventRecords(activeDevnetState), walletMetadata: buildWalletMetadataRecords(activeDevnetState), tokenLaunches: buildTokenLaunchRecords(activeDevnetState), @@ -2152,6 +2451,10 @@ export function buildWorkbenchSnapshot( devnetDashboardState: options.devnetDashboardState ?? null, bridgeTestDeposit, controlPlanePilotStatus: controlPlane.pilotStatus ?? null, + controlPlaneBridgeReadiness: controlPlane.bridgeLiveReadiness ?? null, + controlPlanePilotLifecycle: controlPlane.pilotLifecycle ?? null, + controlPlaneWalletBalances: controlPlane.walletBalances ?? null, + controlPlaneWalletTransfers: controlPlane.walletTransfers ?? null, controlPlaneHealth: controlPlane.health ?? null, controlPlaneState: controlPlane.state ?? null, }, diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css index a26271c5..708011db 100644 --- a/apps/dashboard/src/styles.css +++ b/apps/dashboard/src/styles.css @@ -67,6 +67,12 @@ input { color: var(--ink); } +input[type="checkbox"] { + width: 16px; + height: 16px; + accent-color: var(--accent); +} + select { min-height: 38px; padding: 0 12px; @@ -260,1264 +266,2850 @@ small { padding: 24px; } -.view-stack { - display: grid; - gap: 22px; +.flowchain-bridge-page { + position: relative; + min-height: 100dvh; + overflow: hidden; + background: + radial-gradient(circle at 50% 18%, rgba(255, 255, 255, 0.86), rgba(255, 255, 255, 0) 38rem), + radial-gradient(circle at 18% 62%, rgba(41, 123, 255, 0.18), rgba(41, 123, 255, 0) 22rem), + linear-gradient(180deg, #f4ebdf 0%, #efe2d1 48%, #ead9c5 100%); + color: #1c1a17; +} + +.flowchain-bridge-page::before { + position: fixed; + inset: 0; + z-index: 0; + pointer-events: none; + content: ""; + opacity: 0.42; + background-image: + linear-gradient(rgba(58, 39, 18, 0.035) 1px, transparent 1px), + linear-gradient(90deg, rgba(58, 39, 18, 0.025) 1px, transparent 1px); + background-size: 9px 9px, 11px 11px; + mix-blend-mode: multiply; +} + +.flowchain-bridge-page::after { + position: fixed; + inset: auto -12vw 16dvh -12vw; + z-index: 0; + height: 27dvh; + pointer-events: none; + content: ""; + opacity: 0.9; + background: + radial-gradient(ellipse at 12% 54%, rgba(0, 77, 208, 0.52), rgba(0, 113, 255, 0.18) 34%, transparent 67%), + radial-gradient(ellipse at 42% 44%, rgba(0, 94, 255, 0.72), rgba(0, 111, 255, 0.34) 28%, transparent 66%), + radial-gradient(ellipse at 76% 55%, rgba(0, 91, 229, 0.58), rgba(0, 127, 255, 0.22) 32%, transparent 68%); + filter: blur(10px) saturate(1.12); + transform: rotate(-3deg) skewX(-11deg); +} + +.bridge-flow-ribbon { + position: fixed; + inset: auto -10vw 26dvh -10vw; + z-index: 0; + height: 19dvh; + pointer-events: none; + opacity: 0.86; + background: + linear-gradient(92deg, transparent 0%, rgba(0, 95, 255, 0.2) 14%, rgba(12, 103, 255, 0.72) 42%, rgba(21, 127, 255, 0.7) 64%, transparent 100%), + radial-gradient(ellipse at 35% 45%, rgba(11, 82, 213, 0.85), rgba(29, 130, 255, 0.18) 42%, transparent 70%); + border-radius: 999px 18% 999px 28%; + filter: blur(3px); + transform: rotate(4deg) skewX(-18deg); } -.section-header { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 18px; - align-items: end; +.flowchain-bridge-nav, +.flowchain-bridge-main { + position: relative; + z-index: 1; } -.section-header h1 { - margin: 0; - font-size: 1.8rem; - line-height: 1.15; +.flowchain-bridge-nav { + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; + width: min(100%, 1720px); + margin: 0 auto; + padding: 24px 34px 0; } -.section-header p { - max-width: 76ch; - margin: 8px 0 0; - color: var(--muted); - line-height: 1.5; +.flowchain-brand, +.flowchain-wallet-pill { + display: inline-flex; + align-items: center; + text-decoration: none; } -.section-action { - min-width: min(520px, 100%); +.flowchain-brand { + gap: 10px; + color: #17213a; } -.workbench-header-actions { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 10px; - align-items: center; +.flowchain-brand strong { + font-family: ui-serif, Georgia, "Times New Roman", serif; + font-size: clamp(1.45rem, 2.4vw, 2rem); + font-weight: 600; } -.filter-row { - display: grid; - grid-template-columns: minmax(240px, 1fr) 150px; - gap: 10px; +.flowchain-brand-mark { + position: relative; + width: 52px; + height: 30px; } -.search-box { - display: grid; - grid-template-columns: 18px 1fr; - gap: 8px; - align-items: center; - min-height: 38px; - padding: 0 10px; - border: 1px solid var(--line-strong); - border-radius: 7px; - background: var(--surface); +.flowchain-brand-mark, +.flowchain-brand-mark span { + display: block; } -.metric-grid { - display: grid; - grid-template-columns: repeat(5, minmax(0, 1fr)); - gap: 10px; +.flowchain-brand-mark::before, +.flowchain-brand-mark::after, +.flowchain-brand-mark span { + position: absolute; + left: 0; + width: 48px; + height: 12px; + content: ""; + border: solid #256bda; + border-width: 3px 0 0; + border-radius: 50%; + transform-origin: left center; } -.flowmemory-hero { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(280px, 0.34fr); - gap: 14px; - align-items: stretch; - border: 1px solid var(--line); - border-radius: 8px; - background: linear-gradient(135deg, #fbfcf8 0%, #edf4ef 100%); - box-shadow: var(--shadow); +.flowchain-brand-mark::before { + top: 4px; + transform: rotate(15deg); } -.flowmemory-hero-main { - display: grid; - gap: 12px; - padding: 18px; +.flowchain-brand-mark span { + top: 11px; + border-color: #1d54b6; + transform: rotate(-8deg); } -.flowmemory-hero-main h2 { - max-width: 780px; - margin: 0; - font-size: 1.48rem; - line-height: 1.14; +.flowchain-brand-mark::after { + top: 18px; + width: 36px; + border-color: #5ba2ff; + transform: rotate(20deg); } -.flowmemory-hero-main p { - max-width: 82ch; - margin: 0; - color: #465047; - line-height: 1.5; +.flowchain-wallet-pill { + gap: 9px; + min-height: 44px; + padding: 0 16px; + border: 1px solid rgba(98, 76, 52, 0.14); + border-radius: 8px; + background: rgba(248, 241, 231, 0.84); + box-shadow: 0 14px 34px rgba(44, 31, 17, 0.13); + color: #30281f; + font-size: 0.9rem; + backdrop-filter: blur(18px); + transition: transform 180ms ease, border-color 180ms ease, background 180ms ease; } -.flowmemory-spine { - display: grid; - grid-template-columns: repeat(5, minmax(0, 1fr)); - gap: 8px; +.flowchain-wallet-pill:hover { + border-color: rgba(37, 107, 218, 0.32); + background: rgba(255, 250, 243, 0.92); } -.flowmemory-spine span { - min-height: 34px; - padding: 8px 9px; - color: #284238; - border: 1px solid #bfd1c7; - border-radius: 7px; - background: rgba(251, 252, 248, 0.78); - font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; - font-size: 0.74rem; - text-align: center; +.wallet-orb { + width: 19px; + height: 19px; + border-radius: 50%; + background: + radial-gradient(circle at 36% 32%, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0) 38%), + linear-gradient(135deg, #79c2ff, #0b63ff 70%); + box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.52), 0 7px 16px rgba(0, 91, 255, 0.28); } -.flowmemory-hero-side { +.flowchain-bridge-main { display: grid; - gap: 1px; - min-width: 0; - padding: 1px; - border-left: 1px solid var(--line); + gap: 20px; + width: min(100%, 1540px); + margin: 0 auto; + padding: 14px 34px 46px; } -.flowmemory-hero-side div { +.bridge-title-block { display: grid; - gap: 5px; - align-content: center; - min-width: 0; - padding: 12px; - background: rgba(251, 252, 248, 0.72); + justify-items: center; + text-align: center; } -.canary-hero { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(300px, 0.36fr); - gap: 14px; - align-items: stretch; - padding: 16px; - border: 1px solid #c9d5ce; - border-radius: 8px; - background: - linear-gradient(135deg, rgba(251, 252, 248, 0.94), rgba(229, 239, 235, 0.92)), - linear-gradient(90deg, rgba(47, 125, 107, 0.18), transparent); - box-shadow: var(--shadow); +.bridge-title-block span { + color: rgba(49, 40, 31, 0.68); + font-size: 0.76rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; } -.canary-hero h2 { - max-width: 760px; +.bridge-title-block h1 { + margin: 8px 0 8px; + color: #211b16; + font-family: ui-serif, Georgia, "Times New Roman", serif; + font-size: clamp(3.05rem, 5.7vw, 5.25rem); + font-weight: 500; + line-height: 0.95; + text-wrap: balance; +} + +.bridge-title-block p { + width: min(560px, 100%); + max-width: 560px; margin: 0; - font-size: 1.52rem; - line-height: 1.14; + color: rgba(47, 39, 31, 0.7); + font-family: ui-serif, Georgia, "Times New Roman", serif; + font-size: 1.18rem; + line-height: 1.34; + text-wrap: balance; } -.canary-hero p { - max-width: 84ch; - margin: 10px 0 0; - color: #465047; - line-height: 1.5; +.flowchain-bridge-layout { + display: grid; + grid-template-columns: minmax(210px, 270px) minmax(520px, 650px) minmax(220px, 270px); + gap: clamp(20px, 5vw, 92px); + align-items: center; + justify-content: center; } -.canary-read-window { +.bridge-side-card, +.bridge-console { + border: 1px solid rgba(89, 68, 45, 0.14); + background: rgba(249, 241, 229, 0.74); + box-shadow: 0 28px 68px rgba(46, 31, 15, 0.14), inset 0 1px 0 rgba(255, 255, 255, 0.48); + backdrop-filter: blur(18px); +} + +.bridge-side-card { display: grid; - grid-template-columns: 28px 1fr; - gap: 10px; - align-items: start; - padding: 12px; - border: 1px solid #bfd1c7; + gap: 16px; + padding: 24px; border-radius: 8px; - background: rgba(251, 252, 248, 0.78); } -.canary-read-window dl { - display: grid; - gap: 9px; +.bridge-card-heading { + display: flex; + gap: 10px; + align-items: center; +} + +.bridge-card-heading h2 { margin: 0; + color: #281f18; + font-family: ui-serif, Georgia, "Times New Roman", serif; + font-size: 1.18rem; + font-weight: 500; } -.canary-operator-strip { +.bridge-transfer-list, +.bridge-side-stack, +.bridge-route-list, +.bridge-help-card { display: grid; - grid-template-columns: 1.15fr 1fr 1.1fr; - gap: 10px; + gap: 12px; } -.canary-operator-strip article { +.bridge-transfer-row { display: grid; - gap: 7px; - min-width: 0; - padding: 12px; - border: 1px solid #c9d5ce; - border-radius: 8px; - background: #fbfcf8; - box-shadow: var(--shadow); + grid-template-columns: 28px minmax(0, 1fr) auto; + gap: 10px; + align-items: center; } -.canary-operator-strip span, -.canary-operator-strip strong { +.bridge-transfer-row strong, +.bridge-transfer-row span, +.bridge-readiness-line span, +.bridge-route-list strong, +.bridge-route-list span { overflow-wrap: anywhere; } -.canary-operator-strip span { - color: var(--muted); - font-size: 0.72rem; - font-weight: 800; - text-transform: uppercase; +.bridge-transfer-row strong { + display: block; + color: #221a14; + font-size: 0.9rem; + font-weight: 650; } -.canary-boundaries { - display: grid; - gap: 8px; - padding-top: 12px; +.bridge-transfer-row span { + color: rgba(42, 33, 25, 0.66); + font-size: 0.8rem; } -.canary-boundaries span { - padding: 9px 10px; - color: #4d564d; - border: 1px solid var(--line); - border-radius: 7px; - background: #f8f9f4; - line-height: 1.35; +.bridge-transfer-icon { + display: grid; + place-items: center; + width: 26px; + height: 26px; + color: #0f62db; + border-radius: 50%; + background: #eef5ff; } -.metric-tile, -.panel, -.table-panel, -.rootfield-tile, -.lane-tile, -.node-tile, -.alert-row, -.json-panel { - border: 1px solid var(--line); +.bridge-empty-transfer { + display: grid; + gap: 5px; + padding: 14px; + color: rgba(43, 34, 26, 0.68); + border: 1px dashed rgba(89, 68, 45, 0.18); border-radius: 8px; - background: var(--surface); - box-shadow: var(--shadow); + background: rgba(255, 248, 238, 0.62); + line-height: 1.36; } -.metric-tile { - display: grid; - gap: 8px; - padding: 14px; +.bridge-empty-transfer strong { + color: #271f18; } -.metric-tile > span { - color: var(--muted); - font-size: 0.75rem; - font-weight: 700; - text-transform: uppercase; +.bridge-text-link, +.bridge-help-card a, +.bridge-tx-link { + display: inline-flex; + gap: 8px; + align-items: center; + color: #0d57c4; + text-decoration: none; } -.metric-tile strong { - font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; - font-size: 1.65rem; +.bridge-text-link { + justify-content: center; + min-height: 40px; + border-radius: 8px; + background: rgba(255, 250, 243, 0.48); + font-weight: 650; } -.metric-tile div { - display: flex; - flex-wrap: wrap; - gap: 7px; - align-items: center; +.bridge-console { + display: grid; + gap: 14px; + min-width: 0; + padding: 24px; + border-radius: 10px; } -.overview-grid { +.bridge-route-row { display: grid; - grid-template-columns: minmax(0, 1.4fr) minmax(290px, 0.6fr); + grid-template-columns: minmax(0, 1fr) 140px minmax(0, 1fr); gap: 14px; + align-items: end; } -.panel { +.bridge-route-row label, +.bridge-field { min-width: 0; - padding: 14px; } -.panel-wide { - grid-column: span 1; +.bridge-route-row label > span, +.bridge-field > label, +.bridge-estimate-strip span, +.bridge-mini-facts dt, +.bridge-calldata-facts dt { + display: block; + margin-bottom: 6px; + color: rgba(54, 43, 33, 0.7); + font-size: 0.68rem; + font-weight: 750; + letter-spacing: 0.06em; + text-transform: uppercase; } -.panel-side-bottom { - grid-column: 2; +.bridge-select-shell, +.bridge-token-field > div, +.bridge-field { + border: 1px solid rgba(93, 72, 49, 0.13); + border-radius: 8px; + background: rgba(255, 250, 243, 0.68); + box-shadow: 0 9px 18px rgba(47, 32, 14, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.5); } -.panel-heading { - display: flex; - align-items: center; - justify-content: space-between; +.bridge-select-shell { + display: grid; + grid-template-columns: 32px minmax(0, 1fr) 18px; gap: 10px; - padding-bottom: 10px; - border-bottom: 1px solid var(--line); + align-items: center; + min-height: 58px; + padding: 0 14px; } -.panel-heading div, -.tile-heading, -.record-title, -.json-panel-header { - display: flex; - gap: 8px; - align-items: center; - min-width: 0; +.bridge-select-shell strong, +.bridge-token-field strong { + font-family: ui-serif, Georgia, "Times New Roman", serif; + font-size: 1.12rem; + font-weight: 500; } -.panel-heading h2 { - margin: 0; - font-size: 0.98rem; +.bridge-chain-icon, +.bridge-token-orb { + display: grid; + place-items: center; + color: #fff; + font-size: 0.72rem; + font-weight: 800; + border-radius: 50%; } -.panel-heading > span { - color: var(--muted); - font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; - font-size: 0.78rem; +.bridge-chain-icon { + width: 30px; + height: 30px; } -.record-list, -.compact-list, -.alert-list { - display: grid; +.bridge-chain-icon.base, +.bridge-token-orb { + background: linear-gradient(135deg, #89c6ff, #0a61dd 68%); } -.status-strip { - display: flex; - flex-wrap: wrap; - gap: 8px; - padding: 10px 0 2px; +.bridge-chain-icon.flow { + background: linear-gradient(135deg, #8bc7ff, #163d96 72%); } -.status-strip span { - display: inline-flex; - gap: 6px; +.bridge-route-connector { + display: grid; + grid-template-columns: 1fr 44px 1fr; + gap: 8px; align-items: center; - padding: 3px 6px 3px 3px; - border: 1px solid var(--line); - border-radius: 999px; - background: #f8f9f4; + padding-bottom: 7px; } -.status-strip strong { - font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; - font-size: 0.74rem; +.bridge-route-connector span { + height: 3px; + border-radius: 999px; + background-image: linear-gradient(90deg, rgba(12, 99, 221, 0.22) 0 45%, transparent 45% 100%); + background-size: 12px 3px; } -.record-row { +.bridge-route-connector button, +.bridge-icon-action { display: grid; - grid-template-columns: minmax(0, 1fr) 270px; - gap: 16px; - padding: 14px 0; - border-bottom: 1px solid var(--line); + place-items: center; + border: 1px solid rgba(38, 107, 218, 0.26); + background: rgba(244, 249, 255, 0.78); + color: #145ed3; + text-decoration: none; + transition: transform 180ms ease, border-color 180ms ease, background 180ms ease; } -.record-row:last-child { - border-bottom: 0; +.bridge-route-connector button { + width: 44px; + height: 44px; + border-radius: 50%; } -.record-row p, -.alert-row p { - margin: 8px 0; - color: #3f483f; - line-height: 1.45; +.bridge-field { + display: grid; + gap: 7px; + padding: 11px 14px; } -.record-facts, -.definition-grid, -.lane-stats, -.alert-facts { +.bridge-token-field { + grid-template-columns: minmax(0, 1fr); +} + +.bridge-token-field > div { display: grid; + grid-template-columns: 34px minmax(0, auto) 1fr; gap: 10px; - margin: 0; + align-items: center; + min-height: 52px; + padding: 0 12px; } -.record-facts { - grid-template-columns: repeat(2, minmax(0, 1fr)); +.bridge-token-field small { + color: rgba(50, 39, 29, 0.62); } -.record-facts div, -.definition-grid div, -.lane-stats div, -.alert-facts div { - min-width: 0; +.bridge-token-orb { + width: 30px; + height: 30px; } -dt { - color: var(--subtle); - font-size: 0.68rem; - font-weight: 700; - text-transform: uppercase; +.bridge-amount-field { + grid-template-columns: minmax(0, 1fr) auto; + align-items: end; } -dd { - margin: 3px 0 0; - overflow-wrap: anywhere; +.bridge-amount-field label, +.bridge-recipient-field label { + grid-column: 1 / -1; } -.compact-list article { - display: grid; - grid-template-columns: auto minmax(0, 1fr); - gap: 9px; - align-items: start; - padding: 12px 0; - border-bottom: 1px solid var(--line); +.bridge-amount-field input { + font-size: 1.18rem; } -.compact-list article:last-child { - border-bottom: 0; +.bridge-amount-field > span { + color: #211b16; + font-family: ui-serif, Georgia, "Times New Roman", serif; + font-size: 1.12rem; } -.compact-list strong, -.compact-list small { - display: block; - overflow-wrap: anywhere; +.bridge-recipient-field { + grid-template-columns: minmax(0, 1fr) 32px; + align-items: center; } -.contract-event-list { +.bridge-recipient-field button { display: grid; + place-items: center; + width: 30px; + height: 30px; + padding: 0; + border-color: transparent; + background: transparent; + color: rgba(47, 38, 29, 0.54); } -.contract-event-list article { - display: grid; - gap: 9px; - padding: 12px 0; - border-bottom: 1px solid var(--line); +.bridge-field input:not([type="checkbox"]) { + min-height: 28px; + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.82rem; } -.contract-event-list article:last-child { - border-bottom: 0; +.bridge-advanced { + display: grid; + gap: 12px; + color: rgba(46, 36, 28, 0.78); } -.contract-event-list article > div { +.bridge-advanced summary { display: flex; gap: 8px; align-items: center; - min-width: 0; + width: fit-content; + cursor: pointer; + color: rgba(41, 32, 24, 0.76); + font-size: 0.84rem; + font-weight: 650; } -.contract-event-list strong { - overflow-wrap: anywhere; - font-size: 0.84rem; +.bridge-advanced[open] summary { + margin-bottom: 10px; } -.contract-event-list dl { +.bridge-calldata-facts, +.bridge-mini-facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; + margin: 10px 0 0; +} + +.bridge-calldata-facts div, +.bridge-mini-facts div { + min-width: 0; + padding: 10px; + border: 1px solid rgba(89, 68, 45, 0.12); + border-radius: 8px; + background: rgba(255, 250, 243, 0.52); +} + +.bridge-calldata-facts dd, +.bridge-mini-facts dd { margin: 0; + overflow-wrap: anywhere; + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.74rem; + font-weight: 700; } -.bundle-grid { +.bridge-estimate-strip { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 10px; - padding-top: 14px; + overflow: hidden; + border-radius: 8px; + background: rgba(71, 50, 29, 0.06); } -.bundle-grid article { +.bridge-estimate-strip div { display: grid; - gap: 8px; + gap: 6px; min-width: 0; - padding: 12px; - border: 1px solid var(--line); - border-radius: 7px; - background: #f7f8f4; + padding: 14px; + text-align: center; + border-right: 1px solid rgba(92, 70, 47, 0.08); } -.bundle-grid strong, -.bundle-grid small { - display: block; - overflow-wrap: anywhere; +.bridge-estimate-strip div:last-child { + border-right: 0; } -.block-strip { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 10px; - padding-top: 14px; +.bridge-estimate-strip span { + margin-bottom: 0; } -.block-strip article { - display: grid; - gap: 7px; - padding: 12px; - border: 1px solid var(--line); - border-radius: 7px; - background: #f7f8f4; +.bridge-estimate-strip strong { + overflow-wrap: anywhere; + font-size: 0.9rem; + font-weight: 600; } -.workbench-command-center { +.bridge-ack { display: grid; - grid-template-columns: minmax(0, 1.08fr) minmax(360px, 0.92fr); - gap: 14px; + grid-template-columns: 18px minmax(0, 1fr); + gap: 10px; + align-items: start; + padding: 12px; + border: 1px solid rgba(93, 72, 49, 0.16); + border-radius: 8px; + background: rgba(255, 250, 243, 0.54); + color: rgba(38, 30, 23, 0.72); + font-size: 0.84rem; + line-height: 1.35; } -.workbench-boundary-strip { +.bridge-action-row { display: grid; - grid-template-columns: 1.05fr 1.15fr 1.2fr; + grid-template-columns: minmax(0, 1fr) 44px 44px; gap: 10px; } -.workbench-boundary-strip article { - display: grid; - gap: 6px; - min-width: 0; - padding: 12px; - border: 1px solid #cfd7cd; +.bridge-primary-action { + min-height: 56px; + justify-content: center; + border: 0; border-radius: 8px; - background: #f9faf6; + background: linear-gradient(135deg, #043aa9, #1b78ff); + color: #fff; + box-shadow: 0 16px 32px rgba(0, 78, 216, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.32); + font-family: ui-serif, Georgia, "Times New Roman", serif; + font-size: 1.08rem; + transition: transform 180ms ease, box-shadow 180ms ease, opacity 180ms ease; } -.workbench-boundary-strip strong { - font-size: 0.82rem; +.bridge-primary-action:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 20px 38px rgba(0, 78, 216, 0.34), inset 0 1px 0 rgba(255, 255, 255, 0.34); } -.workbench-boundary-strip span { - color: #4c554c; - font-size: 0.84rem; - line-height: 1.42; +.bridge-icon-action { + width: 44px; + height: 56px; + border-radius: 8px; } -.pilot-status-panel article { - display: grid; - gap: 14px; - min-width: 0; - padding: 14px; - border: 1px solid #b8c7c0; +.bridge-icon-action:hover, +.bridge-route-connector button:hover { + border-color: rgba(20, 94, 211, 0.42); + background: rgba(255, 255, 255, 0.86); +} + +.bridge-validation, +.bridge-status-message, +.bridge-tx-link { + margin: 0; + padding: 11px 12px; border-radius: 8px; - background: #f4f8f6; + font-size: 0.88rem; } -.pilot-status-body { - display: grid; - grid-template-columns: minmax(0, 0.92fr) minmax(320px, 1.08fr); - gap: 14px; - align-items: start; +.bridge-validation { + display: flex; + gap: 8px; + align-items: center; + color: #6f2525; + border: 1px solid #e3b5b5; + background: rgba(255, 240, 240, 0.86); } -.pilot-status-body h3 { - margin: 4px 0 8px; - font-size: 1.35rem; - text-transform: capitalize; +.bridge-status-message { + color: #234e44; + border: 1px solid #b9d8cf; + background: rgba(238, 248, 244, 0.86); } -.pilot-status-body p { - margin: 0; - color: #3f483f; - line-height: 1.48; +.bridge-tx-link { + justify-self: start; + border: 1px solid #b9d8cf; + background: rgba(238, 248, 244, 0.86); } -.product-surface-grid { +.bridge-safety-rail { display: grid; - grid-template-columns: 1.2fr 1fr 1.2fr; - gap: 10px; + grid-template-columns: repeat(3, minmax(0, 1fr)); + overflow: hidden; + margin: 4px -24px -24px; + border-top: 1px solid rgba(88, 67, 44, 0.11); + border-radius: 0 0 10px 10px; + background: rgba(241, 230, 216, 0.72); } -.product-surface { +.bridge-safety-rail span { display: grid; - grid-template-columns: 34px minmax(0, 1fr) auto auto; + grid-template-columns: 24px minmax(0, 1fr); gap: 10px; - align-items: start; + align-items: center; min-width: 0; - min-height: 118px; - padding: 12px; - text-align: left; - transition: background 140ms ease, border-color 140ms ease, transform 140ms ease; + padding: 15px 18px; + color: rgba(38, 30, 23, 0.74); + border-right: 1px solid rgba(88, 67, 44, 0.09); + font-size: 0.84rem; + line-height: 1.2; } -.product-surface:hover { - border-color: #9fb4a8; - background: #eaf1eb; +.bridge-safety-rail span:last-child { + border-right: 0; } -.product-surface-icon { - display: grid; - place-items: center; - width: 32px; - height: 32px; - color: #235f52; - border: 1px solid #bfd1c7; - border-radius: 7px; - background: #edf5ef; +.bridge-readiness-line { + display: flex; + gap: 10px; + align-items: center; + justify-content: space-between; } -.product-surface strong, -.product-surface small, -.product-surface code { - display: block; - overflow-wrap: anywhere; +.bridge-readiness-line > span { + color: rgba(38, 30, 23, 0.76); + font-size: 0.88rem; + font-weight: 650; } -.product-surface small { - margin-top: 4px; - line-height: 1.36; +.bridge-mini-facts { + grid-template-columns: repeat(2, minmax(0, 1fr)); } -.product-surface b { - color: var(--ink); - font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; - font-size: 1.1rem; - line-height: 1.1; +.bridge-route-list div { + display: grid; + grid-template-columns: 22px minmax(0, 1fr) auto; + gap: 10px; + align-items: center; + min-height: 28px; } -.workbench-node-body { +.bridge-chain-dot { + width: 19px; + height: 19px; + border-radius: 50%; + background: #b5aa9a; +} + +.bridge-chain-dot.active { + background: linear-gradient(135deg, #7fc2ff, #0b63ff); +} + +.bridge-chain-dot.staged { + background: linear-gradient(135deg, #d1c3ae, #a58f72); +} + +.bridge-route-list strong { + font-size: 0.88rem; + font-weight: 650; +} + +.bridge-route-list span:not(.bridge-chain-dot) { + color: rgba(43, 34, 26, 0.58); + font-size: 0.76rem; +} + +.bridge-help-card a { + justify-content: space-between; + min-height: 28px; + color: #2b251f; + font-size: 0.86rem; +} + +@media (max-width: 1280px) { + .flowchain-bridge-layout { + grid-template-columns: minmax(0, 650px); + } + + .bridge-recent-card, + .bridge-side-stack { + width: min(650px, 100%); + justify-self: center; + } +} + +@media (max-width: 720px) { + .flowchain-bridge-nav { + display: grid; + grid-template-columns: 1fr; + align-items: flex-start; + gap: 10px; + padding: 18px 16px 0; + } + + .flowchain-wallet-pill { + justify-self: start; + min-height: 38px; + padding: 0 10px; + } + + .flowchain-bridge-main { + padding: 18px 16px 38px; + } + + .flowchain-bridge-layout { + grid-template-columns: minmax(0, 1fr); + gap: 16px; + } + + .bridge-title-block h1 { + font-size: clamp(2.7rem, 16vw, 4rem); + } + + .bridge-title-block p { + max-width: 340px; + font-size: 1.08rem; + } + + .bridge-route-row, + .bridge-action-row, + .bridge-estimate-strip, + .bridge-safety-rail, + .bridge-calldata-facts, + .bridge-mini-facts { + grid-template-columns: 1fr; + } + + .bridge-route-connector { + grid-template-columns: 1fr 44px 1fr; + padding: 0; + } + + .bridge-console { + padding: 18px; + } + + .bridge-transfer-row { + grid-template-columns: 28px minmax(0, 1fr); + } + + .bridge-transfer-row .status-badge { + grid-column: 2; + justify-self: start; + } + + .bridge-route-list div { + grid-template-columns: 22px minmax(0, 1fr); + } + + .bridge-route-list span:not(.bridge-chain-dot) { + grid-column: 2; + } + + .bridge-safety-rail { + margin: 4px -18px -18px; + } +} + +button:disabled { + cursor: not-allowed; + opacity: 0.55; + transform: none; +} + +.view-stack { display: grid; - gap: 16px; - padding-top: 14px; + gap: 22px; } -.workbench-node-body h3 { - margin: 0 0 7px; - font-size: 1.22rem; +.section-header { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 18px; + align-items: end; } -.workbench-node-body p, -.workbench-section-detail, -.boundary-copy p { +.section-header h1 { margin: 0; - color: #465047; - line-height: 1.48; + font-size: 1.8rem; + line-height: 1.15; } -.workbench-fact-grid { +.section-header p { + max-width: 76ch; + margin: 8px 0 0; + color: var(--muted); + line-height: 1.5; +} + +.section-action { + min-width: min(520px, 100%); +} + +.workbench-header-actions { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-columns: minmax(0, 1fr) auto; gap: 10px; - margin: 0; + align-items: center; } -.workbench-fact-grid div { - min-width: 0; - padding: 10px; - border: 1px solid var(--line); +.filter-row { + display: grid; + grid-template-columns: minmax(240px, 1fr) 150px; + gap: 10px; +} + +.search-box { + display: grid; + grid-template-columns: 18px 1fr; + gap: 8px; + align-items: center; + min-height: 38px; + padding: 0 10px; + border: 1px solid var(--line-strong); border-radius: 7px; - background: #f7f8f4; + background: var(--surface); } -.setup-step-list { +.metric-grid { display: grid; - padding-top: 6px; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 10px; } -.setup-step-list article { +.flowmemory-hero { display: grid; - grid-template-columns: auto minmax(0, 1fr); - gap: 9px; - align-items: start; - padding: 11px 0; - border-bottom: 1px solid var(--line); + grid-template-columns: minmax(0, 1fr) minmax(280px, 0.34fr); + gap: 14px; + align-items: stretch; + border: 1px solid var(--line); + border-radius: 8px; + background: linear-gradient(135deg, #fbfcf8 0%, #edf4ef 100%); + box-shadow: var(--shadow); } -.setup-step-list .status-badge { - align-self: start; +.flowmemory-hero-main { + display: grid; + gap: 12px; + padding: 18px; } -.setup-step-list article:last-child { - border-bottom: 0; +.flowmemory-hero-main h2 { + max-width: 780px; + margin: 0; + font-size: 1.48rem; + line-height: 1.14; } -.setup-step-list strong, -.setup-step-list code, -.setup-step-list small { - display: block; - overflow-wrap: anywhere; +.flowmemory-hero-main p { + max-width: 82ch; + margin: 0; + color: #465047; + line-height: 1.5; } -code { - width: fit-content; - max-width: 100%; - margin: 5px 0; - padding: 3px 6px; - color: #25332d; - border: 1px solid var(--line); - border-radius: 6px; - background: #eef1eb; +.flowmemory-spine { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 8px; +} + +.flowmemory-spine span { + min-height: 34px; + padding: 8px 9px; + color: #284238; + border: 1px solid #bfd1c7; + border-radius: 7px; + background: rgba(251, 252, 248, 0.78); font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; - font-size: 0.78rem; + font-size: 0.74rem; + text-align: center; } -.workbench-warning { +.flowmemory-hero-side { display: grid; - grid-template-columns: auto minmax(0, 1fr); - gap: 9px; + gap: 1px; + min-width: 0; + padding: 1px; + border-left: 1px solid var(--line); +} + +.flowmemory-hero-side div { + display: grid; + gap: 5px; + align-content: center; + min-width: 0; + padding: 12px; + background: rgba(251, 252, 248, 0.72); +} + +.canary-hero { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(300px, 0.36fr); + gap: 14px; + align-items: stretch; + padding: 16px; + border: 1px solid #c9d5ce; + border-radius: 8px; + background: + linear-gradient(135deg, rgba(251, 252, 248, 0.94), rgba(229, 239, 235, 0.92)), + linear-gradient(90deg, rgba(47, 125, 107, 0.18), transparent); + box-shadow: var(--shadow); +} + +.canary-hero h2 { + max-width: 760px; + margin: 0; + font-size: 1.52rem; + line-height: 1.14; +} + +.canary-hero p { + max-width: 84ch; + margin: 10px 0 0; + color: #465047; + line-height: 1.5; +} + +.canary-read-window { + display: grid; + grid-template-columns: 28px 1fr; + gap: 10px; align-items: start; - padding: 11px 12px; - color: #6b4a16; - border: 1px solid #d9c393; + padding: 12px; + border: 1px solid #bfd1c7; border-radius: 8px; - background: #fbf6e8; + background: rgba(251, 252, 248, 0.78); } -.workbench-warning strong, -.workbench-warning span { - display: block; - overflow-wrap: anywhere; +.canary-read-window dl { + display: grid; + gap: 9px; + margin: 0; } -.workbench-api-panel { +.canary-operator-strip { display: grid; - gap: 12px; + grid-template-columns: 1.15fr 1fr 1.1fr; + gap: 10px; } -.endpoint-strip { +.canary-operator-strip article { + display: grid; + gap: 7px; + min-width: 0; + padding: 12px; + border: 1px solid #c9d5ce; + border-radius: 8px; + background: #fbfcf8; + box-shadow: var(--shadow); +} + +.canary-operator-strip span, +.canary-operator-strip strong { + overflow-wrap: anywhere; +} + +.canary-operator-strip span { + color: var(--muted); + font-size: 0.72rem; + font-weight: 800; + text-transform: uppercase; +} + +.canary-boundaries { + display: grid; + gap: 8px; + padding-top: 12px; +} + +.canary-boundaries span { + padding: 9px 10px; + color: #4d564d; + border: 1px solid var(--line); + border-radius: 7px; + background: #f8f9f4; + line-height: 1.35; +} + +.metric-tile, +.panel, +.table-panel, +.rootfield-tile, +.lane-tile, +.node-tile, +.alert-row, +.json-panel { + border: 1px solid var(--line); + border-radius: 8px; + background: var(--surface); + box-shadow: var(--shadow); +} + +.metric-tile { + display: grid; + gap: 8px; + padding: 14px; +} + +.metric-tile > span { + color: var(--muted); + font-size: 0.75rem; + font-weight: 700; + text-transform: uppercase; +} + +.metric-tile strong { + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 1.65rem; +} + +.metric-tile div { display: flex; flex-wrap: wrap; gap: 7px; + align-items: center; +} + +.overview-grid { + display: grid; + grid-template-columns: minmax(0, 1.4fr) minmax(290px, 0.6fr); + gap: 14px; +} + +.panel { + min-width: 0; + padding: 14px; +} + +.panel-wide { + grid-column: span 1; +} + +.panel-side-bottom { + grid-column: 2; +} + +.panel-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding-bottom: 10px; + border-bottom: 1px solid var(--line); +} + +.panel-heading div, +.tile-heading, +.record-title, +.json-panel-header { + display: flex; + gap: 8px; + align-items: center; + min-width: 0; +} + +.panel-heading h2 { + margin: 0; + font-size: 0.98rem; +} + +.panel-heading > span { + color: var(--muted); + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.78rem; +} + +.record-list, +.compact-list, +.alert-list { + display: grid; +} + +.status-strip { + display: flex; + flex-wrap: wrap; + gap: 8px; + padding: 10px 0 2px; +} + +.status-strip span { + display: inline-flex; + gap: 6px; + align-items: center; + padding: 3px 6px 3px 3px; + border: 1px solid var(--line); + border-radius: 999px; + background: #f8f9f4; +} + +.status-strip strong { + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.74rem; +} + +.record-row { + display: grid; + grid-template-columns: minmax(0, 1fr) 270px; + gap: 16px; + padding: 14px 0; + border-bottom: 1px solid var(--line); +} + +.record-row:last-child { + border-bottom: 0; +} + +.record-row p, +.alert-row p { + margin: 8px 0; + color: #3f483f; + line-height: 1.45; +} + +.record-facts, +.definition-grid, +.lane-stats, +.alert-facts { + display: grid; + gap: 10px; + margin: 0; +} + +.record-facts { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.record-facts div, +.definition-grid div, +.lane-stats div, +.alert-facts div { + min-width: 0; +} + +dt { + color: var(--subtle); + font-size: 0.68rem; + font-weight: 700; + text-transform: uppercase; +} + +dd { + margin: 3px 0 0; + overflow-wrap: anywhere; +} + +.compact-list article { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 9px; + align-items: start; + padding: 12px 0; + border-bottom: 1px solid var(--line); +} + +.compact-list article:last-child { + border-bottom: 0; +} + +.compact-list strong, +.compact-list small { + display: block; + overflow-wrap: anywhere; +} + +.contract-event-list { + display: grid; +} + +.contract-event-list article { + display: grid; + gap: 9px; + padding: 12px 0; + border-bottom: 1px solid var(--line); +} + +.contract-event-list article:last-child { + border-bottom: 0; +} + +.contract-event-list article > div { + display: flex; + gap: 8px; + align-items: center; + min-width: 0; +} + +.contract-event-list strong { + overflow-wrap: anywhere; + font-size: 0.84rem; +} + +.contract-event-list dl { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; + margin: 0; +} + +.bundle-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 10px; + padding-top: 14px; +} + +.bundle-grid article { + display: grid; + gap: 8px; + min-width: 0; + padding: 12px; + border: 1px solid var(--line); + border-radius: 7px; + background: #f7f8f4; +} + +.bundle-grid strong, +.bundle-grid small { + display: block; + overflow-wrap: anywhere; +} + +.block-strip { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 10px; + padding-top: 14px; +} + +.block-strip article { + display: grid; + gap: 7px; + padding: 12px; + border: 1px solid var(--line); + border-radius: 7px; + background: #f7f8f4; +} + +.workbench-command-center { + display: grid; + grid-template-columns: minmax(0, 1.08fr) minmax(360px, 0.92fr); + gap: 14px; +} + +.workbench-boundary-strip { + display: grid; + grid-template-columns: 1.05fr 1.15fr 1.2fr; + gap: 10px; +} + +.workbench-boundary-strip article { + display: grid; + gap: 6px; + min-width: 0; + padding: 12px; + border: 1px solid #cfd7cd; + border-radius: 8px; + background: #f9faf6; +} + +.workbench-boundary-strip strong { + font-size: 0.82rem; +} + +.workbench-boundary-strip span { + color: #4c554c; + font-size: 0.84rem; + line-height: 1.42; +} + +.pilot-status-panel article { + display: grid; + gap: 14px; + min-width: 0; + padding: 14px; + border: 1px solid #b8c7c0; + border-radius: 8px; + background: #f4f8f6; +} + +.pilot-status-body { + display: grid; + grid-template-columns: minmax(0, 0.92fr) minmax(320px, 1.08fr); + gap: 14px; + align-items: start; +} + +.pilot-status-body h3 { + margin: 4px 0 8px; + font-size: 1.35rem; + text-transform: capitalize; +} + +.pilot-status-body p { + margin: 0; + color: #3f483f; + line-height: 1.48; +} + +.product-surface-grid { + display: grid; + grid-template-columns: 1.2fr 1fr 1.2fr; + gap: 10px; +} + +.product-surface { + display: grid; + grid-template-columns: 34px minmax(0, 1fr) auto auto; + gap: 10px; + align-items: start; + min-width: 0; + min-height: 118px; + padding: 12px; + text-align: left; + transition: background 140ms ease, border-color 140ms ease, transform 140ms ease; +} + +.product-surface:hover { + border-color: #9fb4a8; + background: #eaf1eb; +} + +.product-surface-icon { + display: grid; + place-items: center; + width: 32px; + height: 32px; + color: #235f52; + border: 1px solid #bfd1c7; + border-radius: 7px; + background: #edf5ef; +} + +.product-surface strong, +.product-surface small, +.product-surface code { + display: block; + overflow-wrap: anywhere; +} + +.product-surface small { + margin-top: 4px; + line-height: 1.36; +} + +.product-surface b { + color: var(--ink); + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 1.1rem; + line-height: 1.1; +} + +.workbench-node-body { + display: grid; + gap: 16px; + padding-top: 14px; +} + +.workbench-node-body h3 { + margin: 0 0 7px; + font-size: 1.22rem; +} + +.workbench-node-body p, +.workbench-section-detail, +.boundary-copy p { + margin: 0; + color: #465047; + line-height: 1.48; +} + +.workbench-fact-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; + margin: 0; +} + +.workbench-fact-grid div { + min-width: 0; + padding: 10px; + border: 1px solid var(--line); + border-radius: 7px; + background: #f7f8f4; +} + +.setup-step-list { + display: grid; + padding-top: 6px; +} + +.setup-step-list article { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 9px; + align-items: start; + padding: 11px 0; + border-bottom: 1px solid var(--line); +} + +.setup-step-list .status-badge { + align-self: start; +} + +.setup-step-list article:last-child { + border-bottom: 0; +} + +.setup-step-list strong, +.setup-step-list code, +.setup-step-list small { + display: block; + overflow-wrap: anywhere; +} + +code { + width: fit-content; + max-width: 100%; + margin: 5px 0; + padding: 3px 6px; + color: #25332d; + border: 1px solid var(--line); + border-radius: 6px; + background: #eef1eb; + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.78rem; +} + +.workbench-warning { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 9px; + align-items: start; + padding: 11px 12px; + color: #6b4a16; + border: 1px solid #d9c393; + border-radius: 8px; + background: #fbf6e8; +} + +.workbench-warning strong, +.workbench-warning span { + display: block; + overflow-wrap: anywhere; +} + +.workbench-api-panel { + display: grid; + gap: 12px; +} + +.endpoint-strip { + display: flex; + flex-wrap: wrap; + gap: 7px; +} + +.endpoint-strip span { + padding: 4px 7px; + color: #34443c; + border: 1px solid var(--line); + border-radius: 999px; + background: #f7f8f4; + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.72rem; +} + +.local-action-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; +} + +.local-action-grid article { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 10px; + align-items: center; + min-width: 0; + padding: 11px; + border: 1px solid var(--line); + border-radius: 7px; + background: #f7f8f4; +} + +.local-action-grid strong, +.local-action-grid small { + display: block; + overflow-wrap: anywhere; +} + +.action-result, +.boot-hint { + margin: 0; + color: var(--muted); + line-height: 1.45; +} + +.workbench-layout { + display: grid; + grid-template-columns: 222px minmax(0, 1fr); + gap: 14px; + align-items: start; +} + +.workbench-switcher { + position: sticky; + top: 88px; + display: grid; + gap: 7px; + max-height: calc(100dvh - 112px); + overflow: auto; +} + +.workbench-switch { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 10px; + align-items: center; + min-height: 38px; + padding: 8px 10px; + text-align: left; + transition: background 140ms ease, border-color 140ms ease, transform 140ms ease; +} + +.workbench-switch.active, +.workbench-switch:hover { + border-color: #9fb4a8; + background: #eaf1eb; +} + +.workbench-switch span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.workbench-switch strong { + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.78rem; +} + +.workbench-record-panel { + display: grid; + gap: 14px; +} + +.workbench-record-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; +} + +.workbench-record { + display: grid; + gap: 11px; + min-width: 0; + padding: 13px; + border: 1px solid var(--line); + border-radius: 7px; + background: #f7f8f4; +} + +.workbench-record h3 { + min-width: 0; + margin: 0; + overflow-wrap: anywhere; + font-size: 0.96rem; +} + +.workbench-record p { + margin: 0; + color: #465047; + line-height: 1.44; +} + +.workbench-boundary-panel { + display: grid; + gap: 12px; +} + +.boundary-copy { + display: grid; + gap: 9px; +} + +.table-panel { + min-width: 0; + overflow: hidden; +} + +.table-scroll { + overflow-x: auto; +} + +.table-scroll table { + min-width: 1120px; +} + +.cell-stack { + display: grid; + gap: 4px; + min-width: 0; +} + +.hash-value { + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.82rem; + overflow-wrap: anywhere; +} + +.provenance-line { + display: flex; + flex-wrap: wrap; + gap: 5px; +} + +.rootfield-grid, +.hardware-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; +} + +.rootfield-tile, +.node-tile, +.lane-tile { + display: grid; + gap: 14px; + padding: 14px; +} + +.definition-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.lane-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; +} + +.lane-stats { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.alert-list { + gap: 12px; +} + +.alert-row { + display: grid; + grid-template-columns: minmax(0, 1fr) 300px; + gap: 16px; + padding: 14px; + border-left-width: 4px; +} + +.severity-critical { + border-left-color: var(--danger); +} + +.severity-warning { + border-left-color: var(--warning); +} + +.severity-info { + border-left-color: var(--info); +} + +.alert-action { + padding: 12px; + border-left: 1px solid var(--line); +} + +.alert-action span { + color: var(--subtle); + font-size: 0.7rem; + font-weight: 700; + text-transform: uppercase; +} + +.alert-action p { + margin-bottom: 0; +} + +.json-panel { + overflow: hidden; +} + +.json-panel-header { + justify-content: space-between; + padding: 12px 14px; + border-bottom: 1px solid var(--line); +} + +.json-panel pre { + max-height: 68dvh; + margin: 0; + padding: 16px; + overflow: auto; + background: #1f241f; + color: #eef4ec; + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.78rem; + line-height: 1.5; +} + +.status-badge { + display: inline-flex; + gap: 6px; + align-items: center; + width: fit-content; + max-width: 100%; + min-height: 24px; + padding: 4px 8px; + border: 1px solid var(--status-border); + border-radius: 999px; + background: var(--status-bg); + color: var(--status-fg); + font-size: 0.72rem; + font-weight: 800; + text-transform: uppercase; + white-space: nowrap; +} + +.status-dot { + width: 7px; + height: 7px; + border-radius: 999px; + background: currentColor; +} + +.status-observed { + --status-bg: #e7f0f3; + --status-border: #b9d0d8; + --status-fg: #326b89; +} + +.status-pending { + --status-bg: #f5eddb; + --status-border: #d9c393; + --status-fg: #8a5e14; +} + +.status-finalized { + --status-bg: #e9eee8; + --status-border: #bdcabc; + --status-fg: #3e6541; +} + +.status-verified { + --status-bg: #e2f0e8; + --status-border: #a8ceb7; + --status-fg: #1f6a45; +} + +.status-unresolved { + --status-bg: #f3e7dc; + --status-border: #d4b495; + --status-fg: #8c5527; +} + +.status-failed { + --status-bg: #f5e2e2; + --status-border: #d4a0a0; + --status-fg: #963b3b; +} + +.status-unsupported { + --status-bg: #ece7f1; + --status-border: #c1b6d0; + --status-fg: #665283; +} + +.status-reorged { + --status-bg: #eee6dd; + --status-border: #c6b19e; + --status-fg: #714f35; +} + +.status-offline { + --status-bg: #e7e8e3; + --status-border: #b9beb5; + --status-fg: #555d55; +} + +.status-stale { + --status-bg: #f0ecdc; + --status-border: #cfc38c; + --status-fg: #766a23; +} + +.empty-state { + display: flex; + gap: 12px; + align-items: flex-start; + padding: 22px; + color: var(--muted); + border: 1px dashed var(--line-strong); + border-radius: 8px; + background: #f8f9f4; +} + +.empty-state h3 { + margin: 0 0 5px; + color: var(--ink); + font-size: 1rem; +} + +.empty-state p { + margin: 0; + line-height: 1.45; +} + +.boot-screen { + display: grid; + place-items: center; + min-height: 100dvh; + padding: 24px; + background: #f4f5f1; +} + +.boot-panel, +.error-panel { + width: min(620px, 100%); + border: 1px solid var(--line); + border-radius: 8px; + background: var(--surface); + box-shadow: var(--shadow); +} + +.boot-panel { + display: grid; + gap: 14px; + padding: 24px; +} + +.skeleton-line { + height: 14px; + border-radius: 999px; + background: linear-gradient(90deg, #e1e6de 0%, #f5f6f0 45%, #e1e6de 100%); + background-size: 220% 100%; + animation: shimmer 1.4s ease-in-out infinite; +} + +.skeleton-title { + width: 70%; + height: 24px; +} + +.skeleton-short { + width: 46%; +} + +.boot-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px; + margin-top: 8px; +} + +.boot-grid div { + height: 92px; + border-radius: 8px; + background: #edf0e9; +} + +.error-panel { + display: grid; + grid-template-columns: 42px 1fr; + gap: 14px; + padding: 22px; + color: #873a3a; +} + +.error-panel h1 { + margin: 0 0 8px; + color: var(--ink); + font-size: 1.25rem; +} + +.error-panel p { + margin: 0 0 14px; + color: #6f4c4c; +} + +.button { + display: inline-flex; + gap: 8px; + align-items: center; + min-height: 36px; + padding: 0 12px; +} + +.button-primary { + border-color: #1f6a45; + background: #2f7d6b; + color: #f9fcf7; +} + +@keyframes shimmer { + 0% { + background-position: 100% 0; + } + 100% { + background-position: -100% 0; + } +} + +@media (max-width: 1180px) { + .metric-grid, + .block-strip { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .overview-grid, + .rootfield-grid, + .hardware-grid, + .lane-grid, + .canary-operator-strip, + .workbench-boundary-strip, + .product-surface-grid, + .pilot-status-body, + .local-action-grid, + .workbench-command-center, + .workbench-record-grid { + grid-template-columns: 1fr; + } + + .flowmemory-hero, + .canary-hero, + .flowmemory-spine, + .contract-event-list dl, + .bundle-grid { + grid-template-columns: 1fr; + } + + .flowmemory-hero-side, + .panel-side-bottom { + grid-column: auto; + } + + .flowmemory-hero-side { + border-left: 0; + border-top: 1px solid var(--line); + } + + .workbench-layout { + grid-template-columns: 1fr; + } + + .workbench-switcher { + position: static; + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 860px) { + .app-shell { + grid-template-columns: 1fr; + } + + .sidebar { + position: static; + height: auto; + } + + .nav-list { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .topbar, + .section-header, + .fixture-banner, + .record-row, + .alert-row { + display: grid; + grid-template-columns: 1fr; + } + + .content { + padding: 18px 14px 28px; + } + + .section-action, + .filter-row { + min-width: 0; + grid-template-columns: 1fr; + } + + .fixture-banner { + margin: 14px 14px 0; + } + + .alert-action { + border-left: 0; + border-top: 1px solid var(--line); + } + + .workbench-fact-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 560px) { + .nav-list, + .metric-grid, + .block-strip, + .definition-grid, + .lane-stats, + .record-facts, + .workbench-switcher { + grid-template-columns: 1fr; + } + + .topbar { + padding: 12px 14px; + } + + th, + td { + padding: 10px; + } +} + +/* Flowchain bridge reference layout */ +.flowchain-bridge-page { + min-height: 100dvh; + overflow-x: hidden; + overflow-y: auto; + background: + radial-gradient(circle at 38% 25%, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0) 34rem), + radial-gradient(circle at 8% 86%, rgba(255, 255, 255, 0.58), rgba(255, 255, 255, 0) 20rem), + linear-gradient(180deg, #f5ebdc 0%, #f0dfc7 100%); + color: #0d1b35; +} + +.flowchain-bridge-page::before { + opacity: 0.36; + background-image: + linear-gradient(rgba(77, 56, 34, 0.035) 1px, transparent 1px), + linear-gradient(90deg, rgba(77, 56, 34, 0.025) 1px, transparent 1px), + radial-gradient(circle at 28% 18%, rgba(107, 78, 42, 0.06), transparent 22rem); + background-size: 8px 8px, 10px 10px, auto; +} + +.flowchain-bridge-page::after { + inset: auto -14vw -7dvh -14vw; + height: 32dvh; + opacity: 0.52; + background: + radial-gradient(ellipse at 18% 50%, rgba(1, 88, 224, 0.66), rgba(16, 112, 255, 0.26) 34%, transparent 70%), + radial-gradient(ellipse at 44% 40%, rgba(0, 91, 255, 0.9), rgba(0, 105, 255, 0.32) 36%, transparent 74%), + radial-gradient(ellipse at 79% 54%, rgba(1, 95, 236, 0.68), rgba(38, 132, 255, 0.22) 40%, transparent 76%); + filter: blur(14px) saturate(1.22); + transform: rotate(-4deg) skewX(-14deg); +} + +.bridge-flow-ribbon { + inset: auto -12vw 5dvh -12vw; + height: 18dvh; + opacity: 0.58; + background: + linear-gradient(92deg, transparent 0%, rgba(0, 93, 255, 0.16) 10%, rgba(1, 83, 221, 0.74) 38%, rgba(44, 145, 255, 0.48) 72%, transparent 100%), + radial-gradient(ellipse at 36% 40%, rgba(1, 79, 220, 0.88), rgba(36, 130, 255, 0.26) 48%, transparent 72%); + filter: blur(9px); + transform: rotate(-8deg) skewX(-18deg); +} + +.flowchain-bridge-nav { + align-items: flex-start; + width: min(100%, 1700px); + padding: 24px 66px 0; +} + +.flowchain-brand { + gap: 14px; + color: #071831; +} + +.flowchain-brand strong { + color: #071831; + font-size: clamp(1.75rem, 2.3vw, 2.35rem); + letter-spacing: 0; } -.endpoint-strip span { - padding: 4px 7px; - color: #34443c; - border: 1px solid var(--line); - border-radius: 999px; - background: #f7f8f4; - font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; - font-size: 0.72rem; +.flowchain-brand-mark { + width: 48px; + height: 38px; } -.local-action-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 10px; +.flowchain-brand-mark::before, +.flowchain-brand-mark::after, +.flowchain-brand-mark span { + width: 44px; + height: 15px; + border-color: #1b6fff; + border-width: 5px 0 0; } -.local-action-grid article { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 10px; - align-items: center; - min-width: 0; - padding: 11px; - border: 1px solid var(--line); - border-radius: 7px; - background: #f7f8f4; +.flowchain-brand-mark::before { + top: 2px; + transform: rotate(-10deg); } -.local-action-grid strong, -.local-action-grid small { - display: block; - overflow-wrap: anywhere; +.flowchain-brand-mark span { + top: 13px; + width: 36px; + border-color: #246de0; + transform: rotate(-12deg); } -.action-result, -.boot-hint { - margin: 0; - color: var(--muted); - line-height: 1.45; +.flowchain-brand-mark::after { + top: 24px; + width: 28px; + border-color: #73adff; + transform: rotate(-12deg); } -.workbench-layout { +.bridge-system-rail { display: grid; - grid-template-columns: 222px minmax(0, 1fr); - gap: 14px; - align-items: start; + grid-template-columns: minmax(190px, 1fr) minmax(185px, 1fr) minmax(210px, 1.08fr) 48px; + align-items: stretch; + width: min(730px, 100%); + min-height: 78px; + border: 1px solid rgba(70, 48, 25, 0.14); + border-radius: 10px; + background: rgba(249, 240, 227, 0.8); + box-shadow: 0 18px 45px rgba(48, 32, 14, 0.14), inset 0 1px 0 rgba(255, 255, 255, 0.58); + backdrop-filter: blur(18px); } -.workbench-switcher { - position: sticky; - top: 88px; - display: grid; - gap: 7px; - max-height: calc(100dvh - 112px); - overflow: auto; +.bridge-system-item, +.bridge-system-info { + min-width: 0; + border: 0; + background: transparent; + color: #162138; } -.workbench-switch { +.bridge-system-item { display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 10px; + grid-template-columns: 42px minmax(0, 1fr) 10px; + gap: 12px; align-items: center; - min-height: 38px; - padding: 8px 10px; + padding: 15px 20px; text-align: left; - transition: background 140ms ease, border-color 140ms ease, transform 140ms ease; + border-right: 1px solid rgba(85, 64, 39, 0.12); } -.workbench-switch.active, -.workbench-switch:hover { - border-color: #9fb4a8; - background: #eaf1eb; +.bridge-system-wallet { + cursor: pointer; } -.workbench-switch span { +.bridge-system-item strong, +.bridge-system-item small { + display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.workbench-switch strong { - font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; - font-size: 0.78rem; +.bridge-system-item strong { + font-size: 0.88rem; + font-weight: 750; } -.workbench-record-panel { +.bridge-system-item small { + margin-top: 4px; + color: rgba(22, 33, 56, 0.62); + font-size: 0.82rem; +} + +.bridge-system-icon { display: grid; - gap: 14px; + place-items: center; + width: 40px; + height: 40px; + color: #171a21; + border-radius: 50%; + background: rgba(73, 54, 34, 0.08); } -.workbench-record-grid { +.bridge-system-item i { + width: 7px; + height: 7px; + border-radius: 50%; +} + +.bridge-system-item i.is-green { + background: #1a9c68; +} + +.bridge-system-item i.is-amber { + background: #d79916; +} + +.bridge-system-item i.is-red { + background: #b74638; +} + +.bridge-system-info { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 10px; + place-items: center; + text-decoration: none; } -.workbench-record { +.flowchain-bridge-main { display: grid; - gap: 11px; - min-width: 0; - padding: 13px; - border: 1px solid var(--line); - border-radius: 7px; - background: #f7f8f4; + grid-template-columns: minmax(420px, 660px) minmax(650px, 820px); + gap: clamp(48px, 6vw, 96px); + align-items: start; + width: min(100%, 1700px); + min-height: calc(100dvh - 104px); + padding: 16px 66px 38px; } -.workbench-record h3 { - min-width: 0; - margin: 0; - overflow-wrap: anywhere; - font-size: 0.96rem; +.bridge-title-block { + justify-items: start; + align-self: start; + max-width: 620px; + padding-bottom: 80px; + text-align: left; } -.workbench-record p { - margin: 0; - color: #465047; - line-height: 1.44; +.bridge-title-block > span { + display: inline-flex; + min-height: 28px; + align-items: center; + padding: 0 15px; + border-radius: 999px; + background: rgba(101, 76, 45, 0.08); + color: rgba(31, 25, 20, 0.72); + font-size: 0.75rem; + font-weight: 800; + letter-spacing: 0.22em; + text-transform: uppercase; } -.workbench-boundary-panel { +.bridge-title-block h1 { + max-width: 610px; + margin: 28px 0 18px; + color: #081a36; + font-size: clamp(4.2rem, 6.25vw, 6.25rem); + font-weight: 500; + line-height: 0.88; + letter-spacing: 0; +} + +.bridge-title-block p { + width: min(520px, 100%); + max-width: 520px; + color: rgba(31, 27, 24, 0.82); + font-family: Geist, Satoshi, "Segoe UI", system-ui, sans-serif; + font-size: clamp(1.24rem, 1.55vw, 1.55rem); + line-height: 1.36; +} + +.bridge-benefit-list { display: grid; - gap: 12px; + gap: 26px; + margin-top: 38px; } -.boundary-copy { +.bridge-benefit-list article { display: grid; - gap: 9px; + grid-template-columns: 50px minmax(0, 1fr); + gap: 16px; + align-items: center; } -.table-panel { - min-width: 0; - overflow: hidden; +.bridge-benefit-list article > span { + display: grid; + place-items: center; + width: 44px; + height: 44px; + color: #1d1c18; + border-radius: 50%; + background: rgba(57, 41, 23, 0.12); } -.table-scroll { - overflow-x: auto; +.bridge-benefit-list strong, +.bridge-benefit-list small { + display: block; } -.table-scroll table { - min-width: 1120px; +.bridge-benefit-list strong { + color: #111923; + font-size: 0.95rem; } -.cell-stack { - display: grid; - gap: 4px; +.bridge-benefit-list small { + margin-top: 4px; + color: rgba(33, 28, 23, 0.72); + font-size: 0.85rem; + line-height: 1.35; +} + +.flowchain-bridge-layout { + display: block; + width: 100%; +} + +.bridge-console { + width: 100%; min-width: 0; + gap: 12px; + padding: 26px 32px 22px; + border: 1px solid rgba(72, 50, 27, 0.16); + border-radius: 14px; + background: rgba(250, 241, 229, 0.82); + box-shadow: 0 28px 78px rgba(46, 31, 15, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.62); } -.hash-value { - font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; - font-size: 0.82rem; - overflow-wrap: anywhere; +.bridge-route-row { + grid-template-columns: minmax(0, 1fr) 52px minmax(0, 1fr); + gap: 24px; + align-items: center; } -.provenance-line { - display: flex; - flex-wrap: wrap; - gap: 5px; +.bridge-route-row label > span, +.bridge-field > label, +.bridge-label-row label, +.bridge-estimate-grid span, +.bridge-calldata-facts dt { + color: rgba(42, 32, 22, 0.78); + font-size: 0.72rem; + font-weight: 800; + letter-spacing: 0.12em; } -.rootfield-grid, -.hardware-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 14px; +.bridge-route-row label > small { + display: block; + margin: 5px 0 8px; + color: rgba(42, 32, 22, 0.7); + font-size: 0.9rem; } -.rootfield-tile, -.node-tile, -.lane-tile { +.bridge-route-connector { display: grid; - gap: 14px; - padding: 14px; + place-items: center; + padding: 30px 0 0; + color: #101820; } -.definition-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); +.bridge-route-connector span, +.bridge-route-connector button { + display: none; } -.lane-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 12px; +.bridge-select-shell, +.bridge-token-field > div, +.bridge-field { + border: 1px solid rgba(83, 61, 37, 0.18); + border-radius: 8px; + background: rgba(255, 250, 243, 0.58); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5); } -.lane-stats { - grid-template-columns: repeat(4, minmax(0, 1fr)); +.bridge-select-shell { + grid-template-columns: 36px minmax(0, 1fr) 18px; + min-height: 56px; + padding: 0 16px; } -.alert-list { - gap: 12px; +.bridge-select-shell strong, +.bridge-token-field strong { + font-family: Geist, Satoshi, "Segoe UI", system-ui, sans-serif; + font-size: 1.02rem; + font-weight: 700; } -.alert-row { +.bridge-chain-icon, +.bridge-token-orb { + width: 32px; + height: 32px; +} + +.bridge-token-amount-row { display: grid; - grid-template-columns: minmax(0, 1fr) 300px; - gap: 16px; - padding: 14px; - border-left-width: 4px; + grid-template-columns: minmax(220px, 0.78fr) minmax(300px, 1.42fr); + gap: 30px; } -.severity-critical { - border-left-color: var(--danger); +.bridge-field { + padding: 0; + background: transparent; + border: 0; + box-shadow: none; } -.severity-warning { - border-left-color: var(--warning); +.bridge-token-field > div { + grid-template-columns: 36px auto minmax(0, 1fr) 18px; + min-height: 56px; + padding: 0 14px; } -.severity-info { - border-left-color: var(--info); +.bridge-token-field small { + color: rgba(43, 34, 26, 0.64); + font-size: 0.82rem; } -.alert-action { - padding: 12px; - border-left: 1px solid var(--line); +.bridge-amount-field { + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + min-height: 56px; + padding: 10px 14px; + border: 1px solid rgba(83, 61, 37, 0.18); + border-radius: 8px; + background: rgba(255, 250, 243, 0.58); } -.alert-action span { - color: var(--subtle); - font-size: 0.7rem; - font-weight: 700; - text-transform: uppercase; +.bridge-amount-field label { + grid-column: 1 / -1; + margin: 0; } -.alert-action p { - margin-bottom: 0; +.bridge-amount-field input { + min-height: 30px; + font-family: Geist, Satoshi, "Segoe UI", system-ui, sans-serif; + font-size: 1.55rem; + font-weight: 600; } -.json-panel { - overflow: hidden; +.bridge-amount-field > span { + font-family: Geist, Satoshi, "Segoe UI", system-ui, sans-serif; + font-size: 0.95rem; + font-weight: 800; } -.json-panel-header { - justify-content: space-between; - padding: 12px 14px; - border-bottom: 1px solid var(--line); +.bridge-amount-field small { + grid-column: 1; + color: rgba(43, 34, 26, 0.62); + font-size: 0.82rem; } -.json-panel pre { - max-height: 68dvh; - margin: 0; - padding: 16px; - overflow: auto; - background: #1f241f; - color: #eef4ec; - font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; - font-size: 0.78rem; - line-height: 1.5; +.bridge-amount-field .bridge-usd-note { + color: #0b62df; + font-weight: 750; } -.status-badge { - display: inline-flex; - gap: 6px; - align-items: center; - width: fit-content; - max-width: 100%; - min-height: 24px; - padding: 4px 8px; - border: 1px solid var(--status-border); - border-radius: 999px; - background: var(--status-bg); - color: var(--status-fg); - font-size: 0.72rem; +.bridge-amount-field button { + grid-column: 2; + justify-self: end; + border: 0; + background: transparent; + color: #125bd8; + font-size: 0.82rem; font-weight: 800; - text-transform: uppercase; +} + +.bridge-recipient-field { + display: grid; + grid-template-columns: minmax(0, 1fr) 34px; + gap: 8px; + align-items: center; + min-height: 56px; + padding: 10px 14px; + border: 1px solid rgba(83, 61, 37, 0.18); + border-radius: 8px; + background: rgba(255, 250, 243, 0.58); +} + +.bridge-label-row { + display: flex; + grid-column: 1 / -1; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.bridge-label-row button { + display: inline-flex; + gap: 6px; + align-items: center; + width: auto; + height: auto; + padding: 0; + border: 0; + background: transparent; + color: #125bd8; + font-size: 0.84rem; white-space: nowrap; } -.status-dot { - width: 7px; - height: 7px; - border-radius: 999px; - background: currentColor; +.bridge-recipient-field input { + font-family: Geist, Satoshi, "Segoe UI", system-ui, sans-serif; + font-size: 1.02rem; } -.status-observed { - --status-bg: #e7f0f3; - --status-border: #b9d0d8; - --status-fg: #326b89; +.bridge-recipient-field button[title="Copy recipient"] { + color: rgba(42, 32, 22, 0.52); } -.status-pending { - --status-bg: #f5eddb; - --status-border: #d9c393; - --status-fg: #8a5e14; +.bridge-recipient-field button[title="Copy recipient"]:disabled { + cursor: not-allowed; + opacity: 0.42; } -.status-finalized { - --status-bg: #e9eee8; - --status-border: #bdcabc; - --status-fg: #3e6541; +.bridge-recipient-options { + display: flex; + grid-column: 1 / -1; + flex-wrap: wrap; + gap: 7px; } -.status-verified { - --status-bg: #e2f0e8; - --status-border: #a8ceb7; - --status-fg: #1f6a45; +.bridge-recipient-options button { + display: inline-flex; + align-items: center; + width: auto; + min-height: 30px; + height: auto; + gap: 7px; + padding: 5px 8px; + border: 1px solid rgba(83, 61, 37, 0.16); + border-radius: 8px; + background: rgba(255, 255, 255, 0.62); + color: #2d231a; + font-size: 0.76rem; } -.status-unresolved { - --status-bg: #f3e7dc; - --status-border: #d4b495; - --status-fg: #8c5527; +.bridge-recipient-options span { + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } -.status-failed { - --status-bg: #f5e2e2; - --status-border: #d4a0a0; - --status-fg: #963b3b; +.bridge-recipient-options small { + color: rgba(45, 35, 26, 0.6); + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; } -.status-unsupported { - --status-bg: #ece7f1; - --status-border: #c1b6d0; - --status-fg: #665283; +.bridge-estimate-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + overflow: hidden; + border: 1px solid rgba(83, 61, 37, 0.15); + border-radius: 8px; + background: rgba(235, 222, 205, 0.34); } -.status-reorged { - --status-bg: #eee6dd; - --status-border: #c6b19e; - --status-fg: #714f35; +.bridge-estimate-grid > div { + display: grid; + gap: 4px; + min-height: 54px; + padding: 9px 16px; + border-right: 1px solid rgba(83, 61, 37, 0.12); + border-bottom: 1px solid rgba(83, 61, 37, 0.12); } -.status-offline { - --status-bg: #e7e8e3; - --status-border: #b9beb5; - --status-fg: #555d55; +.bridge-estimate-grid > div:nth-child(2n) { + border-right: 0; } -.status-stale { - --status-bg: #f0ecdc; - --status-border: #cfc38c; - --status-fg: #766a23; +.bridge-estimate-grid > div:nth-last-child(-n + 2) { + border-bottom: 0; } -.empty-state { - display: flex; - gap: 12px; - align-items: flex-start; - padding: 22px; - color: var(--muted); - border: 1px dashed var(--line-strong); - border-radius: 8px; - background: #f8f9f4; +.bridge-estimate-grid span { + display: inline-flex; + gap: 6px; + align-items: center; } -.empty-state h3 { - margin: 0 0 5px; - color: var(--ink); +.bridge-estimate-grid strong { + color: #151a21; font-size: 1rem; + font-weight: 650; } -.empty-state p { - margin: 0; - line-height: 1.45; +.bridge-estimate-grid small { + color: rgba(33, 28, 23, 0.62); + font-size: 0.82rem; } -.boot-screen { - display: grid; - place-items: center; - min-height: 100dvh; - padding: 24px; - background: #f4f5f1; +.bridge-estimate-primary strong { + color: #0b62df; + font-size: 1.12rem; } -.boot-panel, -.error-panel { - width: min(620px, 100%); - border: 1px solid var(--line); +.bridge-advanced { + border: 1px solid rgba(83, 61, 37, 0.16); border-radius: 8px; - background: var(--surface); - box-shadow: var(--shadow); + background: rgba(255, 250, 243, 0.5); } -.boot-panel { - display: grid; - gap: 14px; - padding: 24px; +.bridge-advanced summary { + display: flex; + justify-content: space-between; + width: 100%; + min-height: 42px; + padding: 0 16px; + font-size: 0.95rem; } -.skeleton-line { - height: 14px; - border-radius: 999px; - background: linear-gradient(90deg, #e1e6de 0%, #f5f6f0 45%, #e1e6de 100%); - background-size: 220% 100%; - animation: shimmer 1.4s ease-in-out infinite; +.bridge-advanced-body { + padding: 0 14px 12px; } -.skeleton-title { - width: 70%; - height: 24px; +.bridge-advanced[open] summary { + margin: 0; } -.skeleton-short { - width: 46%; +.bridge-calldata-facts { + grid-template-columns: repeat(3, minmax(0, 1fr)); + padding: 0 14px 14px; } -.boot-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 10px; - margin-top: 8px; +.bridge-action-row { + grid-template-columns: 1fr; } -.boot-grid div { - height: 92px; - border-radius: 8px; - background: #edf0e9; +.bridge-primary-action { + display: flex; + gap: 10px; + align-items: center; + justify-content: center; + min-height: 54px; + font-family: ui-serif, Georgia, "Times New Roman", serif; + font-size: 1.26rem; } -.error-panel { +.bridge-terms-row { display: grid; - grid-template-columns: 42px 1fr; - gap: 14px; - padding: 22px; - color: #873a3a; -} - -.error-panel h1 { - margin: 0 0 8px; - color: var(--ink); - font-size: 1.25rem; + grid-template-columns: 18px minmax(0, 1fr); + gap: 8px; + align-items: center; + color: rgba(35, 30, 25, 0.64); + font-size: 0.82rem; + line-height: 1.35; } -.error-panel p { - margin: 0 0 14px; - color: #6f4c4c; +.bridge-safety-rail { + grid-template-columns: minmax(0, 1fr) auto auto; + gap: 24px; + align-items: center; + margin: 4px 0 0; + padding-top: 10px; + border-top: 1px solid rgba(83, 61, 37, 0.14); + border-radius: 0; + background: transparent; } -.button { +.bridge-safety-rail span, +.bridge-safety-rail a { display: inline-flex; gap: 8px; align-items: center; - min-height: 36px; - padding: 0 12px; + min-width: 0; + padding: 0; + color: rgba(35, 30, 25, 0.64); + border: 0; + font-size: 0.82rem; + text-decoration: none; } -.button-primary { - border-color: #1f6a45; - background: #2f7d6b; - color: #f9fcf7; +.bridge-safety-rail a { + color: rgba(29, 24, 20, 0.82); } -@keyframes shimmer { - 0% { - background-position: 100% 0; - } - 100% { - background-position: -100% 0; - } +.bridge-validation, +.bridge-status-message, +.bridge-tx-link { + padding: 10px 12px; } -@media (max-width: 1180px) { - .metric-grid, - .block-strip { - grid-template-columns: repeat(2, minmax(0, 1fr)); +@media (max-width: 1280px) { + .flowchain-bridge-nav { + display: grid; + grid-template-columns: 1fr; + gap: 18px; + padding: 32px 28px 0; } - .overview-grid, - .rootfield-grid, - .hardware-grid, - .lane-grid, - .canary-operator-strip, - .workbench-boundary-strip, - .product-surface-grid, - .pilot-status-body, - .local-action-grid, - .workbench-command-center, - .workbench-record-grid { - grid-template-columns: 1fr; + .bridge-system-rail { + width: 100%; } - .flowmemory-hero, - .canary-hero, - .flowmemory-spine, - .contract-event-list dl, - .bundle-grid { + .flowchain-bridge-main { grid-template-columns: 1fr; + gap: 28px; + padding: 28px; } - .flowmemory-hero-side, - .panel-side-bottom { - grid-column: auto; + .bridge-title-block { + max-width: 760px; + padding-bottom: 0; } +} - .flowmemory-hero-side { - border-left: 0; - border-top: 1px solid var(--line); +@media (max-width: 760px) { + .flowchain-bridge-nav, + .flowchain-bridge-main { + width: 100%; + max-width: 100vw; + padding-left: 16px; + padding-right: 16px; } - .workbench-layout { + .bridge-system-rail { grid-template-columns: 1fr; } - .workbench-switcher { - position: static; - grid-template-columns: repeat(2, minmax(0, 1fr)); + .bridge-system-item { + border-right: 0; + border-bottom: 1px solid rgba(85, 64, 39, 0.12); } -} -@media (max-width: 860px) { - .app-shell { - grid-template-columns: 1fr; + .bridge-title-block h1 { + font-size: clamp(3.25rem, 16vw, 4.6rem); } - .sidebar { - position: static; - height: auto; + .bridge-title-block { + width: calc(100vw - 32px) !important; + max-width: calc(100vw - 32px) !important; + overflow: hidden; } - .nav-list { - grid-template-columns: repeat(3, minmax(0, 1fr)); + .bridge-title-block p { + width: calc(100vw - 32px) !important; + max-width: calc(100vw - 32px) !important; + font-size: 1.08rem; + overflow-wrap: break-word; + text-wrap: pretty; } - .topbar, - .section-header, - .fixture-banner, - .record-row, - .alert-row { - display: grid; - grid-template-columns: 1fr; + .bridge-benefit-list { + gap: 16px; } - .content { - padding: 18px 14px 28px; + .bridge-benefit-list article { + grid-template-columns: 50px minmax(0, 1fr); } - .section-action, - .filter-row { - min-width: 0; - grid-template-columns: 1fr; + .bridge-benefit-list small, + .bridge-benefit-list strong { + max-width: calc(100vw - 98px); + overflow-wrap: break-word; } - .fixture-banner { - margin: 14px 14px 0; + .bridge-console { + padding: 18px; } - .alert-action { - border-left: 0; - border-top: 1px solid var(--line); + .bridge-route-row, + .bridge-token-amount-row, + .bridge-estimate-grid, + .bridge-calldata-facts, + .bridge-safety-rail { + grid-template-columns: 1fr; } - .workbench-fact-grid { - grid-template-columns: 1fr; + .bridge-route-connector { + padding: 0; } -} -@media (max-width: 560px) { - .nav-list, - .metric-grid, - .block-strip, - .definition-grid, - .lane-stats, - .record-facts, - .workbench-switcher { - grid-template-columns: 1fr; + .bridge-estimate-grid > div, + .bridge-estimate-grid > div:nth-child(2n), + .bridge-estimate-grid > div:nth-last-child(-n + 2) { + border-right: 0; + border-bottom: 1px solid rgba(83, 61, 37, 0.12); } - .topbar { - padding: 12px 14px; + .bridge-estimate-grid > div:last-child { + border-bottom: 0; } - th, - td { - padding: 10px; + .bridge-safety-rail { + gap: 12px; } } diff --git a/apps/dashboard/src/test/dashboardData.test.ts b/apps/dashboard/src/test/dashboardData.test.ts index f4e16394..4defe200 100644 --- a/apps/dashboard/src/test/dashboardData.test.ts +++ b/apps/dashboard/src/test/dashboardData.test.ts @@ -250,6 +250,96 @@ describe("dashboard fixture", () => { lifecycle: [], }); } + if (url.endsWith("/bridge/live-readiness")) { + return Response.json({ + schema: "flowmemory.control_plane.bridge_live_readiness.v0", + baseChainId: 8453, + baseChainName: "Base", + failClosedStatus: "BLOCKED", + readyForOperatorLivePilot: false, + lockbox: { configured: false, envName: "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", ownerVerified: false }, + node: { running: true, chainId: "flowmemory-local-devnet-v0" }, + confirmationDepth: { configured: false, envName: "FLOWCHAIN_BASE8453_CONFIRMATION_DEPTH" }, + missingEnvNames: ["FLOWCHAIN_BASE8453_RPC_URL", "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS"], + currentArtifacts: { base8453DepositCount: 0, localOrMockDepositCount: 1, mockPresentedAsLive: false }, + issues: [{ + reasonCode: "missing_env", + status: "blocked", + title: "Missing live pilot env", + summary: "Live readiness is blocked until all required env names are present.", + envNames: ["FLOWCHAIN_BASE8453_RPC_URL", "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS"], + }], + envValuesPrinted: false, + localOnly: true, + productionReady: false, + }); + } + if (url.endsWith("/pilot/lifecycle")) { + return Response.json({ + schema: "flowmemory.control_plane.bridge_lifecycle_record_list.v0", + count: 1, + lifecycleRecords: [{ + lifecycleRecordId: "lifecycle:1", + baseTxHash: `0x${"1".repeat(64)}`, + logIndex: 0, + depositId: "deposit:1", + replayKey: "replay:1", + replayStatus: "accepted", + creditId: "credit:1", + recipientWallet: "wallet:credited", + withdrawalIntentId: "withdrawal:1", + withdrawalStatus: "requested", + releaseEvidenceId: "release:1", + releaseStatus: "recorded", + asset: "local-test-unit", + amountSmallestUnits: "100", + status: "credited", + artifactClass: "local-or-mock", + liveArtifact: false, + evidenceFilePath: "fixtures/bridge/local-runtime-bridge-handoff.json", + equality: { + depositAmount: "100", + observedAmount: "100", + creditedAmount: "100", + walletDelta: "100", + transferableAmount: "100", + withdrawalAmount: "100", + releaseAmount: "100", + allEqual: true, + equalities: { walletDelta: true }, + }, + }], + }); + } + if (url.endsWith("/wallets/balances")) { + return Response.json({ + schema: "flowmemory.control_plane.wallet_balance_list.v0", + count: 1, + balances: [{ + balanceId: "balance:credited", + walletAddress: "wallet:credited", + asset: "local-test-unit", + amount: "100", + status: "credited", + creditId: "credit:1", + }], + }); + } + if (url.endsWith("/wallets/transfers")) { + return Response.json({ + schema: "flowmemory.control_plane.wallet_transfer_history.v0", + count: 1, + transfers: [{ + transferId: "transfer:1", + txId: "tx:transfer:1", + fromAccountId: "wallet:credited", + toAccountId: "wallet:recipient", + assetId: "local-test-unit", + amount: "100", + status: "applied", + }], + }); + } if (url === WORKBENCH_DEVNET_STATE_PATH) { return Response.json(devnetState); } @@ -270,15 +360,87 @@ describe("dashboard fixture", () => { expect(workbench.raw.controlPlaneHealth).toEqual({ status: "ok" }); expect(workbench.raw.controlPlaneState).toEqual({ state: devnetState }); expect(workbench.raw.controlPlanePilotStatus).toMatchObject({ state: "degraded" }); + expect(workbench.raw.controlPlaneBridgeReadiness).toMatchObject({ failClosedStatus: "BLOCKED" }); + expect(workbench.raw.controlPlanePilotLifecycle).toMatchObject({ count: 1 }); + expect(workbench.sections.realValuePilot.some((record) => record.kind === "Bridge live readiness")).toBe(true); + expect(workbench.sections.realValuePilot.some((record) => record.kind === "Bridge exact lifecycle")).toBe(true); + expect(workbench.sections.realValuePilot.some((record) => record.kind === "Wallet transfer history")).toBe(true); + const lifecycleRecord = workbench.sections.realValuePilot.find((record) => record.kind === "Bridge exact lifecycle"); + expect(lifecycleRecord?.facts.find((fact) => fact.label === "replay key")?.value).toBe("replay:1"); + expect(lifecycleRecord?.facts.find((fact) => fact.label === "withdrawal intent")?.value).toBe("withdrawal:1"); + expect(lifecycleRecord?.facts.find((fact) => fact.label === "release evidence")?.value).toBe("release:1"); + expect(lifecycleRecord?.facts.find((fact) => fact.label === "withdrawal amount")?.value).toBe("100"); + expect(lifecycleRecord?.facts.find((fact) => fact.label === "release amount")?.value).toBe("100"); expect(workbench.raw.devnetState).toEqual(devnetState); expect(workbench.raw.bridgeTestDeposit).toEqual(bridgeTestDeposit); expect(workbench.loadIssues).toEqual([]); expect(fetchMock).toHaveBeenCalledWith("http://127.0.0.1:8787/health", expect.any(Object)); expect(fetchMock).toHaveBeenCalledWith("http://127.0.0.1:8787/pilot/status", expect.any(Object)); + expect(fetchMock).toHaveBeenCalledWith("http://127.0.0.1:8787/bridge/live-readiness", expect.any(Object)); + expect(fetchMock).toHaveBeenCalledWith("http://127.0.0.1:8787/pilot/lifecycle", expect.any(Object)); expect(fetchMock).toHaveBeenCalledWith(WORKBENCH_DEVNET_STATE_PATH, expect.any(Object)); expect(fetchMock).toHaveBeenCalledWith(WORKBENCH_BRIDGE_TEST_DEPOSIT_PATH, expect.any(Object)); }); + it("renders bridge readiness live-blocked without env values", () => { + const configuredButHidden = "https://example.invalid/rpc-redacted"; + const workbench = buildWorkbenchSnapshot(data, { + controlPlane: { + url: "http://127.0.0.1:8787", + status: "available", + checkedAt: "2026-05-14T15:00:00.000Z", + endpoints: ["GET /health", "GET /state", "GET /bridge/live-readiness", "GET /pilot/lifecycle"], + health: { status: "ok" }, + state: devnetState, + pilotStatus: { + schema: "flowmemory.control_plane.real_value_pilot_status.v0", + state: "degraded", + stateReason: "Waiting for Base 8453 deposit.", + baseChainId: 8453, + cappedOwnerTesting: true, + broadPublicReadiness: false, + productionReady: false, + browserStoresSecrets: false, + nextOperatorStep: { command: "npm run control-plane:serve" }, + lifecycle: [], + }, + bridgeLiveReadiness: { + schema: "flowmemory.control_plane.bridge_live_readiness.v0", + baseChainId: 8453, + baseChainName: "Base", + failClosedStatus: "BLOCKED", + readyForOperatorLivePilot: false, + lockbox: { configured: false, envName: "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS", ownerVerified: false }, + node: { running: true, chainId: "flowmemory-local-devnet-v0" }, + confirmationDepth: { configured: false, envName: "FLOWCHAIN_BASE8453_CONFIRMATION_DEPTH" }, + missingEnvNames: ["FLOWCHAIN_BASE8453_RPC_URL", "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS"], + currentArtifacts: { base8453DepositCount: 0, localOrMockDepositCount: 1, mockPresentedAsLive: false }, + issues: [{ + reasonCode: "missing_env", + status: "blocked", + title: "Missing live pilot env", + summary: "Live readiness is blocked until all required env names are present.", + envNames: ["FLOWCHAIN_BASE8453_RPC_URL", "FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS"], + }], + envValuesPrinted: false, + localOnly: true, + productionReady: false, + }, + }, + devnetState, + devnetDashboardState, + }); + const html = renderToStaticMarkup(createElement(WorkbenchView, { data, workbench })); + + expect(html).toContain("Bridge live readiness"); + expect(html).toContain("BLOCKED"); + expect(html).toContain("FLOWCHAIN_BASE8453_RPC_URL"); + expect(html).toContain("FLOWCHAIN_BASE8453_LOCKBOX_ADDRESS"); + expect(html).toContain("env values printed"); + expect(html).toContain("false"); + expect(html).not.toContain(configuredButHidden); + }); + it("renders the critical workbench view labels from fixture fallback", () => { const workbench = buildWorkbenchSnapshot(data, { devnetState, @@ -291,6 +453,7 @@ describe("dashboard fixture", () => { expect(html).toContain("Node and API status"); expect(html).toContain("Control-plane offline"); expect(html).toContain("Real-value pilot"); + expect(html).toContain("Bridge live readiness"); expect(html).toContain("capped owner testing"); expect(html).toContain("public readiness"); expect(html).toContain("Wallet Metadata"); diff --git a/apps/dashboard/src/views/BridgePilotView.tsx b/apps/dashboard/src/views/BridgePilotView.tsx new file mode 100644 index 00000000..1f403212 --- /dev/null +++ b/apps/dashboard/src/views/BridgePilotView.tsx @@ -0,0 +1,754 @@ +import { useEffect, useMemo, useState } from "react"; +import { + AlertTriangle, + ArrowRightLeft, + ChevronDown, + CircleDollarSign, + Copy, + ExternalLink, + Info, + Lock, + ShieldCheck, + Wallet, + Zap, +} from "lucide-react"; +import { StatusBadge } from "../components/StatusBadge"; +import type { DashboardStatus } from "../data/types"; +import type { WorkbenchSnapshot } from "../data/workbench"; + +type EthereumProvider = { + request(args: { method: string; params?: unknown[] | Record }): Promise; +}; + +declare global { + interface Window { + ethereum?: EthereumProvider; + } +} + +interface BridgePilotViewProps { + workbench: WorkbenchSnapshot; +} + +type BridgeReadiness = { + failClosedStatus?: string; + readyForOperatorLivePilot?: boolean; + missingEnvNames?: string[]; + issues?: Array<{ reasonCode?: string; status?: string; title?: string; summary?: string }>; + lockbox?: { configured?: boolean; ownerVerified?: boolean }; + currentArtifacts?: { base8453DepositCount?: number }; + envValuesPrinted?: boolean; + productionReady?: boolean; +}; + +const BASE_CHAIN_ID_DECIMAL = 8453; +const BASE_CHAIN_ID_HEX = "0x2105"; +const BASE_RPC_URL = "https://mainnet.base.org"; +const BASE_EXPLORER_URL = "https://basescan.org"; +const LOCKBOX_ADDRESS = "0xe731Bc6b117d92deDCA40a7ccAec11d16205026a"; +const LOCK_NATIVE_SELECTOR = "0x1326d1ec"; +const MAX_DEPOSIT_WEI = 100000000000000n; +const ETH_USD_RATE_URL = "https://api.coinbase.com/v2/exchange-rates?currency=ETH"; +const ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000"; +const BLOCKED_RECIPIENT_PLACEHOLDER = "0x5555555555555555555555555555555555555555555555555555555555555555"; +const BLOCKED_METADATA_PLACEHOLDER = "0x6666666666666666666666666666666666666666666666666666666666666666"; +const RECIPIENT_CANDIDATE_FIELDS = new Set(["accountId", "signerId", "address", "flowchainRecipient", "recipient"]); + +type BridgeRecipientOption = { + value: string; + label: string; + source: string; +}; + +function asReadiness(value: unknown): BridgeReadiness | null { + if (value === null || typeof value !== "object" || Array.isArray(value)) { + return null; + } + + return value as BridgeReadiness; +} + +function statusFromReadiness(readiness: BridgeReadiness | null): DashboardStatus { + if (!readiness) { + return "pending"; + } + + if (readiness.readyForOperatorLivePilot || readiness.failClosedStatus === "READY_FOR_OPERATOR_LIVE_PILOT") { + return "verified"; + } + + if (readiness.failClosedStatus === "FAILED") { + return "failed"; + } + + return "pending"; +} + +function isBytes32(value: string): boolean { + return /^0x[0-9a-fA-F]{64}$/.test(value); +} + +function isZeroBytes32(value: string): boolean { + return /^0x0{64}$/i.test(value); +} + +function normalizeInput(value: string): string { + return value.trim(); +} + +function isRecord(value: unknown): value is Record { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +function blockedPlaceholderLabel(value: string): string | null { + const normalized = value.toLowerCase(); + if (normalized === BLOCKED_RECIPIENT_PLACEHOLDER) { + return "old 0x555... placeholder"; + } + if (normalized === BLOCKED_METADATA_PLACEHOLDER) { + return "old 0x666... placeholder"; + } + return null; +} + +function addRecipientOption( + options: BridgeRecipientOption[], + seen: Set, + value: unknown, + label: string, + source: string, +): void { + if (typeof value !== "string") { + return; + } + const normalized = normalizeInput(value); + if (!isBytes32(normalized) || isZeroBytes32(normalized) || blockedPlaceholderLabel(normalized)) { + return; + } + const key = normalized.toLowerCase(); + if (seen.has(key)) { + return; + } + seen.add(key); + options.push({ value: normalized, label, source }); +} + +function collectRecipientOptionsFromRaw( + value: unknown, + options: BridgeRecipientOption[], + seen: Set, + source: string, + label: string, + depth = 0, +): void { + if (depth > 3) { + return; + } + if (Array.isArray(value)) { + value.forEach((item, index) => { + collectRecipientOptionsFromRaw(item, options, seen, source, `${label} ${index + 1}`, depth + 1); + }); + return; + } + if (!isRecord(value)) { + return; + } + + for (const [key, nestedValue] of Object.entries(value)) { + if (RECIPIENT_CANDIDATE_FIELDS.has(key)) { + addRecipientOption(options, seen, nestedValue, label, source); + } + if (Array.isArray(nestedValue) || isRecord(nestedValue)) { + collectRecipientOptionsFromRaw(nestedValue, options, seen, source, label, depth + 1); + } + } +} + +function collectRecipientOptions(workbench: WorkbenchSnapshot): BridgeRecipientOption[] { + const options: BridgeRecipientOption[] = []; + const seen = new Set(); + const records = [ + ...workbench.sections.walletMetadata, + ...workbench.sections.accounts, + ...workbench.sections.bridgeCredits, + ...workbench.sections.bridgeDeposits, + ]; + + for (const record of records) { + collectRecipientOptionsFromRaw(record.raw, options, seen, record.kind, record.title); + } + + return options.slice(0, 6); +} + +function validateRecipient(value: string): string | null { + if (value.length === 0) { + return "Enter a real Flowchain account id before bridging."; + } + if (!isBytes32(value)) { + return "Recipient must be a 32-byte Flowchain account id."; + } + if (isZeroBytes32(value)) { + return "Recipient cannot be the zero account id."; + } + const blockedLabel = blockedPlaceholderLabel(value); + if (blockedLabel) { + return `Recipient cannot be the ${blockedLabel}.`; + } + return null; +} + +function validateMetadataHash(value: string): string | null { + if (value.length === 0) { + return null; + } + if (!isBytes32(value)) { + return "Metadata hash must be blank or a bytes32 value."; + } + const blockedLabel = blockedPlaceholderLabel(value); + if (blockedLabel) { + return `Metadata hash cannot be the ${blockedLabel}.`; + } + return null; +} + +function shorten(value: string): string { + return `${value.slice(0, 6)}...${value.slice(-4)}`; +} + +function parseEthToWei(value: string): { wei: bigint | null; error: string | null } { + const trimmed = value.trim(); + if (!/^\d+(\.\d{0,18})?$/.test(trimmed)) { + return { wei: null, error: "Use a decimal ETH amount with up to 18 decimals." }; + } + + const [wholePart, fractionPart = ""] = trimmed.split("."); + const whole = BigInt(wholePart.length > 0 ? wholePart : "0"); + const fraction = BigInt(fractionPart.padEnd(18, "0")); + const wei = whole * 10n ** 18n + fraction; + + if (wei <= 0n) { + return { wei: null, error: "Amount must be greater than zero." }; + } + + if (wei > MAX_DEPOSIT_WEI) { + return { wei: null, error: "Amount exceeds the 0.0001 ETH pilot cap." }; + } + + return { wei, error: null }; +} + +function parseEthDecimal(value: string): number | null { + const trimmed = value.trim(); + if (!/^\d+(\.\d*)?$/.test(trimmed)) { + return null; + } + + const parsed = Number(trimmed); + return Number.isFinite(parsed) ? parsed : null; +} + +function formatUsd(value: number): string { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + maximumFractionDigits: value < 1 ? 4 : 2, + }).format(value); +} + +function usdEstimateFromEth(value: string, rate: number | null): string | null { + const parsed = parseEthDecimal(value); + if (parsed === null || rate === null) { + return null; + } + + return `~ ${formatUsd(parsed * rate)} USD`; +} + +function toQuantityHex(value: bigint): string { + return `0x${value.toString(16)}`; +} + +function buildLockNativeData(flowchainRecipient: string, metadataHash: string): string { + return `${LOCK_NATIVE_SELECTOR}${flowchainRecipient.slice(2)}${metadataHash.slice(2)}`; +} + +async function switchToBase(provider: EthereumProvider): Promise { + try { + await provider.request({ + method: "wallet_switchEthereumChain", + params: [{ chainId: BASE_CHAIN_ID_HEX }], + }); + } catch (error) { + const code = typeof error === "object" && error !== null && "code" in error ? Number((error as { code?: unknown }).code) : 0; + if (code !== 4902) { + throw error; + } + + await provider.request({ + method: "wallet_addEthereumChain", + params: [ + { + chainId: BASE_CHAIN_ID_HEX, + chainName: "Base", + nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, + rpcUrls: [BASE_RPC_URL], + blockExplorerUrls: [BASE_EXPLORER_URL], + }, + ], + }); + } +} + +function errorMessage(error: unknown): string { + if (error instanceof Error && error.message.trim().length > 0) { + return error.message; + } + + return "Wallet request failed or was rejected."; +} + +export function BridgePilotView({ workbench }: BridgePilotViewProps) { + const readiness = asReadiness(workbench.controlPlane.bridgeLiveReadiness); + const [walletAddress, setWalletAddress] = useState(null); + const [chainId, setChainId] = useState(null); + const [amountEth, setAmountEth] = useState("0.00001"); + const [recipient, setRecipient] = useState(""); + const [metadataHash, setMetadataHash] = useState(""); + const [ethUsdRate, setEthUsdRate] = useState(null); + const [priceStatus, setPriceStatus] = useState<"loading" | "ready" | "unavailable">("loading"); + const [statusMessage, setStatusMessage] = useState(null); + const [txHash, setTxHash] = useState(null); + + const normalizedRecipient = normalizeInput(recipient); + const normalizedMetadataHash = normalizeInput(metadataHash); + const effectiveMetadataHash = normalizedMetadataHash.length === 0 ? ZERO_BYTES32 : normalizedMetadataHash; + const amount = useMemo(() => parseEthToWei(amountEth), [amountEth]); + const usdEstimate = useMemo(() => usdEstimateFromEth(amountEth, ethUsdRate), [amountEth, ethUsdRate]); + const pilotCapUsdEstimate = useMemo(() => usdEstimateFromEth("0.0001", ethUsdRate), [ethUsdRate]); + const recipientOptions = useMemo(() => collectRecipientOptions(workbench), [workbench]); + const hardIssues = useMemo( + () => + (readiness?.issues ?? []).filter( + (issue) => issue.status === "blocked" && issue.reasonCode !== "no_deposits_observed", + ), + [readiness], + ); + const missingEnvNames = readiness?.missingEnvNames ?? []; + const validationIssue = + amount.error ?? + validateRecipient(normalizedRecipient) ?? + validateMetadataHash(normalizedMetadataHash) ?? + (missingEnvNames.length > 0 ? `Control-plane is missing ${missingEnvNames.join(", ")}.` : null) ?? + (hardIssues.length > 0 ? hardIssues.map((issue) => issue.reasonCode ?? issue.title ?? "blocked").join(", ") : null); + const sendDisabled = !walletAddress || validationIssue !== null; + const readinessStatus = statusFromReadiness(readiness); + const chainLabel = chainId === BASE_CHAIN_ID_HEX ? `Base ${BASE_CHAIN_ID_DECIMAL}` : chainId ?? "Connect wallet"; + const routeLabel = readinessStatus === "verified" ? "Verified" : "Pilot route"; + const receivedAmount = amount.wei === null ? "0 ETH" : `${amountEth || "0"} ETH`; + const usdEstimateLabel = + usdEstimate ?? + (priceStatus === "loading" ? "Loading ETH/USD quote" : "USD quote unavailable"); + + useEffect(() => { + const controller = new AbortController(); + + async function loadEthUsdRate() { + try { + const response = await fetch(ETH_USD_RATE_URL, { signal: controller.signal }); + if (!response.ok) { + throw new Error(`ETH/USD quote failed with ${response.status}`); + } + + const payload = (await response.json()) as { data?: { rates?: { USD?: string } } }; + const parsed = Number(payload.data?.rates?.USD); + if (!Number.isFinite(parsed) || parsed <= 0) { + throw new Error("ETH/USD quote payload did not include a positive USD rate."); + } + + setEthUsdRate(parsed); + setPriceStatus("ready"); + } catch (error) { + if (error instanceof DOMException && error.name === "AbortError") { + return; + } + setPriceStatus("unavailable"); + } + } + + void loadEthUsdRate(); + + return () => controller.abort(); + }, []); + + const connectWallet = async () => { + const provider = window.ethereum; + if (!provider) { + setStatusMessage("No browser wallet detected."); + return; + } + + try { + const accounts = await provider.request({ method: "eth_requestAccounts" }); + const nextChainId = await provider.request({ method: "eth_chainId" }); + const accountList = Array.isArray(accounts) ? accounts : []; + setWalletAddress(typeof accountList[0] === "string" ? accountList[0] : null); + setChainId(typeof nextChainId === "string" ? nextChainId : null); + setStatusMessage(null); + } catch (error) { + setStatusMessage(errorMessage(error)); + } + }; + + const sendDeposit = async () => { + const provider = window.ethereum; + if (!provider || !walletAddress || amount.wei === null || validationIssue !== null) { + return; + } + + try { + setStatusMessage("Waiting for wallet confirmation."); + setTxHash(null); + await switchToBase(provider); + const transactionHash = await provider.request({ + method: "eth_sendTransaction", + params: [ + { + from: walletAddress, + to: LOCKBOX_ADDRESS, + value: toQuantityHex(amount.wei), + data: buildLockNativeData(normalizedRecipient, effectiveMetadataHash), + }, + ], + }); + if (typeof transactionHash === "string") { + setTxHash(transactionHash); + setStatusMessage("Transaction submitted on Base."); + } else { + setStatusMessage("Wallet returned without a transaction hash."); + } + const nextChainId = await provider.request({ method: "eth_chainId" }); + setChainId(typeof nextChainId === "string" ? nextChainId : null); + } catch (error) { + setStatusMessage(errorMessage(error)); + } + }; + + const handlePrimaryAction = walletAddress ? sendDeposit : connectWallet; + const primaryActionLabel = walletAddress ? "Bridge to Flowchain" : "Connect wallet"; + + return ( +
+