diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7ac1564 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,27 @@ +.git +.gitignore +.env +.env.* +*.md +docs/ +testing/ +.superpowers/ +.claude/ +cache/ +out/ +config/ +!config/ +config/** +!config/coston2/ +config/coston2/** +!config/coston2/concord-deployment.json +contracts/ +scripts/ +proxy/ +foundry.toml +docker-compose*.yaml +Dockerfile* +.dockerignore +frontend/node_modules/ +frontend/dist/ +frontend/*.tsbuildinfo diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b7826b2 --- /dev/null +++ b/.env.example @@ -0,0 +1,75 @@ +# Extension implementation language. +# +# Resolved by directory convention: LANGUAGE= is valid iff /language.env +# exists. The scaffold ships go, python and typescript; adding your own means +# creating a directory with a language.env manifest and a Dockerfile that +# satisfies docs/extension-contract.md — no script changes required. +# +# Reproducibility differs by language: the Go image is bit-for-bit reproducible +# across machines; python/typescript target same-machine determinism only. +# See REPRODUCIBILITY.md. +LANGUAGE=go + +# Private key for proxy signing. Set this only in a local, ignored .env file. +PROXY_PRIVATE_KEY= + +# # Devnet +# INITIAL_OWNER="0x..." # Initial owner address (derived from DEPLOYMENT_PRIVATE_KEY) +# DEPLOYMENT_PRIVATE_KEY="" # Private key for deployments and contract calls + +# Coston +INITIAL_OWNER="" +DEPLOYMENT_PRIVATE_KEY="" + +# TEE governance — the signer set + threshold that governs this extension's TEE +# machines. post-build registers it on-chain (set-governance) AND the TEE node +# signs with it; both read these same vars, so they always agree. If unset, +# defaults to GOVERNANCE_SIGNERS= (the deployer) and +# GOVERNANCE_THRESHOLD=1. Set your own to use a different governance set: +# GOVERNANCE_SIGNERS="0xAbc...,0xDef..." # comma-separated 0x addresses +# GOVERNANCE_THRESHOLD=2 + +# Docker image registry (defaults to "local" for locally-built images) +# Uncomment to use remote registry: +# REGISTRY=registry.gitlab.com/flarenetwork/tee/e2e + +# Chain RPC URL +CHAIN_URL=https://coston2-api.flare.network/ext/C/rpc + +# Path to Coston2 deployed contract addresses +ADDRESSES_FILE=./config/coston2/deployed-addresses.json + +# Must be false on live networks +LOCAL_MODE=false + +# TEE attestation mode: true = use test code hash/platform (no real TEE hardware) +# Set to false when running on actual GCP Confidential VM with real attestation +SIMULATED_TEE=true + +# Normal/FTDC proxy URL on Coston2 +NORMAL_PROXY_URL=https://tee-proxy-coston2-1.flare.rocks + +# Extension proxy URL (local) +# Overwritten automatically when start-services.sh syncs the cloudflared tunnel +# (--tunnel, or any already-running tunnel). Set it by hand for ngrok. +# EXT_PROXY_URL=http://localhost:6674 +EXT_PROXY_URL=https://.ngrok-free.dev + +# Cloudflare tunnel (docs/cloudflared.md). Use a remotely-managed named tunnel +# for FCC registration; quick tunnels are not stable enough for an onchain URL. +# start-services.sh prepends `tunnel --no-autoupdate` to this value. +# TUNNEL_ARGS=run --token eyJ... +# TUNNEL_TARGET=http://host.docker.internal:6664 # only if the proxy is not on 6674 + +# --- Docker Compose overrides (all have defaults, uncomment to customize) --- +# SOURCE_DATE_EPOCH= # Unix timestamp for reproducible builds (auto-derived from git by start-services.sh) +# CHAIN_ID=31337 # Chain id the node binds signatures to (local=31337, coston=16, coston2=114) +# LOG_LEVEL=INFO # TEE node log level +# MODE=1 # TEE node mode +# REDIS_BIND=127.0.0.1:6382 # Host port binding for the extension Redis +# EXT_PROXY_INTERNAL_BIND=0.0.0.0:6673 # Host port binding for proxy internal API +# EXT_PROXY_EXTERNAL_BIND=0.0.0.0:6674 # Host port binding for proxy external API +# COMPOSE_NETWORK=docker_default # Docker network name (base compose joins this external network) +# USE_LOCAL_SIBLINGS=0 # Set to 1 to build node/proxy from on-disk sibling checkouts +# WAIT_TIMEOUT= # Timeout for post-build registration waits +# TEE_VERSION= # TEE version label for allow-tee-version diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bbac0fa --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,856 @@ +name: Concord verification + +on: + push: + pull_request: + workflow_dispatch: + inputs: + deploy_sender: + description: 'Deploy a new ConcordInstructionSender on Coston2' + required: false + default: false + type: boolean + register_extension: + description: 'Register the existing sender as a Flare FCC extension' + required: false + default: false + type: boolean + deploy_facility: + description: 'Deploy the Concord AccordRegistry and CapitalFacility on Coston2' + required: false + default: false + type: boolean + setup_fcc: + description: 'Start the official Coston2 FCC scaffold and register one TEE machine' + required: false + default: false + type: boolean + open_round: + description: 'Create the deterministic FXRP root Accord and open its Makkari round' + required: false + default: false + type: boolean + fund_provider_gas: + description: 'Send 5 C2FLR to each provider for approval and funding gas' + required: false + default: false + type: boolean + execute_fcc_round: + description: 'Submit three signed provider offers, finalize CoFill, verify FCC evidence, and materialize Child Accords' + required: false + default: false + type: boolean + materialize_existing: + description: 'Materialize a previously verified FCC artifact without replaying the finalized round' + required: false + default: false + type: boolean + source_run_id: + description: 'Workflow run ID containing concord-fcc-round-evidence when materialize_existing is true' + required: false + default: '' + type: string + execute_facility_flow: + description: 'Fund all materialized children, execute a multi-child draw, repay it, and assert restored capacity' + required: false + default: false + type: boolean + +permissions: + contents: read + +jobs: + solidity: + name: Solidity / Foundry + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: stable + - name: Show toolchain + run: forge --version + - name: Run Solidity lifecycle tests + run: forge test -vvv + + go: + name: Go extension and tooling + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Install Go 1.25.1 + uses: actions/setup-go@v5 + with: + go-version: "1.25.1" + cache: false + - name: Test Concord FCC extension + working-directory: go + env: + GOTOOLCHAIN: local + run: go test ./... + - name: Test deployment, verifier, API and MCP tooling + working-directory: tools + env: + GOTOOLCHAIN: local + run: go test ./... + - name: Check Flare dependency pin consistency + run: bash scripts/check-versions.sh + + sdk: + name: TypeScript SDK + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Install Node.js 22.10.0 + uses: actions/setup-node@v4 + with: + node-version: "22.10.0" + - name: Install SDK dependencies + working-directory: sdk/typescript + run: npm install --ignore-scripts --no-audit --no-fund + - name: Build SDK declarations + working-directory: sdk/typescript + run: npm run build + + repository: + name: Repository and documentation gates + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Parse shell scripts + run: find scripts -type f -name "*.sh" -print0 | xargs -0 -n1 bash -n + - name: Validate Concord documentation contract + run: bash scripts/check-docs.sh + - name: Validate evidence template + run: bash scripts/check-evidence-template.sh + - name: Validate read-only Coston2 preflight command + run: bash scripts/coston2-preflight.sh --offline + + + codespace_fcc_runtime: + name: Coston2 FCC runtime in disposable Codespace + if: github.event_name == 'workflow_dispatch' && inputs.setup_fcc == true && github.ref == 'refs/heads/agent/concord-rebuild' + runs-on: ubuntu-latest + env: + CODESPACE_NAME: friendly-engine-r7r5qrwvvrj5c5777 + GH_TOKEN: ${{ secrets.CONCORD_CODESPACE_PAT }} + PROXY_PRIVATE_KEY: ${{ secrets.CONCORD_FCC_PROXY_PRIVATE_KEY }} + INDEXER_DB_HOST: ${{ secrets.CONCORD_FCC_INDEXER_DB_HOST }} + INDEXER_DB_NAME: ${{ secrets.CONCORD_FCC_INDEXER_DB_NAME }} + INDEXER_DB_USER: ${{ secrets.CONCORD_FCC_INDEXER_DB_USER }} + INDEXER_DB_PASSWORD: ${{ secrets.CONCORD_FCC_INDEXER_DB_PASSWORD }} + steps: + - name: Check out requested ref + uses: actions/checkout@v4 + - name: Prepare and start official scaffold + shell: bash + run: bash scripts/prepare-coston2-codespace.sh + - name: Register proxy port with Codespaces + shell: bash + run: | + set -euo pipefail + log_file="${RUNNER_TEMP}/concord-codespace-port-forward.log" + gh codespace ports forward 6674:6674 -c "$CODESPACE_NAME" >"$log_file" 2>&1 & + forward_pid=$! + cleanup() { + kill "$forward_pid" 2>/dev/null || true + } + trap cleanup EXIT + for _ in $(seq 1 30); do + if gh codespace ports --codespace "$CODESPACE_NAME" --json sourcePort \ + | jq -e 'any(.[]; .sourcePort == 6674)' >/dev/null; then + echo "Codespaces registered proxy port 6674" + exit 0 + fi + if ! kill -0 "$forward_pid" 2>/dev/null; then + cat "$log_file" >&2 || true + exit 1 + fi + sleep 1 + done + cat "$log_file" >&2 || true + exit 1 + - name: Make proxy port public + shell: bash + run: gh codespace ports visibility 6674:public -c "$CODESPACE_NAME" + - name: Verify public proxy port + shell: bash + run: | + set -euo pipefail + public_url="$(gh codespace ports --codespace "$CODESPACE_NAME" --json sourcePort,browseUrl,visibility \ + | jq -r '.[] | select(.sourcePort == 6674 and .visibility == "public") | .browseUrl' | head -n 1)" + test -n "$public_url" + curl --fail --silent --show-error "$public_url/info" \ + | jq '{extensionId: .machineData.extensionId, codeHash: .machineData.codeHash, platform: .machineData.platform, chainId: .teeInfo.chainId, lastSigningPolicyId: .teeInfo.lastSigningPolicyId}' + - name: Install Go for relay registration + uses: actions/setup-go@v5 + with: + go-version: "1.25.1" + cache: false + - name: Register simulated development TEE through stable Worker relay + shell: bash + env: + CHAIN: coston2 + CHAIN_URL: https://coston2-api.flare.network/ext/C/rpc + NORMAL_PROXY_URL: https://tee-proxy-coston2-1.flare.rocks + EXT_PROXY_URL: ${{ secrets.CONCORD_FCC_PUBLIC_PROXY_URL }} + EXT_PROXY_HOST_URL: ${{ secrets.CONCORD_FCC_PUBLIC_PROXY_URL }} + LOCAL_MODE: "false" + SIMULATED_TEE: "true" + DEPLOYMENT_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_DEPLOYER_PRIVATE_KEY }} + INITIAL_OWNER: "0x1aF42c70837f08c6C89a6FA274EB9eeF040820B3" + GOVERNANCE_SIGNERS: "0x1aF42c70837f08c6C89a6FA274EB9eeF040820B3" + GOVERNANCE_THRESHOLD: "1" + ADDRESSES_FILE: ./config/coston2/deployed-addresses.json + REGISTER_TEE_COMMAND: rRap + run: | + set -euo pipefail + test -n "$DEPLOYMENT_PRIVATE_KEY" + test -n "$EXT_PROXY_URL" + test -n "$EXT_PROXY_HOST_URL" + case "$EXT_PROXY_URL" in + https://*) ;; + *) echo 'relay registration requires a public HTTPS Worker URL' >&2; exit 1 ;; + esac + curl --fail --silent --show-error --max-time 20 "$EXT_PROXY_URL/info" \ + | jq -e '.machineData.extensionId == "0x000000000000000000000000000000000000000000000000000000000001028c" and .teeInfo.chainId == 114' >/dev/null + bash scripts/post-build.sh + - name: Verify one active production TEE machine + shell: bash + env: + CHAIN_URL: https://coston2-api.flare.network/ext/C/rpc + run: | + set -euo pipefail + (cd tools && go run ./cmd/query-tee \ + -rpc "$CHAIN_URL" \ + -reg 0x1a9C4A0f9D76c0b1D91d22E24E573a9b377618aE \ + -ext 66188) | tee "$RUNNER_TEMP/concord-tee-query.txt" + active_count="$(grep -Ec '^[[:space:]]+[0-9]+: 0x[0-9a-fA-F]{40} url="https://' "$RUNNER_TEMP/concord-tee-query.txt" || true)" + test "$active_count" = 1 + tee_id="$(sed -n 's/^[[:space:]]*[0-9]*: \(0x[0-9a-fA-F]\{40\}\) url=.*/\1/p' "$RUNNER_TEMP/concord-tee-query.txt" | head -n 1)" + test -n "$tee_id" + (cd tools && go run ./cmd/query-tee \ + -rpc "$CHAIN_URL" \ + -reg 0x1a9C4A0f9D76c0b1D91d22E24E573a9b377618aE \ + "$tee_id") | tee "$RUNNER_TEMP/concord-tee-machine.txt" + grep -q 'getTeeMachineStatus: 2' "$RUNNER_TEMP/concord-tee-machine.txt" + grep -q 'getTeeMachine:.*url="https://' "$RUNNER_TEMP/concord-tee-machine.txt" + { + echo 'Coston2 FCC scaffold registered exactly one simulated development TEE through the stable Worker relay.' + echo "TEE machine status: 2 (PRODUCTION); teeId: $tee_id" + echo 'The relay is an ingress fallback; it is not a named Cloudflare Tunnel or production hardware-TEE claim.' + } >> "$GITHUB_STEP_SUMMARY" + + + live_fcc_round: + name: Coston2 FCC round and Child Accord materialization + if: github.event_name == 'workflow_dispatch' && inputs.setup_fcc == true && inputs.execute_fcc_round == true && github.ref == 'refs/heads/agent/concord-rebuild' + needs: codespace_fcc_runtime + concurrency: + group: concord-coston2-fcc-round + cancel-in-progress: false + runs-on: ubuntu-latest + env: + CHAIN_URL: https://coston2-api.flare.network/ext/C/rpc + EXT_PROXY_URL: ${{ secrets.CONCORD_FCC_PUBLIC_PROXY_URL }} + DEPLOYMENT_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_DEPLOYER_PRIVATE_KEY }} + CONCORD_E2E_PROVIDER_A_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_PROVIDER_A_PRIVATE_KEY }} + CONCORD_E2E_PROVIDER_B_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_PROVIDER_B_PRIVATE_KEY }} + CONCORD_E2E_PROVIDER_C_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_PROVIDER_C_PRIVATE_KEY }} + INSTRUCTION_SENDER: "0x574b523eA944EFe9143AF9d6c46bfA925beE2968" + FACILITY: "0xfaff601a18a9fca33378953515aa0f3ef9286ecd" + TEE_REGISTRY: "0x1a9C4A0f9D76c0b1D91d22E24E573a9b377618aE" + EXTENSION_ID: "0x000000000000000000000000000000000000000000000000000000000001028c" + ADDRESSES_FILE: ./config/coston2/deployed-addresses.json + steps: + - name: Check out requested ref + uses: actions/checkout@v4 + + - name: Install Go 1.25.1 + uses: actions/setup-go@v5 + with: + go-version: "1.25.1" + cache: false + + - name: Build ephemeral signed provider offers from live round state + shell: bash + run: | + set -euo pipefail + round_id="$(jq -r '.rootRound.roundId' config/coston2/concord-deployment.json)" + root_id="$(jq -r '.rootRound.rootAccordId' config/coston2/concord-deployment.json)" + provider_a="$(jq -r '.rootRound.eligibleProviders[0]' config/coston2/concord-deployment.json)" + provider_b="$(jq -r '.rootRound.eligibleProviders[1]' config/coston2/concord-deployment.json)" + provider_c="$(jq -r '.rootRound.eligibleProviders[2]' config/coston2/concord-deployment.json)" + test "$round_id" != "null" + test "$root_id" != "null" + mkdir -p "$RUNNER_TEMP/concord-quotes" + (cd go && GOTOOLCHAIN=local go run ./cmd/build-quotes \ + -rpc "$CHAIN_URL" \ + -facility "$FACILITY" \ + -extension-id "$EXTENSION_ID" \ + -round-id "$round_id" \ + -root-accord-id "$root_id" \ + -out-dir "$RUNNER_TEMP/concord-quotes" \ + -provider-a "$provider_a" \ + -provider-b "$provider_b" \ + -provider-c "$provider_c") + jq -e '.roundId == $round and .rootAccordId == $root' \ + --arg round "$round_id" --arg root "$root_id" \ + "$RUNNER_TEMP/concord-quotes/finalize-round.json" >/dev/null + + - name: Submit quotes and finalize CoFill through FCC + shell: bash + env: + CONCORD_QUOTES: ${{ runner.temp }}/concord-quotes/quote-a.json,${{ runner.temp }}/concord-quotes/quote-b.json,${{ runner.temp }}/concord-quotes/quote-c.json + CONCORD_FINALIZE: ${{ runner.temp }}/concord-quotes/finalize-round.json + CONCORD_EVIDENCE_OUT: ${{ runner.temp }}/concord-fcc-action.json + LOCAL_MODE: "false" + run: | + set -euo pipefail + case "$EXT_PROXY_URL" in https://*) ;; *) echo 'FCC round requires public HTTPS proxy URL' >&2; exit 1 ;; esac + bash scripts/test.sh + + - name: Verify FCC evidence and materialize Child Accords + shell: bash + run: | + set -euo pipefail + round_id="$(jq -r '.rootRound.roundId' config/coston2/concord-deployment.json)" + root_id="$(jq -r '.rootRound.rootAccordId' config/coston2/concord-deployment.json)" + (cd tools && GOTOOLCHAIN=local go run ./cmd/verify-allocation \ + -c "$CHAIN_URL" \ + -facility "$FACILITY" \ + -teeRegistry "$TEE_REGISTRY" \ + -result "$RUNNER_TEMP/concord-fcc-action.json" \ + -extensionId "$EXTENSION_ID" \ + -roundId "$round_id" \ + -rootAccordId "$root_id" \ + -mark \ + -materialize \ + -out "$RUNNER_TEMP/concord-allocation-verification.json") + jq -e '.broadcast == true and .materialized == true' \ + "$RUNNER_TEMP/concord-allocation-verification.json" >/dev/null + { + echo 'FCC round evidence verified against the active Coston2 TEE machine.' + echo 'Allocation digest authorized and Child Accords materialized on the canonical CapitalFacility.' + } >> "$GITHUB_STEP_SUMMARY" + + - name: Preserve FCC round evidence + if: always() + uses: actions/upload-artifact@v4 + with: + name: concord-fcc-round-evidence + path: | + ${{ runner.temp }}/concord-fcc-action.json + ${{ runner.temp }}/concord-allocation-verification.json + if-no-files-found: ignore + + + live_materialize_existing: + name: Materialize previously verified FCC allocation + if: github.event_name == 'workflow_dispatch' && inputs.materialize_existing == true && github.ref == 'refs/heads/agent/concord-rebuild' + permissions: + actions: read + contents: read + concurrency: + group: concord-coston2-fcc-materialization + cancel-in-progress: false + runs-on: ubuntu-latest + env: + CHAIN_URL: https://coston2-api.flare.network/ext/C/rpc + DEPLOYMENT_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_DEPLOYER_PRIVATE_KEY }} + FACILITY: "0xfaff601a18a9fca33378953515aa0f3ef9286ecd" + TEE_REGISTRY: "0x1a9C4A0f9D76c0b1D91d22E24E573a9b377618aE" + EXTENSION_ID: "0x000000000000000000000000000000000000000000000000000000000001028c" + steps: + - name: Check out requested ref + uses: actions/checkout@v4 + + - name: Install Go 1.25.1 + uses: actions/setup-go@v5 + with: + go-version: "1.25.1" + cache: false + + - name: Require source run id + shell: bash + run: test -n "${{ inputs.source_run_id }}" + + - name: Download signed FCC evidence from source run + uses: actions/download-artifact@v4 + with: + name: concord-fcc-round-evidence + path: ${{ runner.temp }}/concord-fcc-round-evidence + run-id: ${{ inputs.source_run_id }} + github-token: ${{ github.token }} + + - name: Verify digest and materialize Child Accords + shell: bash + run: | + set -euo pipefail + round_id="$(jq -r '.rootRound.roundId' config/coston2/concord-deployment.json)" + root_id="$(jq -r '.rootRound.rootAccordId' config/coston2/concord-deployment.json)" + action_file="$(find "$RUNNER_TEMP/concord-fcc-round-evidence" -name concord-fcc-action.json -type f -print -quit)" + test -n "$action_file" + (cd tools && GOTOOLCHAIN=local go run ./cmd/verify-allocation \ + -c "$CHAIN_URL" \ + -facility "$FACILITY" \ + -teeRegistry "$TEE_REGISTRY" \ + -result "$action_file" \ + -extensionId "$EXTENSION_ID" \ + -roundId "$round_id" \ + -rootAccordId "$root_id" \ + -mark \ + -materialize \ + -out "$RUNNER_TEMP/concord-allocation-verification.json") + jq -e '.broadcast == true and .materialized == true' \ + "$RUNNER_TEMP/concord-allocation-verification.json" >/dev/null + { + echo "Recovered signed FCC artifact from run ${{ inputs.source_run_id }}." + echo 'Allocation digest was already authorized; Child Accords are now materialized on the canonical CapitalFacility.' + } >> "$GITHUB_STEP_SUMMARY" + + + live_facility_flow: + name: Coston2 funding, multi-child draw, and repayment + if: github.event_name == 'workflow_dispatch' && inputs.execute_facility_flow == true && github.ref == 'refs/heads/agent/concord-rebuild' + needs: go + permissions: + contents: read + concurrency: + group: concord-coston2-facility-flow + cancel-in-progress: false + runs-on: ubuntu-latest + env: + CHAIN_URL: https://coston2-api.flare.network/ext/C/rpc + DEPLOYMENT_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_DEPLOYER_PRIVATE_KEY }} + CONCORD_E2E_PROVIDER_A_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_PROVIDER_A_PRIVATE_KEY }} + CONCORD_E2E_PROVIDER_B_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_PROVIDER_B_PRIVATE_KEY }} + CONCORD_E2E_PROVIDER_C_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_PROVIDER_C_PRIVATE_KEY }} + FACILITY: "0xfaff601a18a9fca33378953515aa0f3ef9286ecd" + USDT0: "0xC1A5B41512496B80903D1f32d6dEa3a73212E71F" + steps: + - name: Check out requested ref + uses: actions/checkout@v4 + + - name: Install Go 1.25.1 + uses: actions/setup-go@v5 + with: + go-version: "1.25.1" + cache: false + + - name: Run funded facility lifecycle + shell: bash + run: | + set -euo pipefail + root_id="$(jq -r '.rootRound.rootAccordId' config/coston2/concord-deployment.json)" + test "$root_id" != "null" + mkdir -p "$RUNNER_TEMP/concord-facility-flow" + (cd tools && GOTOOLCHAIN=local go run ./cmd/facility-flow \ + -rpc "$CHAIN_URL" \ + -facility "$FACILITY" \ + -token "$USDT0" \ + -root-accord-id "$root_id" \ + -out "$RUNNER_TEMP/concord-facility-flow/facility-flow.json") + jq -e 'all(.invariants[]; . == true) and .drawLegCount >= 2 and .drawnAfter == "0"' \ + "$RUNNER_TEMP/concord-facility-flow/facility-flow.json" >/dev/null + { + echo 'All materialized Child Accords funded with real Coston2 USDT0 transfers.' + echo 'A multi-child draw was settled, fully repaid, and available capacity was restored.' + } >> "$GITHUB_STEP_SUMMARY" + + - name: Preserve facility lifecycle evidence + if: always() + uses: actions/upload-artifact@v4 + with: + name: concord-facility-flow-evidence + path: ${{ runner.temp }}/concord-facility-flow/facility-flow.json + if-no-files-found: ignore + + + live_sender_deploy: + name: Coston2 sender deployment (manual only) + if: github.event_name == 'workflow_dispatch' && inputs.deploy_sender == true + runs-on: ubuntu-latest + env: + CHAIN_URL: https://coston2-api.flare.network/ext/C/rpc + DEPLOYMENT_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_DEPLOYER_PRIVATE_KEY }} + steps: + - name: Check out requested ref + uses: actions/checkout@v4 + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: stable + + - name: Deploy instruction sender + shell: bash + run: | + set -euo pipefail + test -n "$DEPLOYMENT_PRIVATE_KEY" + owner="$(cast wallet address --private-key "$DEPLOYMENT_PRIVATE_KEY")" + tee_registry="$(jq -r '.[] | select(.name == "FlareTeeManager") | .address' config/coston2/deployed-addresses.json)" + test "$owner" != "" + test "$tee_registry" != "null" + test "$tee_registry" != "" + export DEPLOYMENT_OWNER="$owner" + export TEE_EXTENSION_REGISTRY="$tee_registry" + export TEE_MACHINE_REGISTRY="$tee_registry" + DEPLOY_PHASE=sender bash scripts/deploy-coston2.sh + + + live_extension_register: + name: Coston2 FCC extension registration (manual only) + if: github.event_name == 'workflow_dispatch' && inputs.register_extension == true + runs-on: ubuntu-latest + env: + CHAIN_URL: https://coston2-api.flare.network/ext/C/rpc + DEPLOYMENT_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_DEPLOYER_PRIVATE_KEY }} + INSTRUCTION_SENDER: "0x574b523eA944EFe9143AF9d6c46bfA925beE2968" + steps: + - name: Check out requested ref + uses: actions/checkout@v4 + + - name: Install Go 1.25.1 + uses: actions/setup-go@v5 + with: + go-version: "1.25.1" + cache: false + + - name: Register FCC extension + working-directory: tools + shell: bash + run: | + set -euo pipefail + test -n "$DEPLOYMENT_PRIVATE_KEY" + test "$INSTRUCTION_SENDER" != "" + extension_id="$(go run ./cmd/register-extension \ + -a ../config/coston2/deployed-addresses.json \ + -c "$CHAIN_URL" \ + --instructionSender "$INSTRUCTION_SENDER" \ + 2>"$RUNNER_TEMP/concord-register-extension.log" | tail -1)" + cat "$RUNNER_TEMP/concord-register-extension.log" >&2 + [[ "$extension_id" =~ ^0x[0-9a-fA-F]{64}$ ]] + echo "Registered FCC extension ID: $extension_id" >> "$GITHUB_STEP_SUMMARY" + + + live_facility_deploy: + name: Coston2 Concord facility deployment (manual only) + concurrency: + group: concord-coston2-facility-deployment + cancel-in-progress: false + if: github.event_name == 'workflow_dispatch' && inputs.deploy_facility == true + runs-on: ubuntu-latest + env: + CHAIN_URL: https://coston2-api.flare.network/ext/C/rpc + DEPLOYMENT_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_DEPLOYER_PRIVATE_KEY }} + DEPLOYMENT_OWNER: "0x1aF42c70837f08c6C89a6FA274EB9eeF040820B3" + ALLOCATION_VERIFIER: "0x1aF42c70837f08c6C89a6FA274EB9eeF040820B3" + CONCORD_EXTENSION_ID: "0x000000000000000000000000000000000000000000000000000000000001028c" + steps: + - name: Check out requested ref + uses: actions/checkout@v4 + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: stable + + - name: Deploy Concord facility + shell: bash + run: | + set -euo pipefail + test -n "$DEPLOYMENT_PRIVATE_KEY" + test "$DEPLOYMENT_OWNER" != "" + test "$ALLOCATION_VERIFIER" != "" + test "$CONCORD_EXTENSION_ID" != "" + existing_facility="$(jq -r '.canonicalFacility.capitalFacility // empty' config/coston2/concord-deployment.json 2>/dev/null || true)" + if [[ -n "$existing_facility" ]] && [[ "$(cast code "$existing_facility" --rpc-url "$CHAIN_URL")" != "0x" ]]; then + echo "canonical CapitalFacility already has code at $existing_facility; refusing a duplicate deployment" >&2 + exit 1 + fi + DEPLOY_PHASE=facility bash scripts/deploy-coston2.sh + + + live_fcc_setup: + name: Coston2 FCC scaffold setup (manual only) + if: github.event_name == 'workflow_dispatch' && inputs.setup_fcc == true && github.ref != 'refs/heads/agent/concord-rebuild' + concurrency: + group: concord-coston2-fcc-setup + cancel-in-progress: false + runs-on: ubuntu-latest + env: + CHAIN: coston2 + CHAIN_URL: https://coston2-api.flare.network/ext/C/rpc + NORMAL_PROXY_URL: https://tee-proxy-coston2-1.flare.rocks + EXT_PROXY_URL: ${{ secrets.CONCORD_FCC_PUBLIC_PROXY_URL }} + EXT_PROXY_HOST_URL: ${{ secrets.CONCORD_FCC_PUBLIC_PROXY_URL }} + TUNNEL_ARGS: ${{ secrets.CONCORD_FCC_TUNNEL_ARGS }} + LOCAL_MODE: "false" + SIMULATED_TEE: "true" + DEPLOYMENT_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_DEPLOYER_PRIVATE_KEY }} + DEPLOYMENT_OWNER: "0x1aF42c70837f08c6C89a6FA274EB9eeF040820B3" + INITIAL_OWNER: "0x1aF42c70837f08c6C89a6FA274EB9eeF040820B3" + GOVERNANCE_SIGNERS: "0x1aF42c70837f08c6C89a6FA274EB9eeF040820B3" + GOVERNANCE_THRESHOLD: "1" + EXTENSION_ID: "0x000000000000000000000000000000000000000000000000000000000001028c" + INSTRUCTION_SENDER: "0x574b523eA944EFe9143AF9d6c46bfA925beE2968" + PROXY_PRIVATE_KEY: ${{ secrets.CONCORD_FCC_PROXY_PRIVATE_KEY }} + INDEXER_DB_HOST: ${{ secrets.CONCORD_FCC_INDEXER_DB_HOST }} + INDEXER_DB_NAME: ${{ secrets.CONCORD_FCC_INDEXER_DB_NAME }} + INDEXER_DB_USER: ${{ secrets.CONCORD_FCC_INDEXER_DB_USER }} + INDEXER_DB_PASSWORD: ${{ secrets.CONCORD_FCC_INDEXER_DB_PASSWORD }} + steps: + - name: Check out requested ref + uses: actions/checkout@v4 + + - name: Install Go 1.25.1 + uses: actions/setup-go@v5 + with: + go-version: "1.25.1" + cache: false + + - name: Prepare ignored live scaffold configuration + shell: bash + run: | + set -euo pipefail + test -n "$DEPLOYMENT_PRIVATE_KEY" + test -n "$PROXY_PRIVATE_KEY" + test -n "$EXT_PROXY_URL" + test -n "$EXT_PROXY_HOST_URL" + test -n "$TUNNEL_ARGS" + case "$EXT_PROXY_URL" in + https://*) ;; + *) echo 'FCC setup requires a public HTTPS EXT_PROXY_URL' >&2; exit 1 ;; + esac + case "$EXT_PROXY_URL" in + *trycloudflare.com*|*ngrok*|*loca.lt*) + echo 'FCC setup refuses a rotating quick-tunnel URL; use a stable named tunnel hostname' >&2 + exit 1 + ;; + esac + [[ "$TUNNEL_ARGS" == run\ --token\ * ]] || { + echo 'FCC setup requires TUNNEL_ARGS in the form: run --token ' >&2 + exit 1 + } + [[ "$TUNNEL_ARGS" != *--url* ]] || { + echo 'FCC setup refuses quick-tunnel --url arguments for a registered machine' >&2 + exit 1 + } + test -n "$INDEXER_DB_HOST" + test -n "$INDEXER_DB_NAME" + test -n "$INDEXER_DB_USER" + test -n "$INDEXER_DB_PASSWORD" + umask 077 + printf '%s\n' \ + "CHAIN=$CHAIN" \ + "CHAIN_URL=$CHAIN_URL" \ + "NORMAL_PROXY_URL=$NORMAL_PROXY_URL" \ + "EXT_PROXY_URL=$EXT_PROXY_URL" \ + "EXT_PROXY_HOST_URL=$EXT_PROXY_HOST_URL" \ + "LOCAL_MODE=$LOCAL_MODE" \ + "SIMULATED_TEE=$SIMULATED_TEE" \ + "PROXY_PRIVATE_KEY=$PROXY_PRIVATE_KEY" \ + "DEPLOYMENT_PRIVATE_KEY=$DEPLOYMENT_PRIVATE_KEY" \ + "INITIAL_OWNER=$INITIAL_OWNER" \ + "GOVERNANCE_SIGNERS=$GOVERNANCE_SIGNERS" \ + "GOVERNANCE_THRESHOLD=$GOVERNANCE_THRESHOLD" \ + "ADDRESSES_FILE=./config/coston2/deployed-addresses.json" \ + > .env + printf 'TUNNEL_ARGS=%q\n' "$TUNNEL_ARGS" >> .env + printf 'EXTENSION_ID=%s\nINSTRUCTION_SENDER=%s\n' "$EXTENSION_ID" "$INSTRUCTION_SENDER" > config/extension.env + printf '%s\n' \ + 'redis_port = "redis:6379"' \ + 'private_key_variable = "PROXY_PRIVATE_KEY"' \ + 'initial_signing_policy_offset = 2' \ + 'signing_policy_fetch_interval = "20s"' \ + '' \ + 'chain_id = 114' \ + '' \ + '[db]' \ + "host = \"$INDEXER_DB_HOST\"" \ + 'port = 3306' \ + "database = \"$INDEXER_DB_NAME\"" \ + "username = \"$INDEXER_DB_USER\"" \ + "password = \"$INDEXER_DB_PASSWORD\"" \ + 'log_queries = false' \ + '' \ + '[addresses]' \ + 'flare_systems_manager = "0xA90Db6D10F856799b10ef2A77EBCbF460aC71e52"' \ + 'relay = "0xa10B672D1c62e5457b17af63d4302add6A99d7dE"' \ + 'voter_registry = "0x6a0AF07b7972177B176d3D422555cbc98DfDe914"' \ + '' \ + '[ports]' \ + 'internal = "6663"' \ + 'external = "6664"' \ + '' \ + '[info_timing]' \ + 'cycle_internal = "10s"' \ + 'cycle_queue_response_wait = "2s"' \ + '' \ + '[voting]' \ + 'proposal_expiration = "12s"' \ + 'max_pending_request = 10000' \ + > config/proxy/extension_proxy.coston2.docker.toml + + - name: Start official Coston2 FCC scaffold with HTTPS tunnel + shell: bash + run: ./scripts/start-services.sh --chain coston2 --tunnel + + - name: Register simulated development TEE machine + shell: bash + run: | + set -euo pipefail + proxy_info="$(curl --fail --silent --show-error --max-time 20 "$EXT_PROXY_URL/info")" + proxy_extension_id="$(jq -r '.extensionId // .machineData.extensionId // empty' <<<"$proxy_info")" + test "$proxy_extension_id" = "$EXTENSION_ID" || { + echo "extension proxy /info is bound to '$proxy_extension_id', expected '$EXTENSION_ID'" >&2 + exit 1 + } + REGISTER_TEE_COMMAND=rRap ./scripts/post-build.sh + (cd tools && go run ./cmd/query-tee \ + -rpc "$CHAIN_URL" \ + -reg 0x1a9C4A0f9D76c0b1D91d22E24E573a9b377618aE \ + -ext 66188) | tee "$RUNNER_TEMP/concord-tee-query.txt" + active_count="$(grep -Ec '^[[:space:]]+[0-9]+: 0x[0-9a-fA-F]{40} url="https://' "$RUNNER_TEMP/concord-tee-query.txt" || true)" + test "$active_count" = 1 || { + echo "expected exactly one active Coston2 TEE machine for extension 66188, found $active_count" >&2 + exit 1 + } + tee_id="$(sed -n 's/^[[:space:]]*[0-9]*: \(0x[0-9a-fA-F]\{40\}\) url=.*/\1/p' "$RUNNER_TEMP/concord-tee-query.txt" | head -n1)" + test -n "$tee_id" + (cd tools && go run ./cmd/query-tee \ + -rpc "$CHAIN_URL" \ + -reg 0x1a9C4A0f9D76c0b1D91d22E24E573a9b377618aE \ + "$tee_id") | tee "$RUNNER_TEMP/concord-tee-machine.txt" + grep -q 'getTeeMachineStatus: 2' "$RUNNER_TEMP/concord-tee-machine.txt" + grep -q 'getTeeMachine:.*url="https://' "$RUNNER_TEMP/concord-tee-machine.txt" + { + echo 'Coston2 FCC scaffold registered exactly one development TEE machine.' + echo "TEE machine status: 2 (PRODUCTION); teeId: $tee_id" + echo 'The machine is valid only while this host and its stable HTTPS URL remain available.' + } >> "$GITHUB_STEP_SUMMARY" + + - name: Tear down ephemeral FCC services + if: always() + shell: bash + run: | + docker compose -f docker-compose.yaml -f docker-compose.coston2.yaml down --remove-orphans || true + docker compose -f docker-compose.cloudflared.yaml down --remove-orphans || true + + + live_open_round: + name: Coston2 root Accord and Makkari round (manual only) + if: github.event_name == 'workflow_dispatch' && inputs.open_round == true + concurrency: + group: concord-coston2-open-round + cancel-in-progress: false + runs-on: ubuntu-latest + env: + CHAIN_URL: https://coston2-api.flare.network/ext/C/rpc + DEPLOYMENT_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_DEPLOYER_PRIVATE_KEY }} + CAPITAL_FACILITY: "" + FXRP_TOKEN: "" + ROOT_ID: "" + ROUND_ID: "" + POLICY_HASH: "" + TARGET_CAPACITY: "9000000" + COLLATERAL_AMOUNT: "1000000" + ROOT_VALID_UNTIL: "" + ROUND_EXPIRY: "" + MAX_FEE_BPS: "700" + PROVIDER_A: "0x8d9340278094DF3F032c674C912D8eA08c17437C" + PROVIDER_B: "0xF2E25459108A0454a85d03848AD6059C271cc9a5" + PROVIDER_C: "0x9537eA25C28e8c1663e8B57C52A0761a244183A1" + steps: + - name: Check out requested ref + uses: actions/checkout@v4 + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: stable + + - name: Create root Accord and open Makkari round + shell: bash + run: | + set -euo pipefail + test -n "$DEPLOYMENT_PRIVATE_KEY" + CAPITAL_FACILITY="$(jq -r '.canonicalFacility.capitalFacility // empty' config/coston2/concord-deployment.json)" + FXRP_TOKEN="$(jq -r '.assets.fxrp' config/coston2/concord-deployment.json)" + test -n "$CAPITAL_FACILITY" + test -n "$FXRP_TOKEN" + test "$(cast code "$CAPITAL_FACILITY" --rpc-url "$CHAIN_URL")" != "0x" + ROOT_ID="$(cast keccak "concord-coston2-root-${GITHUB_RUN_ID}")" + ROUND_ID="$(cast keccak "concord-coston2-round-${GITHUB_RUN_ID}")" + POLICY_HASH="$(cast keccak 'concord-coston2-policy-v2')" + ROOT_VALID_UNTIL="$(date -u -d '+7 days' +%s)" + ROUND_EXPIRY="$(date -u -d '+3 days' +%s)" + send() { + local label="$1" + shift + local output tx + output="$(cast send "$@" --rpc-url "$CHAIN_URL" --private-key "$DEPLOYMENT_PRIVATE_KEY" --json)" + tx="$(jq -r '.transactionHash // .hash // empty' <<<"$output")" + test "$tx" != "" + echo "$label: $tx" + echo "$label: $tx" >> "$GITHUB_STEP_SUMMARY" + } + send approve_fxrp \ + "$FXRP_TOKEN" 'approve(address,uint256)' "$CAPITAL_FACILITY" "$COLLATERAL_AMOUNT" + send create_root \ + "$CAPITAL_FACILITY" 'createRootAccord(bytes32,uint256,uint64,bytes32)' \ + "$ROOT_ID" "$TARGET_CAPACITY" "$ROOT_VALID_UNTIL" "$POLICY_HASH" + send lock_collateral \ + "$CAPITAL_FACILITY" 'lockCollateral(bytes32,uint256)' "$ROOT_ID" "$COLLATERAL_AMOUNT" + send open_syndication \ + "$CAPITAL_FACILITY" 'openSyndication(bytes32,bytes32,uint32,uint64,address[])' \ + "$ROOT_ID" "$ROUND_ID" "$MAX_FEE_BPS" "$ROUND_EXPIRY" \ + "[$PROVIDER_A,$PROVIDER_B,$PROVIDER_C]" + echo "Root ID: $ROOT_ID" >> "$GITHUB_STEP_SUMMARY" + echo "Round ID: $ROUND_ID" >> "$GITHUB_STEP_SUMMARY" + echo 'Root Accord is PROPOSED/SYNDICATING with 1 FXRP locked and a 9 USDT0 target (6 decimals).' >> "$GITHUB_STEP_SUMMARY" + + + live_provider_gas: + name: Coston2 provider gas funding (manual only) + if: github.event_name == 'workflow_dispatch' && inputs.fund_provider_gas == true + concurrency: + group: concord-coston2-provider-gas + cancel-in-progress: false + runs-on: ubuntu-latest + env: + CHAIN_URL: https://coston2-api.flare.network/ext/C/rpc + DEPLOYMENT_PRIVATE_KEY: ${{ secrets.CONCORD_E2E_DEPLOYER_PRIVATE_KEY }} + PROVIDER_A: "0x8d9340278094DF3F032c674C912D8eA08c17437C" + PROVIDER_B: "0xF2E25459108A0454a85d03848AD6059C271cc9a5" + PROVIDER_C: "0x9537eA25C28e8c1663e8B57C52A0761a244183A1" + GAS_AMOUNT: "5ether" + steps: + - name: Check out requested ref + uses: actions/checkout@v4 + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: stable + + - name: Fund provider gas balances + shell: bash + run: | + set -euo pipefail + test -n "$DEPLOYMENT_PRIVATE_KEY" + sender="$(cast wallet address --private-key "$DEPLOYMENT_PRIVATE_KEY")" + echo "Funding providers from $sender with $GAS_AMOUNT each." + for provider in "$PROVIDER_A" "$PROVIDER_B" "$PROVIDER_C"; do + output="$(cast send "$provider" --value "$GAS_AMOUNT" --rpc-url "$CHAIN_URL" --private-key "$DEPLOYMENT_PRIVATE_KEY" --json)" + tx="$(jq -r '.transactionHash // .hash // empty' <<<"$output")" + test -n "$tx" + echo "$provider: $tx" + echo "$provider gas funding: $tx" >> "$GITHUB_STEP_SUMMARY" + done diff --git a/.github/workflows/coston2-assets.yml b/.github/workflows/coston2-assets.yml new file mode 100644 index 0000000..b356f95 --- /dev/null +++ b/.github/workflows/coston2-assets.yml @@ -0,0 +1,33 @@ +name: Coston2 asset resolution + +on: + workflow_dispatch: + push: + branches: + - agent/concord-rebuild + +permissions: + contents: read + +jobs: + resolve: + name: Resolve official Coston2 asset bindings + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: stable + - name: Resolve live Coston2 assets + run: | + set -euo pipefail + ./scripts/resolve-coston2-assets.sh | tee "$RUNNER_TEMP/coston2-assets.json" + - name: Upload resolver output + uses: actions/upload-artifact@v4 + with: + name: coston2-asset-resolution-${{ github.sha }} + path: ${{ runner.temp }}/coston2-assets.json + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml new file mode 100644 index 0000000..e6b3e33 --- /dev/null +++ b/.github/workflows/frontend.yml @@ -0,0 +1,40 @@ +name: Concord frontend + +on: + push: + paths: + - "frontend/**" + - "sdk/typescript/**" + - "config/coston2/concord-deployment.json" + - ".github/workflows/frontend.yml" + pull_request: + paths: + - "frontend/**" + - "sdk/typescript/**" + - "config/coston2/concord-deployment.json" + - ".github/workflows/frontend.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "24.19.0" + cache: npm + cache-dependency-path: frontend/package-lock.json + - name: Install exact frontend dependencies + working-directory: frontend + run: npm ci --ignore-scripts --no-audit --no-fund + - name: Typecheck, test, and build + working-directory: frontend + run: | + npm run build + npm test + - name: Build production container + run: docker build -f frontend/Dockerfile -t concord-frontend:${{ github.sha }} . diff --git a/.github/workflows/hosted-fcc-smoke.yml b/.github/workflows/hosted-fcc-smoke.yml new file mode 100644 index 0000000..a0bfa6c --- /dev/null +++ b/.github/workflows/hosted-fcc-smoke.yml @@ -0,0 +1,32 @@ +name: Hosted FCC read-only smoke check + +on: + workflow_dispatch: + inputs: + proxy_url: + description: "Stable public HTTPS URL of the extension proxy" + required: true + type: string + extension_id: + description: "Expected Concord FCC extension ID" + required: true + default: "66188" + type: string + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.25.1" + cache-dependency-path: tools/go.sum + - name: Confirm proxy binding and active TEE registry state + env: + EXT_PROXY_URL: ${{ inputs.proxy_url }} + CONCORD_EXTENSION_ID: ${{ inputs.extension_id }} + run: ./scripts/check-hosted-fcc.sh diff --git a/.github/workflows/northflank-concord-deploy.yml b/.github/workflows/northflank-concord-deploy.yml index 6a65e1b..56108ce 100644 --- a/.github/workflows/northflank-concord-deploy.yml +++ b/.github/workflows/northflank-concord-deploy.yml @@ -177,9 +177,9 @@ jobs: { protocol: "HTTP", type: "readinessProbe", - path: "/info", - port: 6664, - initialDelaySeconds: 30, + path: "/healthy", + port: 6663, + initialDelaySeconds: 15, periodSeconds: 30, timeoutSeconds: 5, failureThreshold: 5, @@ -188,9 +188,9 @@ jobs: { protocol: "HTTP", type: "livenessProbe", - path: "/info", - port: 6664, - initialDelaySeconds: 90, + path: "/healthy", + port: 6663, + initialDelaySeconds: 30, periodSeconds: 30, timeoutSeconds: 5, failureThreshold: 5 @@ -263,6 +263,8 @@ jobs: echo "Created Northflank service concord-fcc-proxy (${proxy_id})." else echo "Reusing Northflank service concord-fcc-proxy (${proxy_id})." + api PATCH "/projects/${NF_PROJECT}/services/combined/${proxy_id}" "$proxy_body" >/dev/null + echo "Updated proxy health checks and ports." fi if [[ -z "$tee_id" ]]; then diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94ed673 --- /dev/null +++ b/.gitignore @@ -0,0 +1,71 @@ +.env* +*.env +!*.example + +# Language manifests must be committed — they are what makes a directory a +# discoverable extension implementation (scripts glob */language.env). The +# broad *.env rule above would otherwise swallow them and a fresh clone would +# find no languages at all. +!language.env +!*/language.env + +# Coston/Coston2 proxy configs (contain DB credentials) +config/proxy/extension_proxy.coston.toml +config/proxy/extension_proxy.coston.docker.toml +config/proxy/extension_proxy.coston2.toml +config/proxy/extension_proxy.coston2.docker.toml +config/extension.env +out/ +cache/ +.DS_Store + +# Uploaded artifacts and local evidence must never enter the source tree. +upload/ +evidence/ +*.log + +# go.work is generated on demand by start-services.sh for USE_LOCAL_SIBLINGS=1 +# (points host builds at sibling tee-node / tee-proxy). Never committed. +go.work +go.work.sum + +# Compiled Go binaries +tools/start-* +tools/allow-* +tools/deploy-* +tools/register-* +tools/run-* +tools/verify-* +tools/query-* +tools/set-* +tools/audit-* +tools/test-* + +# Generated contract bindings (produced by scripts/generate-bindings.sh) +tools/pkg/contracts/*/autogen.go +tools/pkg/contracts/*/*.abi +tools/pkg/contracts/*/*.bin + +# Language implementation build artifacts. +# package-lock.json is deliberately NOT ignored — it is the reproducibility +# anchor for `npm ci` in typescript/Dockerfile. +typescript/node_modules/ +typescript/dist/ +frontend/node_modules/ +frontend/dist/ +frontend/*.tsbuildinfo +python/__pycache__/ +python/**/__pycache__/ +python/.venv/ +python/.pytest_cache/ +**/*.pyc + +docs/superpowers + +notes/ + +# Private deployment guide (SSH, redeploy instructions) +DEPLOYMENT.md + +config/**/*.log +config/register-tee.state diff --git a/README.md b/README.md index 20feb0e..5faed38 100644 --- a/README.md +++ b/README.md @@ -1,174 +1,188 @@ -# Hello World Extension +# Concord -A working Hello World example for building Flare Confidential Compute (FCC) extensions. This repository demonstrates a complete, runnable extension with on-chain contracts, deployment tooling, and registration scripts — everything you need to understand how extensions work on the Flare TEE infrastructure. +Concord is a confidential programmable relationship system for coordinating +capital between independent parties on Flare. -**The same extension is implemented in Go, Python and TypeScript.** Pick one with `LANGUAGE` in `.env`; everything else — contracts, deployment, registration, tests — is identical regardless of choice. +The MVP is an FXRP-backed syndicated capital facility: -## Choosing a Language - -```bash -LANGUAGE=go # default. Smallest image (~22MB distroless), bit-for-bit reproducible -LANGUAGE=python # ~268MB. Same-machine reproducible -LANGUAGE=typescript # ~472MB. Same-machine reproducible +```text +Root Accord + └─ Makkari session + └─ CoFill allocation + └─ Child Accords + └─ funded commitments + └─ multi-child draw and DrawLegs + └─ repayment and restored capacity ``` -All three implement identical behaviour and are verified against the same golden wire fixtures by `./scripts/test-conformance.sh`. The Go path is the most thoroughly reproducible because it produces a static binary on a distroless base; Python wheels and `node_modules` trees embed build-host variance (see [REPRODUCIBILITY.md](REPRODUCIBILITY.md)). - -Language selection is **convention-based**: `LANGUAGE=` is valid iff `/language.env` exists, so adding a fourth language requires no changes to any script, tool or compose file — you create one directory. - -> **→ [Working in Multiple Languages](docs/languages.md)** covers choosing between them, the same handler written three ways, and a step-by-step for adding your own. The normative spec an implementation must satisfy is [docs/extension-contract.md](docs/extension-contract.md). +The persistent economic relationship is the canonical object. Transactions, +FCC sessions, allocations, child relationships, draws, legs, settlements, and +repayments are explicit nodes in its lineage. + +## Current implementation + +- Solidity `AccordRegistry` stores relationship identity and parent-child + lineage. +- `CapitalFacility` enforces FXRP collateral, USDT0 funding, selected versus + funded capacity, deterministic multi-child draws, exposure accounting, + repayment, expiry, and close rules. +- `ConcordInstructionSender` submits encrypted `SUBMIT_QUOTE` and + `FINALIZE_ROUND` instructions through the official Flare FCC scaffold. +- The Go extension validates signed provider quotes and runs deterministic + lowest-fee-first CoFill with a partial final allocation. +- The FCC extension requires ECIES-encrypted payloads through the TEE decrypt + endpoint. Local unit tests inject a decrypt function; they do not turn the + production handler into a plaintext path. +- The React frontend renders the recorded Coston2 Root Accord, Makkari/CoFill + formation, three funded Child Accords, explicit draw legs, repayment, + restored capacity, and evidence boundary through one shared SDK model. It + connects injected wallets, switches to Coston2, reads live capacity, prepares + canonical draw calldata locally, requires a separate wallet approval, and + tracks the public receipt without retaining keys. + +## Truth boundary + +This repository does not claim private FXRP or USDT0 transfers, private EVM +execution, or production hardware-backed confidentiality. Coston2 FCC +development may use the documented simulated TEE path. Public settlement, +root/child exposure, accepted executable capacity, and lineage remain onchain. + +The allocation verifier is an explicit boundary: an offchain verifier must +validate the FCC result binding, active machine/signature evidence, operation, +round, root, and digest before calling `markAllocationVerified`. The first +checkpoint does not pretend that an EOA is a native FCC proof verifier. + +## Architecture in plain language + +| Term | Plain-language meaning | +|---|---| +| Accord | A persistent economic relationship recorded onchain. | +| Root Accord | The facility-level relationship: collateral, capacity, exposure, and lifecycle. | +| Child Accord | One provider's governed commitment inside the root relationship. | +| Makkari session | The bounded FCC session where provider terms are coordinated confidentially. | +| CoFill | The deterministic allocator that chooses the lowest-fee eligible offers. | +| FCC proxy | The HTTPS doorway that receives FCC traffic and forwards it to the private runtime. | +| TEE | The service that decrypts and signs FCC development responses; here it is simulated, not hardware-backed. | +| Redis | Private queue/state support for the proxy; it is not the economic source of truth. | + +The public economic truth is on Coston2: accepted capacity, funding, draws, +repayment, and parent-child lineage. The hosted proxy/TEE is an operational +development path around that onchain relationship. + +## Build and test + +The canonical extension runtime is the current Flare FCE scaffold, with Go as +the Concord implementation language. Install Foundry and Go 1.25.1+, then: -## Repository Structure - -The repo splits into a **language-neutral spine** (contracts, deployment tooling, scripts) and **pluggable language implementations**. You customize one language directory plus the Solidity contract. +```bash +forge test -``` -├── go/ # ── Go implementation -│ ├── cmd/main.go # ★ Extension server entry point (standalone, for dev) -│ ├── cmd/docker/main.go # Combined TEE node + extension (single-process image) -│ ├── cmd/start-tee/main.go # Host-process runner for --local mode -│ ├── internal/config/config.go # ★ OPType constants, version, port defaults -│ ├── internal/extension/extension.go # ★ MAIN CUSTOMIZATION POINT: processAction routing -│ ├── internal/extension/utils.go # Boilerplate: actionHandler, buildResult -│ ├── pkg/types/types.go # ★ Request/response types -│ ├── Dockerfile # Single-process image (distroless) -│ └── language.env # Language manifest (marks this dir as an implementation) -├── python/ # ── Python implementation -│ ├── base/ # Framework: server, wire types, encoding, node client -│ ├── app/config.py # ★ OPType constants and version -│ ├── app/handlers.py # ★ MAIN CUSTOMIZATION POINT: your handlers -│ ├── app/abi.py # ★ ABI decoding for non-JSON payloads -│ ├── tests/ # pytest suite -│ ├── Dockerfile # Two-process image (tee-node binary + python) -│ └── language.env -├── typescript/ # ── TypeScript implementation -│ ├── src/base/ # Framework: server, wire types, encoding, node client -│ ├── src/app/config.ts # ★ OPType constants and version -│ ├── src/app/handlers.ts # ★ MAIN CUSTOMIZATION POINT: your handlers -│ ├── src/app/abi.ts # ★ ABI decoding for non-JSON payloads -│ ├── src/__tests__/ # vitest suite -│ ├── Dockerfile # Two-process image (tee-node binary + node) -│ └── language.env -│ -├── contracts/InstructionSender.sol # ★ Your extension's on-chain entry point (shared) -├── docker/node-base.Dockerfile # Shared tee-node builder for non-Go images -├── testdata/conformance/ # Golden wire fixtures, asserted against every language -├── config/ -│ ├── extension.env # Generated by pre-build (gitignored) -│ └── proxy/extension_proxy.toml # Proxy config (Redis, DB, ports, addresses) -├── scripts/ # ── Language-neutral -│ ├── full-setup.sh # Chains all phases: pre-build → compose → post-build → test -│ ├── pre-build.sh # Compile + deploy + register → writes config -│ ├── post-build.sh # Allow TEE version + register TEE on-chain -│ ├── test.sh # On-chain end-to-end test (identical for all languages) -│ ├── test-unit.sh # Unit tests, dispatched via language.env -│ ├── test-conformance.sh # Wire-contract conformance, no chain required -│ ├── check-versions.sh # Fails when dependency pins drift apart -│ ├── build-node-base.sh # Builds the shared tee-node base image -│ ├── generate-bindings.sh # Compile contract → generate Go bindings -│ └── lib/{language,versions}.sh # Language resolution + version derivation -├── tools/ # ── Language-neutral deployment tooling (Go) -│ ├── cmd/deploy-contract/ # Deploys InstructionSender to chain -│ ├── cmd/register-extension/ # Registers extension on TeeExtensionRegistry -│ ├── cmd/allow-tee-version/ # Registers TEE code hash as allowed version -│ ├── cmd/register-tee/ # Registers extension TEE machine on-chain -│ ├── cmd/run-test/ # Sends instructions and verifies results -│ └── pkg/utils/instructions.go # Deploy, SetExtensionId, SendSayHello helpers -├── docker-compose.yaml # Redis + proxy + extension-tee -├── foundry.toml # Foundry config for compiling contracts -└── .env.example # Sample env vars, including LANGUAGE - -★ = Files developers MUST modify for their extension +(cd go && GOTOOLCHAIN=local go test ./...) +(cd tools && GOTOOLCHAIN=local go test ./...) ``` -`tools/` is deliberately independent of every language implementation, which is what lets one deployment and test path serve all of them. +Use an installed Foundry/Go toolchain, or set `PATH` and `FOUNDRY_SOLC` to +your own toolchain locations. The repository does not depend on a +workspace-specific tool path. -## Creating Your Extension - -The scaffold ships with a working Hello World. To build your extension you modify four things: the operation constants, the handlers, the Solidity contract, and the test assertions. - -| # | File | What you do | -|---|------|-------------| -| 1 | `` config — `go/internal/config/config.go`, `python/app/config.py`, or `typescript/src/app/config.ts` | Define your OPType and OPCommand constants | -| 2 | `` handlers — `go/internal/extension/extension.go`, `python/app/handlers.py`, or `typescript/src/app/handlers.ts` | Implement your action handlers and state | -| 3 | `contracts/InstructionSender.sol` | Add matching `bytes32` constants and send functions | -| 4 | `tools/cmd/run-test/main.go` | Write test payloads and response assertions | - -Go additionally has `go/pkg/types/types.go` for request/response structs; Python and TypeScript declare those shapes inline in the handlers. - -The key link between your Solidity contract and your handlers is the **OPType** and **OPCommand** pair, which must match exactly across every layer: +Generate deployment bindings after changing the instruction sender: +```bash +./scripts/generate-bindings.sh ``` -Solidity: bytes32 constant OP_TYPE_GREETING = bytes32("GREETING"); - bytes32 constant OP_COMMAND_SAY_HELLO = bytes32("SAY_HELLO"); - -Go: OPTypeGreeting = "GREETING" // internal/config/config.go - OPCommandSayHello = "SAY_HELLO" -Python: OP_TYPE_GREETING = "GREETING" # app/config.py - OP_COMMAND_SAY_HELLO = "SAY_HELLO" +The current Coston2 FCC sequence remains the official scaffold sequence: -TypeScript: OP_TYPE_GREETING = "GREETING" // src/app/config.ts - OP_COMMAND_SAY_HELLO = "SAY_HELLO" +```text +pre-build → start-services --chain coston2 → post-build → run-test ``` -A mismatch means the action falls through to "unsupported op type" (HTTP 501). - -Every handler follows the same 4-step pattern in all three languages: decode the request, validate it, execute your logic, return a result. +The observed Coston2 sender, FCC extension registration, and facility deployment +receipts are recorded in [docs/CONCORD_STATUS.md](docs/CONCORD_STATUS.md). The +complete provider-funding, multi-child settlement, repayment, and +restored-capacity proof is recorded there with transaction and workflow +evidence. The latest hosted development checkpoint is recorded in +[docs/current-runtime.md](docs/current-runtime.md); it has a fresh public +`/info` check, but its newly generated simulated TEE identity is not yet an +onchain-registered machine. -> ### **→ [Read the Extension Development Guide](docs/extension-guide.md)** for a detailed walkthrough, and **[docs/extension-contract.md](docs/extension-contract.md)** for the normative wire and container contract. - -## Making It Your Own - -This repository works out of the box as a Hello World extension. When you're ready to build your own extension, you'll rename the HelloWorld placeholders to your own names and replace the SAY_HELLO logic with your own operations. - -> ### **→ [Follow the Making It Your Own guide](docs/manual-setup.md)** for step-by-step renaming instructions. -> -> Using [Claude Code](https://claude.ai/code)? Run `/rename-scaffold` to do it automatically. - - -## Run It - -With local infrastructure up (`docker compose up` from `e2e/`): +Build the frontend from its exact lockfile: ```bash -cp .env.example .env # set DEPLOYMENT_PRIVATE_KEY and CHAIN_ID -LANGUAGE=go ./scripts/full-setup.sh --test # or python, typescript +(cd frontend && npm ci --ignore-scripts --no-audit --no-fund) +(cd frontend && npm run build && npm test) ``` -Prerequisites, configuration, ports and the failure table are in -[docs/getting-started.md](docs/getting-started.md). Coston2 deployment and the platform -traps that cost redeploys are in [docs/deployment-steps.md](docs/deployment-steps.md). - -## Testing - -Three layers, cheapest first — unit (`test-unit.sh`), wire conformance against golden -fixtures with no chain required (`test-conformance.sh`), and on-chain end-to-end -(`test.sh`). Conformance is what guarantees the three implementations stay -byte-identical on the wire, and it is the acceptance test for any new language. See -[docs/testing.md](docs/testing.md). - -## Further Reading - -**Building your extension** - -- [Extension Development Guide](docs/extension-guide.md) — how the code works and how to add your own logic -- [Working in Multiple Languages](docs/languages.md) — choosing a language, working in each, and **adding your own** -- [Making It Your Own](docs/manual-setup.md) — renaming from HelloWorld to your own extension -- [InstructionSender Contract](docs/instruction-sender.md) — how the on-chain contract works and how to customize it +The root [`vercel.json`](vercel.json) deploys the same frontend from the +repository root so the shared SDK and recorded Coston2 deployment remain +available to the build. It also applies the single-page application fallback +for direct facility, funding, evidence, child, and draw URLs. Vercel Deployment +Protection must be disabled for the production URL before it is described as +public. + +The 2026-08-14 checkpoint has no public Vercel frontend URL yet: the connected +team rejected Production deployment with HTTP 403 until the integration is +granted Production Deploy permission. The Vercel project must also have +Settings → Deployment Protection → Vercel Authentication disabled for +Production. The FCC development runtime is now running on Northflank with the +Cloudflare Worker relay; see the [current status](docs/CONCORD_STATUS.md) and +[current runtime checkpoint](docs/current-runtime.md) before making a +hosting or registration claim. + +The current always-on FCC development-host plan and safe simulated-TEE identity +cutover are in +[docs/fcc-always-on-hosting.md](docs/fcc-always-on-hosting.md). The +judge-ready walkthrough, evidence ledger, claim boundary, and submission copy +are in [docs/DEMO_AND_SUBMISSION.md](docs/DEMO_AND_SUBMISSION.md). +From a clean checkout, run `./scripts/judge-check.sh` for the credential-free, +read-only judge preflight. Use `--registry` only after the current simulated +TEE has been deliberately registered and its availability has been verified. + +## Repository map + +```text +contracts/ + AccordRegistry.sol + CapitalFacility.sol + ConcordInstructionSender.sol + ConcordTypes.sol + interfaces/ + test/ +go/ FCC extension and CoFill +tools/ deployment, FCC verification, and test commands +config/ network and proxy configuration templates +docs/ product, architecture, truth, and runbooks +scripts/ scaffold lifecycle and binding commands +api/ REST/OpenAPI integration contract +sdk/typescript/ typed read and unsigned-intent client +frontend/ frontend boundary and implementation map +infra/northflank/ Northflank FCC proxy/TEE/Redis deployment contract +infra/railway/ long-running FCC proxy/TEE deployment config +``` -**Reference** +## Shared surfaces -- [Extension Container Contract](docs/extension-contract.md) — the normative wire format and container spec every implementation must satisfy -- [Testing Guide](docs/testing.md) — the test layers, conformance fixtures, and what to run when -- [Reproducibility](REPRODUCIBILITY.md) — what each language's build actually guarantees +The shared product contract is documented in +[docs/shared-product-contract.md](docs/shared-product-contract.md). The +frontend map is in [docs/frontend-map.md](docs/frontend-map.md), and the +machine-readable REST contract is [api/openapi.yaml](api/openapi.yaml). -**Per-language** +The unified CLI can check the environment and run the read-only API/MCP +surfaces: -- [go/README.md](go/README.md) · [python/README.md](python/README.md) · [typescript/README.md](typescript/README.md) +```bash +./scripts/concord doctor --offline +./scripts/concord api -facility 0x... -registry 0x... +./scripts/concord mcp -api-url http://127.0.0.1:8080 +``` ---- +These surfaces prepare unsigned transaction intents only. They do not custody, +sign, broadcast, or grant FCC verifier authority. -## Built On +## Official Flare sources -Flare Confidential Compute — see the [FCC overview](https://dev.flare.network/fcc/overview) for the underlying primitives (extensions, signing policies, data providers, attestation, Protocol Managed Wallets). +- [Build Your First FCC Extension](https://dev.flare.network/fcc/guides/getting-started) +- [Coston2 network configuration](https://dev.flare.network/network/overview) +- [FXRP address guidance](https://dev.flare.network/fxrp/token-interactions/fxrp-address) +- [Official FCE scaffold](https://github.com/flare-foundation/fce-extension-scaffold) +- [Official Foundry periphery package](https://github.com/flare-foundation/flare-foundry-periphery-package) diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..da94ea3 --- /dev/null +++ b/api/README.md @@ -0,0 +1,20 @@ +# Concord REST surface + +`openapi.yaml` is the v1 integration contract. The runnable implementation is +the read-only chain projection and unsigned-intent server exposed by: + +```bash +./scripts/concord api \ + -facility 0x... \ + -registry 0x... +``` + +The server reads `CapitalFacility` and `AccordRegistry` through the configured +RPC. It does not use an indexer yet, and it does not sign, broadcast, custody, +or authorize transactions. A successful `POST /v1/transactions/prepare` +response means only that calldata was constructed and current read +preconditions were checked. + +The API deliberately returns amounts as base-unit decimal strings and marks +private quote fields as `withheld`. The frontend and SDK must consume these +same fields rather than reconstructing protocol state independently. diff --git a/api/openapi.yaml b/api/openapi.yaml new file mode 100644 index 0000000..7f92724 --- /dev/null +++ b/api/openapi.yaml @@ -0,0 +1,482 @@ +openapi: 3.1.0 +info: + title: Concord API + version: 0.1.0 + license: + name: MIT + identifier: MIT + description: >- + Read and prepare-only access to Concord's FXRP-backed syndicated capital + facility. The API projects authoritative onchain state and verified FCC + evidence; it does not custody assets or replace Accord authority. +servers: + - url: https://api.concord.invalid + description: Placeholder; use the local CLI or an explicitly configured deployment URL. +tags: + - name: Read model + description: Authoritative onchain relationship projections. + - name: Transaction intents + description: Unsigned calldata preparation with explicit approval boundaries. + - name: Evidence + description: FCC verification metadata and disclosure status. +security: [] +x-security-status: >- + The local scaffold is intentionally unauthenticated. A production deployment + must add caller authentication and role-scoped disclosure before exposing + private quote or institution data. +paths: + /v1/health: + get: + tags: [Read model] + summary: Check API and configured network + operationId: getHealth + responses: + '200': + description: API process and configured network status + content: + application/json: + schema: + $ref: '#/components/schemas/HealthResponse' + '500': + $ref: '#/components/responses/InternalError' + '400': + $ref: '#/components/responses/InvalidRequest' + /v1/facilities/{rootAccordId}: + get: + tags: [Read model] + summary: Read one Root Accord facility + operationId: getFacility + parameters: + - $ref: '#/components/parameters/RootAccordId' + responses: + '200': + description: Root Accord with observed child relationships and round summary + content: + application/json: + schema: + $ref: '#/components/schemas/FacilityResponse' + '404': + $ref: '#/components/responses/NotObserved' + /v1/facilities/{rootAccordId}/lineage: + get: + tags: [Read model] + summary: Read Root Accord causal lineage + operationId: getFacilityLineage + parameters: + - $ref: '#/components/parameters/RootAccordId' + responses: + '200': + description: Causal relationship graph rooted at the Accord + content: + application/json: + schema: + $ref: '#/components/schemas/LineageResponse' + '404': + $ref: '#/components/responses/NotObserved' + /v1/rounds/{roundId}: + get: + tags: [Read model] + summary: Read one Makkari round + operationId: getRound + parameters: + - $ref: '#/components/parameters/RoundId' + responses: + '200': + description: Public round metadata and allocation status + content: + application/json: + schema: + $ref: '#/components/schemas/RoundResponse' + '404': + $ref: '#/components/responses/NotObserved' + /v1/draws/{drawId}: + get: + tags: [Read model] + summary: Read one draw and its DrawLegs + operationId: getDraw + parameters: + - $ref: '#/components/parameters/DrawId' + responses: + '200': + description: Root draw and explicit child DrawLeg objects + content: + application/json: + schema: + $ref: '#/components/schemas/DrawResponse' + '404': + $ref: '#/components/responses/NotObserved' + /v1/evidence/{resultDigest}: + get: + tags: [Evidence] + summary: Read allocation evidence metadata + operationId: getAllocationEvidence + parameters: + - $ref: '#/components/parameters/ResultDigest' + responses: + '200': + description: Evidence metadata available to the caller + content: + application/json: + schema: + $ref: '#/components/schemas/EvidenceResponse' + '404': + $ref: '#/components/responses/NotObserved' + /v1/transactions/prepare: + post: + tags: [Transaction intents] + summary: Prepare unsigned transaction calldata + operationId: prepareTransaction + description: >- + Builds unsigned calldata only. The caller must review, authorize, sign, + broadcast, and wait for the chain receipt. The API never returns a + claim that the action succeeded. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PrepareTransactionRequest' + responses: + '200': + description: Unsigned transaction intent + content: + application/json: + schema: + $ref: '#/components/schemas/TransactionIntentResponse' + '400': + $ref: '#/components/responses/InvalidRequest' +components: + parameters: + RootAccordId: + name: rootAccordId + in: path + required: true + schema: { $ref: '#/components/schemas/Bytes32' } + RoundId: + name: roundId + in: path + required: true + schema: { $ref: '#/components/schemas/Bytes32' } + DrawId: + name: drawId + in: path + required: true + schema: { $ref: '#/components/schemas/Bytes32' } + ResultDigest: + name: resultDigest + in: path + required: true + schema: { $ref: '#/components/schemas/Bytes32' } + responses: + NotObserved: + description: The requested authoritative object was not observed + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + InvalidRequest: + description: Invalid identifier or action arguments + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + InternalError: + description: The API could not produce a read model response + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + schemas: + Bytes32: + type: string + pattern: '^0x[0-9a-fA-F]{64}$' + description: Hex-encoded Solidity bytes32 value. + Address: + type: string + pattern: '^0x[0-9a-fA-F]{40}$' + DecimalString: + type: string + pattern: '^[0-9]+$' + description: Non-negative integer in base-10 notation; never a floating-point JSON number. + Observation: + type: object + required: [status, source, network, chainId, observedAt] + properties: + status: + type: string + enum: [observed, partial, not_observed] + source: + type: string + enum: [onchain, fcc_evidence, derived] + network: { type: string, example: coston2 } + chainId: { type: integer, example: 114 } + blockNumber: + $ref: '#/components/schemas/DecimalString' + observedAt: + type: string + format: date-time + warning: { type: string } + Meta: + type: object + required: [observation] + properties: + observation: { $ref: '#/components/schemas/Observation' } + apiVersion: { type: string, example: v1 } + RootState: + type: string + enum: [NONE, PROPOSED, SYNDICATING, FUNDING, ACTIVE, CLOSED, FROZEN, EXPIRED] + ChildState: + type: string + enum: [NONE, SELECTED, FUNDED, ACTIVE, CLOSED, EXPIRED, DEFAULTED] + RoundStatus: + type: string + enum: [NONE, OPEN, FINALIZED, EXPIRED] + LineageKind: + type: string + enum: [ROOT_ACCORD, MAKKARI_SESSION, COFILL_ALLOCATION, CHILD_ACCORD, DRAW, DRAW_LEG, SETTLEMENT, REPAYMENT] + StateCopy: + type: object + required: [state, label, explanation] + properties: + state: { type: string } + label: { type: string } + explanation: { type: string } + Asset: + type: object + required: [address, symbol, decimals] + properties: + address: { $ref: '#/components/schemas/Address' } + symbol: { type: string, example: USDT0 } + decimals: { type: integer, minimum: 0, maximum: 255 } + ChildAccord: + type: object + required: [id, rootAccordId, allocationId, provider, selectedCapacity, committedCapacity, drawnPrincipal, availableCapacity, feeBps, validUntil, termsCommitment, state, stateCopy] + properties: + id: { $ref: '#/components/schemas/Bytes32' } + rootAccordId: { $ref: '#/components/schemas/Bytes32' } + allocationId: { $ref: '#/components/schemas/Bytes32' } + provider: { $ref: '#/components/schemas/Address' } + selectedCapacity: { $ref: '#/components/schemas/DecimalString' } + committedCapacity: { $ref: '#/components/schemas/DecimalString' } + drawnPrincipal: { $ref: '#/components/schemas/DecimalString' } + availableCapacity: { $ref: '#/components/schemas/DecimalString' } + feeBps: { type: integer, minimum: 0, maximum: 10000 } + validUntil: { type: string, format: date-time } + validUntilUnix: { $ref: '#/components/schemas/DecimalString' } + termsCommitment: { $ref: '#/components/schemas/Bytes32' } + state: { $ref: '#/components/schemas/ChildState' } + stateCopy: { $ref: '#/components/schemas/StateCopy' } + Round: + type: object + required: [id, rootAccordId, targetCapacity, maxFeeBps, roundExpiry, status, stateCopy, eligibleProviderCount] + properties: + id: { $ref: '#/components/schemas/Bytes32' } + rootAccordId: { $ref: '#/components/schemas/Bytes32' } + targetCapacity: { $ref: '#/components/schemas/DecimalString' } + maxFeeBps: { type: integer, minimum: 0, maximum: 10000 } + roundExpiry: { type: string, format: date-time } + roundExpiryUnix: { $ref: '#/components/schemas/DecimalString' } + status: { $ref: '#/components/schemas/RoundStatus' } + stateCopy: { $ref: '#/components/schemas/StateCopy' } + eligibleProviderCount: { type: integer, minimum: 0 } + privateQuoteData: + type: string + enum: [withheld, authorized] + description: Indicates whether private quote fields are withheld from this caller. + Facility: + type: object + required: [id, borrower, collateralAsset, liquidityAsset, targetCapacity, committedCapacity, drawnPrincipal, collateralLocked, availableCapacity, validUntil, policyHash, syndicationRoundId, state, stateCopy, children, invariants] + properties: + id: { $ref: '#/components/schemas/Bytes32' } + borrower: { $ref: '#/components/schemas/Address' } + collateralAsset: { $ref: '#/components/schemas/Asset' } + liquidityAsset: { $ref: '#/components/schemas/Asset' } + targetCapacity: { $ref: '#/components/schemas/DecimalString' } + committedCapacity: { $ref: '#/components/schemas/DecimalString' } + drawnPrincipal: { $ref: '#/components/schemas/DecimalString' } + collateralLocked: { $ref: '#/components/schemas/DecimalString' } + availableCapacity: { $ref: '#/components/schemas/DecimalString' } + validUntil: { type: string, format: date-time } + validUntilUnix: { $ref: '#/components/schemas/DecimalString' } + policyHash: { $ref: '#/components/schemas/Bytes32' } + syndicationRoundId: { $ref: '#/components/schemas/Bytes32' } + state: { $ref: '#/components/schemas/RootState' } + stateCopy: { $ref: '#/components/schemas/StateCopy' } + round: { $ref: '#/components/schemas/Round' } + children: + type: array + items: { $ref: '#/components/schemas/ChildAccord' } + invariants: + type: object + required: [rootDrawWithinCommitment, childDrawWithinCommitment, rootExposureMatchesChildren, committedMatchesFundedChildren] + properties: + rootDrawWithinCommitment: { type: boolean } + childDrawWithinCommitment: { type: boolean } + rootExposureMatchesChildren: { type: boolean } + committedMatchesFundedChildren: { type: boolean } + LineageNode: + type: object + required: [id, parentId, kind, createdAt, children] + properties: + id: { $ref: '#/components/schemas/Bytes32' } + parentId: { $ref: '#/components/schemas/Bytes32' } + kind: { $ref: '#/components/schemas/LineageKind' } + createdAt: { type: string, format: date-time } + children: + type: array + items: { $ref: '#/components/schemas/Bytes32' } + DrawLeg: + type: object + required: [id, drawId, childAccordId, provider, principal, repaidPrincipal, outstandingPrincipal] + properties: + id: { $ref: '#/components/schemas/Bytes32' } + drawId: { $ref: '#/components/schemas/Bytes32' } + childAccordId: { $ref: '#/components/schemas/Bytes32' } + provider: { $ref: '#/components/schemas/Address' } + principal: { $ref: '#/components/schemas/DecimalString' } + repaidPrincipal: { $ref: '#/components/schemas/DecimalString' } + outstandingPrincipal: { $ref: '#/components/schemas/DecimalString' } + Draw: + type: object + required: [id, rootAccordId, principal, repaidPrincipal, outstandingPrincipal, createdAt, legs] + properties: + id: { $ref: '#/components/schemas/Bytes32' } + rootAccordId: { $ref: '#/components/schemas/Bytes32' } + principal: { $ref: '#/components/schemas/DecimalString' } + repaidPrincipal: { $ref: '#/components/schemas/DecimalString' } + outstandingPrincipal: { $ref: '#/components/schemas/DecimalString' } + createdAt: { type: string, format: date-time } + legs: + type: array + items: { $ref: '#/components/schemas/DrawLeg' } + Evidence: + type: object + required: [resultDigest, status, disclosure] + properties: + resultDigest: { $ref: '#/components/schemas/Bytes32' } + status: + type: string + enum: [verified, invalid, withheld, not_observed] + disclosure: + type: string + enum: [metadata_only, authorized_full, withheld] + extensionId: { $ref: '#/components/schemas/Bytes32' } + roundId: { $ref: '#/components/schemas/Bytes32' } + rootAccordId: { $ref: '#/components/schemas/Bytes32' } + instructionId: { $ref: '#/components/schemas/Bytes32' } + source: { type: string, enum: [coston2_fcc, local_simulated_tee, not_observed] } + warning: { type: string } + PrepareTransactionRequest: + type: object + required: [action] + properties: + action: + type: string + enum: [create_root, lock_collateral, open_syndication, approve_asset, fund_child, close_child, expire_child, draw, repay, close_root, expire_root] + rootAccordId: { $ref: '#/components/schemas/Bytes32' } + childAccordId: { $ref: '#/components/schemas/Bytes32' } + drawId: { $ref: '#/components/schemas/Bytes32' } + roundId: { $ref: '#/components/schemas/Bytes32' } + targetCapacity: { $ref: '#/components/schemas/DecimalString' } + amount: { $ref: '#/components/schemas/DecimalString' } + validUntilUnix: { $ref: '#/components/schemas/DecimalString' } + roundExpiryUnix: { $ref: '#/components/schemas/DecimalString' } + maxFeeBps: { $ref: '#/components/schemas/DecimalString' } + eligibleProviders: + type: array + items: { $ref: '#/components/schemas/Address' } + policyHash: { $ref: '#/components/schemas/Bytes32' } + asset: { $ref: '#/components/schemas/Address' } + spender: { $ref: '#/components/schemas/Address' } + actor: + type: string + enum: [treasury, provider, institution, agent] + TransactionIntent: + type: object + required: [action, chainId, to, data, value, summary, requiresExplicitApproval, preconditions] + properties: + action: { type: string } + chainId: { type: integer, example: 114 } + to: { $ref: '#/components/schemas/Address' } + data: { type: string, pattern: '^0x[0-9a-fA-F]*$' } + value: { $ref: '#/components/schemas/DecimalString' } + summary: { type: string } + requiresExplicitApproval: { type: boolean, const: true } + preconditions: + type: array + items: { type: string } + warnings: + type: array + items: { type: string } + Health: + type: object + required: [service, network, chainId, configured, readModel] + properties: + service: { type: string, const: concord-api } + network: { type: string } + chainId: { type: integer } + configured: { type: boolean } + readModel: { type: string, enum: [chain, unavailable] } + facilityAddress: { $ref: '#/components/schemas/Address' } + registryAddress: { $ref: '#/components/schemas/Address' } + FacilityResponse: + type: object + required: [data, meta] + properties: + data: { $ref: '#/components/schemas/Facility' } + meta: { $ref: '#/components/schemas/Meta' } + RoundResponse: + type: object + required: [data, meta] + properties: + data: { $ref: '#/components/schemas/Round' } + meta: { $ref: '#/components/schemas/Meta' } + DrawResponse: + type: object + required: [data, meta] + properties: + data: { $ref: '#/components/schemas/Draw' } + meta: { $ref: '#/components/schemas/Meta' } + LineageResponse: + type: object + required: [data, meta] + properties: + data: + type: object + required: [rootAccordId, nodes] + properties: + rootAccordId: { $ref: '#/components/schemas/Bytes32' } + nodes: + type: array + items: { $ref: '#/components/schemas/LineageNode' } + meta: { $ref: '#/components/schemas/Meta' } + EvidenceResponse: + type: object + required: [data, meta] + properties: + data: { $ref: '#/components/schemas/Evidence' } + meta: { $ref: '#/components/schemas/Meta' } + TransactionIntentResponse: + type: object + required: [data, meta] + properties: + data: { $ref: '#/components/schemas/TransactionIntent' } + meta: { $ref: '#/components/schemas/Meta' } + HealthResponse: + type: object + required: [data, meta] + properties: + data: { $ref: '#/components/schemas/Health' } + meta: { $ref: '#/components/schemas/Meta' } + ErrorResponse: + type: object + required: [error, meta] + properties: + error: + type: object + required: [code, message] + properties: + code: { type: string } + message: { type: string } + details: { type: object, additionalProperties: true } + meta: { $ref: '#/components/schemas/Meta' } diff --git a/config/.gitkeep b/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/config/coston2/concord-deployment.json b/config/coston2/concord-deployment.json new file mode 100644 index 0000000..6e3ba8c --- /dev/null +++ b/config/coston2/concord-deployment.json @@ -0,0 +1,351 @@ +{ + "network": "Coston2", + "chainId": 114, + "observedAt": "2026-08-13T19:14:40Z", + "rpcUrl": "https://coston2-api.flare.network/ext/C/rpc", + "explorerUrl": "https://coston2-explorer.flare.network", + "teeRegistry": "0x1a9C4A0f9D76c0b1D91d22E24E573a9b377618aE", + "extension": { + "id": "0x000000000000000000000000000000000000000000000000000000000001028c", + "decimalId": 66188, + "instructionSender": "0x574b523eA944EFe9143AF9d6c46bfA925beE2968", + "activeTeeMachines": 1, + "activeTeeIds": [ + "0xeE39d5e7d1C5043232282e3CC884B41a9Db22c85" + ], + "activeTeeUrls": [ + "https://concord-fcc-ingress.microcosm.workers.dev" + ], + "teeMode": "simulated_development" + }, + "assets": { + "fxrp": "0x0b6A3645c240605887a5532109323A3E12273dc7", + "fxrpDecimals": 6, + "usdt0": "0xC1A5B41512496B80903D1f32d6dEa3a73212E71F", + "usdt0OnchainSymbol": "USD₮0", + "usdt0Decimals": 6, + "usdt0Source": "https://coston2-explorer.flare.network/tokens" + }, + "assetCorrection": { + "verifiedAt": "2026-08-13", + "current": { + "token": "0xC1A5B41512496B80903D1f32d6dEa3a73212E71F", + "name": "USDT0 test", + "onchainSymbol": "USD₮0", + "decimals": 6 + }, + "superseded": { + "token": "0x479854495cefBc8D12B971A3Ec4d18E6dbcE81a3", + "decimals": 18 + }, + "reason": "CapitalFacility.liquidityAsset is immutable; the previously deployed facility and root round cannot consume balances of the current faucet token." + }, + "canonicalFacility": { + "accordRegistry": "0x68b19a3967760489b57341669cd7ea960b5f7367", + "capitalFacility": "0xfaff601a18a9fca33378953515aa0f3ef9286ecd", + "owner": "0x1aF42c70837f08c6C89a6FA274EB9eeF040820B3", + "allocationVerifier": "0x1aF42c70837f08c6C89a6FA274EB9eeF040820B3", + "liquidityAsset": "0xC1A5B41512496B80903D1f32d6dEa3a73212E71F", + "liquidityAssetDecimals": 6, + "registrySetFacility": "0x7d4512a8f0adcaabd6896e3bfea32f88b5e29d9ba0a5773cc29eb300c888009d", + "registryDeploy": "0xdcaf8538ecb6f416e1d85500d14d13e0476d6135e6c05583344b14b8653a3b5c", + "facilityDeploy": "0x476b51a35934c84f1c8b593ff07c29a3a69e29a72559aa8e9f63f902fb1a1905", + "workflowRun": "https://github.com/etvjay/Concord/actions/runs/31700945393", + "blocks": { + "registryDeploy": 34008030, + "facilityDeploy": 34008030, + "registrySetFacility": 34008031 + } + }, + "supersededAssetFacility": { + "accordRegistry": "0x9A5663519C3D4B36ef155A4AE0e2d8Be2E7a89cF", + "capitalFacility": "0x0AcBb062BE75491b5992dddD59aEf64a4f4Cc8b8", + "owner": "0x1aF42c70837f08c6C89a6FA274EB9eeF040820B3", + "allocationVerifier": "0x1aF42c70837f08c6C89a6FA274EB9eeF040820B3", + "registrySetFacility": "0xa0b7a26bf42231dd8e0c2ea59f29339418f31717f129eadd57a842b78c7ad0c4", + "registryDeploy": "0x1058604bb6650650feee1b980973e55fddf1db52d24f8215bf07b52aa67efd9e", + "facilityDeploy": "0x0b0c5acb99a20025e6b6a029474bcad882bbdd9589de39604d06cf41d6f9b3c7", + "blocks": { + "registryDeploy": 33993407, + "facilityDeploy": 33993408 + } + }, + "transactions": { + "instructionSenderDeploy": "0x85e6056f410be261830f5e4e8e35b172886b915ea977ad0fba46953c19577612", + "extensionRegister": "0x20e1bf7f6e14d93fd5c06bd35b2d3eda93e711b46dff0ecf2433da121dac16ca", + "extensionAllowMachineOwners": "0xa4fb47c35c0c42bf7681bed2f48b206486730806c34fe1e04331fc8fcb91c7be", + "extensionAllowProjectOwners": "0x38f3f152d1d4d5578961e55ae595e1422552a363d073690f6fbf05d35b9a8ed3", + "extensionAllowKeyType": "0x33f25969e199780b0962b868a92a4871d0ef6845827e6a92274e6f0128216140" + }, + "supersededFacility": { + "reason": "A second manual facility dispatch completed before the first result was observed; this pair is retained as observed but not used for the canonical flow.", + "accordRegistry": "0xb7f23e078eebc57e624ad06af5caef4526998eee", + "capitalFacility": "0x8d70adbd1b36fb05bae2056013f8bfb9b8e14848", + "registrySetFacility": "0x8b8ee8baefe7bb574dc6001df2caa8da078900c76c6e71b7b7b66f5d02f90f97", + "registryDeploy": "0xdeda1310493445b2966014aab334fd78b741c8b33dbd188bc1ad7e61f9a11a38", + "facilityDeploy": "0xe66c23ed9ac4543196fc9cfa58372f7e9764ab6dc52950a22ed3dd57da73d8d0" + }, + "status": { + "facilityContractsDeployed": true, + "canonicalFacilityDeployed": true, + "assetBindingCurrent": true, + "extensionRegistered": true, + "activeTeeMachine": true, + "providerWalletsFunded": true, + "providerUSDT0BalancesObserved": true, + "usdt0AvailableToTreasury": true, + "rootAccordCreated": true, + "rootRoundAssetBindingCurrent": true, + "fccRoundExecuted": true, + "childrenMaterialized": true, + "facilityFunded": true, + "drawSettled": true, + "repaid": true + }, + "warnings": [ + "The previously deployed facility and root round use a separate 18-decimal token at 0x479854495cefBc8D12B971A3Ec4d18E6dbcE81a3; they are superseded for the current Coston2 USDT0 test flow.", + "The current faucet token is 0xC1A5B41512496B80903D1f32d6dEa3a73212E71F (USD₮0, 6 decimals). The replacement facility completed the observed FCC, funding, multi-child draw, and repayment flow.", + "The current Flare/Foundry toolchain still reports chain 114 as unsupported for EIP-3855; deployment succeeded after targeting solc 0.8.27, optimizer=true, optimizer_runs=200, and evm_version=paris. Keep this warning in the deployment record.", + "The Coston2 FCC development path is not hardware-TEE evidence; the intended path uses the documented simulated development TEE.", + "The Concord extension is registered through a stable Cloudflare Workers.dev ingress relay backed by the disposable Codespace public port; this is not a named Cloudflare Tunnel or a production ingress claim.", + "No private key is stored in this file." + ], + "explorerLinks": { + "instructionSenderDeploy": "https://coston2-explorer.flare.network/tx/0x85e6056f410be261830f5e4e8e35b172886b915ea977ad0fba46953c19577612", + "extensionRegister": "https://coston2-explorer.flare.network/tx/0x20e1bf7f6e14d93fd5c06bd35b2d3eda93e711b46dff0ecf2433da121dac16ca", + "canonicalFacilityDeploy": "https://coston2-explorer.flare.network/tx/0x476b51a35934c84f1c8b593ff07c29a3a69e29a72559aa8e9f63f902fb1a1905", + "canonicalRegistryDeploy": "https://coston2-explorer.flare.network/tx/0xdcaf8538ecb6f416e1d85500d14d13e0476d6135e6c05583344b14b8653a3b5c", + "canonicalRegistrySetFacility": "https://coston2-explorer.flare.network/tx/0x7d4512a8f0adcaabd6896e3bfea32f88b5e29d9ba0a5773cc29eb300c888009d", + "supersededAssetFacilityDeploy": "https://coston2-explorer.flare.network/tx/0x0b0c5acb99a20025e6b6a029474bcad882bbdd9589de39604d06cf41d6f9b3c7", + "supersededAssetRegistryDeploy": "https://coston2-explorer.flare.network/tx/0x1058604bb6650650feee1b980973e55fddf1db52d24f8215bf07b52aa67efd9e", + "rootApprove": "https://coston2-explorer.flare.network/tx/0xddba5e78d15ea4c1b38c2aabcc8bc6eb340e2e70b0864f42f7ee21ad22532b3", + "rootCreate": "https://coston2-explorer.flare.network/tx/0x0f66d8b7c96929dcb18e7e39492ee94e9c758a50811b2533156efc6a33edc675", + "rootLockCollateral": "https://coston2-explorer.flare.network/tx/0xd8f0644744a5b9b2e724e9f637f4b4fbbeda0ed885cbf67c2616fbea828698e6", + "openSyndication": "https://coston2-explorer.flare.network/tx/0x8d32a236da024a63d57302df74dd5e7493d52048b4048f9dd43fcd4a108e68fb" + }, + "accounts": { + "observedAt": "2026-08-13T12:48:12Z", + "treasury": { + "address": "0x1aF42c70837f08c6C89a6FA274EB9eeF040820B3", + "c2flr": "62.76633304163635", + "fxrp": "10", + "usdt0": "10" + }, + "providers": [ + { + "role": "provider_a", + "address": "0x8d9340278094DF3F032c674C912D8eA08c17437C", + "c2flr": "105", + "fxrp": "0", + "usdt0": "10" + }, + { + "role": "provider_b", + "address": "0xF2E25459108A0454a85d03848AD6059C271cc9a5", + "c2flr": "105", + "fxrp": "0", + "usdt0": "10" + }, + { + "role": "provider_c", + "address": "0x9537eA25C28e8c1663e8B57C52A0761a244183A1", + "c2flr": "105", + "fxrp": "0", + "usdt0": "10" + } + ], + "note": "Provider private keys are stored only as encrypted Actions secrets; no private key is recorded here." + }, + "faucet": { + "url": "https://faucet.flare.network/", + "perAddress": "100 C2FLR, 10 USDT0, 10 FXRP per 24 hours", + "neededForNextStep": [ + "frontend wiring and interface QA against the observed relationship state", + "optional named Cloudflare Tunnel if the Workers.dev development relay is replaced" + ] + }, + "providerGasFunding": { + "amountPerProvider": "5 C2FLR", + "workflowRun": "https://github.com/etvjay/Concord/actions/runs/31701743746", + "transactions": { + "provider_a": "0x1abd6e28d90cb9ad15aef85a9e7aa3bf64d4406078afe3ab6afcee5836b84982", + "provider_b": "0xc14f3e74a67624dd50a1b501515c7959aac4c1a2bdcd010f0f8c36eec65ebb1d", + "provider_c": "0x22a0db432f8a782e84ddf69cb2734e57644909b18b20453a5eadab8f85941182" + } + }, + "rootRound": { + "status": "CURRENT", + "facility": "0xfaff601a18a9fca33378953515aa0f3ef9286ecd", + "rootAccordId": "0x6e03af41b0194c5a369a50629474090cfc5b041a712144855e6efb1a574cfddd", + "roundId": "0x7323280ede4786463744a755e4cddf9ce2a91be952d05d152679b58b67d176ce", + "rootState": "ACTIVE", + "roundStatus": "FINALIZED", + "borrower": "0x1aF42c70837f08c6C89a6FA274EB9eeF040820B3", + "targetCapacity": "9000000", + "targetDisplay": "9 USDT0", + "targetDecimals": 6, + "validUntilUnix": "1787229810", + "validUntil": "2026-08-20T12:43:30Z", + "policyHash": "0xcb41179c81b6d45a22487986ae6ca4faa1c025ff4492c02ce3d26c5b0de8443e", + "maxFeeBps": 700, + "roundExpiryUnix": "1786884210", + "roundExpiry": "2026-08-16T12:43:30Z", + "collateralLocked": "1000000", + "collateralDisplay": "1 FXRP", + "eligibleProviders": [ + "0x8d9340278094DF3F032c674C912D8eA08c17437C", + "0xF2E25459108A0454a85d03848AD6059C271cc9a5", + "0x9537eA25C28e8c1663e8B57C52A0761a244183A1" + ], + "transactions": { + "approveFxrp": "0x790db768cbbe95450d25b73c3d6afa1a040acc70b6ae25803e194704814c2e22", + "createRoot": "0xd6e0e48abdf3e7cd3efc336df9d616f10c923ad69d0f6dbcfddbbe79f43cca71", + "lockCollateral": "0x618097ee2b6ab99e6eee868c596876e9cadf9b409335f7741d6fab8acbc77297", + "openSyndication": "0x393174832b260cdd34a91a3df24f23066ac3672a64f1b0bcf25468236b7314c8" + }, + "workflowRun": "https://github.com/etvjay/Concord/actions/runs/31701379940", + "verifiedAt": "2026-08-13T19:14:40Z", + "verifiedOnchain": { + "facilityFxrpBalance": "1", + "committedCapacity": "9000000", + "drawnPrincipal": "0", + "eligibleProviderCount": 3, + "registryHasRoot": true, + "rootChildIds": [ + "0x4335182fe9119ef6af37b5bd838f514d7a896a877be0a63578015b373a0bdfc0", + "0x2225a13ff17ac4f577d169d626b51e5b8311c01b336c6d5b09861a3363bfe97a", + "0xbf169c1b664684a8cfde0fea5a870e9358a903a1dd5042d650fe2d21bf43086a" + ] + }, + "allocation": { + "resultDigest": "0xf17a292655b898d8b00c9794565972ed838cd3de2812f6fa59d877d6963c88ec", + "selectedProviders": [ + "0x8d9340278094DF3F032c674C912D8eA08c17437C", + "0xF2E25459108A0454a85d03848AD6059C271cc9a5", + "0x9537eA25C28e8c1663e8B57C52A0761a244183A1" + ], + "allocatedCapacity": ["3000000", "3000000", "3000000"], + "acceptedFeeBps": [610, 640, 680], + "submitQuoteTransactions": [ + "0xa6a200f2ecd1a3c06366ad2b88ecbc7046378a3f290ddab3e118a56298dbddb3", + "0x64f6eb5f6b94848718b7e3da4657e2fc68e5243aaa1527be4c90fc9f7fbd311b", + "0xef861e50be8dc58a47b8fdfe556597779ee56fbe436ecfe7ba8ecd483834d02a" + ], + "finalizeRoundTransaction": "0x2c5b06bf45e6318ece5262f30e5df91a8cbbacb4676f6efd997e714fcb3a8f0f", + "verifyAllocationTransaction": "0xb77400b70e8d54157fdb455b47797d5c583a387968afeb699319fe13fae95544", + "materializeTransaction": "0x4cdd56ca3a80f83b6333e4e0434d4f202fcb9cf5ad5dd3ced820a53779a7559a", + "workflowRun": "https://github.com/etvjay/Concord/actions/runs/31733200629", + "recoveryWorkflowRun": "https://github.com/etvjay/Concord/actions/runs/31733740564", + "evidenceArtifact": "https://github.com/etvjay/Concord/actions/runs/31733200629/artifacts/9194157272" + }, + "children": [ + { + "id": "0x4335182fe9119ef6af37b5bd838f514d7a896a877be0a63578015b373a0bdfc0", + "allocationId": "0xf17a292655b898d8b00c9794565972ed838cd3de2812f6fa59d877d6963c88ec", + "provider": "0x8d9340278094DF3F032c674C912D8eA08c17437C", + "selectedCapacity": "3000000", + "committedCapacity": "3000000", + "drawnPrincipal": "0", + "feeBps": 610, + "validUntilUnix": "1786884210", + "termsCommitment": "0x73ba825304d56f474d450c58780aa250b5cd98e0924e9a56c79d2d1f5b0988bb", + "state": "ACTIVE" + }, + { + "id": "0x2225a13ff17ac4f577d169d626b51e5b8311c01b336c6d5b09861a3363bfe97a", + "allocationId": "0xf17a292655b898d8b00c9794565972ed838cd3de2812f6fa59d877d6963c88ec", + "provider": "0xF2E25459108A0454a85d03848AD6059C271cc9a5", + "selectedCapacity": "3000000", + "committedCapacity": "3000000", + "drawnPrincipal": "0", + "feeBps": 640, + "validUntilUnix": "1786884210", + "termsCommitment": "0x12730a1f45e22aec3175729182a7192e4ed51cc07b5493e4f4d8f5185c419f6a", + "state": "ACTIVE" + }, + { + "id": "0xbf169c1b664684a8cfde0fea5a870e9358a903a1dd5042d650fe2d21bf43086a", + "allocationId": "0xf17a292655b898d8b00c9794565972ed838cd3de2812f6fa59d877d6963c88ec", + "provider": "0x9537eA25C28e8c1663e8B57C52A0761a244183A1", + "selectedCapacity": "3000000", + "committedCapacity": "3000000", + "drawnPrincipal": "0", + "feeBps": 680, + "validUntilUnix": "1786884210", + "termsCommitment": "0xa4af953d87f72ee095b6ba6940055ee7f8fdfcd10c278278d60e10774569a7d7", + "state": "ACTIVE" + } + ], + "facilityLifecycle": { + "drawId": "0x85f634b07d8dfd83fe0f0f1a9b34504973e5e2d5e1e3886656bfb072c26f56ec", + "drawAmount": "4000000", + "drawLegCount": 2, + "drawCreatedAtUnix": "1786648474", + "drawCreatedAt": "2026-08-13T19:14:34Z", + "drawLegs": [ + { + "id": "0xb9afb4dc836bc1dacfbd810b7a4fa69c3d015f76a9ad8cc6057dfb994548b791", + "childAccordId": "0x4335182fe9119ef6af37b5bd838f514d7a896a877be0a63578015b373a0bdfc0", + "principal": "3000000", + "repaidPrincipal": "3000000" + }, + { + "id": "0xfabc9644b2e712870eddc4e6d195912bb34b56f0bc2a90a3b72559b3fb5835a1", + "childAccordId": "0x2225a13ff17ac4f577d169d626b51e5b8311c01b336c6d5b09861a3363bfe97a", + "principal": "1000000", + "repaidPrincipal": "1000000" + } + ], + "fundingTransactions": [ + "0xfcb3ae5b2cdca68fbb974d30f1780defeb0ea65b49d3fe542e2b6ff395fd3bf0", + "0x061e3b64c8978a405cc22510bb9620263697860be4fa3e7badcb7feb4b2ebb0f", + "0xfbb3fa72ab3294807697a6aa973247c786f85021f30d41568f2300dc27b0be0c" + ], + "drawTransaction": "0xea0348757169428b73dbbc20eca7f2faf267c29941e5f0c0291bc3923b12cbe5", + "repaymentApprovalTransaction": "0x3ed2f0b47c3c853d38fb654167a3236877a402b422437285b993fd769031f213", + "repaymentTransaction": "0xe2146fb4c498964754b5754a1dd91992c62e29d3d627cd6fb0ebba6432d8c46c", + "finalRootState": "ACTIVE", + "finalCommittedCapacity": "9000000", + "finalDrawnPrincipal": "0", + "finalAvailableCapacity": "9000000", + "workflowRun": "https://github.com/etvjay/Concord/actions/runs/31734593116", + "evidenceArtifact": "https://github.com/etvjay/Concord/actions/runs/31734593116/artifacts/9194681063" + } + }, + "supersededRootRound": { + "status": "SUPERSEDED_ASSET_BINDING", + "facility": "0x0AcBb062BE75491b5992dddD59aEf64a4f4Cc8b8", + "rootAccordId": "0xee94755eb101d1604d83a4d77eb4c20cd0b4a2ee8fd596c8c63b04109f954042", + "roundId": "0xb281fe0fe8def27b3cb557a13ef1ef03a7e6823e8fadacb83f1dc599b529c93b", + "rootState": "SYNDICATING", + "roundStatus": "OPEN", + "borrower": "0x1aF42c70837f08c6C89a6FA274EB9eeF040820B3", + "targetCapacity": "9000000000000000000", + "targetDisplay": "9 USDT0", + "maxFeeBps": 700, + "collateralLocked": "1000000", + "collateralDisplay": "1 FXRP", + "eligibleProviders": [ + "0x8d9340278094DF3F032c674C912D8eA08c17437C", + "0xF2E25459108A0454a85d03848AD6059C271cc9a5", + "0x9537eA25C28e8c1663e8B57C52A0761a244183A1" + ], + "transactions": { + "approveFxrp": "0xddba5e78d15ea4c1b38c2aabcc8bc6eb340e2e70b0864f42f7ee21ad22532b3b", + "createRoot": "0x0f66d8b7c96929dcb18e7e39492ee94e9c758a50811b2533156efc6a33edc675", + "lockCollateral": "0xd8f0644744a5b9b2e724e9f637f4b4fbbeda0ed885cbf67c2616fbea828698e6", + "openSyndication": "0x8d32a236da024a63d57302df74dd5e7493d52048b4048f9dd43fcd4a108e68fb" + }, + "verifiedAt": "2026-08-13T05:15:00Z", + "verifiedOnchain": { + "facilityFxrpBalance": "1", + "committedCapacity": "0", + "drawnPrincipal": "0", + "eligibleProviderCount": 3, + "registryHasRoot": true, + "rootChildIds": [ + "0xb281fe0fe8def27b3cb557a13ef1ef03a7e6823e8fadacb83f1dc599b529c93b" + ] + } + } +} diff --git a/config/coston2/deployed-addresses.json b/config/coston2/deployed-addresses.json new file mode 100644 index 0000000..4f53220 --- /dev/null +++ b/config/coston2/deployed-addresses.json @@ -0,0 +1,517 @@ +[ + { + "name": "Submission", + "contractName": "Submission.sol", + "address": "0x2cA6571Daa15ce734Bbd0Bf27D5C9D16787fc33f" + }, + { + "name": "RNat", + "contractName": "RNat.sol", + "address": "0x221D27529e7788B929E13533edc3b00ec1ac5e8A" + }, + { + "name": "RNatAccount", + "contractName": "RNatAccount.sol", + "address": "0x14174a6A12172ad47FFF001C8BEe7aea2Bbf1d9f" + }, + { + "name": "EntityManager", + "contractName": "EntityManager.sol", + "address": "0xE62c5557210a5D095BfC2fDc8B2b5D64609cfDf1" + }, + { + "name": "NodePossessionVerifier", + "contractName": "NodePossessionVerifier.sol", + "address": "0x8DE9e19E8DB504572cF8e5B789CCD68Afe543bB0" + }, + { + "name": "VoterRegistry", + "contractName": "VoterRegistry.sol", + "address": "0x6a0AF07b7972177B176d3D422555cbc98DfDe914" + }, + { + "name": "FlareSystemsCalculator", + "contractName": "FlareSystemsCalculator.sol", + "address": "0x7faEB56E8e98B8507db618631Be65994Ae2065Df" + }, + { + "name": "FlareSystemsManager", + "contractName": "FlareSystemsManager.sol", + "address": "0xA90Db6D10F856799b10ef2A77EBCbF460aC71e52" + }, + { + "name": "RewardManager", + "contractName": "RewardManager.sol", + "address": "0xB4f43E342c5c77e6fe060c0481Fe313Ff2503454" + }, + { + "name": "FtsoRewardManager", + "contractName": "FtsoRewardManagerProxy.sol", + "address": "0x7A0bFB85387314d7F8C0FcCD9D9B74A76115c322" + }, + { + "name": "Relay", + "contractName": "Relay.sol", + "address": "0xa10B672D1c62e5457b17af63d4302add6A99d7dE" + }, + { + "name": "WNatDelegationFee", + "contractName": "WNatDelegationFee.sol", + "address": "0xe97611e7678CFd534532eBC4f6aD7F142C012501" + }, + { + "name": "FtsoInflationConfigurations", + "contractName": "FtsoInflationConfigurations.sol", + "address": "0x14Cf71e1a73e22B1ef9b47d85eC492259D8C3C81" + }, + { + "name": "FtsoRewardOffersManager", + "contractName": "FtsoRewardOffersManager.sol", + "address": "0x1b7ffab226b66b5eCBdC79a42287BC09C05bCb33" + }, + { + "name": "FtsoFeedDecimals", + "contractName": "FtsoFeedDecimals.sol", + "address": "0xdC338222e3Bf815A87F561C6BD42C094a5d171d4" + }, + { + "name": "FtsoFeedPublisher", + "contractName": "FtsoFeedPublisher.sol", + "address": "0x4fEcE851632C2A88519bE4C92eA238bbE71D882D" + }, + { + "name": "FtsoFeedIdConverter", + "contractName": "FtsoFeedIdConverter.sol", + "address": "0x1224AE03e0564c4C2dD3B49B1834F2ac765a529E" + }, + { + "name": "PollingFoundation", + "contractName": "PollingFoundation.sol", + "address": "0x6D7ca85Cb3451b772B87EBB32A9E5cFc500BfA94" + }, + { + "name": "ValidatorRewardOffersManager", + "contractName": "ValidatorRewardOffersManager.sol", + "address": "0xb0B93eb5ef1a15A12F68f824Ab7d21fe5d876983" + }, + { + "name": "FastUpdateIncentiveManager", + "contractName": "FastUpdateIncentiveManager.sol", + "address": "0xC71C1C6E6FB31eF6D948B2C074fA0d38a07D4f68" + }, + { + "name": "FastUpdatesConfiguration", + "contractName": "FastUpdatesConfiguration.sol", + "address": "0x222768c5AbCe56Af9Fd75c5f59614a8B7F5dBe80" + }, + { + "name": "FastUpdater", + "contractName": "FastUpdater.sol", + "address": "0x0B162CA3acf3482d3357972e12d794434085D839" + }, + { + "name": "FeeCalculator", + "contractName": "FeeCalculator.sol", + "address": "0x88A9315f96c9b5518BBeC58dC6a914e13fAb13e2" + }, + { + "name": "FtsoManager", + "contractName": "FtsoManagerProxy.sol", + "address": "0x2675cB89AE83f19cEF8600Eb0E9F2071EDBDf11A" + }, + { + "name": "FtsoC2Flr", + "contractName": "FtsoProxy.sol", + "address": "0xd7351C8bbFD6F508d674C87c75Bc39F2D83e22CB" + }, + { + "name": "FtsoTestXrp", + "contractName": "FtsoProxy.sol", + "address": "0x22d10E7305Fd39833B4d14d113Bca3602bA1F701" + }, + { + "name": "FtsoTestLtc", + "contractName": "FtsoProxy.sol", + "address": "0xE31D989C09ae2a944451b71519B65eaf207EDC4B" + }, + { + "name": "FtsoTestXlm", + "contractName": "FtsoProxy.sol", + "address": "0x6B1244b81b3b6ad289Db61fefFe67DEeF9671750" + }, + { + "name": "FtsoTestDoge", + "contractName": "FtsoProxy.sol", + "address": "0xb7Af7fB7693A922Ba4fa7b04C5bd7d441758b98D" + }, + { + "name": "FtsoTestAda", + "contractName": "FtsoProxy.sol", + "address": "0x9414b2aE2E252D4f90d6e141196Eb9505f134Af1" + }, + { + "name": "FtsoTestAlgo", + "contractName": "FtsoProxy.sol", + "address": "0xc49B4F03cbf206F473Fe130Ff1cAb23Cdccd9771" + }, + { + "name": "FtsoTestBtc", + "contractName": "FtsoProxy.sol", + "address": "0xc6757dF6e0f4Cd997Be8B41aF0E58d5d45e36111" + }, + { + "name": "FtsoTestEth", + "contractName": "FtsoProxy.sol", + "address": "0xA08C5aa9E2e1F4F55f68f89DC9130b533AD2a673" + }, + { + "name": "FtsoTestFil", + "contractName": "FtsoProxy.sol", + "address": "0xACAa2031cb540d0a4aFfba94DBd773a6cA3A0440" + }, + { + "name": "FtsoTestArb", + "contractName": "FtsoProxy.sol", + "address": "0x75EfFD69D9e2f79183c9389598E98F3d2885a535" + }, + { + "name": "FtsoTestAvax", + "contractName": "FtsoProxy.sol", + "address": "0x8E38dB4C429a67a5f6e05D480da83f2Ba31365Fb" + }, + { + "name": "FtsoTestBnb", + "contractName": "FtsoProxy.sol", + "address": "0xa3e66Cf49bF299984dD57290819445C33eB40A5a" + }, + { + "name": "FtsoTestMatic", + "contractName": "FtsoProxy.sol", + "address": "0x964cf992F8d563154EdAb2A4Db1530279DFDA9aF" + }, + { + "name": "FtsoTestPol", + "contractName": "FtsoProxy.sol", + "address": "0x8a37960CFEec34EB436bf15Ec5DE2F24b412ef4E" + }, + { + "name": "FtsoTestSol", + "contractName": "FtsoProxy.sol", + "address": "0xBC8d04BF260306cAc7c952081ce39a2a3166a030" + }, + { + "name": "FtsoTestUsdc", + "contractName": "FtsoProxy.sol", + "address": "0x250d28B44287151D2aDa269e715E2b8B639448A3" + }, + { + "name": "FtsoTestUsdt", + "contractName": "FtsoProxy.sol", + "address": "0xf32bE3d2bF466916e9a8d9EAA7Ad0179da18268c" + }, + { + "name": "FtsoTestXdc", + "contractName": "FtsoProxy.sol", + "address": "0x481931Feb34Ef3CE97BB4Fef9b74855b4c8Fd7aA" + }, + { + "name": "FtsoV2", + "contractName": "FtsoV2Proxy.sol", + "address": "0xC4e9c78EA53db782E28f28Fdf80BaF59336B304d" + }, + { + "name": "PriceSubmitter", + "contractName": "PriceSubmitterProxy.sol", + "address": "0x9179aAB4C8b6Ac1402617eFa0fD130AbFa42c6e4" + }, + { + "name": "VoterWhitelister", + "contractName": "VoterWhitelisterProxy.sol", + "address": "0xbF4FaE78d3e5aF75A091f01a5405a1B82d740270" + }, + { + "name": "FdcHub", + "contractName": "FdcHub.sol", + "address": "0x48aC463d7975828989331F4De43341627b9c5f1D" + }, + { + "name": "FdcInflationConfigurations", + "contractName": "FdcInflationConfigurations.sol", + "address": "0x5C670a6950111D6f38B0D7cAdEB58D534fd9D209" + }, + { + "name": "FdcRequestFeeConfigurations", + "contractName": "FdcRequestFeeConfigurations.sol", + "address": "0x191a1282Ac700edE65c5B0AaF313BAcC3eA7fC7e" + }, + { + "name": "FdcVerification", + "contractName": "FdcVerificationProxy.sol", + "address": "0x906507E0B64bcD494Db73bd0459d1C667e14B933" + }, + { + "name": "VoterPreRegistry", + "contractName": "VoterPreRegistry.sol", + "address": "0xee963D1867d8B5f3D3E304802B40BC55B3eDeCB8" + }, + { + "name": "PollingManagementGroup", + "contractName": "PollingManagementGroup.sol", + "address": "0x056A8AcdCd2B5D3bF7a4F1d218B8A1660BB4D912" + }, + { + "name": "FtsoV2Implementation", + "contractName": "FtsoV2.sol", + "address": "0xA9E744A9dfA9177196816C9B92791065655c74aB" + }, + { + "name": "USDTSwapper", + "contractName": "USDTSwapper.sol", + "address": "0x0B70c0C7A242d4f81D85e9C8384C8AB1593851Cb" + }, + { + "name": "FdcVerificationImplementation", + "contractName": "FdcVerification.sol", + "address": "0x6E33205293aE1C6dcC91249951A5A67C863918A7" + }, + { + "name": "ChainlinkAdapterImplementation", + "contractName": "ChainlinkAdapter.sol", + "address": "0xAB8A8ABE1B73dE9a8c67D4591961E0a3be5274c7" + }, + { + "name": "ChainlinkAdapterFlr", + "contractName": "ChainlinkAdapterProxy.sol", + "address": "0x230052d081dFdF5C72BdAE1cB262B6F79563DDC6" + }, + { + "name": "ChainlinkAdapterXrp", + "contractName": "ChainlinkAdapterProxy.sol", + "address": "0x9b4f882C5a43bd98F7f6FAC7F24E5fBb697dB0fB" + }, + { + "name": "ChainlinkAdapterUsdt", + "contractName": "ChainlinkAdapterProxy.sol", + "address": "0xdf020706adB01E393A9026150FBFB810cDda789A" + }, + { + "name": "ChainlinkAdapterUsdc", + "contractName": "ChainlinkAdapterProxy.sol", + "address": "0x2fa3D57fa4ED7cD44443166F240D8223D42cB38B" + }, + { + "name": "DiamondGovernanceFacet", + "contractName": "DiamondGovernanceFacet.sol", + "address": "0x58e8C512678482b7eF8D97DBcF90238FC7D16645" + }, + { + "name": "DiamondLoupeFacet", + "contractName": "DiamondLoupeFacet.sol", + "address": "0x1aFDba98059527E1af069490fa009A390eE6303e" + }, + { + "name": "ExtensionManagerFacet", + "contractName": "ExtensionManagerFacet.sol", + "address": "0x13ebf34c3Fd436A657cb0f819c59790dF55CE14B" + }, + { + "name": "InstructionsFacet", + "contractName": "InstructionsFacet.sol", + "address": "0xe0958De99d4C9Fcb960AEd936Ba5964506AA62Ff" + }, + { + "name": "MachineManagerFacet", + "contractName": "MachineManagerFacet.sol", + "address": "0xF40B9a2e70EE96042217F10D94A4B1eDf13096a8" + }, + { + "name": "VerificationFacet", + "contractName": "VerificationFacet.sol", + "address": "0x78203332236cF39A0079746385F33060aCC95778" + }, + { + "name": "OperationFeesFacet", + "contractName": "OperationFeesFacet.sol", + "address": "0x7747a3012aFCA610Ec519eB8DCFD51dFC154a3dF" + }, + { + "name": "OwnerAllowlistFacet", + "contractName": "OwnerAllowlistFacet.sol", + "address": "0xDD692398C91c077798aEE6aC5c0e404FbD15c8C9" + }, + { + "name": "WalletManagerFacet", + "contractName": "WalletManagerFacet.sol", + "address": "0xcbf21163bC2A47E8a0FF69cC006C94684bC8Dc9b" + }, + { + "name": "WalletKeyManagerFacet", + "contractName": "WalletKeyManagerFacet.sol", + "address": "0x9Aeb4C3959Ba15464241F7b8daf38Ac2Fa1Cca13" + }, + { + "name": "WalletProjectManagerFacet", + "contractName": "WalletProjectManagerFacet.sol", + "address": "0x9B2767BEaFf0d48147390fA15f001162FdcB33e1" + }, + { + "name": "WalletBackupManagerFacet", + "contractName": "WalletBackupManagerFacet.sol", + "address": "0x9391Caba91a99590C87F421A0C3Fb5E92d36DCb4" + }, + { + "name": "VrfFacet", + "contractName": "VrfFacet.sol", + "address": "0x90c2210E899db9f27Eb713aF5acF97226DD7cEa1" + }, + { + "name": "ExternalAddressesFacet", + "contractName": "ExternalAddressesFacet.sol", + "address": "0xF515D79df67c549C379E64Ec4Ff8f151906ADB0A" + }, + { + "name": "ExtensionGovernanceFacet", + "contractName": "ExtensionGovernanceFacet.sol", + "address": "0x8756eF3d5e491c101DC703bf1AeaB1123988dD1B" + }, + { + "name": "MachinePathManagerFacet", + "contractName": "MachinePathManagerFacet.sol", + "address": "0x2Fc4E122BC234f56a0D05aa24d8CE7c28cD25141" + }, + { + "name": "WalletProjectPauseFacet", + "contractName": "WalletProjectPauseFacet.sol", + "address": "0xc2333fa57d96BeFceB1A45e53C52C533d28eFc74" + }, + { + "name": "MachineEmergencyPauseFacet", + "contractName": "MachineEmergencyPauseFacet.sol", + "address": "0x8F6FBdcC73819CD0a2E71A3E1D40bE036D335E3d" + }, + { + "name": "FlareTeeManagerInit", + "contractName": "FlareTeeManagerInit.sol", + "address": "0x8025F92764a9e5936A02570c0534F7B3dbFBDf20" + }, + { + "name": "FlareTeeManager", + "contractName": "FlareTeeManager.sol", + "address": "0x1a9C4A0f9D76c0b1D91d22E24E573a9b377618aE" + }, + { + "name": "Fdc2HubImplementation", + "contractName": "Fdc2Hub.sol", + "address": "0xF0338A1b9f8902000c562F34f43B4400FCc77A8f" + }, + { + "name": "Fdc2Hub", + "contractName": "Fdc2HubProxy.sol", + "address": "0x04dd3Ba33aC798d400bEc42A26F82f9812A421dc" + }, + { + "name": "Fdc2RequestFeeConfigurationsImplementation", + "contractName": "Fdc2RequestFeeConfigurations.sol", + "address": "0x85884d027d270Ed66801bDe56FBC3F2195A147ca" + }, + { + "name": "Fdc2RequestFeeConfigurations", + "contractName": "Fdc2RequestFeeConfigurationsProxy.sol", + "address": "0xbC135843998E0669a603e89599E9D184a713cac7" + }, + { + "name": "Fdc2VerificationImplementation", + "contractName": "Fdc2Verification.sol", + "address": "0x49484b974b3bBbF4e22cb5f5f3891EfacF5b1B2F" + }, + { + "name": "Fdc2Verification", + "contractName": "Fdc2VerificationProxy.sol", + "address": "0xA34Ff9be42b2C7782786270a51d33b1baC0462Cd" + }, + { + "name": "TeePaymentsRegistryImplementation", + "contractName": "TeePaymentsRegistry.sol", + "address": "0xD205AcEe3a785A4C59f090f71D909DB1F6CAeFf9" + }, + { + "name": "TeePaymentsRegistry", + "contractName": "TeePaymentsRegistryProxy.sol", + "address": "0x1A78D02DD19400138f4F3A817E95fC715B6c34Fe" + }, + { + "name": "TeePaymentsConfigVerifierImplementation", + "contractName": "TeePaymentsConfigVerifier.sol", + "address": "0x4ea574f16c87a9474A8Ae8ac65516b5f167B071a" + }, + { + "name": "TeePaymentsConfigVerifier", + "contractName": "TeePaymentsConfigVerifierProxy.sol", + "address": "0xC2973CEAD7c193e3e915Eeb6b4DA672660d84F7D" + }, + { + "name": "AddressValidatorImplementation", + "contractName": "AddressValidator.sol", + "address": "0x4860B2bf6237bF9a1779C60E9Bfb263330930661" + }, + { + "name": "AddressValidator", + "contractName": "AddressValidatorProxy.sol", + "address": "0x2dB76e4E0162400747561bD72dE52442897f6dA2" + }, + { + "name": "TeePaymentsImplementation", + "contractName": "TeePayments.sol", + "address": "0xA8b8E5174C2a389ba115cE8C4Ac508374aEBC957" + }, + { + "name": "TeePayments", + "contractName": "TeePaymentsProxy.sol", + "address": "0x86CB3e2dA1a4Ec30e2Ff2E5bA1A2D5ff9C9D44f4" + }, + { + "name": "TeePaymentsFeeScheduleManagerImplementation", + "contractName": "TeePaymentsFeeScheduleManager.sol", + "address": "0x4df266f37FBdcB6a1FCEd842e284f73B527D4cf2" + }, + { + "name": "TeePaymentsFeeScheduleManager", + "contractName": "TeePaymentsFeeScheduleManagerProxy.sol", + "address": "0x8640E30B0B165FA3F91497fE4AA01e003905681A" + }, + { + "name": "TeeRewardOffersManagerImplementation", + "contractName": "TeeRewardOffersManager.sol", + "address": "0x2ae91Ff299DF011e1056F2B1BF56C8C53e2D723F" + }, + { + "name": "TeeRewardOffersManager", + "contractName": "TeeRewardOffersManagerProxy.sol", + "address": "0x512f156Ff1a3F1E0D8fa8BA112Ff5a35637dbC9A" + }, + { + "name": "Fdc2InflationConfigurationsImplementation", + "contractName": "Fdc2InflationConfigurations.sol", + "address": "0x5bCC1274C3EE4Feae6359f59c9C3965D2c701032" + }, + { + "name": "Fdc2InflationConfigurations", + "contractName": "Fdc2InflationConfigurationsProxy.sol", + "address": "0x54f53294E21C729b98aa095E01BDb2770DB7d96d" + }, + { + "name": "Fdc2RewardOffersManagerImplementation", + "contractName": "Fdc2RewardOffersManager.sol", + "address": "0x86470A859EC5F6d91c426488Fc693Ce2C5f09CB1" + }, + { + "name": "Fdc2RewardOffersManager", + "contractName": "Fdc2RewardOffersManagerProxy.sol", + "address": "0xfa5DE5C1547297F79A974646DA80a625D6FaaD48" + }, + { + "name": "VrfVerifier", + "contractName": "VrfVerifier.sol", + "address": "0x7214D4c13aaFE0e26F5EC5Fc625a6b60AfF99533" + } +] \ No newline at end of file diff --git a/config/networks/coston2.json b/config/networks/coston2.json new file mode 100644 index 0000000..0f4f901 --- /dev/null +++ b/config/networks/coston2.json @@ -0,0 +1,22 @@ +{ + "network": "coston2", + "chainId": 114, + "rpcUrl": "https://coston2-api.flare.network/ext/C/rpc", + "explorerUrl": "https://coston2-explorer.flare.network", + "contractRegistry": "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019", + "fxrp": { + "assetManagerRegistryName": "AssetManagerFXRP", + "assetManager": "0xc1Ca88b937d0b528842F95d5731ffB586f4fbDFA", + "token": "0x0b6A3645c240605887a5532109323A3E12273dc7", + "decimals": 6, + "resolution": "Resolve AssetManagerFXRP from ContractRegistry, then call fAsset()." + }, + "usdt0": { + "symbol": "USDT0", + "onchainSymbol": "USD₮0", + "token": "0xC1A5B41512496B80903D1f32d6dEa3a73212E71F", + "decimals": 6, + "resolution": "Current Coston2 USDT0 test token; validate on-chain symbol USD₮0, decimals, and bytecode with the resolver.", + "source": "https://coston2-explorer.flare.network/tokens" + } +} diff --git a/config/proxy/extension_proxy.coston2.docker.toml.example b/config/proxy/extension_proxy.coston2.docker.toml.example new file mode 100644 index 0000000..817b81a --- /dev/null +++ b/config/proxy/extension_proxy.coston2.docker.toml.example @@ -0,0 +1,33 @@ +# Docker-specific Coston2 proxy config. Service names instead of localhost. +redis_port = "redis:6379" +private_key_variable = "PROXY_PRIVATE_KEY" +initial_signing_policy_offset = 2 +signing_policy_fetch_interval = "20s" + +chain_id = 114 + +[db] +host = "" +port = 3306 +database = "" +username = "" +password = "" +log_queries = false + +[addresses] +flare_systems_manager = "0xA90Db6D10F856799b10ef2A77EBCbF460aC71e52" +relay = "0xa10B672D1c62e5457b17af63d4302add6A99d7dE" +voter_registry = "0x6a0AF07b7972177B176d3D422555cbc98DfDe914" + + +[ports] +internal = "6663" +external = "6664" + +[info_timing] +cycle_internal = "10s" +cycle_queue_response_wait = "2s" + +[voting] +proposal_expiration = "12s" +max_pending_request = 10000 diff --git a/config/proxy/extension_proxy.coston2.toml.example b/config/proxy/extension_proxy.coston2.toml.example new file mode 100644 index 0000000..c68f126 --- /dev/null +++ b/config/proxy/extension_proxy.coston2.toml.example @@ -0,0 +1,36 @@ +redis_port = ":6380" + +private_key_variable = "" +initial_signing_policy_offset = 2 +signing_policy_fetch_interval = "20s" + +chain_id = 114 + +[db] +host = "" +port = 3306 +database = "" +username = "" +password = "" +log_queries = false + +[addresses] +flare_systems_manager = "0xA90Db6D10F856799b10ef2A77EBCbF460aC71e52" +relay = "0xa10B672D1c62e5457b17af63d4302add6A99d7dE" +voter_registry = "0x6a0AF07b7972177B176d3D422555cbc98DfDe914" + + + +[ports] +internal = "6663" +external = "6664" + + +[info_timing] +cycle_internal = "10s" +cycle_queue_response_wait ="2s" + + +[voting] +proposal_expiration = "12s" +max_pending_request = 10000 diff --git a/config/proxy/extension_proxy.docker.toml b/config/proxy/extension_proxy.docker.toml new file mode 100644 index 0000000..c990c0b --- /dev/null +++ b/config/proxy/extension_proxy.docker.toml @@ -0,0 +1,34 @@ +# Docker-specific proxy config. +# ext-proxy runs in a separate compose from the infrastructure. It reaches +# infrastructure services (indexer-db, blockchain node) via host.docker.internal. +redis_port = "redis:6379" +private_key_variable = "PROXY_PRIVATE_KEY" +initial_signing_policy_offset = 0 +signing_policy_fetch_interval = "20s" + +chain_id = 31337 + +[db] +host = "host.docker.internal" +port = 3306 +database = "db" +username = "root" +password = "root" +log_queries = false + +[addresses] +flare_systems_manager = "0xa4bcDF64Cdd5451b6ac3743B414124A6299B65FF" +relay = "0x5A0773Ff307Bf7C71a832dBB5312237fD3437f9F" +voter_registry = "0xB00cC45B4a7d3e1FEE684cFc4417998A1c183e6d" + +[ports] +internal = "6663" +external = "6664" + +[info_timing] +cycle_internal = "10s" +cycle_queue_response_wait = "2s" + +[voting] +proposal_expiration = "12s" +max_pending_request = 10000 diff --git a/config/proxy/extension_proxy.toml b/config/proxy/extension_proxy.toml new file mode 100644 index 0000000..cdc22bd --- /dev/null +++ b/config/proxy/extension_proxy.toml @@ -0,0 +1,37 @@ +redis_port = ":6382" + +private_key_variable = "PROXY_PRIVATE_KEY" +initial_signing_policy_offset = 0 +signing_policy_fetch_interval = "20s" + +chain_id = 114 + + +[db] +host = "localhost" +port = 3306 +database = "db" +username = "root" +password = "root" +log_queries = false + + +[addresses] +flare_systems_manager = "0xa4bcDF64Cdd5451b6ac3743B414124A6299B65FF" +relay = "0x5A0773Ff307Bf7C71a832dBB5312237fD3437f9F" +voter_registry = "0xB00cC45B4a7d3e1FEE684cFc4417998A1c183e6d" + + +[ports] +internal = "6663" +external = "6664" + + +[info_timing] +cycle_internal = "10s" +cycle_queue_response_wait ="2s" + + +[voting] +proposal_expiration = "12s" +max_pending_request = 10000 diff --git a/contracts/AccordRegistry.sol b/contracts/AccordRegistry.sol new file mode 100644 index 0000000..021b2f4 --- /dev/null +++ b/contracts/AccordRegistry.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +/// @title AccordRegistry +/// @notice Stores Concord relationship identity and explicit parent-child +/// lineage. Economic state remains in CapitalFacility; this registry answers +/// which relationship authorized each derived action. +contract AccordRegistry { + enum NodeKind { + ROOT_ACCORD, + MAKKARI_SESSION, + COFILL_ALLOCATION, + CHILD_ACCORD, + DRAW, + DRAW_LEG, + SETTLEMENT, + REPAYMENT + } + + struct LineageNode { + bytes32 id; + bytes32 parentId; + NodeKind kind; + uint64 createdAt; + bool exists; + } + + struct SettlementRecord { + bytes32 id; + bytes32 parentId; + address asset; + address from; + address to; + uint256 principal; + uint64 createdAt; + } + + struct RepaymentRecord { + bytes32 id; + bytes32 drawId; + uint256 principal; + uint64 createdAt; + } + + address public immutable admin; + address public facility; + + mapping(bytes32 => LineageNode) public nodes; + mapping(bytes32 => bytes32[]) private childrenByParent; + mapping(bytes32 => SettlementRecord) public settlements; + mapping(bytes32 => RepaymentRecord) public repayments; + + event FacilitySet(address indexed facility); + event LineageRegistered(bytes32 indexed id, bytes32 indexed parentId, NodeKind kind); + event SettlementRegistered( + bytes32 indexed id, + bytes32 indexed parentId, + address indexed asset, + address from, + address to, + uint256 principal + ); + event RepaymentRegistered(bytes32 indexed id, bytes32 indexed drawId, uint256 principal); + + error Unauthorized(); + error AlreadyInitialized(); + error InvalidLineage(); + error DuplicateNode(); + + constructor(address admin_) { + require(admin_ != address(0), "admin is zero"); + admin = admin_; + } + + modifier onlyFacility() { + if (msg.sender != facility) revert Unauthorized(); + _; + } + + function setFacility(address facility_) external { + if (msg.sender != admin) revert Unauthorized(); + if (facility != address(0)) revert AlreadyInitialized(); + require(facility_ != address(0), "facility is zero"); + facility = facility_; + emit FacilitySet(facility_); + } + + function registerNode(bytes32 id, bytes32 parentId, NodeKind kind) public onlyFacility { + if (id == bytes32(0)) revert InvalidLineage(); + if (nodes[id].exists) revert DuplicateNode(); + if (kind != NodeKind.ROOT_ACCORD && !nodes[parentId].exists) revert InvalidLineage(); + + nodes[id] = LineageNode({ + id: id, + parentId: parentId, + kind: kind, + createdAt: uint64(block.timestamp), + exists: true + }); + childrenByParent[parentId].push(id); + emit LineageRegistered(id, parentId, kind); + } + + function registerSettlement( + bytes32 id, + bytes32 parentId, + address asset, + address from, + address to, + uint256 principal + ) external onlyFacility { + registerNode(id, parentId, NodeKind.SETTLEMENT); + settlements[id] = SettlementRecord({ + id: id, + parentId: parentId, + asset: asset, + from: from, + to: to, + principal: principal, + createdAt: uint64(block.timestamp) + }); + emit SettlementRegistered(id, parentId, asset, from, to, principal); + } + + function registerRepayment(bytes32 id, bytes32 drawId, uint256 principal) external onlyFacility { + registerNode(id, drawId, NodeKind.REPAYMENT); + repayments[id] = RepaymentRecord({ + id: id, + drawId: drawId, + principal: principal, + createdAt: uint64(block.timestamp) + }); + emit RepaymentRegistered(id, drawId, principal); + } + + function getChildren(bytes32 parentId) external view returns (bytes32[] memory) { + return childrenByParent[parentId]; + } + + function hasNode(bytes32 id) external view returns (bool) { + return nodes[id].exists; + } +} diff --git a/contracts/CapitalFacility.sol b/contracts/CapitalFacility.sol new file mode 100644 index 0000000..e957ca0 --- /dev/null +++ b/contracts/CapitalFacility.sol @@ -0,0 +1,618 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {AccordRegistry} from "./AccordRegistry.sol"; +import {ConcordTypes} from "./ConcordTypes.sol"; +import {IERC20Minimal} from "./interfaces/IERC20Minimal.sol"; + +/// @title CapitalFacility +/// @notice The economic lifecycle for one FXRP-backed, USDT0-liquidity +/// syndicated facility. Makkari/CoFill decide an allocation; this contract +/// owns funding, draws, exposure, settlement, and repayment. +contract CapitalFacility { + uint256 private constant BPS = 10_000; + + address public immutable owner; + address public immutable allocationVerifier; + bytes32 public immutable extensionId; + AccordRegistry public immutable accordRegistry; + address public immutable collateralAsset; + address public immutable liquidityAsset; + + struct MakkariRound { + bytes32 id; + bytes32 rootAccordId; + uint256 targetCapacity; + uint32 maxFeeBps; + uint64 roundExpiry; + ConcordTypes.RoundStatus status; + } + + struct Draw { + bytes32 id; + bytes32 rootAccordId; + uint256 principal; + uint256 repaidPrincipal; + uint64 createdAt; + bool exists; + } + + struct DrawLeg { + bytes32 id; + bytes32 drawId; + bytes32 childAccordId; + uint256 principal; + uint256 repaidPrincipal; + } + + mapping(bytes32 => ConcordTypes.RootAccord) public rootAccords; + mapping(bytes32 => ConcordTypes.ChildAccord) public childAccords; + mapping(bytes32 => MakkariRound) public rounds; + mapping(bytes32 => Draw) public draws; + mapping(bytes32 => DrawLeg) public drawLegs; + mapping(bytes32 => bytes32[]) private childrenByRoot; + mapping(bytes32 => bytes32[]) private legsByDraw; + mapping(bytes32 => address[]) private eligibleProvidersByRound; + mapping(bytes32 => mapping(address => bool)) public eligibleProvider; + mapping(bytes32 => bool) public verifiedAllocationDigest; + mapping(bytes32 => bool) public consumedAllocationDigest; + + uint256 private repaymentNonce; + + uint256 private _entered; + + event RootAccordCreated( + bytes32 indexed rootId, + address indexed borrower, + uint256 targetCapacity, + uint64 validUntil, + bytes32 policyHash + ); + event CollateralLocked(bytes32 indexed rootId, address indexed borrower, uint256 amount); + event SyndicationOpened( + bytes32 indexed rootId, + bytes32 indexed roundId, + uint256 targetCapacity, + uint32 maxFeeBps, + uint64 roundExpiry + ); + event AllocationVerified(bytes32 indexed resultDigest); + event ChildAccordSelected( + bytes32 indexed childId, + bytes32 indexed rootId, + address indexed provider, + uint256 selectedCapacity, + uint32 feeBps + ); + event ChildAccordFunded(bytes32 indexed childId, address indexed provider, uint256 amount); + event RootActivated(bytes32 indexed rootId, uint256 committedCapacity); + event DrawCreated(bytes32 indexed drawId, bytes32 indexed rootId, uint256 principal); + event DrawLegCreated( + bytes32 indexed legId, + bytes32 indexed drawId, + bytes32 indexed childId, + uint256 principal + ); + event Settlement( + bytes32 indexed settlementId, + bytes32 indexed parentId, + address indexed asset, + address from, + address to, + uint256 principal + ); + event Repayment(bytes32 indexed repaymentId, bytes32 indexed drawId, uint256 principal); + event ChildClosed(bytes32 indexed childId, address indexed provider, uint256 returnedAmount); + event RootClosed(bytes32 indexed rootId, uint256 collateralReturned); + event RootExpired(bytes32 indexed rootId); + event ChildExpired(bytes32 indexed childId, uint256 returnedAmount); + + error Unauthorized(); + error InvalidState(); + error InvalidAmount(); + error InvalidExpiry(); + error InvalidAllocation(); + error AllocationNotVerified(); + error AllocationAlreadyConsumed(); + error InsufficientCapacity(); + error UnknownAccord(); + error TransferFailed(); + error Reentrancy(); + + modifier onlyOwner() { + if (msg.sender != owner) revert Unauthorized(); + _; + } + + modifier nonReentrant() { + if (_entered == 1) revert Reentrancy(); + _entered = 1; + _; + _entered = 0; + } + + constructor( + AccordRegistry registry_, + address collateralAsset_, + address liquidityAsset_, + address allocationVerifier_, + bytes32 extensionId_ + ) { + require(address(registry_) != address(0), "registry is zero"); + require(collateralAsset_ != address(0), "collateral asset is zero"); + require(liquidityAsset_ != address(0), "liquidity asset is zero"); + require(allocationVerifier_ != address(0), "verifier is zero"); + require(extensionId_ != bytes32(0), "extension id is zero"); + owner = msg.sender; + accordRegistry = registry_; + collateralAsset = collateralAsset_; + liquidityAsset = liquidityAsset_; + allocationVerifier = allocationVerifier_; + extensionId = extensionId_; + } + + function createRootAccord( + bytes32 rootId, + uint256 targetCapacity, + uint64 validUntil, + bytes32 policyHash + ) external { + if (rootId == bytes32(0) || targetCapacity == 0) revert InvalidAmount(); + if (validUntil <= block.timestamp) revert InvalidExpiry(); + if (rootAccords[rootId].state != ConcordTypes.RootState.NONE) revert InvalidState(); + + rootAccords[rootId] = ConcordTypes.RootAccord({ + id: rootId, + borrower: msg.sender, + collateralAsset: collateralAsset, + liquidityAsset: liquidityAsset, + targetCapacity: targetCapacity, + committedCapacity: 0, + drawnPrincipal: 0, + collateralLocked: 0, + validUntil: validUntil, + policyHash: policyHash, + syndicationRoundId: bytes32(0), + state: ConcordTypes.RootState.PROPOSED + }); + accordRegistry.registerNode(rootId, bytes32(0), AccordRegistry.NodeKind.ROOT_ACCORD); + emit RootAccordCreated(rootId, msg.sender, targetCapacity, validUntil, policyHash); + } + + function lockCollateral(bytes32 rootId, uint256 amount) external nonReentrant { + ConcordTypes.RootAccord storage root = _root(rootId); + if (msg.sender != root.borrower) revert Unauthorized(); + if (root.state != ConcordTypes.RootState.PROPOSED || amount == 0) revert InvalidState(); + _safeTransferFrom(collateralAsset, msg.sender, address(this), amount); + root.collateralLocked += amount; + emit CollateralLocked(rootId, msg.sender, amount); + } + + function openSyndication( + bytes32 rootId, + bytes32 roundId, + uint32 maxFeeBps, + uint64 roundExpiry, + address[] calldata eligibleProviders + ) external { + ConcordTypes.RootAccord storage root = _root(rootId); + if (msg.sender != root.borrower) revert Unauthorized(); + if (root.state != ConcordTypes.RootState.PROPOSED || root.collateralLocked == 0) { + revert InvalidState(); + } + if (roundId == bytes32(0) || rounds[roundId].status != ConcordTypes.RoundStatus.NONE) { + revert InvalidAllocation(); + } + if (maxFeeBps > BPS || roundExpiry <= block.timestamp || roundExpiry > root.validUntil) { + revert InvalidExpiry(); + } + if (eligibleProviders.length == 0) revert InvalidAllocation(); + + rounds[roundId] = MakkariRound({ + id: roundId, + rootAccordId: rootId, + targetCapacity: root.targetCapacity, + maxFeeBps: maxFeeBps, + roundExpiry: roundExpiry, + status: ConcordTypes.RoundStatus.OPEN + }); + root.syndicationRoundId = roundId; + root.state = ConcordTypes.RootState.SYNDICATING; + + for (uint256 i; i < eligibleProviders.length; ++i) { + address provider = eligibleProviders[i]; + if (provider == address(0) || eligibleProvider[roundId][provider]) revert InvalidAllocation(); + eligibleProvider[roundId][provider] = true; + eligibleProvidersByRound[roundId].push(provider); + } + + accordRegistry.registerNode(roundId, rootId, AccordRegistry.NodeKind.MAKKARI_SESSION); + emit SyndicationOpened(rootId, roundId, root.targetCapacity, maxFeeBps, roundExpiry); + } + + /// @notice Called only by the configured result verifier after it has + /// checked the FCC response and its active machine/signature binding. + function markAllocationVerified(bytes32 resultDigest) external { + // The explicit verifier boundary is kept as a separate immutable + // address so an owner key cannot silently impersonate the FCC path. + if (msg.sender != allocationVerifier) revert Unauthorized(); + if (resultDigest == bytes32(0)) revert InvalidAllocation(); + verifiedAllocationDigest[resultDigest] = true; + emit AllocationVerified(resultDigest); + } + + function materializeAllocation(ConcordTypes.AllocationResult calldata result) external { + ConcordTypes.RootAccord storage root = _root(result.rootAccordId); + MakkariRound storage round = rounds[result.roundId]; + + // The verifier authorizes the digest; the treasury still controls + // when its root relationship materializes those selected children. + if (msg.sender != root.borrower) revert Unauthorized(); + + if (result.extensionId != extensionId || result.rootAccordId != round.rootAccordId) { + revert InvalidAllocation(); + } + if (root.state != ConcordTypes.RootState.SYNDICATING || round.status != ConcordTypes.RoundStatus.OPEN) { + revert InvalidState(); + } + if (block.timestamp > round.roundExpiry || result.roundExpiry != round.roundExpiry) revert InvalidExpiry(); + if (!result.success) revert InvalidAllocation(); + if (!verifiedAllocationDigest[result.resultDigest]) revert AllocationNotVerified(); + if (consumedAllocationDigest[result.resultDigest]) revert AllocationAlreadyConsumed(); + if (_allocationDigest(result) != result.resultDigest) revert InvalidAllocation(); + + uint256 length = result.selectedProviders.length; + if (length == 0 || result.allocatedCapacity.length != length || result.acceptedFeeBps.length != length) { + revert InvalidAllocation(); + } + if (result.termsCommitments.length != length) revert InvalidAllocation(); + + uint256 total; + for (uint256 i; i < length; ++i) { + address provider = result.selectedProviders[i]; + if (!eligibleProvider[result.roundId][provider] || result.allocatedCapacity[i] == 0) { + revert InvalidAllocation(); + } + if (result.acceptedFeeBps[i] > round.maxFeeBps) revert InvalidAllocation(); + for (uint256 j; j < i; ++j) { + if (result.selectedProviders[j] == provider) revert InvalidAllocation(); + } + total += result.allocatedCapacity[i]; + } + if (total != root.targetCapacity) revert InsufficientCapacity(); + + consumedAllocationDigest[result.resultDigest] = true; + round.status = ConcordTypes.RoundStatus.FINALIZED; + root.state = ConcordTypes.RootState.FUNDING; + accordRegistry.registerNode( + result.resultDigest, + result.roundId, + AccordRegistry.NodeKind.COFILL_ALLOCATION + ); + + for (uint256 i; i < length; ++i) { + bytes32 childId = keccak256(abi.encode(result.rootAccordId, result.resultDigest, result.selectedProviders[i])); + childAccords[childId] = ConcordTypes.ChildAccord({ + id: childId, + rootId: result.rootAccordId, + allocationId: result.resultDigest, + provider: result.selectedProviders[i], + selectedCapacity: result.allocatedCapacity[i], + committedCapacity: 0, + drawnPrincipal: 0, + feeBps: result.acceptedFeeBps[i], + validUntil: result.roundExpiry, + termsCommitment: result.termsCommitments[i], + state: ConcordTypes.ChildState.SELECTED + }); + childrenByRoot[result.rootAccordId].push(childId); + accordRegistry.registerNode(childId, result.resultDigest, AccordRegistry.NodeKind.CHILD_ACCORD); + emit ChildAccordSelected( + childId, + result.rootAccordId, + result.selectedProviders[i], + result.allocatedCapacity[i], + result.acceptedFeeBps[i] + ); + } + } + + function fundChild(bytes32 childId, uint256 amount) external nonReentrant { + ConcordTypes.ChildAccord storage child = _child(childId); + ConcordTypes.RootAccord storage root = _root(child.rootId); + if (msg.sender != child.provider) revert Unauthorized(); + if ( + (child.state != ConcordTypes.ChildState.SELECTED && child.state != ConcordTypes.ChildState.FUNDED) || + amount == 0 + ) revert InvalidState(); + if (root.state != ConcordTypes.RootState.FUNDING) revert InvalidState(); + if (child.committedCapacity + amount > child.selectedCapacity) revert InsufficientCapacity(); + + _safeTransferFrom(liquidityAsset, msg.sender, address(this), amount); + child.committedCapacity += amount; + root.committedCapacity += amount; + // Any successful USDT0 transfer makes the relationship funded. The + // provider may finish a selected allocation in more than one transfer, + // but selected capacity is never counted as committed before transfer. + child.state = ConcordTypes.ChildState.FUNDED; + emit ChildAccordFunded(childId, msg.sender, amount); + + if (root.committedCapacity == root.targetCapacity) { + root.state = ConcordTypes.RootState.ACTIVE; + bytes32[] storage childIds = childrenByRoot[root.id]; + for (uint256 i; i < childIds.length; ++i) { + if (childAccords[childIds[i]].state == ConcordTypes.ChildState.FUNDED) { + childAccords[childIds[i]].state = ConcordTypes.ChildState.ACTIVE; + } + } + emit RootActivated(root.id, root.committedCapacity); + } + } + + function draw(bytes32 drawId, bytes32 rootId, uint256 amount) external nonReentrant { + ConcordTypes.RootAccord storage root = _root(rootId); + if (msg.sender != root.borrower) revert Unauthorized(); + if (root.state != ConcordTypes.RootState.ACTIVE || amount == 0) revert InvalidState(); + if (drawId == bytes32(0) || draws[drawId].exists) revert InvalidAllocation(); + if (amount > availableCapacity(rootId)) revert InsufficientCapacity(); + + bytes32[] storage childIds = childrenByRoot[rootId]; + bytes32[] memory selectedChildren = new bytes32[](childIds.length); + uint256[] memory selectedAmounts = new uint256[](childIds.length); + uint256 selectedCount; + uint256 remaining = amount; + for (uint256 i; i < childIds.length && remaining > 0; ++i) { + ConcordTypes.ChildAccord storage child = childAccords[childIds[i]]; + if (child.state != ConcordTypes.ChildState.ACTIVE) continue; + uint256 available = child.committedCapacity - child.drawnPrincipal; + if (available == 0) continue; + uint256 legAmount = available < remaining ? available : remaining; + selectedChildren[selectedCount] = childIds[i]; + selectedAmounts[selectedCount] = legAmount; + ++selectedCount; + remaining -= legAmount; + } + if (remaining != 0) revert InsufficientCapacity(); + + _safeTransfer(liquidityAsset, root.borrower, amount); + draws[drawId] = Draw({ + id: drawId, + rootAccordId: rootId, + principal: amount, + repaidPrincipal: 0, + createdAt: uint64(block.timestamp), + exists: true + }); + root.drawnPrincipal += amount; + accordRegistry.registerNode(drawId, rootId, AccordRegistry.NodeKind.DRAW); + emit DrawCreated(drawId, rootId, amount); + + for (uint256 i; i < selectedCount; ++i) { + bytes32 childId = selectedChildren[i]; + bytes32 legId = keccak256(abi.encode(drawId, childId)); + DrawLeg memory leg = DrawLeg({ + id: legId, + drawId: drawId, + childAccordId: childId, + principal: selectedAmounts[i], + repaidPrincipal: 0 + }); + drawLegs[legId] = leg; + legsByDraw[drawId].push(legId); + childAccords[childId].drawnPrincipal += selectedAmounts[i]; + accordRegistry.registerNode(legId, drawId, AccordRegistry.NodeKind.DRAW_LEG); + emit DrawLegCreated(legId, drawId, childId, selectedAmounts[i]); + } + + bytes32 settlementId = keccak256(abi.encode("DRAW_SETTLEMENT", drawId)); + accordRegistry.registerSettlement( + settlementId, + drawId, + liquidityAsset, + address(this), + root.borrower, + amount + ); + emit Settlement(settlementId, drawId, liquidityAsset, address(this), root.borrower, amount); + } + + function repay(bytes32 drawId, uint256 amount) external nonReentrant { + Draw storage drawState = draws[drawId]; + if (!drawState.exists || amount == 0) revert InvalidAmount(); + ConcordTypes.RootAccord storage root = _root(drawState.rootAccordId); + if (msg.sender != root.borrower) revert Unauthorized(); + uint256 outstanding = drawState.principal - drawState.repaidPrincipal; + if (amount > outstanding) revert InvalidAmount(); + + _safeTransferFrom(liquidityAsset, msg.sender, address(this), amount); + drawState.repaidPrincipal += amount; + root.drawnPrincipal -= amount; + + uint256 remaining = amount; + bytes32[] storage legIds = legsByDraw[drawId]; + for (uint256 i; i < legIds.length && remaining > 0; ++i) { + DrawLeg storage leg = drawLegs[legIds[i]]; + uint256 legOutstanding = leg.principal - leg.repaidPrincipal; + if (legOutstanding == 0) continue; + uint256 legRepayment = legOutstanding < remaining ? legOutstanding : remaining; + leg.repaidPrincipal += legRepayment; + childAccords[leg.childAccordId].drawnPrincipal -= legRepayment; + remaining -= legRepayment; + } + if (remaining != 0) revert InvalidAmount(); + + bytes32 repaymentId = keccak256(abi.encode(drawId, amount, repaymentNonce++)); + accordRegistry.registerRepayment(repaymentId, drawId, amount); + emit Repayment(repaymentId, drawId, amount); + + bytes32 settlementId = keccak256(abi.encode("REPAYMENT_SETTLEMENT", repaymentId)); + accordRegistry.registerSettlement( + settlementId, + repaymentId, + liquidityAsset, + root.borrower, + address(this), + amount + ); + emit Settlement(settlementId, repaymentId, liquidityAsset, root.borrower, address(this), amount); + } + + function closeChild(bytes32 childId) external nonReentrant { + ConcordTypes.ChildAccord storage child = _child(childId); + ConcordTypes.RootAccord storage root = _root(child.rootId); + if (msg.sender != child.provider) revert Unauthorized(); + if ( + child.state != ConcordTypes.ChildState.ACTIVE && + child.state != ConcordTypes.ChildState.FUNDED && + child.state != ConcordTypes.ChildState.SELECTED + ) revert InvalidState(); + if (child.drawnPrincipal != 0) revert InvalidState(); + + uint256 returned = child.committedCapacity; + if (returned != 0) { + root.committedCapacity -= returned; + child.committedCapacity = 0; + _safeTransfer(liquidityAsset, child.provider, returned); + } + child.state = ConcordTypes.ChildState.CLOSED; + if (root.state == ConcordTypes.RootState.ACTIVE && root.committedCapacity < root.targetCapacity) { + root.state = ConcordTypes.RootState.FUNDING; + } + emit ChildClosed(childId, child.provider, returned); + } + + function expireChild(bytes32 childId) external nonReentrant { + ConcordTypes.ChildAccord storage child = _child(childId); + ConcordTypes.RootAccord storage root = _root(child.rootId); + if (block.timestamp <= child.validUntil || child.drawnPrincipal != 0) revert InvalidState(); + if ( + child.state != ConcordTypes.ChildState.SELECTED && + child.state != ConcordTypes.ChildState.FUNDED && + child.state != ConcordTypes.ChildState.ACTIVE + ) revert InvalidState(); + + uint256 returned = child.committedCapacity; + if (returned != 0) { + root.committedCapacity -= returned; + child.committedCapacity = 0; + _safeTransfer(liquidityAsset, child.provider, returned); + } + child.state = ConcordTypes.ChildState.EXPIRED; + // An expired child can reduce an already active root below target. + // Preserve the root state invariant so no draw can proceed against + // a facility that is no longer fully funded. + if ( + root.state == ConcordTypes.RootState.ACTIVE && + root.committedCapacity < root.targetCapacity + ) { + root.state = ConcordTypes.RootState.FUNDING; + } + emit ChildExpired(childId, returned); + } + + function closeRoot(bytes32 rootId) external nonReentrant { + ConcordTypes.RootAccord storage root = _root(rootId); + if (msg.sender != root.borrower) revert Unauthorized(); + if (root.drawnPrincipal != 0 || root.committedCapacity != 0) revert InvalidState(); + bytes32[] storage childIds = childrenByRoot[rootId]; + for (uint256 i; i < childIds.length; ++i) { + ConcordTypes.ChildState state = childAccords[childIds[i]].state; + if (state != ConcordTypes.ChildState.CLOSED && state != ConcordTypes.ChildState.EXPIRED) { + revert InvalidState(); + } + } + if (root.collateralLocked == 0) revert InvalidState(); + uint256 returnedCollateral = root.collateralLocked; + root.collateralLocked = 0; + root.state = ConcordTypes.RootState.CLOSED; + _safeTransfer(collateralAsset, root.borrower, returnedCollateral); + emit RootClosed(rootId, returnedCollateral); + } + + function expireRoot(bytes32 rootId) external nonReentrant { + ConcordTypes.RootAccord storage root = _root(rootId); + if (block.timestamp <= root.validUntil || root.drawnPrincipal != 0 || root.committedCapacity != 0) { + revert InvalidState(); + } + if (root.collateralLocked == 0) revert InvalidState(); + uint256 returnedCollateral = root.collateralLocked; + root.collateralLocked = 0; + root.state = ConcordTypes.RootState.EXPIRED; + _safeTransfer(collateralAsset, root.borrower, returnedCollateral); + emit RootExpired(rootId); + } + + function availableCapacity(bytes32 rootId) public view returns (uint256) { + ConcordTypes.RootAccord storage root = _root(rootId); + return root.committedCapacity - root.drawnPrincipal; + } + + function childAvailableCapacity(bytes32 childId) public view returns (uint256) { + ConcordTypes.ChildAccord storage child = _child(childId); + return child.committedCapacity - child.drawnPrincipal; + } + + function getRoot(bytes32 rootId) external view returns (ConcordTypes.RootAccord memory) { + return _root(rootId); + } + + function getChild(bytes32 childId) external view returns (ConcordTypes.ChildAccord memory) { + return _child(childId); + } + + function getChildIds(bytes32 rootId) external view returns (bytes32[] memory) { + return childrenByRoot[rootId]; + } + + function getEligibleProviders(bytes32 roundId) external view returns (address[] memory) { + return eligibleProvidersByRound[roundId]; + } + + function getDrawLegIds(bytes32 drawId) external view returns (bytes32[] memory) { + return legsByDraw[drawId]; + } + + function _root(bytes32 rootId) internal view returns (ConcordTypes.RootAccord storage root) { + root = rootAccords[rootId]; + if (root.state == ConcordTypes.RootState.NONE) revert UnknownAccord(); + } + + function _child(bytes32 childId) internal view returns (ConcordTypes.ChildAccord storage child) { + child = childAccords[childId]; + if (child.state == ConcordTypes.ChildState.NONE) revert UnknownAccord(); + } + + function _allocationDigest(ConcordTypes.AllocationResult calldata result) internal pure returns (bytes32) { + bytes memory encoded = abi.encodePacked( + result.extensionId, + result.roundId, + result.rootAccordId, + result.success, + result.roundExpiry + ); + for (uint256 i; i < result.selectedProviders.length; ++i) { + encoded = bytes.concat( + encoded, + abi.encodePacked( + result.selectedProviders[i], + result.allocatedCapacity[i], + result.acceptedFeeBps[i], + result.termsCommitments[i] + ) + ); + } + return keccak256(encoded); + } + + function _safeTransferFrom(address token, address from, address to, uint256 amount) internal { + (bool ok, bytes memory data) = token.call( + abi.encodeWithSelector(IERC20Minimal.transferFrom.selector, from, to, amount) + ); + if (!ok || (data.length != 0 && !abi.decode(data, (bool)))) revert TransferFailed(); + } + + function _safeTransfer(address token, address to, uint256 amount) internal { + (bool ok, bytes memory data) = token.call(abi.encodeWithSelector(IERC20Minimal.transfer.selector, to, amount)); + if (!ok || (data.length != 0 && !abi.decode(data, (bool)))) revert TransferFailed(); + } +} diff --git a/contracts/ConcordInstructionSender.sol b/contracts/ConcordInstructionSender.sol new file mode 100644 index 0000000..ba93525 --- /dev/null +++ b/contracts/ConcordInstructionSender.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {ITeeExtensionRegistry} from "./interfaces/ITeeExtensionRegistry.sol"; +import {ITeeMachineRegistry} from "./interfaces/ITeeMachineRegistry.sol"; + +/// @title ConcordInstructionSender +/// @notice Concord's Flare Confidential Compute instruction entry point. +/// The message is expected to be encrypted to the active FCC machine public +/// key before it is submitted. Settlement and relationship state remain public +/// onchain; quote terms and losing offers remain inside the FCC boundary. +contract ConcordInstructionSender { + bytes32 public constant OP_TYPE_CONCORD = bytes32("CONCORD"); + bytes32 public constant OP_COMMAND_SUBMIT_QUOTE = bytes32("SUBMIT_QUOTE"); + bytes32 public constant OP_COMMAND_FINALIZE_ROUND = bytes32("FINALIZE_ROUND"); + + ITeeExtensionRegistry public immutable TEE_EXTENSION_REGISTRY; + ITeeMachineRegistry public immutable TEE_MACHINE_REGISTRY; + + uint256 private constant FIRST_PUBLIC_EXTENSION_ID = 0x10000; + uint256 private _extensionId; + + constructor(ITeeExtensionRegistry extensionRegistry_, ITeeMachineRegistry machineRegistry_) { + require(address(extensionRegistry_) != address(0), "extension registry is zero"); + require(address(machineRegistry_) != address(0), "machine registry is zero"); + require(address(extensionRegistry_).code.length > 0, "extension registry has no code"); + require(address(machineRegistry_).code.length > 0, "machine registry has no code"); + TEE_EXTENSION_REGISTRY = extensionRegistry_; + TEE_MACHINE_REGISTRY = machineRegistry_; + } + + function setExtensionId() external { + require(_extensionId == 0, "Extension ID already set."); + uint256 nextId = TEE_EXTENSION_REGISTRY.nextPublicExtensionId(); + for (uint256 i = FIRST_PUBLIC_EXTENSION_ID; i < nextId; ++i) { + if (TEE_EXTENSION_REGISTRY.getTeeExtensionInstructionsSender(i) == address(this)) { + _extensionId = i; + return; + } + } + revert("Extension ID not found."); + } + + function extensionId() external view returns (uint256) { + return _extensionId; + } + + function sendSubmitQuote(bytes calldata encryptedMessage) external payable returns (bytes32 instructionId) { + instructionId = _send(OP_COMMAND_SUBMIT_QUOTE, encryptedMessage); + } + + function sendFinalizeRound(bytes calldata encryptedMessage) external payable returns (bytes32 instructionId) { + instructionId = _send(OP_COMMAND_FINALIZE_ROUND, encryptedMessage); + } + + function _send(bytes32 command, bytes calldata encryptedMessage) internal returns (bytes32 instructionId) { + require(encryptedMessage.length != 0, "encrypted message is empty"); + address[] memory teeIds = TEE_MACHINE_REGISTRY.getRandomTeeIds(_getExtensionId(), 1); + address[] memory cosigners = new address[](0); + ITeeExtensionRegistry.TeeInstructionParams memory params = ITeeExtensionRegistry.TeeInstructionParams({ + opType: OP_TYPE_CONCORD, + opCommand: command, + message: encryptedMessage, + cosigners: cosigners, + cosignersThreshold: 0, + claimBackAddress: msg.sender + }); + instructionId = TEE_EXTENSION_REGISTRY.sendInstructions{value: msg.value}(teeIds, params); + } + + function _getExtensionId() internal view returns (uint256) { + require(_extensionId != 0, "Extension ID is not set."); + return _extensionId; + } +} diff --git a/contracts/ConcordTypes.sol b/contracts/ConcordTypes.sol new file mode 100644 index 0000000..e16526f --- /dev/null +++ b/contracts/ConcordTypes.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +/// @title ConcordTypes +/// @notice Shared value types for Concord's relationship and facility layers. +/// +/// The transaction is not Concord's canonical object. A RootAccord is. Child +/// Accords, rounds, draws, legs, and settlements are bounded by that root. +library ConcordTypes { + enum RootState { + NONE, + PROPOSED, + SYNDICATING, + FUNDING, + ACTIVE, + CLOSED, + FROZEN, + EXPIRED + } + + enum ChildState { + NONE, + SELECTED, + FUNDED, + ACTIVE, + CLOSED, + EXPIRED, + DEFAULTED + } + + enum RoundStatus { + NONE, + OPEN, + FINALIZED, + EXPIRED + } + + struct RootAccord { + bytes32 id; + address borrower; + address collateralAsset; + address liquidityAsset; + uint256 targetCapacity; + uint256 committedCapacity; + uint256 drawnPrincipal; + uint256 collateralLocked; + uint64 validUntil; + bytes32 policyHash; + bytes32 syndicationRoundId; + RootState state; + } + + struct ChildAccord { + bytes32 id; + bytes32 rootId; + bytes32 allocationId; + address provider; + uint256 selectedCapacity; + uint256 committedCapacity; + uint256 drawnPrincipal; + uint32 feeBps; + uint64 validUntil; + bytes32 termsCommitment; + ChildState state; + } + + /// @dev This is the verified boundary between FCC and materialization. + /// `resultDigest` commits to every field in this struct using the canonical + /// packed encoding implemented by CapitalFacility._allocationDigest(). + struct AllocationResult { + bytes32 extensionId; + bytes32 roundId; + bytes32 rootAccordId; + bool success; + address[] selectedProviders; + uint256[] allocatedCapacity; + uint32[] acceptedFeeBps; + bytes32[] termsCommitments; + uint64 roundExpiry; + bytes32 resultDigest; + } +} diff --git a/contracts/interfaces/IERC20Minimal.sol b/contracts/interfaces/IERC20Minimal.sol new file mode 100644 index 0000000..e164fe7 --- /dev/null +++ b/contracts/interfaces/IERC20Minimal.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +interface IERC20Minimal { + function transfer(address to, uint256 amount) external returns (bool); + + function transferFrom(address from, address to, uint256 amount) external returns (bool); + + function balanceOf(address account) external view returns (uint256); +} diff --git a/contracts/interfaces/ITeeExtensionRegistry.sol b/contracts/interfaces/ITeeExtensionRegistry.sol new file mode 100644 index 0000000..918ed72 --- /dev/null +++ b/contracts/interfaces/ITeeExtensionRegistry.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.6 <0.9; + +// TODO: Replace this minimal interface with the full import once flare-smart-contracts-v2 +// is published as a package: +// import { ITeeExtensionRegistry } from "flare-smart-contracts-v2/contracts/userInterfaces/tee/ITeeExtensionRegistry.sol"; +interface ITeeExtensionRegistry { + struct TeeInstructionParams { + bytes32 opType; + bytes32 opCommand; + bytes message; + address[] cosigners; + uint64 cosignersThreshold; + address claimBackAddress; + } + + function sendInstructions( + address[] calldata _teeIds, + TeeInstructionParams calldata _instructionParams + ) external payable returns (bytes32 _instructionId); + + function nextPublicExtensionId() external view returns (uint256); + + function getTeeExtensionInstructionsSender(uint256 _extensionId) + external view returns (address); +} diff --git a/contracts/interfaces/ITeeMachineRegistry.sol b/contracts/interfaces/ITeeMachineRegistry.sol new file mode 100644 index 0000000..8333d79 --- /dev/null +++ b/contracts/interfaces/ITeeMachineRegistry.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.6 <0.9; + +// TODO: Replace this minimal interface with the full import once flare-smart-contracts-v2 +// is published as a package: +// import { ITeeMachineRegistry } from "flare-smart-contracts-v2/contracts/userInterfaces/tee/ITeeMachineRegistry.sol"; +interface ITeeMachineRegistry { + function getRandomTeeIds(uint256 _extensionId, uint256 _count) + external view returns (address[] memory); +} diff --git a/contracts/test/ConcordFlow.t.sol b/contracts/test/ConcordFlow.t.sol new file mode 100644 index 0000000..7174a18 --- /dev/null +++ b/contracts/test/ConcordFlow.t.sol @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {AccordRegistry} from "../AccordRegistry.sol"; +import {CapitalFacility} from "../CapitalFacility.sol"; +import {ConcordTypes} from "../ConcordTypes.sol"; +import {MockERC20} from "./MockERC20.sol"; +import {ReentrantERC20} from "./ReentrantERC20.sol"; + +interface Vm { + function warp(uint256 timestamp) external; +} + +contract ConcordActor { + function approve(MockERC20 token, address spender, uint256 amount) external { + require(token.approve(spender, amount), "approve failed"); + } + + function createRoot( + CapitalFacility facility, + bytes32 rootId, + uint256 target, + uint64 validUntil, + bytes32 policyHash + ) external { + facility.createRootAccord(rootId, target, validUntil, policyHash); + } + + function lock(CapitalFacility facility, bytes32 rootId, uint256 amount) external { + facility.lockCollateral(rootId, amount); + } + + function open( + CapitalFacility facility, + bytes32 rootId, + bytes32 roundId, + uint32 maxFeeBps, + uint64 roundExpiry, + address[] calldata providers + ) external { + facility.openSyndication(rootId, roundId, maxFeeBps, roundExpiry, providers); + } + + function fund(CapitalFacility facility, bytes32 childId, uint256 amount) external { + facility.fundChild(childId, amount); + } + + function draw(CapitalFacility facility, bytes32 drawId, bytes32 rootId, uint256 amount) external { + facility.draw(drawId, rootId, amount); + } + + function repay(CapitalFacility facility, bytes32 drawId, uint256 amount) external { + facility.repay(drawId, amount); + } + + function closeChild(CapitalFacility facility, bytes32 childId) external { + facility.closeChild(childId); + } + + function expireChild(CapitalFacility facility, bytes32 childId) external { + facility.expireChild(childId); + } + + function closeRoot(CapitalFacility facility, bytes32 rootId) external { + facility.closeRoot(rootId); + } + + function mark(CapitalFacility facility, bytes32 digest) external { + facility.markAllocationVerified(digest); + } + + function materialize(CapitalFacility facility, ConcordTypes.AllocationResult calldata result) external { + facility.materializeAllocation(result); + } +} + +contract ConcordFlowTest { + bytes32 private constant EXTENSION_ID = keccak256("CONCORD_FCC_EXTENSION"); + bytes32 private constant ROOT_ID = keccak256("ROOT-01"); + bytes32 private constant ROUND_ID = keccak256("ROUND-01"); + bytes32 private constant ALLOCATION_ID = keccak256("ALLOCATION-01"); + bytes32 private constant DRAW_ID = keccak256("DRAW-01"); + Vm private constant VM = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + MockERC20 private fxrp; + MockERC20 private usdt0; + AccordRegistry private registry; + CapitalFacility private facility; + ConcordActor private treasury; + ConcordActor private providerA; + ConcordActor private providerB; + ConcordActor private providerC; + + function setUp() public { + fxrp = new MockERC20("Flare XRP", "FXRP", 6); + usdt0 = new MockERC20("Tether USD0", "USDT0", 18); + registry = new AccordRegistry(address(this)); + treasury = new ConcordActor(); + providerA = new ConcordActor(); + providerB = new ConcordActor(); + providerC = new ConcordActor(); + facility = new CapitalFacility( + registry, + address(fxrp), + address(usdt0), + address(providerC), + EXTENSION_ID + ); + registry.setFacility(address(facility)); + + fxrp.mint(address(treasury), 100); + usdt0.mint(address(providerA), 600); + usdt0.mint(address(providerB), 400); + treasury.approve(fxrp, address(facility), type(uint256).max); + providerA.approve(usdt0, address(facility), type(uint256).max); + providerB.approve(usdt0, address(facility), type(uint256).max); + } + + function testFullRelationshipLifecycle() public { + uint64 expiry = uint64(block.timestamp + 30 days); + treasury.createRoot(facility, ROOT_ID, 1_000, expiry, keccak256("POLICY")); + treasury.lock(facility, ROOT_ID, 100); + + address[] memory providers = new address[](3); + providers[0] = address(providerA); + providers[1] = address(providerB); + providers[2] = address(providerC); + treasury.open(facility, ROOT_ID, ROUND_ID, 700, expiry - 1 days, providers); + + address[] memory selected = new address[](2); + selected[0] = address(providerA); + selected[1] = address(providerB); + uint256[] memory allocations = new uint256[](2); + allocations[0] = 600; + allocations[1] = 400; + uint32[] memory fees = new uint32[](2); + fees[0] = 610; + fees[1] = 640; + bytes32[] memory terms = new bytes32[](2); + terms[0] = keccak256("A-TERMS"); + terms[1] = keccak256("B-TERMS"); + + ConcordTypes.AllocationResult memory result = ConcordTypes.AllocationResult({ + extensionId: EXTENSION_ID, + roundId: ROUND_ID, + rootAccordId: ROOT_ID, + success: true, + selectedProviders: selected, + allocatedCapacity: allocations, + acceptedFeeBps: fees, + termsCommitments: terms, + roundExpiry: expiry - 1 days, + resultDigest: bytes32(0) + }); + result.resultDigest = allocationDigest(result); + + // Only the configured verifier may authorize materialization. + providerC.mark(facility, result.resultDigest); + (bool unauthorizedMaterializeOk,) = address(providerA).call( + abi.encodeWithSelector(ConcordActor.materialize.selector, facility, result) + ); + require(!unauthorizedMaterializeOk, "provider materialized allocation"); + treasury.materialize(facility, result); + + bytes32 childA = keccak256(abi.encode(ROOT_ID, result.resultDigest, address(providerA))); + bytes32 childB = keccak256(abi.encode(ROOT_ID, result.resultDigest, address(providerB))); + require(facility.getChild(childA).state == ConcordTypes.ChildState.SELECTED, "A not selected"); + require(facility.getChild(childB).state == ConcordTypes.ChildState.SELECTED, "B not selected"); + + providerA.fund(facility, childA, 600); + providerB.fund(facility, childB, 400); + require(facility.getRoot(ROOT_ID).state == ConcordTypes.RootState.ACTIVE, "root not active"); + require(facility.availableCapacity(ROOT_ID) == 1_000, "available mismatch after funding"); + + treasury.draw(facility, DRAW_ID, ROOT_ID, 750); + require(facility.getRoot(ROOT_ID).drawnPrincipal == 750, "root exposure mismatch"); + require(facility.getChild(childA).drawnPrincipal == 600, "A exposure mismatch"); + require(facility.getChild(childB).drawnPrincipal == 150, "B exposure mismatch"); + require(facility.getDrawLegIds(DRAW_ID).length == 2, "draw was not split"); + + (bool overdrawOk,) = address(treasury).call( + abi.encodeWithSelector(ConcordActor.draw.selector, facility, keccak256("DRAW-02"), ROOT_ID, 251) + ); + require(!overdrawOk, "draw exceeded available capacity"); + (bool exposedCloseOk,) = address(providerA).call( + abi.encodeWithSelector(ConcordActor.closeChild.selector, facility, childA) + ); + require(!exposedCloseOk, "provider closed a child with exposure"); + + usdt0.mint(address(treasury), 750); + treasury.approve(usdt0, address(facility), type(uint256).max); + treasury.repay(facility, DRAW_ID, 100); + require(facility.getRoot(ROOT_ID).drawnPrincipal == 650, "partial root repayment mismatch"); + require(facility.getChild(childA).drawnPrincipal == 500, "partial A repayment mismatch"); + require(facility.getChild(childB).drawnPrincipal == 150, "partial B repayment mismatch"); + + treasury.repay(facility, DRAW_ID, 650); + require(facility.getRoot(ROOT_ID).drawnPrincipal == 0, "root exposure not repaid"); + require(facility.getChild(childA).drawnPrincipal == 0, "A exposure not repaid"); + require(facility.getChild(childB).drawnPrincipal == 0, "B exposure not repaid"); + require(facility.availableCapacity(ROOT_ID) == 1_000, "capacity not restored"); + + providerA.closeChild(facility, childA); + providerB.closeChild(facility, childB); + treasury.closeRoot(facility, ROOT_ID); + require(facility.getRoot(ROOT_ID).state == ConcordTypes.RootState.CLOSED, "root not closed"); + require(fxrp.balanceOf(address(treasury)) == 100, "collateral not returned"); + } + + function testPartialFundingLineageAndAllocationReplay() public { + uint64 expiry = uint64(block.timestamp + 30 days); + treasury.createRoot(facility, ROOT_ID, 1_000, expiry, keccak256("POLICY")); + treasury.lock(facility, ROOT_ID, 100); + + address[] memory providers = new address[](2); + providers[0] = address(providerA); + providers[1] = address(providerB); + treasury.open(facility, ROOT_ID, ROUND_ID, 700, expiry - 1 days, providers); + + address[] memory selected = new address[](2); + selected[0] = address(providerA); + selected[1] = address(providerB); + uint256[] memory allocations = new uint256[](2); + allocations[0] = 600; + allocations[1] = 400; + uint32[] memory fees = new uint32[](2); + fees[0] = 610; + fees[1] = 640; + bytes32[] memory terms = new bytes32[](2); + terms[0] = keccak256("A-TERMS"); + terms[1] = keccak256("B-TERMS"); + + ConcordTypes.AllocationResult memory result = ConcordTypes.AllocationResult({ + extensionId: EXTENSION_ID, + roundId: ROUND_ID, + rootAccordId: ROOT_ID, + success: true, + selectedProviders: selected, + allocatedCapacity: allocations, + acceptedFeeBps: fees, + termsCommitments: terms, + roundExpiry: expiry - 1 days, + resultDigest: bytes32(0) + }); + result.resultDigest = allocationDigest(result); + providerC.mark(facility, result.resultDigest); + treasury.materialize(facility, result); + + bytes32 childA = keccak256(abi.encode(ROOT_ID, result.resultDigest, address(providerA))); + bytes32 childB = keccak256(abi.encode(ROOT_ID, result.resultDigest, address(providerB))); + + providerA.fund(facility, childA, 300); + require(facility.getChild(childA).state == ConcordTypes.ChildState.FUNDED, "partial funding not recorded"); + require(facility.getChild(childA).committedCapacity == 300, "partial commitment mismatch"); + require(facility.getRoot(ROOT_ID).state == ConcordTypes.RootState.FUNDING, "root activated too early"); + require(facility.getRoot(ROOT_ID).committedCapacity == 300, "root partial commitment mismatch"); + + providerA.fund(facility, childA, 300); + providerB.fund(facility, childB, 400); + require(facility.getRoot(ROOT_ID).state == ConcordTypes.RootState.ACTIVE, "root did not activate"); + require(facility.getRoot(ROOT_ID).committedCapacity == 1_000, "root commitment sum mismatch"); + + bytes32[] memory rootChildren = registry.getChildren(ROOT_ID); + require(rootChildren.length == 1 && rootChildren[0] == ROUND_ID, "root lineage mismatch"); + bytes32[] memory roundChildren = registry.getChildren(ROUND_ID); + require(roundChildren.length == 1 && roundChildren[0] == result.resultDigest, "round lineage mismatch"); + bytes32[] memory allocationChildren = registry.getChildren(result.resultDigest); + require(allocationChildren.length == 2, "child Accord lineage missing"); + require( + facility.getChild(childA).validUntil <= facility.getRoot(ROOT_ID).validUntil && + facility.getChild(childB).validUntil <= facility.getRoot(ROOT_ID).validUntil, + "child validity exceeded root" + ); + + (bool replayOk,) = address(treasury).call( + abi.encodeWithSelector(ConcordActor.materialize.selector, facility, result) + ); + require(!replayOk, "allocation replay succeeded"); + } + + function testUnauthorizedResultAndOverfundingFail() public { + uint64 expiry = uint64(block.timestamp + 30 days); + treasury.createRoot(facility, ROOT_ID, 1_000, expiry, keccak256("POLICY")); + treasury.lock(facility, ROOT_ID, 100); + address[] memory providers = new address[](1); + providers[0] = address(providerA); + treasury.open(facility, ROOT_ID, ROUND_ID, 700, expiry - 1 days, providers); + + address[] memory selected = new address[](1); + selected[0] = address(providerA); + uint256[] memory allocations = new uint256[](1); + allocations[0] = 1_000; + uint32[] memory fees = new uint32[](1); + fees[0] = 700; + bytes32[] memory terms = new bytes32[](1); + terms[0] = keccak256("A-TERMS"); + ConcordTypes.AllocationResult memory result = ConcordTypes.AllocationResult({ + extensionId: EXTENSION_ID, + roundId: ROUND_ID, + rootAccordId: ROOT_ID, + success: true, + selectedProviders: selected, + allocatedCapacity: allocations, + acceptedFeeBps: fees, + termsCommitments: terms, + roundExpiry: expiry - 1 days, + resultDigest: bytes32(0) + }); + result.resultDigest = allocationDigest(result); + + (bool unauthorizedOk,) = address(treasury).call( + abi.encodeWithSelector(ConcordActor.materialize.selector, facility, result) + ); + require(!unauthorizedOk, "unverified materialization succeeded"); + + providerC.mark(facility, result.resultDigest); + // The provider list is eligible but the allocation is insufficiently + // funded for the requested target; it remains a selected child. + treasury.materialize(facility, result); + bytes32 childA = keccak256(abi.encode(ROOT_ID, result.resultDigest, address(providerA))); + usdt0.mint(address(providerA), 400); + providerA.fund(facility, childA, 1_000); + (bool overfundOk,) = address(providerA).call( + abi.encodeWithSelector(ConcordActor.fund.selector, facility, childA, 1) + ); + require(!overfundOk, "overfunding succeeded"); + } + + + function testExpiredActiveChildDemotesRootToFunding() public { + uint64 rootExpiry = uint64(block.timestamp + 30 days); + uint64 roundExpiry = uint64(block.timestamp + 1 days); + treasury.createRoot(facility, ROOT_ID, 1_000, rootExpiry, keccak256("POLICY")); + treasury.lock(facility, ROOT_ID, 100); + + address[] memory providers = new address[](2); + providers[0] = address(providerA); + providers[1] = address(providerB); + treasury.open(facility, ROOT_ID, ROUND_ID, 700, roundExpiry, providers); + + address[] memory selected = new address[](2); + selected[0] = address(providerA); + selected[1] = address(providerB); + uint256[] memory allocations = new uint256[](2); + allocations[0] = 600; + allocations[1] = 400; + uint32[] memory fees = new uint32[](2); + fees[0] = 610; + fees[1] = 640; + bytes32[] memory terms = new bytes32[](2); + terms[0] = keccak256("A-TERMS"); + terms[1] = keccak256("B-TERMS"); + + ConcordTypes.AllocationResult memory result = ConcordTypes.AllocationResult({ + extensionId: EXTENSION_ID, + roundId: ROUND_ID, + rootAccordId: ROOT_ID, + success: true, + selectedProviders: selected, + allocatedCapacity: allocations, + acceptedFeeBps: fees, + termsCommitments: terms, + roundExpiry: roundExpiry, + resultDigest: bytes32(0) + }); + result.resultDigest = allocationDigest(result); + + providerC.mark(facility, result.resultDigest); + treasury.materialize(facility, result); + bytes32 childA = keccak256(abi.encode(ROOT_ID, result.resultDigest, address(providerA))); + bytes32 childB = keccak256(abi.encode(ROOT_ID, result.resultDigest, address(providerB))); + + providerA.fund(facility, childA, 600); + providerB.fund(facility, childB, 400); + require(facility.getRoot(ROOT_ID).state == ConcordTypes.RootState.ACTIVE, "root not active"); + + VM.warp(uint256(roundExpiry) + 1); + providerA.expireChild(facility, childA); + + require(facility.getChild(childA).state == ConcordTypes.ChildState.EXPIRED, "child not expired"); + require(facility.getChild(childA).committedCapacity == 0, "expired child commitment remains"); + require(facility.getRoot(ROOT_ID).committedCapacity == 400, "root commitment not reduced"); + require(facility.getRoot(ROOT_ID).state == ConcordTypes.RootState.FUNDING, "active root was not demoted"); + } + + function testMaliciousERC20ReentryIsBlocked() public { + ReentrantERC20 malicious = new ReentrantERC20(); + AccordRegistry guardedRegistry = new AccordRegistry(address(this)); + CapitalFacility guardedFacility = new CapitalFacility( + guardedRegistry, + address(fxrp), + address(malicious), + address(providerC), + EXTENSION_ID + ); + guardedRegistry.setFacility(address(guardedFacility)); + + treasury.approve(fxrp, address(guardedFacility), type(uint256).max); + uint64 rootExpiry = uint64(block.timestamp + 30 days); + uint64 roundExpiry = uint64(block.timestamp + 1 days); + treasury.createRoot(guardedFacility, ROOT_ID, 100, rootExpiry, keccak256("POLICY")); + treasury.lock(guardedFacility, ROOT_ID, 100); + + address[] memory providers = new address[](1); + providers[0] = address(malicious); + treasury.open(guardedFacility, ROOT_ID, ROUND_ID, 700, roundExpiry, providers); + + address[] memory selected = new address[](1); + selected[0] = address(malicious); + uint256[] memory allocations = new uint256[](1); + allocations[0] = 100; + uint32[] memory fees = new uint32[](1); + fees[0] = 700; + bytes32[] memory terms = new bytes32[](1); + terms[0] = keccak256("MALICIOUS-TERMS"); + ConcordTypes.AllocationResult memory result = ConcordTypes.AllocationResult({ + extensionId: EXTENSION_ID, + roundId: ROUND_ID, + rootAccordId: ROOT_ID, + success: true, + selectedProviders: selected, + allocatedCapacity: allocations, + acceptedFeeBps: fees, + termsCommitments: terms, + roundExpiry: roundExpiry, + resultDigest: bytes32(0) + }); + result.resultDigest = allocationDigest(result); + + providerC.mark(guardedFacility, result.resultDigest); + treasury.materialize(guardedFacility, result); + bytes32 childId = keccak256(abi.encode(ROOT_ID, result.resultDigest, address(malicious))); + + malicious.mintSelf(100); + malicious.approveSelf(address(guardedFacility), 100); + malicious.configureReentry(address(guardedFacility), childId); + malicious.attackFund(address(guardedFacility), childId, 100); + + require(malicious.reentryBlocked(), "reentrant callback was not blocked"); + require(guardedFacility.getRoot(ROOT_ID).committedCapacity == 100, "funding was not recorded"); + require(guardedFacility.getRoot(ROOT_ID).state == ConcordTypes.RootState.ACTIVE, "root did not activate"); + } + function allocationDigest(ConcordTypes.AllocationResult memory result) internal pure returns (bytes32) { + bytes memory encoded = abi.encodePacked( + result.extensionId, + result.roundId, + result.rootAccordId, + result.success, + result.roundExpiry + ); + for (uint256 i; i < result.selectedProviders.length; ++i) { + encoded = bytes.concat( + encoded, + abi.encodePacked( + result.selectedProviders[i], + result.allocatedCapacity[i], + result.acceptedFeeBps[i], + result.termsCommitments[i] + ) + ); + } + return keccak256(encoded); + } +} diff --git a/contracts/test/MockERC20.sol b/contracts/test/MockERC20.sol new file mode 100644 index 0000000..b74aa8b --- /dev/null +++ b/contracts/test/MockERC20.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +contract MockERC20 { + string public name; + string public symbol; + uint8 public immutable decimals; + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + constructor(string memory name_, string memory symbol_, uint8 decimals_) { + name = name_; + symbol = symbol_; + decimals = decimals_; + } + + function mint(address to, uint256 amount) external { + balanceOf[to] += amount; + totalSupply += amount; + } + + function approve(address spender, uint256 amount) external returns (bool) { + allowance[msg.sender][spender] = amount; + return true; + } + + function transfer(address to, uint256 amount) external returns (bool) { + _move(msg.sender, to, amount); + return true; + } + + function transferFrom(address from, address to, uint256 amount) external returns (bool) { + uint256 allowed = allowance[from][msg.sender]; + require(allowed >= amount, "allowance"); + allowance[from][msg.sender] = allowed - amount; + _move(from, to, amount); + return true; + } + + function _move(address from, address to, uint256 amount) internal { + require(balanceOf[from] >= amount, "balance"); + balanceOf[from] -= amount; + balanceOf[to] += amount; + } +} diff --git a/contracts/test/ReentrantERC20.sol b/contracts/test/ReentrantERC20.sol new file mode 100644 index 0000000..570c41d --- /dev/null +++ b/contracts/test/ReentrantERC20.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +interface IReentrantFacility { + function fundChild(bytes32 childId, uint256 amount) external; +} + +/// @notice Test-only ERC-20 that attempts a callback during transferFrom. +/// The callback is swallowed so the outer token transfer can complete; the +/// test can then assert that CapitalFacility's nonReentrant guard blocked it. +contract ReentrantERC20 { + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + address public callbackTarget; + bytes32 public callbackChild; + bool public callbackEnabled; + bool public reentryBlocked; + + function mintSelf(uint256 amount) external { + balanceOf[address(this)] += amount; + } + + function approveSelf(address spender, uint256 amount) external { + allowance[address(this)][spender] = amount; + } + + function configureReentry(address target, bytes32 childId) external { + callbackTarget = target; + callbackChild = childId; + callbackEnabled = true; + reentryBlocked = false; + } + + function attackFund(address facility, bytes32 childId, uint256 amount) external { + IReentrantFacility(facility).fundChild(childId, amount); + } + + function transfer(address to, uint256 amount) external returns (bool) { + _move(msg.sender, to, amount); + return true; + } + + function transferFrom(address from, address to, uint256 amount) external returns (bool) { + uint256 allowed = allowance[from][msg.sender]; + require(allowed >= amount, "allowance"); + allowance[from][msg.sender] = allowed - amount; + + if (callbackEnabled && msg.sender == callbackTarget) { + callbackEnabled = false; + (bool ok,) = callbackTarget.call( + abi.encodeWithSelector(IReentrantFacility.fundChild.selector, callbackChild, 1) + ); + reentryBlocked = !ok; + } + + _move(from, to, amount); + return true; + } + + function _move(address from, address to, uint256 amount) internal { + require(balanceOf[from] >= amount, "balance"); + balanceOf[from] -= amount; + balanceOf[to] += amount; + } +} \ No newline at end of file diff --git a/docker-compose.cloudflared.yaml b/docker-compose.cloudflared.yaml new file mode 100644 index 0000000..911a145 --- /dev/null +++ b/docker-compose.cloudflared.yaml @@ -0,0 +1,13 @@ +# cloudflared replacement for ngrok on the external ext-proxy port. +# start-services.sh / stop-services.sh drive this for you on coston/coston2. +name: tunnel +services: + cloudflared: + image: cloudflare/cloudflared:latest + # Project name + `command:` must match every extension's copy — that is what + # makes them reuse one container instead of minting a new URL each time. + command: tunnel --no-autoupdate ${TUNNEL_ARGS:---url ${TUNNEL_TARGET:-http://host.docker.internal:6674}} + extra_hosts: + - "host.docker.internal:host-gateway" + # No restart policy: a quick-tunnel restart mints a new URL and strands + # EXT_PROXY_URL. Named tunnel (TUNNEL_ARGS) is stable — see docs/cloudflared.md. diff --git a/docker-compose.coston2.yaml b/docker-compose.coston2.yaml new file mode 100644 index 0000000..5d6fecc --- /dev/null +++ b/docker-compose.coston2.yaml @@ -0,0 +1,19 @@ +# Docker Compose override for Coston2 deployment. +# Usage: docker compose -f docker-compose.yaml -f docker-compose.coston2.yaml up +services: + ext-proxy: + volumes: + - ./config/proxy/extension_proxy.coston2.docker.toml:/app/config/config.toml:ro + + extension-tee: + environment: + - CHAIN_URL=${CHAIN_URL:-https://coston2-api.flare.network/ext/C/rpc} + # Coston2 chain id. The node binds every signature to this value; it must + # match the proxy's chain_id (114) and the on-chain registry, or signature + # verification fails (InvalidTeePublicKeyOrSignature / proxy panic). + - CHAIN_ID=${CHAIN_ID:-114} + +networks: + default: + name: ${COMPOSE_NETWORK:-extension-scaffold-coston2} + external: false diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..3d4ab34 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,62 @@ +services: + redis: + image: redis:7-alpine + command: redis-server --save "" --appendonly no + ports: + - "${REDIS_BIND:-127.0.0.1:6382}:6379" + tmpfs: + - /data + + ext-proxy: + image: ${REGISTRY:-local}/tee-proxy + volumes: + - ./config/proxy/extension_proxy.docker.toml:/app/config/config.toml:ro + environment: + - PROXY_PRIVATE_KEY=${PROXY_PRIVATE_KEY:?PROXY_PRIVATE_KEY must be set in .env} + extra_hosts: + - "host.docker.internal:host-gateway" + ports: + - "${EXT_PROXY_INTERNAL_BIND:-0.0.0.0:6673}:6663" + - "${EXT_PROXY_EXTERNAL_BIND:-0.0.0.0:6674}:6664" + depends_on: + - redis + + extension-tee: + build: + context: . + # Selected by LANGUAGE in .env; start-services.sh resolves /Dockerfile + # by directory convention. Adding a language needs no change here. + dockerfile: ${EXTENSION_DOCKERFILE:-go/Dockerfile} + args: + SOURCE_DATE_EPOCH: ${SOURCE_DATE_EPOCH:-0} + # Consumed by language images that build tee-node from source. A Go + # pseudo-version pin is a commit SHA, not a tag — see scripts/lib/versions.sh. + TEE_NODE_REF: ${TEE_NODE_REF:-} + env_file: + - path: ./config/extension.env + required: false + environment: + - MODE=${MODE:-1} + # Chain id the node binds signatures to — must match the proxy's chain_id + # and the chain itself. Local e2e Hardhat = 31337; the coston/coston2 + # overrides set 16/114 for those networks. + - CHAIN_ID=${CHAIN_ID:-31337} + - INITIAL_OWNER=${INITIAL_OWNER:?INITIAL_OWNER must be set in .env} + # TEE governance the node signs with. Must match what set-governance + # registers on-chain (post-build reads the same vars). Defaults to the + # deployer (INITIAL_OWNER) as sole signer, threshold 1; override in .env. + - GOVERNANCE_SIGNERS=${GOVERNANCE_SIGNERS:?GOVERNANCE_SIGNERS must be set in .env} + - GOVERNANCE_THRESHOLD=${GOVERNANCE_THRESHOLD:-1} + - PROXY_URL=http://ext-proxy:6663 + - CONFIG_PORT=5501 + - SIGN_PORT=7701 + - EXTENSION_PORT=7702 + - LOG_LEVEL=${LOG_LEVEL:-INFO} + - SIMULATED_TEE=${SIMULATED_TEE:-true} + depends_on: + - ext-proxy + +networks: + default: + name: ${COMPOSE_NETWORK:-docker_default} + external: true diff --git a/docker/node-base.Dockerfile b/docker/node-base.Dockerfile new file mode 100644 index 0000000..272a10d --- /dev/null +++ b/docker/node-base.Dockerfile @@ -0,0 +1,90 @@ +# Shared tee-node builder — produces the `server` binary that non-Go language +# images run alongside their extension process. +# +# Built once as local/tee-node-base:${TEE_NODE_REF} and consumed by every +# two-process language image, so a new language's Dockerfile is ~30 lines rather +# than ~110. The Go image does not use this: it links tee-node as a library and +# ships a single binary (see go/Dockerfile and docs/extension-contract.md §1). +# +# Build directly: +# docker build -f docker/node-base.Dockerfile --build-arg TEE_NODE_REF= -t local/tee-node-base: docker/ +# +# scripts/start-services.sh builds this automatically when the selected language +# needs it, tagging by TEE_NODE_REF so a pin bump invalidates the cache naturally. + +# Same digest-pinned golang image as go/Dockerfile, so the server binary bytes +# are identical no matter which language image embeds it. +FROM golang:1.25.1-trixie@sha256:ff83f3762390c2cccb53618ccc18af23e556aff9b1db4428637e9f63287c8171 AS builder + +ARG SOURCE_DATE_EPOCH +ENV SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH + +# Git-resolvable tee-node ref, derived from the Go module pin by +# scripts/lib/versions.sh. Required — there is no default, because a stale +# default is exactly the drift this image exists to prevent. +ARG TEE_NODE_REF + +WORKDIR /build + +# apt normally resolves to whatever package versions the mirror serves at build time. +# Redirect apt at snapshot.debian.org keyed on SOURCE_DATE_EPOCH so every build +# installs the exact package set that existed at that instant. +# NOTE: adapted from https://github.com/reproducible-containers/repro-sources-list.sh/blob/master/alternative/Dockerfile.debian-13 +RUN \ + --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + if [ "${SOURCE_DATE_EPOCH:-0}" -gt 0 ] 2>/dev/null; then \ + snapshot="$(/bin/bash -euc "printf \"%(%Y%m%dT%H%M%SZ)T\n\" \"${SOURCE_DATE_EPOCH}\"")" && \ + sed -i -e '/Types: deb/ a\Snapshot: true' /etc/apt/sources.list.d/debian.sources && \ + rm -f /etc/apt/apt.conf.d/docker-clean && \ + echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache && \ + apt-get install --update --snapshot "${snapshot}" -o Acquire::Check-Valid-Until=false -o Acquire::https::Verify-Peer=false -y ca-certificates && \ + rm -rf /var/log/* /var/cache/ldconfig/aux-cache; \ + else \ + apt-get update && apt-get install -y ca-certificates && \ + rm -rf /var/log/* /var/cache/ldconfig/aux-cache; \ + fi + +# Fetch tee-node at exactly the pinned ref. +# +# Two approaches are deliberately NOT used here: +# +# git clone --branch ${TEE_NODE_REF} +# The Go module pin is frequently a pseudo-version +# (v0.0.21-0.20260619120252-31fc839ae6d2) whose last segment is a commit +# SHA. --branch resolves tags and branches only, so it fails on a SHA. +# +# git fetch --depth 1 origin ${TEE_NODE_REF} +# Fetching a raw commit requires the server to allow it +# (uploadpack.allowAnySHA1InWant) AND a full 40-char SHA. The pin gives an +# abbreviated 12-char SHA, and GitHub rejects it: "couldn't find remote ref". +# +# A blobless partial clone fetches all refs cheaply (deferring file contents), +# after which an abbreviated SHA resolves locally. Works for tags and SHAs alike. +RUN test -n "${TEE_NODE_REF}" || (echo "ERROR: TEE_NODE_REF build arg is required" >&2 && exit 1) +RUN git clone --filter=blob:none https://github.com/flare-foundation/tee-node.git tee-node && \ + cd tee-node && \ + git checkout "${TEE_NODE_REF}" && \ + echo "tee-node at $(git rev-parse HEAD)" + +WORKDIR /build/tee-node + +RUN go mod download +RUN go mod verify + +# -trimpath strips build-host paths; -buildid= clears go's non-deterministic build id; +# -s -w drop symbol and dwarf tables; -buildvcs=false omits embedded vcs metadata; +# CGO_ENABLED=0 produces a static binary, avoiding link-time libc variance. +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOFLAGS="-buildvcs=false" \ + go build -trimpath -ldflags="-buildid= -s -w" -o /app/server ./cmd/extension + +# Carry the Confidential Space root cert alongside the binary — language images +# copy both from this stage. +RUN cp /build/tee-node/assets/google_confidential_space_root.crt /app/google_confidential_space_root.crt + +RUN if [ "${SOURCE_DATE_EPOCH:-0}" -gt 0 ] 2>/dev/null; then find /app -exec touch -h -d @${SOURCE_DATE_EPOCH} {} +; fi + +# Minimal final stage: language images only ever COPY --from this image. +FROM scratch +COPY --from=builder /app/server /app/server +COPY --from=builder /app/google_confidential_space_root.crt /app/assets/google_confidential_space_root.crt diff --git a/docs/CONCORD_STATUS.md b/docs/CONCORD_STATUS.md new file mode 100644 index 0000000..77468f1 --- /dev/null +++ b/docs/CONCORD_STATUS.md @@ -0,0 +1,294 @@ +# Concord status + +Updated 2026-08-14 after read-only verification of the Northflank FCC runtime +and reconciliation of the public repository branch. The complete observed +Coston2 lifecycle remains the canonical economic proof. The hosted runtime is +now reachable as a development checkpoint, but its newly generated simulated +TEE identity is not yet registered on Coston2 and no new Coston2 write was made +in this pass. + +- The fresh source snapshot used for this checkpoint is [source-snapshot-2026-08-14.yaml](source-snapshot-2026-08-14.yaml). It records the current official Coston2 FCC development path and the Northflank/Railway availability decision. +- The deployment-attempt source refresh is [source-snapshot-2026-08-14-deployment.yaml](source-snapshot-2026-08-14-deployment.yaml). It records the current official source revisions, verified development values, and access blockers. +- The follow-up live blocker check is [source-snapshot-2026-08-14-live-blocker.yaml](source-snapshot-2026-08-14-live-blocker.yaml). It is retained as a historical record of the stopped-Codespace attempt and is superseded for current reachability by [current-runtime.md](current-runtime.md). + +## Works in the durable checkpoint + +- AccordRegistry, CapitalFacility, ConcordInstructionSender and shared Concord + types compile with Solidity 0.8.27. +- Foundry flow tests pass locally, including the two-child draw, repayment, + expiry-demotion, and malicious ERC-20 callback lifecycle. +- The Go Concord extension compiles and tests signed quotes and deterministic + CoFill inside the official FCE scaffold structure, including exact zero-fee + round bounds. +- `tools/cmd/run-test` preserves the complete signed FCC action response and + signed TEE-info envelope instead of reducing the result to unverified JSON. +- `tools/cmd/verify-allocation` independently checks the action signature, + proxy-signed TEE identity, chain/extension binding, facility round/digest, + and—before `-mark`—the active TEE machine for the extension. +- Deployment tooling generates a binding for ConcordInstructionSender and has + no embedded private-key fallback. +- Coston2 asset resolution is centralized in config/networks/coston2.json and + validated by scripts/resolve-coston2-assets.sh. +- REST/OpenAPI, the TypeScript SDK, MCP, and CLI share unsigned lifecycle intent + vocabulary for round opening and relationship close/expiry; allocation + materialization remains verifier-gated. +- `scripts/coston2-preflight.sh` provides a read-only offline/live operator gate, + and the adversarial cases are recorded in + `docs/experiments/coston2-failure-matrix.md`. +- Hosted `/info` consumers now read the official nested + `machineData.extensionId`, `machineData.codeHash`, `machineData.platform`, + `teeInfo.publicKey`, and `teeInfo.chainId` fields. The read-only registry + query derives `FlareTeeManager` from `config/coston2/deployed-addresses.json` + unless an explicit override is supplied. +- The React/Vite frontend now implements the outcome-led institutional landing page, + facility register, Root Accord workspace, funding, activity, evidence, + lineage, child, and draw views from the recorded Coston2 deployment and the + shared TypeScript SDK types. Injected-wallet connection, Coston2 switching, + native balance reads, the official faucet link, live capacity reads, local + draw-intent generation, explicit wallet submission, and receipt tracking are live. Its build and + ten semantic, invariant, learning, and wayfinding tests pass locally. The + shell now exposes a single product-level destination, stable facility tabs, + plain parent breadcrumbs, canonical terms with in-place definitions, and an + optional first-use tour plus persistent glossary. Forced Back/Next journey + controls and duplicate mobile navigation were removed. +- `docs/DEMO_AND_SUBMISSION.md` provides a four-minute evidence-led demo, + exact Coston2 receipt ledger, submission copy, and recording/claim gates. +- `infra/northflank/`, `infra/railway/`, and + `docs/fcc-always-on-hosting.md` package the official two-service plus Redis + FCC topology. Northflank is the primary continuously running hackathon + development host; paid Railway is the fallback. The Northflank proxy, the + simulated TEE, and private Redis are currently deployed, but no new hosted + machine has been registered yet. + +## Current hosted runtime + +The latest hosted-runtime checkpoint is [current-runtime.md](current-runtime.md). +The following is the compact claim boundary for it: + +- Northflank project `concord` is running in `europe-west` with + `concord-fcc-proxy`, `concord-fcc-tee`, and private Redis `concord-redis`. + Both services have completed builds/deployments and Redis is running. +- The proxy keeps `/healthy` private on port `6663` and exposes FCC traffic on + public port `6664`. The Cloudflare Worker + `https://concord-fcc-ingress.microcosm.workers.dev` relays to that + Northflank public port. +- Direct and relayed `/info` checks return HTTP `200`, extension `66188`, and + Coston2 chain ID `114`, with matching code/governance/platform bindings and a + proxy signature. +- The current simulated identity is + `0xb1e7a4c1930f1f3c4905b34fafc9c1b8359029a5`. It is ephemeral and has not + been registered or availability-checked onchain. +- Read-only Systems Explorer inspection found the pre-existing records + `0x65721B35EAF2648Fd061aB6901e1355ec2eCffd2` (`INITIALIZED`) and + `0xeE39d5e7d1C5043232282e3CC884B41a9Db22c85` (`PRODUCTION`) for the same + owner/extension. The latter is the historical active machine; it has not + been paused. +- No registration, availability, pause, dispatch, or other Coston2 broadcast + was made for the current Northflank identity. Those actions remain + confirmation-gated. + +The Vercel frontend still has no public deployment URL. Its existing build and +tests remain documented, but frontend cleanup/deployment is deliberately a +separate next pass. + +## Observed Coston2 deployment + +The following values were read from the live Coston2 RPC and explorer on +2026-08-13 and are recorded in +[config/coston2/concord-deployment.json](../config/coston2/concord-deployment.json). + +- `ConcordInstructionSender` deployed at `0x574b523eA944EFe9143AF9d6c46bfA925beE2968`; transaction + [0x85e605…77612](https://coston2-explorer.flare.network/tx/0x85e6056f410be261830f5e4e8e35b172886b915ea977ad0fba46953c19577612). +- FCC extension `0x…1028c` (decimal `66188`) registered against that sender; + registration and allowlist/key-type transactions succeeded. The primary + registration receipt is + [0x20e1bf…16ca](https://coston2-explorer.flare.network/tx/0x20e1bf7f6e14d93fd5c06bd35b2d3eda93e711b46dff0ecf2433da121dac16ca). +- The earlier facility pair was deployed successfully, but it is bound + immutably to a different 18-decimal ERC-20 deployment and is now retained + only as historical evidence. It must not be used for the current faucet + balance or the canonical flow. +- The replacement pair is now deployed with the current Coston2 `USDT0 test` + token: `AccordRegistry 0x68b19a3967760489b57341669cd7ea960b5f7367` and + `CapitalFacility 0xfaff601a18a9fca33378953515aa0f3ef9286ecd`. The deployment + receipts and registry-link transaction are in the evidence file. +- The canonical root is now `ACTIVE` with three `ACTIVE` child Accords, each + funded with `3 USDT0` base units (`3,000,000` at six decimals). The final + observed root state, child lineage, allocation digest, and lifecycle receipts + are recorded in the deployment file. + +## Asset binding correction + +The earlier checkpoint used `0x479854495cefBc8D12B971A3Ec4d18E6dbcE81a3` and +18 decimals. Live Coston2 RPC verification confirms that the current faucet +asset is `USDT0 test` at +`0xC1A5B41512496B80903D1f32d6dEa3a73212E71F`, with on-chain symbol `USD₮0` +and 6 decimals. The [official Coston2 explorer token list](https://coston2-explorer.flare.network/tokens) +is the recorded address source. Because `CapitalFacility.liquidityAsset` is immutable, this +is a deployment contradiction, not a config-only change; the old facility and +its 18-decimal root round are superseded. The replacement facility is now +bound to the current token, and its current root round is recorded below. + +## Historical Coston2 vertical slice evidenced + +- The recorded run used one simulated development TEE machine for extension + `66188` at + status `2` (`PRODUCTION`): tee id + `0xeE39d5e7d1C5043232282e3CC884B41a9Db22c85`. Its registered stable HTTPS + URL is `https://concord-fcc-ingress.microcosm.workers.dev`, a narrow + Cloudflare Workers.dev relay to the disposable Codespace proxy. The setup + workflow obtained the availability proof and verified the on-chain machine + record at that time. Current reachability is tracked separately above and is + not inferred from this historical record. +- The three disposable provider wallets and the treasury now each hold 10 + units of the current six-decimal `USDT0 test` token. Each provider also has + 105 C2FLR after the guarded gas-funding step, so approval and funding + transactions can proceed. +- The replacement facility has the current root Accord and Makkari round: + root `0x6e03af…cfddd`, round `0x732328…76ce`, 1 FXRP locked, and a 9 USDT0 + target at 6 decimals. The round is finalized and the root is now `ACTIVE`; + the creation, FCC, funding, draw, and repayment receipts are in the + deployment file. +- The old facility's live round remains historical only because it uses the + superseded 18-decimal liquidity token. +- Three signed provider quotes entered the confidential flow, CoFill finalized + the deterministic allocation, and the verified result digest is + `0xf17a292655b898d8b00c9794565972ed838cd3de2812f6fa59d877d6963c88ec`. + The signed action evidence, active TEE binding, and recovery materialization + run are linked from the deployment file. +- Child Accords were materialized for providers A, B, and C. Each provider then + transferred its exact `3,000,000` base-unit allocation, moving the root from + `FUNDING` to `ACTIVE` and increasing committed capacity to `9,000,000`. +- The treasury executed draw + `0x85f634b07d8dfd83fe0f0f1a9b34504973e5e2d5e1e3886656bfb072c26f56ec` for + `4,000,000` base units across two DrawLegs, then repaid the draw through a + real USDT0 transfer. Final observed state is root `ACTIVE`, committed + `9,000,000`, drawn `0`, and available `9,000,000`. +- The complete funding/draw/repayment job passed in + [GitHub Actions run 31734593116](https://github.com/etvjay/Concord/actions/runs/31734593116), + with the public evidence artifact at + [9194681063](https://github.com/etvjay/Concord/actions/runs/31734593116/artifacts/9194681063). +- The FCC round and materialization receipts are preserved in the earlier + [FCC run 31733200629](https://github.com/etvjay/Concord/actions/runs/31733200629), + its [signed action artifact 9194157272](https://github.com/etvjay/Concord/actions/runs/31733200629/artifacts/9194157272), + and the [materialization recovery run 31733740564](https://github.com/etvjay/Concord/actions/runs/31733740564). +- The Coston2 indexer configuration is stored as encrypted GitHub Actions + secrets. The recorded FCC registration and instruction path used the stable + Workers.dev relay as a development ingress fallback, not a named Cloudflare + Tunnel or production hardware-TEE claim. + +## Not yet evidenced / intentionally bounded + +- Production hardware-backed TEE execution has not been claimed; the hosted + runtime uses the documented Coston2 simulated development TEE path. +- A named Cloudflare Tunnel or custom-domain ingress has not been provisioned; + the current Workers.dev relay is a development Worker forwarding to + Northflank, not a production ingress claim. +- The current public `/info` binding is evidenced by the credential-free + `scripts/judge-check.sh` path. The optional live registry check remains + intentionally pending until the current simulated identity is deliberately + registered and a fresh availability proof exists. +- The institutional frontend now builds canonical draw calldata locally, + requires the recorded treasury borrower on Coston2, shows the unsigned intent + before a separate wallet-approval action, and waits for a public receipt. + Repayment and the broader lifecycle actions remain available through the + unsigned-intent API/SDK but are not yet surfaced as frontend controls. The + canonical evidence above still comes from the contract/FCC runners and + explorer receipts; browser-submitted transactions are reported separately. +- The Northflank runtime bundle is deployed and reproducible. The fresh hosted + simulated TEE identity must still be registered and the stale machine paused + before it can be presented as the active onchain machine. No endpoint is + called continuously available or used for a new FCC dispatch until those + gates are complete. + +## Privacy truth + +The implementation does not claim private FXRP transfers, private USDT0 +transfers, private EVM execution, or production hardware-backed TEE security. +The FCC development path may use the documented simulated TEE. Provider terms, +losing quotes and allocation computation are intended for the confidential +boundary; accepted capacity, exposure, transfers and lineage are public where +the facility requires them. + +## Current Flare inputs + +- Coston2 chain ID: 114. +- FXRP manager is resolved from ContractRegistry under AssetManagerFXRP. +- FXRP token is resolved from that manager's fAsset(). +- USDT0 is centralized in the network config as the current Coston2 `USDT0 + test` deployment; the resolver checks bytecode, on-chain symbol `USD₮0`, and + 6 decimals. The product-facing alias remains USDT0. + +The authoritative implementation/docs links are kept in the root README. If +the official Flare registry, scaffold or FCC behaviour changes, update the +affected configuration and record the contradiction before continuing. + +## Deployment evidence template + +The observed deployment and root/round values are recorded in +`config/coston2/concord-deployment.json`. Use the template below only for +lifecycle fields that have not yet produced live receipts; do not infer them +from local tests: + +~~~text +Network: +AccordRegistry: +CapitalFacility: +ConcordInstructionSender: +FCC extension id: +Makkari round: +SUBMIT_QUOTE instruction ids: +FINALIZE_ROUND instruction id: +allocation result digest: +root / child / draw / repayment transaction hashes: +Explorer links: +~~~ + +## Durable CI evidence + +- Current handoff commit `155c05ea41262ae391ed3426ada5cb677092203b` passed the + [Concord verification run](https://github.com/etvjay/Concord/actions/runs/31792244127), + including Solidity/Foundry, Go extension and tooling, TypeScript SDK, and + repository/documentation gates. +- The same commit passed the read-only + [Coston2 asset-resolution run](https://github.com/etvjay/Concord/actions/runs/31792244126) + and the manually dispatched + [frontend workflow](https://github.com/etvjay/Concord/actions/runs/31792436520), + including the production frontend Docker build. + +- Commit `039460c65b98eb02ccf577bcd92a02827ebb000e` passed the deterministic + [GitHub Actions verification run](https://github.com/etvjay/Concord/actions/runs/31633719358). +- The passing jobs were Solidity/Foundry lifecycle tests, Go extension and + tooling tests, TypeScript SDK compilation, and repository/documentation gates. +- The Coston2 asset resolver passed on commit `8cc4eff4fe76e08d672c56bbd5021fc9e5270ce6` in the read-only + [Coston2 asset-resolution run](https://github.com/etvjay/Concord/actions/runs/31640930512). +- The resolver artifact recorded ContractRegistry/FXRP resolution and USDT0 metadata checks. This is + asset-resolution evidence only, not contract deployment or FCC lifecycle evidence. +- The same commit passed the complete deterministic + [Concord verification run](https://github.com/etvjay/Concord/actions/runs/31640930525) across Solidity, + Go, TypeScript SDK, and repository/documentation gates. +- Green CI confirms repository-level correctness checks only. It does not prove + deployed contracts, FCC registration, funded provider accounts, settlement, + repayment, or production hardware-backed confidential execution. + +- The hardening commit `35b756b38a6b97b7f63dc08074dd1c4ff8a52ccc` passed the full + [push verification run](https://github.com/etvjay/Concord/actions/runs/31646160590) + and the equivalent [PR verification run](https://github.com/etvjay/Concord/actions/runs/31646164208), + including lifecycle expiry demotion, partial repayment, API/SDK/MCP intent + parity, and the read-only preflight gate. +- The same commit passed the read-only + [Coston2 asset-resolution run](https://github.com/etvjay/Concord/actions/runs/31646160617). +- Commit `e36cea5bde63bde58bbcbb19ede7f43b82e558eb` passed the guarded + [Coston2 FCC setup run](https://github.com/etvjay/Concord/actions/runs/31731326000): + the official scaffold remained running, the Codespace proxy port was made + public, the Worker relay was verified, the simulated TEE availability proof + was obtained, and the machine was promoted to status `2`. +- That setup run proves registration and live FCC ingress. The later FCC + recovery and facility lifecycle runs below extend the evidence to quote + submission, CoFill finalization, provider funding, draw settlement, + repayment, and restored capacity. +- The live FCC round, signed-result verification, Child Accord materialization, + provider funding, two-leg draw, real USDT0 repayment, and restored-capacity + assertions passed in the [FCC recovery run](https://github.com/etvjay/Concord/actions/runs/31733740564) + and the [complete facility lifecycle run](https://github.com/etvjay/Concord/actions/runs/31734593116). + This is Coston2 simulated-development-TEE evidence with public settlement + receipts; it is not production hardware-TEE evidence. diff --git a/docs/DEMO_AND_SUBMISSION.md b/docs/DEMO_AND_SUBMISSION.md new file mode 100644 index 0000000..604559b --- /dev/null +++ b/docs/DEMO_AND_SUBMISSION.md @@ -0,0 +1,210 @@ +# Concord demo and submission handoff + +This runbook presents the verified Concord MVP without making privacy or +deployment claims beyond the evidence. The canonical story is one persistent +capital relationship: + +```text +Root Accord → Makkari → CoFill → Child Accords → funding +→ multi-provider draw → public settlement → repayment → restored capacity +``` + +## One-sentence product explanation + +Concord lets a treasury form one FXRP-backed facility from multiple independent +USDT0 providers, coordinate eligible offers through Flare Confidential Compute, +and preserve explicit onchain lineage for every funded commitment, draw leg, +settlement, and repayment. + +## Four-minute demo + +### 0:00–0:35 — Start with the outcome + +Open the landing page and say: + +> A treasury needs one reusable facility, but its capital may come from several +> independent providers. Concord makes the facility the persistent object and +> keeps every provider commitment and movement attributable. + +Open **Explore the facility record**. Point out that the page is rendering a +recorded Coston2 proof, not invented dashboard data. The frontend is a local or +preview presentation surface until a public host has been independently +verified. + +### 0:35–1:15 — Explain the relationship + +On **Overview**, show: + +- one active Root Accord; +- 1 FXRP locked as collateral; +- 9 USDT0 committed by three providers; +- zero current exposure; and +- 9 USDT0 restored capacity after repayment. + +Use the in-place Root Accord explanation. Do not describe a Root Accord as one +transaction; it is the relationship that persists across the lifecycle. + +### 1:15–1:55 — Show private coordination and public commitments + +Open **Funding**. Explain that Makkari is the bounded FCC funding session and +CoFill is the deterministic allocation. Three signed provider offers were +selected at 610, 640, and 680 bps for 3 USDT0 each. + +State the boundary precisely: losing quotes and private provider constraints are +withheld; accepted commitments, token transfers, exposure, and lineage are +public on Coston2. + +### 1:55–2:35 — Follow the capital + +Open **Activity**, then the 4 USDT0 draw. Show the two explicit Draw Legs: + +- Provider 1 supplied 3 USDT0. +- Provider 2 supplied 1 USDT0. + +Show the repayment entry and return to Overview to connect repayment with the +restored 9 USDT0 capacity. + +### 2:35–3:10 — Show verifiable evidence + +Open **Evidence** and explain: + +- FCC extension 66188 produced the signed allocation result; +- the verifier checked TEE identity/signature and chain, extension, round, root, + and digest binding before materialization; +- the Coston2 run used the documented simulated development TEE path; and +- Concord does not claim private EVM execution, private token transfers, or + production hardware-backed TEE security. + +Open one explorer receipt and one GitHub Actions evidence run from the ledger +below. + +### 3:10–4:00 — Show the action boundary + +Return to Overview and select **Prepare a draw**. + +1. The app reads live available capacity from the canonical CapitalFacility. +2. Connect the recorded treasury borrower and switch to Coston2. +3. Enter an amount and select **Prepare unsigned intent**. +4. Inspect chain 114, contract, Root Accord, amount, Draw ID, native value, + preconditions, warnings, and full calldata. +5. Stop before **Approve in wallet** for a non-mutating demo. + +If a fresh testnet transaction is intentionally required, the treasury must +approve it in the wallet. The presenter should then wait for the Coston2 receipt +and show the explorer link. Never use a provider or unrelated wallet for the +draw. + +## Evidence ledger + +| Claim | Evidence | +| --- | --- | +| Canonical contracts | [AccordRegistry](https://coston2-explorer.flare.network/address/0x68b19a3967760489b57341669cd7ea960b5f7367), [CapitalFacility](https://coston2-explorer.flare.network/address/0xfaff601a18a9fca33378953515aa0f3ef9286ecd) | +| Root created | [Transaction](https://coston2-explorer.flare.network/tx/0xd6e0e48abdf3e7cd3efc336df9d616f10c923ad69d0f6dbcfddbbe79f43cca71) | +| 1 FXRP collateral locked | [Transaction](https://coston2-explorer.flare.network/tx/0x618097ee2b6ab99e6eee868c596876e9cadf9b409335f7741d6fab8acbc77297) | +| Makkari round opened | [Transaction](https://coston2-explorer.flare.network/tx/0x393174832b260cdd34a91a3df24f23066ac3672a64f1b0bcf25468236b7314c8) | +| FCC result verified | [Verification transaction](https://coston2-explorer.flare.network/tx/0xb77400b70e8d54157fdb455b47797d5c583a387968afeb699319fe13fae95544), [FCC run](https://github.com/etvjay/Concord/actions/runs/31733200629) | +| Three Child Accords materialized | [Transaction](https://coston2-explorer.flare.network/tx/0x4cdd56ca3a80f83b6333e4e0434d4f202fcb9cf5ad5dd3ced820a53779a7559a) | +| 9 USDT0 funded | [Final provider funding transaction](https://coston2-explorer.flare.network/tx/0xfbb3fa72ab3294807697a6aa973247c786f85021f30d41568f2300dc27b0be0c) | +| 4 USDT0, two-leg draw | [Transaction](https://coston2-explorer.flare.network/tx/0xea0348757169428b73dbbc20eca7f2faf267c29941e5f0c0291bc3923b12cbe5) | +| Principal repaid; capacity restored | [Transaction](https://coston2-explorer.flare.network/tx/0xe2146fb4c498964754b5754a1dd91992c62e29d3d627cd6fb0ebba6432d8c46c), [lifecycle run](https://github.com/etvjay/Concord/actions/runs/31734593116) | +| Repository verification | [Full verification](https://github.com/etvjay/Concord/actions/runs/31792244127), [frontend verification](https://github.com/etvjay/Concord/actions/runs/31792436520) | + +The machine-readable source of truth is +[`config/coston2/concord-deployment.json`](../config/coston2/concord-deployment.json). +It records the complete identifiers, receipts, blocks, warnings, and evidence +artifacts. + +## Submission copy + +### Approved voiceover + +Use this version for the recording. It preserves the product thesis while +stating the privacy and development-TEE boundary precisely: + +> Money is easy to transfer. Trust between institutions is much harder to +> coordinate. +> +> Today, capital coordination is fragmented across legal agreements, manual +> reviews, and intermediaries. Public blockchains make settlement visible, but +> institutions still need sensitive terms and constraints to remain private. +> +> Concord is a confidential programmable relationship system built on Flare. +> It treats the capital relationship—not a one-off transaction—as the primary +> object. +> +> Our first implementation is an FXRP-backed syndicated capital facility. One +> treasury creates a Root Accord that defines the borrower, collateral, target, +> policy, participants, and expiry. The treasury locks 1 FXRP, then opens a +> Makkari funding round for a 9 USDT0 target. +> +> Independent providers submit signed offers with capacity, pricing, and +> constraints. Concord's CoFill extension evaluates those offers through the +> Flare Confidential Compute development path. The provider inputs and losing +> terms remain within the intended confidential boundary while the result is +> deterministic and signed. +> +> A verifier checks the result against the active extension, round, Root Accord, +> chain, and allocation digest. Only then are Child Accords materialized—one +> explicit relationship per provider. Each provider transfers its 3 USDT0 +> allocation, and the Root Accord becomes active with 9 USDT0 of committed +> capacity. +> +> Now follow one draw. A 4 USDT0 draw is split across two Child Accords: 3 USDT0 +> from Provider 1 and 1 USDT0 from Provider 2. Those Draw Legs preserve exactly +> who funded each portion. The draw is settled publicly on Coston2, then repaid +> through the same facility. Exposure returns to zero and all 9 USDT0 of +> capacity is available again. +> +> This is a recorded Coston2 proof using the documented simulated development +> TEE path. Concord does not claim private FXRP or USDT0 transfers, private EVM +> state, or production hardware-backed TEE security. Its claim is narrower and +> more useful: confidential coordination can produce a verifiable public +> commitment, and that commitment can remain a living relationship through +> funding, draws, repayment, and restored capacity. + +### Short description + +Concord is a confidential programmable relationship system for syndicated +capital on Flare. Its MVP creates one FXRP-backed Root Accord, coordinates +signed provider offers through a Makkari FCC session, deterministically composes +eligible capacity with CoFill, materializes funded Child Accords, and records +multi-provider Draw Legs, public settlement, repayment, restored capacity, and +lineage. The observed Coston2 proof funded 9 USDT0 from three providers, drew 4 +USDT0 across two commitments, repaid it, and restored all capacity. + +### What is technically distinctive + +- The persistent economic relationship—not a one-off transaction—is the primary + object. +- Confidential coordination outputs are bound to public execution by a verifier + checking signed FCC evidence and exact chain/extension/round/root/digest + context. +- Selected capacity does not become committed capacity until the provider's + USDT0 transfer succeeds. +- Every draw is decomposed into onchain provider Draw Legs; repayment reduces + those same obligations and restores capacity. +- User actions retain a hard authority boundary: Concord prepares transparent + unsigned intents; the institution's wallet reviews, signs, and broadcasts. + +## Recording checklist + +- Use the canonical branch and a green commit. +- Record at 1440p or higher with browser zoom between 90% and 100%. +- Keep the connected address visible when demonstrating the wallet boundary. +- Use the recorded facility for the main story; do not create a second facility + for presentation convenience. +- Do not submit a fresh draw unless a subsequent repayment is also planned and + the resulting receipts will be added to the deployment evidence. +- Show at least one Coston2 explorer receipt and one GitHub Actions evidence run. +- State “simulated development TEE” once, clearly. +- Add the final public frontend URL to the submission form only after its host + and exact commit have been verified. +- Run `scripts/judge-check.sh` from the clean submission commit to verify the + recorded proof and current public `/info` binding without credentials. +- The current Northflank/Worker runtime may be described as a reachable + development checkpoint, not as a registered active machine, until + `scripts/judge-check.sh --registry` passes during an explicitly approved + live window. +- Do not dispatch a new Coston2 instruction or submit a new lifecycle + transaction as part of a demo without recording the resulting receipts and + obtaining the separate operator confirmation. diff --git a/docs/JUDGES.md b/docs/JUDGES.md new file mode 100644 index 0000000..0290e0e --- /dev/null +++ b/docs/JUDGES.md @@ -0,0 +1,115 @@ +# Concord judge quickstart + +This page is the shortest truthful path for evaluating Concord. A live Docker +endpoint is not required to inspect the completed Coston2 proof. A current +public FCC development endpoint is available for read-only binding checks, but +it must not be confused with the recorded onchain evidence or an onchain +registered machine. + +## What Concord is + +Concord coordinates one FXRP-backed capital facility across independent +providers. A treasury creates a root Accord, provider terms enter a Makkari / +Flare Confidential Compute session, CoFill determines the allocation, and the +selected providers become independently governed child Accords. Funding, a +multi-child draw, repayment, and restored capacity remain queryable through the +root-child lineage. + +## Five-minute evaluation path + +1. Read the [current status](CONCORD_STATUS.md) and the + [deployment record](../config/coston2/concord-deployment.json). +2. From a clean checkout, run the credential-free preflight: + `./scripts/judge-check.sh`. +3. Open the [complete facility lifecycle run](https://github.com/etvjay/Concord/actions/runs/31734593116) + and its [public evidence artifact](https://github.com/etvjay/Concord/actions/runs/31734593116/artifacts/9194681063). +4. Inspect the [FCC round run](https://github.com/etvjay/Concord/actions/runs/31733200629), + [signed action evidence](https://github.com/etvjay/Concord/actions/runs/31733200629/artifacts/9194157272), + and [materialization recovery run](https://github.com/etvjay/Concord/actions/runs/31733740564). +5. Verify the public Coston2 contract and settlement receipts in the + [Coston2 explorer](https://coston2-explorer.flare.network): + `CapitalFacility 0xfaff601a18a9fca33378953515aa0f3ef9286ecd` and + `AccordRegistry 0x68b19a3967760489b57341669cd7ea960b5f7367`. +6. Review the contract lifecycle tests and the deterministic CoFill tests in + `contracts/test/`, `go/`, and `tools/`. + +## Observed result + +The completed Coston2 flow used the six-decimal Coston2 USDT0 test token and +one FXRP collateral unit: + +| Stage | Observed result | +|---|---| +| Root Accord | `ACTIVE`; `9,000,000` base units committed | +| FCC / CoFill | Three signed provider offers; digest `0xf17a292655b898d8b00c9794565972ed838cd3de2812f6fa59d877d6963c88ec` | +| Child Accords | Three children, `3,000,000` selected and funded per provider | +| Draw | `4,000,000` base units across two DrawLegs | +| Repayment | Real USDT0 repayment transaction completed | +| Final capacity | `9,000,000` committed, `0` drawn, `9,000,000` available | + +The exact child IDs, transaction hashes, and invariant results are in the +deployment record and evidence artifact. + +## Live verification boundary + +Concord's recorded proof does not depend on a continuously running public app. +The current development runtime is Northflank-backed and can be checked +without credentials: + +- Worker relay: `https://concord-fcc-ingress.microcosm.workers.dev` +- Direct Northflank proxy: `https://pub6664--concord-fcc-proxy--n4krppffn8ms.code.run` +- Both `/info` paths currently return HTTP `200`, extension `66188`, and chain + ID `114`. +- Private readiness is `/healthy` on proxy port `6663`; FCC traffic uses public + port `6664`. + +The relay used by the recorded run was: + +`https://concord-fcc-ingress.microcosm.workers.dev` + +It is a development ingress relay to Northflank. It is not a named Cloudflare +Tunnel, production hosting, or a production hardware-backed TEE. The current +simulated identity is `0xb1e7a4c1930f1f3c4905b34fafc9c1b8359029a5`; it is not yet +registered on Coston2. The previously registered `PRODUCTION` machine remains +the historical record and has not been paused. Do not present the current +runtime as an active onchain machine until `./scripts/judge-check.sh +--registry` passes during an explicitly approved live window. Restarting the +simulated TEE can rotate its identity and requires the same deliberate +registration/cutover process. + +## What is and is not claimed + +Claimed: + +- Concord coordinates persistent capital relationships on Flare Coston2. +- Multiple providers contribute to one facility through child Accords. +- FXRP is used as root-facility collateral. +- FCC is used for confidential provider coordination. +- Public settlement and lineage receipts verify the economic lifecycle. + +Not claimed: + +- private FXRP or USDT0 transfers; +- fully private EVM execution; +- continuous public availability; +- production hardware-backed TEE security; +- production institutional readiness. + +Never send private keys, indexer credentials, GitHub tokens, or tunnel tokens to +judges. They are not required to inspect the recorded proof. + +## Reproducibility levels + +- **Repository level:** contract, Go, tooling, and documentation checks can be + run from a clean checkout. +- **Recorded-chain level:** judges can inspect the Coston2 transactions, + workflow runs, allocation digest, child lineage, draw, and repayment without + any private credential. +- **Live-FCC level:** public binding checks are redoable with + `./scripts/judge-check.sh`; a new confidential round still requires the + team-operated credential-gated runtime, deliberate machine registration, and + an agreed live verification window. + +The submission should be judged primarily from the first two levels. The third +level is an optional demonstration convenience, not a reason to claim that the +runtime is permanently hosted. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..d1819ca --- /dev/null +++ b/docs/README.md @@ -0,0 +1,66 @@ +# Concord documentation + +Concord documentation is split between product semantics, implementation +guidance, interface contracts, and evidence boundaries. Start with the status +record before making a deployment or privacy claim. + +## Product and protocol + +- [architecture.md](architecture.md) — Accord, Makkari, CoFill, and lifecycle. +- [lineage.md](lineage.md) — queryable parent-child causal graph. +- [shared-product-contract.md](shared-product-contract.md) — shared semantics + for the web interface, REST, SDK, MCP, CLI, and agent integrations. +- [frontend-map.md](frontend-map.md) — routes, role surfaces, and state-to-action + behavior for the eventual webapp. +- [frontend-design-system.md](frontend-design-system.md) — visual system and + component direction. +- [frontend-art-direction.md](frontend-art-direction.md) — visual narrative + and motion direction. + +## Build, test, and deployment + +- [getting-started.md](getting-started.md) — local checks, asset resolution, + deployment inputs, and the FCC sequence. +- [testing.md](testing.md) — contract, extension, and tooling tests. +- [testing-against-coston2.md](testing-against-coston2.md) — Coston2 test path. +- [coston2-live-runbook.md](coston2-live-runbook.md) — credential-gated live + integration sequence and evidence checklist. +- [cloudflared.md](cloudflared.md) — proxy exposure guidance. +- [fcc-always-on-hosting.md](fcc-always-on-hosting.md) — Northflank topology, + identity cutover, and deployment blockers. +- [cli.md](cli.md) — unified CLI, API, and MCP entry points. +- [pre-deployment-hardening.md](pre-deployment-hardening.md) — immediate CI, + correctness, and productization gates before live proof. +- [experiments/coston2-failure-matrix.md](experiments/coston2-failure-matrix.md) + — adversarial contract, FCC, control-plane, and live-evidence cases. + +## Evidence and truth + +- [CONCORD_STATUS.md](CONCORD_STATUS.md) — what is and is not evidenced. +- [current-runtime.md](current-runtime.md) — latest Northflank, Worker, and + simulated-TEE checkpoint (including the registration boundary). +- [source-snapshot-2026-08-14-deployment.yaml](source-snapshot-2026-08-14-deployment.yaml) +- [source-snapshot-2026-08-14-live-blocker.yaml](source-snapshot-2026-08-14-live-blocker.yaml) + — current FCC source refresh and deployment-attempt boundary. +- [JUDGES.md](JUDGES.md) — five-minute evaluation path, public evidence, and + live-verification boundaries. +- `../scripts/judge-check.sh` — credential-free, read-only judge preflight. +- [templates/coston2-evidence.template.json](templates/coston2-evidence.template.json) + — blank evidence shape with `not_observed` defaults. +- [canonical-reconciliation-audit.md](canonical-reconciliation-audit.md) — + reconciliation of the handoff, addendum, whitepaper, and implementation. +- [skills-foundry-pack-ingestion.md](skills-foundry-pack-ingestion.md) — durable + record of the supplied Foundry skills pack and validator status. +- [source-snapshot-2026-08-13.yaml](source-snapshot-2026-08-13.yaml) — dated official Flare/FCC source snapshot and live-gate inputs. + +## Official Flare runtime references + +The official Flare FCE scaffold, Flare AI skills, and Developer Hub remain the +technical authority for changing FCC, Coston2, FAssets, and network behavior. +Concord-specific documents preserve product semantics but do not replace those +current Flare sources. + +- [Build Your First FCC Extension](https://dev.flare.network/fcc/guides/getting-started) +- [Coston2 network configuration](https://dev.flare.network/network/overview) +- [FXRP address guidance](https://dev.flare.network/fxrp/token-interactions/fxrp-address) +- [Official FCE scaffold](https://github.com/flare-foundation/fce-extension-scaffold) diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..719d64e --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,85 @@ +# Concord architecture + +Concord coordinates one persistent economic relationship: an FXRP-backed +syndicated capital facility. The canonical object is the relationship, not an +individual transaction. + +~~~text +Root Accord + ↓ +Makkari session (confidential FCC boundary) + ↓ +CoFill allocation + ↓ +Child Accords + ↓ +funded commitments → Draw → DrawLegs → Settlement → Repayment +~~~ + +## Read it this way + +| Technical term | What it means in the product | +|---|---| +| Accord | A durable relationship between parties, not just a transaction. | +| Root Accord | The facility that owns the shared collateral, capacity, and lifecycle. | +| Child Accord | A provider-specific relationship linked to the root. | +| Makkari | The confidential FCC session used to coordinate provider terms. | +| CoFill | The deterministic matching step that fills the facility from eligible offers. | +| FCC proxy | The public HTTP entry point for the FCC runtime. | +| TEE | The private decrypt/sign service behind the proxy; the current Coston2 path simulates it. | + +Redis supports runtime queue/state needs only. The contracts and recorded +Coston2 receipts remain the source of truth for economic state. + +## Onchain responsibilities + +| Component | Responsibility | +|---|---| +| AccordRegistry | Relationship identity, parent-child links, lineage nodes, settlement and repayment references. | +| CapitalFacility | FXRP collateral, USDT0 commitments, child funding, exposure, deterministic multi-child draws, repayment, expiry and close rules. | +| ConcordInstructionSender | Encrypted SUBMIT_QUOTE and FINALIZE_ROUND instruction submission through the official FCC registry path. | +| ConcordTypes | Root Accord, Child Accord, round and allocation data shapes. | + +The facility does not run CoFill. It accepts a verified, bound allocation +result and materializes each selected provider as a child relationship. + +## Makkari and CoFill + +The Go extension is built inside the official Flare FCE scaffold. It requires +the instruction payload to be encrypted to the active TEE machine and calls +the scaffold's decrypt endpoint. It validates provider signatures, quote +expiry, fee policy, provider eligibility and round/root binding. + +CoFill sorts by lowest fee, then provider address, then quote nonce. It permits +a partial final allocation and fails explicitly when eligible capacity is +insufficient. The returned digest binds the extension, round, root, expiry, +providers, amounts, fees and terms commitments. + +## Economic state + +Provider capacity is intentionally distinct from provider quotes: + +~~~text +quoted → selected → funded → active → closed +~~~ + +selectedCapacity is the FCC result. committedCapacity increases only after the +provider's USDT0 transferFrom succeeds. Root committed capacity is the sum of +actual child commitments, not the sum of selected quotes. + +Draws are not flattened. A root draw creates one Draw node and one DrawLeg per +child relationship consumed. Repayment reduces the draw leg, child and root +exposures only after the ERC-20 transfer succeeds. + +## Trust and privacy boundary + +The current checkpoint does not claim private FXRP or USDT0 transfers, private +EVM execution, or production hardware-backed TEE security. FCC development may +use the documented simulated TEE path. Settlement, accepted executable +capacity, exposure, and lineage are public where the facility requires them. + +allocationVerifier is an explicit integration boundary. It must verify the FCC +result's operation, active machine/signature evidence, extension, round, root +and digest before calling markAllocationVerified. The Solidity tests use a +verifier actor; that is not proof that the live Coston2 verifier has been +deployed. diff --git a/docs/assets/concord-visual-territories-v1.png b/docs/assets/concord-visual-territories-v1.png new file mode 100644 index 0000000..02c5ec5 Binary files /dev/null and b/docs/assets/concord-visual-territories-v1.png differ diff --git a/docs/canonical-reconciliation-audit.md b/docs/canonical-reconciliation-audit.md new file mode 100644 index 0000000..b3ad2bb --- /dev/null +++ b/docs/canonical-reconciliation-audit.md @@ -0,0 +1,216 @@ +# Concord canonical reconciliation audit + +Status: refreshed for the durable `agent/concord-rebuild` checkpoint on 2026-08-13. + +This audit compares the implementation with the following authority order: + +1. Canonical Build Handoff — what the MVP must build. +2. Canonical Product Definition Addendum — current product semantics. +3. Concord Whitepaper — architectural thesis and future direction. +4. Current official Flare sources — changing implementation facts. + +The whitepaper and Product Definition Addendum were read in full. The supplied +Foundry Skills Pack is now durably installed and its Product Foundry 0.4.3, +Research Foundry 0.2.0, Experiment Foundry 0.1.0, Interface Foundry 0.3.0, +Demo Foundry 0.5.3, and Concord FCC 0.1.0 instructions are loaded as applicable. +The repository's durable source and current official Flare-controlled sources +remain the implementation truth for changing Flare facts. + +## Verdict + +No contradiction was found between the Build Handoff, Product Definition +Addendum, and whitepaper. The implementation still expresses Concord as a +persistent programmable capital relationship rather than as an RFQ venue, +lending pool, matching engine, or isolated transaction system. + +The MVP is not complete. The outstanding blockers are evidence and execution +gates, not a product reinterpretation: + +- no live FCC machine, instruction IDs, settlement receipts, or complete + explorer evidence are recorded; +- the current runner does not contain `forge` or Go, so this audit could not + re-run the Solidity and Go suites; +- the full frontend remains intentionally unimplemented until the economic and + FCC path is evidenced. + +## Cross-layer reconciliation + +| Area | Result | Current truth | Decision | +| --- | --- | --- | --- | +| Product object | PASS | Root Accord is the persistent facility relationship; transactions and FCC actions are lineage nodes. | Preserve. | +| Primitive composition | PASS | Accord, Makkari, CoFill, and Lineage remain separate responsibilities. | Preserve. | +| MVP scope | PASS | The repository implements the FXRP-backed facility slice and does not add receivables, KYC/KYB, FDC, cross-border FX, collections, or legal enforcement. | Keep future capabilities as seams only. | +| Economic states | PASS | `selectedCapacity` is separate from `committedCapacity`; provider funding increases committed capacity only after `transferFrom` succeeds. | Preserve the distinction in every surface. | +| Allocation binding | PASS | Result digest binds extension, round, root, success, expiry, providers, amounts, fees, and terms. Materialization is verifier-gated and replay-protected. | Preserve. | +| Draw model | PASS | A root draw creates explicit DrawLeg objects and consumes multiple child relationships deterministically when capacity requires it. | Preserve; do not flatten draws. | +| Repayment | PASS | ERC-20 transfer occurs before exposure reduction; repayment reduces legs, child exposure, and root exposure. | Preserve. | +| Privacy | PASS | Quote inputs and FCC computation are inside the intended confidential boundary; transfers, exposure, accepted capacity, and lineage are not claimed private. | Keep claim language bounded. | +| API / SDK / MCP / CLI | PASS | Read surfaces share one read model; write surfaces prepare unsigned intents and do not custody, sign, broadcast, or become the verifier. | Preserve. | +| Frontend design | PASS / DESIGN ONLY | The design is relationship-first, progressive-disclosure, role-aware, and explicit about observed versus unavailable state. | Implement only after the live economic path. | +| Future authority | GAP / INTENTIONAL | General parent-delegated authority and external evidence fields are not yet modeled. | Roadmap seam; do not add to this MVP. | +| Live Coston2 proof | BLOCKER | Facility/root/provider funding evidence exists, but no active FCC machine, instruction IDs, or settlement receipts are recorded. | Required before completion claims. | +| Local validation | BLOCKER | `forge` and the recorded Go toolchain are absent from this runner. | Restore toolchain, then rerun the suites. | + +## Implementation evidence + +### Solidity + +`contracts/ConcordTypes.sol` contains the Root Accord, Child Accord, round, +and allocation shapes required by the handoff. `contracts/CapitalFacility.sol` +owns collateral, provider funding, activation, deterministic multi-child draws, +DrawLegs, repayment, exposure, expiry, and close rules. It rejects: + +- root or child overdraw; +- provider funding above selected capacity; +- materialization without a configured verifier authorization; +- duplicate allocation materialization; +- allocation results bound to another extension, round, or root; +- draws above available capacity; +- provider capital return while that child has exposure; +- collateral return while the root has exposure or commitments. + +`contracts/AccordRegistry.sol` records the causal chain: + +```text +ROOT_ACCORD + → MAKKARI_SESSION + → COFILL_ALLOCATION + → CHILD_ACCORD + → DRAW + → DRAW_LEG + → SETTLEMENT + → REPAYMENT +``` + +The registry currently enforces parent-node existence and facility authority. +It does not yet implement the whitepaper's future generalized delegated +authority model. That is an intentional MVP boundary, not a contradiction. + +### FCC / Makkari / CoFill + +The Go extension remains inside the official FCE scaffold. It requires an +encrypted payload and calls the TEE decrypt endpoint; its local tests inject a +decrypt function only for unit isolation. Signed provider quotes are validated +with EIP-191 recovery. CoFill validates round/root/eligibility/fee/expiry +bounds, sorts by fee then provider address then nonce, permits a partial final +allocation, and fails explicitly when capacity is insufficient. + +The offchain `verify-allocation` command is deliberately a separate evidence +boundary. It checks the returned action envelope, signed TEE information, +active machine binding, extension/round/root binding, round state and bounds, +eligibility, canonical digest, and facility verifier address before its +optional `markAllocationVerified` broadcast. Local Solidity actors are not +presented as native FCC proof verification. + +### Layered surfaces + +The following surfaces use the shared contract in +`docs/shared-product-contract.md`: + +- REST/OpenAPI: observed facilities, rounds, draws, lineage, evidence status, + health, and unsigned transaction intents; +- TypeScript SDK: typed REST client and decimal-safe formatting; +- MCP: read resources and safe preparation tools over REST; +- CLI: doctor, API, MCP, and FCC/deployment operator commands; +- frontend boundary: relationship workspace and role-aware disclosure, + without a second state model. + +x402 is not implemented in this checkpoint. The shared contract correctly +keeps it as a future access-gating layer; it must not become a settlement rail +or bypass Accord authority. + +## Gaps that do not change MVP semantics + +These items are real follow-up work, but none authorizes broadening the MVP: + +1. The live verifier and Coston2 deployment need to be executed and evidenced. +2. The failure-focused Experiment Foundry phase has not been run in this + environment. The next test pass must cover expiry, replay, unauthorized + materialization, reentrancy/ERC-20 behavior, provider withdrawal limits, + collateral recovery limits, and invariant divergence. +3. The current round record stores `OPEN`/`FINALIZED`; a read model should + explicitly represent an elapsed open round as expired or document that the + expiry is derived from `roundExpiry`. Allocation materialization already + rejects an elapsed round, and the FCC path rejects expired quotes. +4. Future evidence references (`evidenceRef`, `evidenceRoot`, and related + policy/authorization evidence) and generalized delegated child authority + remain architectural seams only. +5. The full React application, visual prototypes, and browser QA are not yet + present. The design documents are the current interface contract, not a + claim that the webapp has been built. + +## Repository-truth corrections in this checkpoint + +- Removed workspace-specific `/workspace/tooling` paths from the durable build + and testing instructions. Tool locations are now environment-specific. +- Marked inherited Hello World/Orderbook scaffold pages as reference-only in + the documentation index. Concord-specific documents remain authoritative for + product semantics and runbooks. +- No Solidity or product primitive was renamed or broadened as part of this + audit. + +## Current FCC credential gate + +The current DevHub guide was refreshed on 2026-08-13. Its Coston2 indexer +configuration is host `34.38.42.208`, port `3306`, database `indexer`; the +username and password are stored only as encrypted GitHub Actions secrets. +The repository does not record their values. The stable public extension URL +and named-tunnel token are still absent, so the guarded FCC setup workflow has +not been dispatched. + +## Validation record + +Completed during this audit: + +- static source and documentation inspection across Solidity, Go, REST, + TypeScript SDK, MCP, CLI, and frontend design files; +- `git diff --check` passed in the local source checkout. + +Not re-run because the current runner lacks the required binaries: + +- `forge test` — `forge` was not found; +- `(cd go && go test ./...)` — the recorded Go binary path was absent and no + system `go` binary was available; +- `(cd tools && go test ./...)` — same toolchain blocker; +- live Coston2 asset resolver and FCC lifecycle — no live deployment inputs or + toolchain were available. + +Earlier test statements in `docs/CONCORD_STATUS.md` describe the prior durable +checkpoint; they are retained as historical evidence and are not relabeled as +fresh results from this audit. + +## Official Flare sources refreshed + +- [Build Your First FCC Extension](https://dev.flare.network/fcc/guides/getting-started) + — current Coston2 FCC development sequence and simulated-TEE boundary. +- [Coston2 network overview](https://dev.flare.network/network/overview) + — chain ID `114`, RPC, explorer, and testnet assets. +- [Get FXRP Address](https://dev.flare.network/fxrp/token-interactions/fxrp-address) + — resolve FXRP through `AssetManager.fAsset()` rather than scattering a + guessed token address. +- [Get FXRP Asset Manager Address](https://dev.flare.network/fassets/developer-guides/fassets-asset-manager-address-contracts-registry) + — Contract Registry resolution rule. +- [Official FCE scaffold](https://github.com/flare-foundation/fce-extension-scaffold) + — runtime and registration foundation. +- [Official Flare Foundry periphery package](https://github.com/flare-foundation/flare-foundry-periphery-package) + — current official interface and network-package source. + +The Flare Developer Hub MCP was not available among the connected tools in +this runner. The official web and GitHub sources above were used for the +source refresh; any material deployment-path change must be checked again +before execution. + +## Completion gate + +Concord remains in the implementation-and-integration phase. The next +authoritative milestone is not another product design pass: + +```text +restore toolchain + → run contract/FCC/tooling tests + → register the current FCC extension path + → deploy Concord contracts on Coston2 + → execute funded three-provider flow + → record signed evidence and transaction hashes + → then implement and QA the frontend +``` diff --git a/docs/cli.md b/docs/cli.md new file mode 100644 index 0000000..20c2252 --- /dev/null +++ b/docs/cli.md @@ -0,0 +1,49 @@ +# Concord CLI + +The unified CLI is available through `scripts/concord`: + +```bash +./scripts/concord version +./scripts/concord doctor --offline +./scripts/concord doctor +./scripts/concord api -facility 0x... -registry 0x... +./scripts/concord mcp -api-url http://127.0.0.1:8080 +``` + +If Go is not on the normal path, set `CONCORD_GO_BIN`. The live doctor check +requires the configured RPC to be reachable; `--offline` validates local +configuration and tools without making a network request. + +## What is ready + +- one command namespace for configuration checks, API startup, and MCP startup; +- Coston2 network configuration is read from `config/networks/coston2.json`; +- `doctor` validates the configured chain ID and optionally checks bytecode at + supplied facility/registry addresses; +- the API uses read-only chain calls and returns unsigned transaction intents; +- MCP exposes facility, round, draw, and lineage resources plus the same safe + read/prepare operations. + +## What is not ready + +The CLI is not a complete live-facility operator yet. It does not replace the +existing bounded FCC/deployment commands, does not hold a signer, and does not +claim that Concord contracts or an FCC extension are deployed on Coston2. The +live operator path still requires deployed addresses, funded accounts, a +configured FCC proxy/TEE flow, provider offers, and recorded receipts. + +## Coston2 preflight + +Run the read-only preflight before any credentialed operator session: + +~~~bash +./scripts/coston2-preflight.sh --offline +./scripts/coston2-preflight.sh --proxy "$EXT_PROXY_URL" --extension-id "$CONCORD_EXTENSION_ID" +~~~ + +The live form confirms chain ID 114, re-runs the centralized FXRP/USDT0 asset +resolver, and can check public bytecode and the official FCC proxy `/info` +binding when addresses are supplied. It never signs, broadcasts, or stores +credentials. The transaction-intent vocabulary shared by the API, SDK, MCP, +and CLI includes round opening plus child/root close and expiry actions; FCC +allocation materialization remains verifier-gated. \ No newline at end of file diff --git a/docs/cloudflared.md b/docs/cloudflared.md new file mode 100644 index 0000000..77a5479 --- /dev/null +++ b/docs/cloudflared.md @@ -0,0 +1,159 @@ +# Cloudflare Tunnel (ngrok replacement) + +Exposes the extension proxy's external port (host `6674`, container `6664`) over a public +HTTPS URL, so Flare's TEE infrastructure can reach it. A registered Coston2 machine must +use a stable named-tunnel hostname; rotating quick tunnels are only for local smoke tests +before registration. Compose file: +`docker-compose.cloudflared.yaml`. + +Only testnets need this. On `--chain local` nothing is started — a local devnet reaches +the proxy on localhost. + +## The scripts drive it + +`start-services.sh` syncs the tunnel *before* every other container, then writes the URL +into `.env` as `EXT_PROXY_URL`. `full-setup.sh` calls it in Phase 2, and `post-build.sh` / +`test.sh` re-read `.env`, so a whole run picks the URL up by itself: + +```bash +./scripts/full-setup.sh --chain coston2 --tunnel --test +``` + +| Situation | What `start-services.sh` does | +|---|---| +| Tunnel already running | Reuses it and adopts its URL — no flag needed | +| No tunnel, `--tunnel` passed | Starts a quick tunnel for local smoke only; do not register its URL | +| No tunnel, no `--tunnel` | Warns; `EXT_PROXY_URL` must already be valid in `.env` | +| `TUNNEL_ARGS` set (named tunnel) | Starts it and leaves your `EXT_PROXY_URL` alone | + +`stop-services.sh` leaves the tunnel up unless you pass `--tunnel` — see below. + +## One tunnel, shared by every extension + +The tunnel does **not** belong to this extension. It runs in a compose project called +`tunnel`, set by the `name:` line at the top of `docker-compose.cloudflared.yaml`. + +Compose identifies a container by **project name + service name** and nothing else — not +the file's path, not the folder it sits in. So every extension's copy of this file +addresses the *same* container: the first `up -d` creates it, every later one finds it +running and inherits its URL. That works because every extension here publishes ext-proxy +on host `6674`, so only one stack is ever behind the tunnel; it is handed from extension to +extension, keeping one URL across the switch. + +That is also why `stop-services.sh` keeps it alive by default: tearing it down rotates the +URL for every other extension and strands their `EXT_PROXY_URL`. + +**Copy `docker-compose.cloudflared.yaml` verbatim.** Compose compares the *resolved service +config* to decide reuse-vs-recreate. Comments and file paths are not part of that +comparison, but `command:` is — so a differing `TUNNEL_TARGET` recreates the container and +mints a new URL for everyone. Setting `TUNNEL_TARGET` by hand warns you for this reason. + +Two cases that legitimately need their own tunnel, and get their own project: + +| Case | Project | Why | +|---|---|---| +| `--local` (host Go proxy) | `tunnel-local` | Proxy listens on 6664, a different origin — the script handles this | +| `prediction_market` | give it its own | Publishes on 6676, cannot share | + +## Quick tunnel (local smoke only — never register this URL) + +From the repo root, in Git Bash: + +```bash +# 1. Start it (pulls cloudflare/cloudflared on first run) +docker compose -f docker-compose.cloudflared.yaml up -d + +# 2. Read the URL cloudflared printed at startup +docker compose -f docker-compose.cloudflared.yaml logs cloudflared \ + | grep -o 'https://[a-z0-9-]*\.trycloudflare\.com' | tail -1 +``` + +Put that URL in `.env` as `EXT_PROXY_URL=` only for an unregistered local smoke +test, **then** start the containers. Never use a `trycloudflare.com` or other rotating +quick-tunnel URL in `post-build.sh` for a Coston2 machine; Flare stores the URL onchain. +`start-services.sh` blocks on `$EXT_PROXY_URL/info`, so a stale value there is what makes +it fail. Stop with `docker compose -f docker-compose.cloudflared.yaml down`. + +Skipping the copy-paste — does step 2 and writes the result straight into `.env` for a +local smoke test: + +```bash +URL=$(docker compose -f docker-compose.cloudflared.yaml logs cloudflared \ + | grep -o 'https://[a-z0-9-]*\.trycloudflare\.com' | tail -1) \ + && sed -i "s|^EXT_PROXY_URL=.*|EXT_PROXY_URL=$URL|" .env \ + && grep '^EXT_PROXY_URL=' .env +``` + +Rewrites an existing `EXT_PROXY_URL=` line only — if the echo comes back empty, add the +line once by hand and this works from then on. + +## Already registered on-chain? Repoint the machine record too + +`EXT_PROXY_URL` in `.env` only feeds the local scripts. Flare's TEE infra delivers +instructions to the URL stored in the `MachineManager`, written once at registration — +`post-build.sh` will **not** rewrite it for an already-registered machine. A rotated URL +therefore looks like every on-chain op timing out at `pollAction` even though the chain leg +succeeded. + +```bash +./scripts/update-tee-url.sh # defaults to EXT_PROXY_URL from .env +``` + +Calls `MachineManager.updateTeeMachineSettings` directly — no attestation, one transaction, +ledger untouched. A full re-registration would trip `ChallengeExpired` on a long-running +TEE. This is the strongest reason to want a stable hostname. + +## Making the URL stable + +Quick tunnels always rotate and no flag pins them. Cheapest fix first: + +**Don't restart the tunnel.** It has no dependency on the main stack, so `down && up` on the +app containers does not touch it. Only restarting `cloudflared` itself rotates the URL. + +**Named tunnel — permanently fixed hostname.** Needs a free Cloudflare account with a domain +on it. This is required for the Coston2 FCC registration path: + +1. Zero Trust → Networks → Tunnels → *Create a tunnel* → Cloudflared. Copy the `eyJ...` token. +2. Add a public hostname pointing at `http://host.docker.internal:6674`. +3. In `.env`: + ``` + TUNNEL_ARGS="run --token eyJhIjoi..." + EXT_PROXY_URL=https://tee.yourdomain.com + ``` +4. `docker compose -f docker-compose.cloudflared.yaml up -d` — same command, named mode now. + +`EXT_PROXY_URL` and the on-chain machine record then survive every restart, and +`restart: unless-stopped` becomes safe to add to the service. + +## Hackathon fallback when Workers VPC is unavailable + +If the Cloudflare account cannot grant the current user Connectivity Directory +Admin, do not guess at a VPC service or modify an unrelated tunnel. The +development fallback is a separately deployed Worker at the account's +workers.dev hostname, forwarding the narrow FCC proxy paths to a public HTTPS +GitHub Codespaces port. The implementation is in +infra/cloudflare/fcc-ingress/. + +This preserves the official FCE runtime and Concord semantics, but changes the +network ingress boundary. It is suitable only for a controlled hackathon run: + +1. Start the Concord Coston2 proxy in the persistent Codespace. +2. Make only host port 6674 public. +3. Set the Worker's CONCORD_UPSTREAM_URL secret to the Codespaces HTTPS URL. +4. Verify /info, /instruction, and /action/status through the Worker. +5. Use the Worker URL only with an explicit development-path privacy and + availability label. + +Codespaces public ports are anonymous internet endpoints and revert to private +after a Codespace restart. Reapply the port visibility and re-run the health +checks before every FCC session. This fallback must not be presented as a +production hardware-backed FCC or permanent institutional ingress. + +## Know this + +| | | +|---|---| +| A quick tunnel's URL changes on every start | There is deliberately no `restart:` policy — a silent restart would mint a new URL and strand `EXT_PROXY_URL` | +| The tunnel starts fine with nothing behind it | It just 502s until the containers are up | +| No network dependency on the main stack | Start order does not matter | +| Proxy running as a local Go binary on 6664? | `TUNNEL_TARGET=http://host.docker.internal:6664` — use a separate `-p` project so the shared tunnel is untouched | diff --git a/docs/coston2-live-runbook.md b/docs/coston2-live-runbook.md new file mode 100644 index 0000000..e0f94c4 --- /dev/null +++ b/docs/coston2-live-runbook.md @@ -0,0 +1,222 @@ +# Coston2 live integration runbook + +This is the operator path for the first truthful Concord vertical slice. It is +an execution checklist, not deployment evidence by itself or a substitute for +the recorded deployment evidence. The observed +2026-08-13 Coston2 run is recorded in +`config/coston2/concord-deployment.json` and summarized in +`docs/CONCORD_STATUS.md`. A fresh run must produce its own receipts before it +changes the recorded status. + +## Current gate + +The first complete credential-gated Coston2 run has passed. The read-only asset +resolver can still run from GitHub Actions or a local Foundry installation, and +the same guarded workflow can be repeated against a newly created disposable +environment. No private key or proxy credential belongs in the repository. + +The current hosted development runtime is Northflank-backed and passes a fresh +public `/info` binding check through the Cloudflare Worker relay. Its current +simulated identity is not registered onchain, so this fact does not authorize a +registration, pause, or FCC dispatch. Run `./scripts/judge-check.sh` for the +credential-free repository and public-binding gate. + +The current development path may use `SIMULATED_TEE=true`. That proves the +documented development integration path only; it is not production +hardware-backed TEE evidence. The Coston2 machine must still reach status `2` +(`PRODUCTION`) with a fresh availability check, a registered `teeId`, and one +stable public HTTPS URL. The extension proxy receives provider delivery on its +external port `6664` (the scaffold's Docker host mapping is `6674`). + +## Required inputs + +| Input | Why it is required | +|---|---| +| Funded Coston2 deployment key | Pays gas for sender, facility, root, collateral, and lifecycle transactions | +| Treasury and provider accounts | Demonstrate actual USDT0 funding from at least two selected providers | +| Current Flare scaffold registry configuration | Registers the Concord extension against the live Coston2 FCC path | +| Stable public `EXT_PROXY_URL` | Lets the official proxy route instructions to the Go extension; the current Worker relay is usable for the development path, while rotating quick tunnels are not valid for registration | +| Indexer/proxy configuration | Required by the official scaffold runtime; credentials stay outside Git | +| Three signed Concord quote payloads | Demonstrate the private multi-provider input path | +| One signed finalize payload | Binds the round, root Accord, policy bounds, and eligible providers | + +For the current hackathon development path, the stable Worker relay forwards to +the Northflank proxy. The direct Northflank URL and the Worker URL must return +the same `/info` binding before any live operator step. This is an ingress +substitution, not a change to the FCE runtime or Concord product semantics. + +Never paste credentials into issues, logs, fixtures, or committed config. Use +ignored environment files or the deployment system secret store. + +## Execution order + +### 1. Run the read-only preflight + +Validate the local config before using any funded account. In offline mode the +command makes no RPC request: + +~~~bash +./scripts/coston2-preflight.sh --offline +~~~ + +After the public addresses and FCC proxy are available, repeat it with live +bindings: + +~~~bash +./scripts/coston2-preflight.sh \ + --proxy "$EXT_PROXY_URL" \ + --extension-id "$CONCORD_EXTENSION_ID" \ + --facility "$CAPITAL_FACILITY" \ + --registry "$ACCORD_REGISTRY" \ + --sender "$CONCORD_INSTRUCTION_SENDER" +~~~ + +It confirms Coston2 chain ID 114, re-runs the current asset resolver, and +optionally checks public bytecode and the proxy `/info` extension binding. It +never signs or broadcasts. If `SIMULATED_TEE=true`, the output is development +path evidence only. +### 2. Resolve current Coston2 assets + +Run the resolver before deployment and stop on any mismatch. It obtains the FXRP +manager through Flare's ContractRegistry, resolves `fAsset()`, and validates the +configured USDT0 contract by bytecode, symbol, and decimals. + +~~~bash +./scripts/resolve-coston2-assets.sh +~~~ + +The GitHub workflow named `Coston2 asset resolution` is the same read-only gate. +A successful run may be cited for asset resolution, but it is not deployment or +FCC evidence. + +### 3. Configure the official FCE scaffold + +Use the current official scaffold sequence and set the Coston2 values explicitly: + +~~~bash +export CHAIN=coston2 +export CHAIN_URL=https://coston2-api.flare.network/ext/C/rpc +export LOCAL_MODE=false +export SIMULATED_TEE=true +export EXT_PROXY_URL=https://concord-fcc-ingress.microcosm.workers.dev +export DEPLOYMENT_PRIVATE_KEY=0x... +~~~ + +The proxy must expose the scaffold port required by the checked-in configuration. +Confirm its `/info` response before sending any Concord instruction. + +~~~bash +curl -s "$EXT_PROXY_URL/info" | jq '{machineData: {extensionId: .machineData.extensionId, codeHash: .machineData.codeHash, platform: .machineData.platform}, teeInfo: {publicKey: .teeInfo.publicKey, chainId: .teeInfo.chainId}}' +~~~ + +If `platform` is the simulated development value or the code hash is documented +as simulated, preserve that limitation in every demo and release note. + +The current read-only deployment uses the Worker relay above, extension +`66188`, and simulated TEE id +`0xb1e7a4c1930f1f3c4905b34fafc9c1b8359029a5`; its direct Northflank proxy is +`https://pub6664--concord-fcc-proxy--n4krppffn8ms.code.run`. Both `/info` +responses currently return HTTP `200` and chain ID `114`. The earlier Coston2 +vertical slice used the historical machine +`0xeE39d5e7d1C5043232282e3CC884B41a9Db22c85`. Do not substitute the current +ephemeral identity into registration or dispatch commands without explicit +operator confirmation. + +### 4. Deploy and register in two phases + +Deploy the instruction sender first. Register the extension through the official +scaffold, call the one-shot `setExtensionId()`, and record the resulting live +extension id. Only then deploy the Accord registry and CapitalFacility with that +id and the configured allocation verifier. + +~~~bash +export DEPLOYMENT_OWNER=0x... +export TEE_EXTENSION_REGISTRY=0x... +export TEE_MACHINE_REGISTRY=0x... +DEPLOY_PHASE=sender ./scripts/deploy-coston2.sh + +source ./config/extension.env +export CONCORD_EXTENSION_ID=0x... +export ALLOCATION_VERIFIER=0x... +DEPLOY_PHASE=facility ./scripts/deploy-coston2.sh +~~~ + +Do not fill registry addresses or extension ids from memory. Resolve them from +the current official scaffold/configuration and record the source used. + +### 5. Run the relationship lifecycle + +1. Treasury creates the FXRP-backed root Accord and locks FXRP. +2. Treasury opens the Makkari syndication round with target, fee, expiry, and + eligible-provider bounds. +3. At least three independent providers submit signed quote payloads through + encrypted `SUBMIT_QUOTE` instructions. +4. Submit one `FINALIZE_ROUND` instruction and retain the complete signed FCC + action-evidence envelope. +5. Verify the signed result offchain against the live extension, round, root, + policy, eligible providers, active TEE machine, and canonical digest. +6. Use `-mark` only after the read-only verification succeeds and only from the + configured allocation verifier. +7. Materialize the selected Child Accords. They remain selected until providers + actually transfer USDT0. +8. Have at least two selected providers fund their exact allocations. Capture + the ERC-20 transfer receipts before treating commitments as funded. +9. Activate the root only after funded child capacity is sufficient. +10. Draw once across at least two Child Accords and retain every DrawLeg. +11. Repay through the actual USDT0 transfer, then verify child and root exposure + decrease and available capacity returns. + +### 6. Verify and record evidence + +Use the committed template as the shape of the final evidence packet: + +[coston2-evidence.template.json](templates/coston2-evidence.template.json) + +Record only observed values: + +- network, chain id, and resolver output; +- AccordRegistry, CapitalFacility, and instruction-sender addresses; +- extension id, active machine identity, round id, instruction ids, and result digest; +- root, child, draw, DrawLeg, funding, repayment, and closure transaction hashes; +- explorer links and timestamps; +- whether the TEE path was simulated or hardware-backed; +- any failure, retry, expiry, or replay result that affects the claim. + +For the completed 2026-08-13 run, use the recorded +[facility lifecycle workflow](https://github.com/etvjay/Concord/actions/runs/31734593116), +[facility evidence artifact](https://github.com/etvjay/Concord/actions/runs/31734593116/artifacts/9194681063), +[FCC workflow](https://github.com/etvjay/Concord/actions/runs/31733200629), and +[materialization recovery workflow](https://github.com/etvjay/Concord/actions/runs/31733740564) +as the public CI evidence trail. The exact hashes remain in the deployment JSON. + +## Hard stop conditions + +- Asset resolver output differs from the centralized config. +- The public proxy `/info` response is unreachable or bound to the wrong extension. +- The machine is not status `2` (`PRODUCTION`) or its availability check is stale. +- More than one active machine is registered for the extension. +- Any quote signature, expiry, fee bound, root binding, or provider eligibility check fails. +- A result is replayed, bound to another round/root/extension, or has no verified digest. +- A selected child is described as committed before the provider's USDT0 transfer succeeds. +- A repayment state change is recorded before the relevant ERC-20 transfer succeeds. +- Any step would require putting a private key or credential in Git. + +## Claim boundary + +A successful local test or resolver run does not prove the Concord vertical slice. +The MVP is proven only when the causal chain is visible with Coston2 evidence: + +~~~text +Root Accord → Makkari → CoFill → Child Accords → funded commitments +→ multi-child draw → real settlement → repayment → restored capacity → lineage +~~~ + +Do not claim private FXRP transfers, private USDT0 transfers, fully private EVM +execution, or production hardware-backed confidential execution from this path. + +## Official references + +- [Build Your First FCC Extension](https://dev.flare.network/fcc/guides/getting-started) +- [Coston2 network overview](https://dev.flare.network/network/overview) +- [FXRP address guidance](https://dev.flare.network/fxrp/token-interactions/fxrp-address) +- [Official FCE scaffold](https://github.com/flare-foundation/fce-extension-scaffold) diff --git a/docs/current-runtime.md b/docs/current-runtime.md new file mode 100644 index 0000000..8ef9582 --- /dev/null +++ b/docs/current-runtime.md @@ -0,0 +1,84 @@ +# Current Concord runtime checkpoint + +Observed `2026-08-14T18:37:32Z`. This is the latest hosted-runtime evidence; +it supersedes the earlier deployment-attempt notes that describe the +Workers.dev relay as backed by a stopped Codespace. + +## Verified now + +- Northflank project: `concord`, region `europe-west`. +- Proxy service: `concord-fcc-proxy`, build `SUCCESS`, deployment `COMPLETED`. +- Simulated TEE service: `concord-fcc-tee`, build `SUCCESS`, deployment + `COMPLETED`. +- Private Redis addon: `concord-redis`, status `running`. +- Proxy readiness and liveness: private `GET /healthy` on port `6663`. +- Public proxy port: `6664`, HTTPS hostname + `pub6664--concord-fcc-proxy--n4krppffn8ms.code.run`. +- Cloudflare relay: + `https://concord-fcc-ingress.microcosm.workers.dev`. +- Direct and relayed `/info`: HTTP `200`, extension `66188`, Coston2 chain ID + `114`, matching code/governance/platform bindings, and a proxy signature. + +The local sandbox's native resolver still cannot resolve the public hostnames; +the configured HTTPS egress reaches both public URLs and is the basis for this +checkpoint. That local DNS limitation is not presented as a hosted-runtime +health result. + +The Worker is a development relay to Northflank. It is not a named Cloudflare +Tunnel and does not establish production hosting or hardware-backed TEE +security. + +## Current simulated identity + +The live `/info` public key currently derives to: + +```text +0xb1e7a4c1930f1f3c4905b34fafc9c1b8359029a5 +``` + +This identity is generated by the simulated TEE process and is ephemeral. It +is not a substitute for an onchain registration record. At this checkpoint no +Coston2 registration, availability, pause, dispatch, or other broadcast was +performed for this Northflank identity. + +Read-only Systems Explorer inspection found these pre-existing records for the +same owner and extension: + +| TEE ID | Status | Interpretation | +|---|---|---| +| `0x65721B35EAF2648Fd061aB6901e1355ec2eCffd2` | `INITIALIZED` | Pre-existing, not active | +| `0xeE39d5e7d1C5043232282e3CC884B41a9Db22c85` | `PRODUCTION` | Pre-existing active record | + +The current identity must be registered before it can be presented as the +active Northflank machine. The stale `PRODUCTION` record must be paused only +after the replacement reaches `PRODUCTION`; the `INITIALIZED` record is left +untouched unless an operator explicitly decides otherwise. + +## Judge checks + +From a clean public checkout: + +```bash +./scripts/judge-check.sh +``` + +This verifies the recorded Coston2 evidence, local documentation gates, and +the public `/info` binding without credentials or transaction authority. During +an agreed live window, a runner with Go can add the read-only registry query: + +```bash +./scripts/judge-check.sh --registry +``` + +The optional registry mode never signs or broadcasts. It only passes once the +current hosted identity has been registered, has a fresh availability proof, +and exactly one active machine remains for extension `66188`. + +## Claims boundary + +The completed Coston2 economic proof remains the canonical submission proof: +Root Accord, FCC/CoFill result, funded Child Accords, multi-provider draw, +repayment, and restored capacity. The hosted Northflank runtime is a current +development-path availability checkpoint. Do not claim private token +transfers, private EVM execution, continuous production availability, or +hardware-backed TEE security from it. diff --git a/docs/experiments/coston2-failure-matrix.md b/docs/experiments/coston2-failure-matrix.md new file mode 100644 index 0000000..860a4f0 --- /dev/null +++ b/docs/experiments/coston2-failure-matrix.md @@ -0,0 +1,101 @@ +# Coston2 and lifecycle failure matrix + +Status: active pre-deployment experiment plan + +This matrix applies Experiment Foundry discipline to Concord's MVP. It tests +whether every state-changing path either rejects an invalid action or waits for +an authoritative onchain/FCC observation before describing the action as +successful. It does not expand Concord beyond the FXRP-backed syndicated +facility vertical slice. + +## Hypothesis + +Concord's contract, FCC extension, verifier, API, SDK, CLI, and MCP boundaries +preserve the same relationship semantics under malformed input, replay, expiry, +partial funding, insufficient capacity, and missing live evidence. No client +surface can turn an unsigned intent, local test actor, or simulated development +result into a committed capital, settlement, or production-confidentiality +claim. + +## Automated cases + +| Area | Failure or invariant case | Expected observation | +|---|---|---| +| FCC | invalid provider signature | quote rejected; no allocation slot | +| FCC | expired quote | quote rejected deterministically | +| FCC | fee above round policy | quote rejected deterministically | +| FCC | malformed action payload | strict decode failure | +| FCC | duplicate provider or nonce ambiguity | explicit failure or deterministic rejection | +| CoFill | equal fees and provider tie | same sorted result for repeated input | +| CoFill | partial final provider allocation | allocated capacity stops exactly at target | +| CoFill | insufficient eligible capacity | explicit failure; no partial success | +| Verifier | wrong root, round, extension, digest, or TEE binding | verification fails before materialization | +| Contract | repeated allocation result | replay rejected | +| Contract | selected child before provider transfer | root committed capacity remains unchanged | +| Contract | provider overfunds selected allocation | transfer rejected | +| Contract | malicious ERC-20 callback during funding | reentrant action rejected; outer transfer and commitment remain correct | +| Contract | draw over available capacity | draw rejected; exposure unchanged | +| Contract | draw across two children | explicit DrawLegs sum to draw principal | +| Contract | partial repayment | draw-leg, child, and root exposure decrease together | +| Contract | active child expires with commitment below target | root demotes from ACTIVE to FUNDING | +| Contract | provider withdrawal while exposure exists | withdrawal rejected | +| Contract | collateral recovery while exposure exists | recovery rejected | +| Control plane | agent/API/SDK/MCP prepare action | unsigned intent only; explicit approval remains true | +| Control plane | no observed read model | response is not_observed; no success claim | + +## Commands + +Run the deterministic suites from a clean checkout: + +~~~bash +forge test -vvv +(cd go && GOTOOLCHAIN=local go test ./...) +(cd tools && GOTOOLCHAIN=local go test ./...) +(cd sdk/typescript && npm install --ignore-scripts --no-audit --no-fund && npm run build) +./scripts/coston2-preflight.sh --offline +~~~ + +The shell and documentation gates also run in GitHub Actions. The preflight +offline mode validates the centralized Coston2 configuration without reaching +the network. + +## Live-gated cases + +These cases require current Coston2 credentials and a reachable official Flare +FCC scaffold/proxy. They are not claimed by local tests: + +- `/info` identifies the intended Concord extension and reports its code hash; +- the extension is registered and an active TEE machine is observed; +- signed `SUBMIT_QUOTE` and `FINALIZE_ROUND` instructions produce a verified + result bound to the intended root, round, extension, and digest; +- at least two selected providers transfer actual Coston2 USDT0; +- the root activates, a multi-child draw settles, and repayment restores + capacity; +- explorer receipts make the full relationship lineage queryable. + +## Evidence handling + +Record only observed values in the evidence packet: transaction hashes, +instruction IDs, result digest, extension ID, machine identity, asset +resolution, state transitions, and explorer links. Keep private quote contents, +provider constraints, signing keys, proxy credentials, and decrypted FCC +inputs out of GitHub, logs, fixtures, and screenshots. + +A simulated TEE may be used for development-path evidence when the current Flare +scaffold requires it. It must be labeled simulated development execution, not +production hardware-backed confidential execution. No test in this matrix +proves private FXRP transfers, private USDT0 transfers, or private EVM +settlement. + +## Decision rule + +The implementation may advance from this phase only when the automated cases +pass and the live-gated cases have real Coston2 artifacts. A green unit test, +frontend render, or mock FCC response is not sufficient to declare Concord's +vertical slice complete. + +## Related records + +- [Coston2 live runbook](../coston2-live-runbook.md) +- [Pre-deployment hardening](../pre-deployment-hardening.md) +- [Concord status](../CONCORD_STATUS.md) \ No newline at end of file diff --git a/docs/fcc-always-on-hosting.md b/docs/fcc-always-on-hosting.md new file mode 100644 index 0000000..9503441 --- /dev/null +++ b/docs/fcc-always-on-hosting.md @@ -0,0 +1,185 @@ +# Always-on Concord FCC development hosting + +This runbook moves Concord's current Coston2 FCC development runtime from the +disposable Codespace relay to a stable, long-running host. It does not alter +Accord, Makkari, CoFill, Lineage, or the completed facility evidence. + +## Current deployment checkpoint + +At the 2026-08-14 checkpoint, the Northflank development runtime is deployed +and reachable. Project `concord` runs in `europe-west` with proxy service +`concord-fcc-proxy`, simulated TEE service `concord-fcc-tee`, and private Redis +addon `concord-redis`. Both `/info` paths return HTTP `200`, extension `66188`, +and chain ID `114`: + +- Worker relay: `https://concord-fcc-ingress.microcosm.workers.dev` +- Direct Northflank proxy: `https://pub6664--concord-fcc-proxy--n4krppffn8ms.code.run` + +The private readiness path is `/healthy` on port `6663`; public FCC traffic is +on port `6664`. The Vercel frontend still has no public URL because the +connected team rejected Production deployment with HTTP 403 until the +integration receives Production Deploy permission. The current simulated TEE +identity is not registered on Coston2, and no registration, pause, availability, +dispatch, or other Coston2 broadcast is recorded for this deployment. + +The earlier blocked deployment attempt is retained in the source snapshots +below as historical context; it is superseded for current reachability by +`docs/current-runtime.md`. + +The exact source refresh for this attempt is recorded in +[`docs/source-snapshot-2026-08-14-deployment.yaml`](source-snapshot-2026-08-14-deployment.yaml). +The follow-up live blocker record is +[`docs/source-snapshot-2026-08-14-live-blocker.yaml`](source-snapshot-2026-08-14-live-blocker.yaml). + +## Recommended topology: Northflank + +Northflank is the primary target for the public Coston2 test because its +long-running services do not sleep, it supports private service DNS, stable +public HTTP domains, health checks, and a private Redis addon. The Northflank +sandbox is useful for the hackathon development runtime, but Northflank does +not describe the sandbox as production. + +Create one Northflank project with two combined services and one addon: + +1. `concord-fcc-proxy` from this repository, using + `infra/railway/proxy.Dockerfile`. Give port `6664` a stable public HTTPS + domain; keep port `6663` private. +2. `concord-fcc-tee` from this repository, using + `go/Dockerfile`. Keep ports `5501`, `7701`, and `7702` private. +3. A private Redis addon. Persistence is not an economic source of truth; do + not expose Redis publicly. + +The provider-facing path is direct: + +```text +Flare providers + -> https://stable-proxy-host/instruction :6664 + -> tee-proxy + -> private Northflank DNS :6663 + -> Concord Go extension TEE +``` + +### Proxy variables + +Store these as Northflank secret-group variables. Never commit their values. + +```text +PROXY_PRIVATE_KEY +REDIS_ENDPOINT=: +REDIS_PASSWORD= +INDEXER_DB_HOST +INDEXER_DB_PORT=3306 +INDEXER_DB_NAME +INDEXER_DB_USER +INDEXER_DB_PASSWORD +PORT=6664 +``` + +The runtime renderer writes the proxy TOML inside the container. Current +Coston2 system addresses are defaults in that renderer and are recorded in the +fresh source snapshot. Override `FLARE_SYSTEMS_MANAGER`, `FLARE_RELAY`, and +`VOTER_REGISTRY` if a newer official scaffold changes them. + +### TEE variables + +```text +CHAIN_ID=114 +CHAIN_URL=https://coston2-api.flare.network/ext/C/rpc +MODE=1 +SIMULATED_TEE=true +EXTENSION_ID=0x000000000000000000000000000000000000000000000000000000000001028c +INITIAL_OWNER= +GOVERNANCE_SIGNERS= +GOVERNANCE_THRESHOLD=1 +PROXY_URL=http://concord-fcc-proxy:6663 +CONFIG_PORT=5501 +SIGN_PORT=7701 +EXTENSION_PORT=7702 +LOG_LEVEL=INFO +``` + +Service names determine Northflank private DNS. If the proxy service has another +name, update `PROXY_URL` accordingly. + +The exact setup contract and environment templates are in +`infra/northflank/README.md`. + +The proxy's Redis endpoint and its MySQL-compatible Coston2 indexer database +are separate private dependencies. The official Coston2 scaffold requires the +indexer credentials; Redis cannot be used as an indexer substitute. + +## Identity and cutover rule + +The stable hostname survives a restart; the simulated TEE identity does not. +Restarting `concord-fcc-tee` creates a new `teeId`. The safe cutover is: + +1. Verify all three services and confirm the direct and relayed `/info` + responses report extension `66188`. +2. Read the current TEE identity from `/info` and, only after explicit operator + confirmation, register it through the current official scaffold tooling. +3. Wait for status `2` (`PRODUCTION`) and a fresh availability check. +4. Pause the stale identity. Maintain exactly one active machine for extension + `66188` and one machine per endpoint. +5. Run `./scripts/check-hosted-fcc.sh https://your-stable-domain 66188`. +6. Dispatch a new development-path instruction and retain the transaction, + proxy status, and result evidence before updating deployment records. + +Prepare the official `rRap` sequence with a temporary state file outside the +checkout (for example `/tmp/concord-register-tee.state`). A restart must not +automatically re-register its rotated simulated identity. + +Do not automate registration on every restart: that can accumulate stale active +machines and make provider selection appear random. Registration and stale +identity pause remain deliberate operator actions. + +The currently recorded historical machine is +`0xeE39d5e7d1C5043232282e3CC884B41a9Db22c85`, proxy identity +`0x801470C95f78D0cA444e589aF8Ea0858Ce6d613e`, status `2`, using the +Workers.dev-to-Codespace development relay. It remains historical evidence; it +is not evidence that the old upstream is continuously available. + +The current Northflank process reports simulated identity +`0xb1e7a4c1930f1f3c4905b34fafc9c1b8359029a5`. It is ephemeral and remains +unregistered. The registration, stale-machine pause, and any FCC dispatch are +deliberate operator actions; this repository's judge preflight never performs +them. + +## Railway fallback + +Railway can run the same two Dockerfiles with a private Redis service. Use the +checked-in Railway TOML files, a paid plan with `ALWAYS` restart, one replica, +and a stable public proxy domain. Free or trial Railway deployments must not be +described as continuously available. + +Use the same variables and the same identity cutover. The hosting provider +changes service DNS only; it does not change Concord or FCC semantics. + +## Reproducibility and operations + +- `frontend.yml` reproduces the frontend typecheck, semantic tests, bundle, and + production container. +- `hosted-fcc-smoke.yml` performs a manual, read-only `/info` and active-machine + registry check against any stable deployment. +- `proxy.Dockerfile` pins `tee-proxy` to the same `v0.0.18` line as the current + Concord extension and does not mix Flare dependency versions. +- Northflank configuration is recorded as an exact service checklist and + environment contract because platform secrets and generated addon endpoints + must remain outside Git. Import it into a GitOps template after the first + project is created and export Northflank's generated specification back to + `infra/northflank` for drift control. + +## Truth boundary + +This setup can improve development availability. It does not prove production +hardware-backed TEE security, private token transfers, private EVM execution, +or institutional production readiness. Coston2 USDT0/FXRP transfers and public +contract state remain public. + +Official platform references used for this decision: + +- Flare FCE scaffold: https://github.com/flare-foundation/fce-extension-scaffold +- Flare FCC getting started: https://dev.flare.network/fcc/guides/getting-started +- Northflank infrastructure as code: https://northflank.com/docs/v1/application/infrastructure-as-code/infrastructure-as-code +- Northflank private ports: https://northflank.com/docs/v1/application/network/configure-ports +- Northflank health checks: https://northflank.com/docs/v1/application/observe/configure-health-checks +- Railway config-as-code: https://docs.railway.com/config-as-code/reference diff --git a/docs/frontend-art-direction.md b/docs/frontend-art-direction.md new file mode 100644 index 0000000..1c8e716 --- /dev/null +++ b/docs/frontend-art-direction.md @@ -0,0 +1,596 @@ +# Concord frontend art direction and information architecture + +Status: first React implementation checkpoint, updated from the supplied +institutional porcelain reference. + +This document turns the first visual territory exploration into a complete +product direction. It governs the webapp's visual language, navigation, +responsive behavior, motion, imagery, typography, and user-facing page +architecture. It does not change Concord's product semantics. + +## Direction decision + +Concord uses a hybrid visual system: + +1. **Light Ledger** is the default product mode. +2. **Midnight Treasury** is the optional dark mode. +3. **Signal Atlas** is a supporting language for lineage, evidence, empty + states, onboarding, and selected marketing moments. + +This gives Concord institutional seriousness without making the first-use +experience feel like a terminal. The default interface is bright, calm, and +highly legible. Dark mode is not a separate brand or data model. + +The implemented public narrative follows progressive disclosure: + +1. outcome: one facility composed from multiple accountable providers; +2. participant value: treasury, provider, and shared understanding; +3. topology: one Root Accord and independently governed Child Accords; +4. journey: creation, private coordination, composition, funding, draw, and + restored capacity; +5. privacy boundary; and +6. recorded Coston2 proof. + +Protocol primitive names never have to disappear, but they arrive after the +human outcome they explain. + +The visual territory board is a reference study, not a production asset: +`docs/assets/concord-visual-territories-v1.png`. + +## Product feeling + +The intended feeling is: + +> “I understand what this relationship is, what is happening to the capital, +> and what I can safely do next.” + +The product should feel closer to a high-quality institutional treasury +workspace, a modern custody operations console, and an excellent banking +product than to a crypto exchange. + +### Qualities to optimize + +- calm rather than loud; +- precise rather than technical for its own sake; +- warm rather than sterile; +- globally legible rather than region-specific; +- evidence-aware rather than trust-me; +- beautiful through proportion, typography, and hierarchy rather than effects. + +### Explicitly avoid + +- generic SaaS dashboard sameness; +- neon DeFi styling; +- candlestick charts and yield theatrics; +- glassmorphism as a substitute for hierarchy; +- giant graphs with unreadable nodes; +- decorative “AI” language; +- stock photographs of traders, skyscrapers, or handshakes; +- fake transaction success, fake zeros, or fake privacy indicators. + +## Typography + +### Primary recommendation + +Use **Inter Variable** for interface, headings, labels, numerals, and body copy, +paired with the platform monospace stack for IDs, hashes, addresses, block +references, and exact base-unit values. Inter is self-hosted in the frontend +bundle; the system fallback remains responsible for scripts outside its +coverage and must be tested before a locale is declared supported. + +### Type roles + +```text +Inter Variable primary UI, headings, explanations, navigation, buttons +System mono IDs, hashes, addresses, exact units, technical evidence +System fallback unavailable font or unsupported glyph fallback +``` + +Do not add a display font merely to appear premium. Concord's premium quality +should come from rhythm, whitespace, rule placement, and information clarity. + +### Type scale + +```text +Display 40 / 46 facility title, only on spacious desktop views +Heading 1 28 / 34 page title +Heading 2 20 / 26 section title +Heading 3 16 / 22 card and table group title +Body 15 / 23 explanations and instructions +Body small 13 / 19 supporting metadata +Label 11 / 15 tracked state/eyebrow labels +Data 13 / 18 mono IDs and exact values +``` + +The scale is a starting token set. It must be tested with long organization +names, large amounts, and translated labels before being frozen. + +## Color and material system + +### Light Ledger tokens + +```css +:root { + --canvas: #f6f5f1; + --surface: #ffffff; + --surface-subtle: #efeee9; + --ink: #10161d; + --ink-muted: #66717c; + --rule: #d8dce0; + --rule-strong: #b8c0c7; + --proof: #087d68; + --proof-surface: #e1f3ed; + --pending: #a56a00; + --pending-surface: #fff1d5; + --blocked: #b8495b; + --blocked-surface: #fbe7eb; + --focus: #205cf3; +} +``` + +### Midnight Treasury tokens + +```css +[data-theme="dark"] { + --canvas: #0a0e12; + --surface: #121820; + --surface-subtle: #19212a; + --ink: #f4f1ea; + --ink-muted: #aeb8c0; + --rule: #2b353e; + --rule-strong: #52606c; + --proof: #7ee8c3; + --proof-surface: #14392f; + --pending: #e8b451; + --pending-surface: #3a2e17; + --blocked: #ee7a8b; + --blocked-surface: #3c2028; + --focus: #75a9ff; +} +``` + +Proof color means an observed/verified state only. It must not be reused for +primary buttons, decoration, or generic links everywhere. Pending and blocked +states always include text and icon cues in addition to color. + +### Surface rules + +- Use one main canvas, one raised surface, and one subtle surface. +- Prefer borders and contrast to heavy shadows. +- Use radius sparingly: `8px` for controls, `12px` for major panels, no + pill-shaped everything. +- Use corner details and container lines only at page/section scale. +- Avoid background textures in data-dense screens. +- Any grain or atlas texture belongs in onboarding/marketing, not the facility + ledger where it reduces contrast. + +## Global navigation + +### Desktop header + +Use one 72px global header. Do not add a permanent left sidebar to the first +version; it creates a second hierarchy and makes the product feel like an +internal admin tool. + +```text +┌──────────────────────────────────────────────────────────────────────┐ +│ Concord Facilities Activity Evidence Coston2 Wallet │ +└──────────────────────────────────────────────────────────────────────┘ +``` + +#### Header zones + +Left: + +- Concord wordmark; +- active product area: `Facilities`. + +Center: + +- `Facilities`; +- `Activity`; +- `Evidence` (visible to authorized users; advanced users can pin it); +- no protocol primitive names in the primary nav. + +Right: + +- network status: `Coston2` with observation indicator; +- connected wallet or organization identity; +- compact help/preferences menu. + +The header must remain stable across roles. The content and available actions +change by authorization, not the global navigation vocabulary. + +### Facility-local navigation + +Inside a facility, use a local sub-navigation row below the relationship +header: + +```text +Overview Funding Activity Evidence +``` + +`Overview` is the default. `Funding` contains the Makkari/CoFill/child +formation context. `Activity` contains public lifecycle history. `Evidence` +contains sources and verification. Draws and child relationships open from +the relevant rows/cards and do not need to become global nav items. + +### Mobile header and hamburger menu + +The mobile header is 64px: + +```text +☰ Concord wallet/status +``` + +The hamburger button is a 44px accessible target using Heroicons `bars-3`. +It opens a left-side navigation drawer with a scrim. It is not a “more” menu +and it must not contain the primary facility action. + +#### Drawer structure + +```text +Concord [close] + +WORKSPACE + Facilities + Activity + +TRUST & EVIDENCE + Evidence + Network status + +ACCOUNT + Wallet / organization + Appearance + Language and region + Help +``` + +Rules: + +- keep the drawer one level deep; +- show the active destination with label, icon, and a left rule; +- close on navigation, Escape, scrim click, or close button; +- trap focus while open; +- preserve scroll position when closing; +- keep the facility's next action outside the drawer; +- respect reduced-motion preferences; +- never put hidden protocol settings in the ordinary user menu. + +### Footer + +The authenticated application footer is quiet and functional. It is not a +marketing block at the end of every screen. + +```text +Concord Coston2 · chain 114 · Observed +Status Documentation Source Privacy / disclosure +``` + +On public onboarding or future marketing pages, the footer can expand into +product, developers, evidence, and legal columns. Those pages must not imply +production readiness or privacy guarantees that the implementation does not +prove. + +## Page architecture + +### Public and entry pages + +#### `/welcome` + +Purpose: explain Concord in one screen before wallet connection. + +```text +Coordinate one treasury need across independent capital providers. + +One facility. Multiple providers. Visible obligations. + +[Start as treasury] [Provide capital] [View a facility] +``` + +The page uses a restrained relationship animation or generated abstract visual, +not a fake screenshot of a live facility. + +#### `/connect` + +Purpose: establish wallet/organization context and network. + +Show: + +- what connecting enables; +- selected network; +- supported asset names and decimals; +- data disclosure boundary; +- wallet/organization approval step. + +Do not ask users to understand chain IDs before showing the human reason for +connection. + +### Core application pages + +#### `/` + +Facilities home. Show active and relevant facilities, a plain-language +explanation, search/filter, and one role-appropriate creation or participation +action. + +#### `/facilities/new` + +Treasury setup wizard. Four steps maximum: + +1. facility need: target liquidity, asset, expiry; +2. collateral and policy; +3. provider coordination settings; +4. review and wallet approval. + +Each step has a short “why we ask” explanation. Canonical IDs and policy hashes +are available in Detailed mode, not in the first form view. + +#### `/facilities/:rootAccordId` + +The primary relationship workspace. The first viewport shows: + +```text +facility identity → state → committed/drawn/available → next action +``` + +The causal relationship spine begins below the primary facts and can be +expanded. This page is not a graph explorer. + +#### `/facilities/:rootAccordId/funding` + +Funding view for the round, selected allocations, funded children, and +provider-specific actions. It clearly separates: + +```text +selected capacity ≠ funded commitment +``` + +#### `/facilities/:rootAccordId/activity` + +Chronological public lifecycle events, each linked to the relationship node +that authorized it. + +#### `/facilities/:rootAccordId/evidence` + +Evidence and disclosure page. It shows public source, observation state, +transaction references, FCC result metadata, and what remains withheld. It +does not claim private transfers or production TEE security. + +### Contextual detail pages + +These exist for deep linking, exports, and agents, but are reached from the +workspace rather than promoted as primary nav: + +- `/children/:childAccordId` — one provider relationship; +- `/draws/:drawId` — one root draw with explicit DrawLegs; +- `/rounds/:roundId` — Makkari/CoFill round detail; +- `/evidence/:resultDigest` — direct evidence lookup. + +### Settings and support + +`/settings` contains: + +- network and asset configuration; +- wallet/organization context; +- appearance and reduced-motion preference; +- language/region/number formatting; +- notification preferences; +- developer/API access where authorized. + +Do not build a separate admin console for MVP. Institutional approval and +organization controls should appear as role-scoped panels inside the same +relationship workspace until a demonstrated need requires separation. + +## Core screen behavior + +### Above-the-fold contract + +Every facility workspace must show, without scrolling: + +1. facility name and relationship identity; +2. current root state and observation state; +3. committed, drawn, and available liquidity; +4. expiry; +5. one next action or a reason no action is available. + +### Progressive disclosure layers + +```text +Layer 1 human facts and next action +Layer 2 relationship explanation and provider allocation +Layer 3 activity, receipts, and evidence +Layer 4 canonical IDs, calldata, fee bps, hashes, and block references +``` + +The interface should feel simple because the layers are ordered, not because +the protocol is falsely simplified. + +### Empty, loading, and error states + +Every page needs distinct states for: + +- loading/connecting; +- no facilities found; +- no facilities observed; +- partial related data; +- not observed; +- expired/frozen; +- wallet rejected; +- transaction submitted but not yet confirmed; +- receipt observed but projection not yet indexed; +- invariant failure. + +Never show a blank card or `$0`/`0 USDT0` when the value is unavailable. + +## Motion system + +Motion should explain relationship and state. It must never perform spectacle +around capital. + +### Motion primitives + +| Interaction | Motion | Purpose | +|---|---:|---| +| Drawer open/close | 180ms | establish navigation context | +| Local tab transition | 160ms | preserve page continuity | +| Expand relationship node | 220ms | reveal causal detail | +| New observed state | 240ms | acknowledge source update | +| Evidence receipt reveal | 320ms | guide the eye to provenance | +| Toast/notification | 180ms in, 160ms out | confirm UI state only | + +Use a standard ease-out curve for entry and ease-in for exit. No perpetual +movement, no auto-advancing carousel, no number animation from fake zero, and no +animation that suggests a transfer happened before the chain observation. + +### First-visit relationship animation + +On `/welcome`, a thin line can form: + +```text +treasury need → provider contributions → shared facility → repayment return +``` + +It should play once, finish quickly, and be replaceable by a static diagram for +reduced-motion users. The facility workspace uses a static or user-triggered +version of the same causal line. + +### Motion accessibility + +- respect `prefers-reduced-motion`; +- never communicate state through motion alone; +- allow keyboard users to reach every expanded state without animation; +- keep the action-review panel usable if all transitions are disabled. + +## Image and visual asset policy + +### Where images belong + +Images are appropriate for: + +- `/welcome` and onboarding; +- empty states; +- future education pages; +- a carefully constrained public product overview. + +Images do not belong behind dense facility metrics, child tables, or evidence +rows. + +### Visual language + +Use generated or commissioned abstract visuals built from: + +- relationship lines; +- measured grids; +- paper/metal/glass only when materially useful; +- restrained light and shadow; +- global, non-geographic abstractions; +- no token logos or financial clichés. + +The visual territory board is for selection and critique. Any final raster +asset must be generated or sourced only after its intended placement, crop, +contrast, alt text, loading cost, and licensing are documented. + +### Accessibility and performance + +- every meaningful image has alt text; +- decorative images use empty alt text and do not compete with content; +- first-load visuals must not block facility state; +- use responsive sizes and modern formats; +- motion/video must have a static poster and reduced-motion alternative; +- no text is baked into generated imagery when the text is part of the product + interface. + +## User-friendliness contract + +### Language + +Write like a careful human operator explaining capital movement: + +- “Your allocation was selected. Funding has not been observed yet.” +- “This draw uses 250,000 from Provider A and 150,000 from Provider C.” +- “The wallet request is ready. Nothing has been signed yet.” + +Avoid: + +- “Execute” when the user is only preparing a transaction; +- “Committed” for selected but unfunded capital; +- “Private” as a blanket claim; +- “Success” before a receipt and state observation; +- “Continue” when the next action is known. + +### Forms + +- one decision per step; +- show unit, token, decimals, and expiry near the input; +- show impact before wallet approval; +- preserve entered values after validation errors; +- explain rejection in plain language; +- provide a detailed view without forcing it on first-time users. + +### Trust cues + +Trust is expressed through: + +- source and observation timestamps; +- explicit public/withheld data labels; +- readable receipts; +- relationship lineage; +- invariant warnings; +- an unmistakable wallet approval boundary. + +Badges, shields, and green checkmarks are secondary cues only. + +## Institutional completeness gate + +The design is not complete until static prototypes demonstrate: + +1. treasury onboarding from `/welcome` to `/facilities/new`; +2. active facility overview with three child relationships; +3. selected-but-not-funded provider state; +4. multi-child draw with explicit DrawLegs; +5. repayment and restored capacity; +6. evidence and partial/not-observed states; +7. wallet action review and rejected/confirmed/not-observed outcomes; +8. provider view with another provider's private data withheld; +9. auditor read-only view; +10. developer/agent unsigned intent view; +11. mobile hamburger drawer and facility-local navigation; +12. light mode, dark mode, keyboard navigation, reduced motion, and long + localized strings. + +Reviewers must be able to complete the core journey without knowing the words +Accord, Makkari, or CoFill first. Reviewers must still be able to reach those +canonical concepts when they need to inspect lineage or evidence. + +## Design workflow from here + +```text +1. Select/adjust the hybrid visual direction +2. Freeze type, color, spacing, radius, and icon tokens +3. Produce static wireframes for the required states +4. Produce high-fidelity screen compositions +5. Define motion clips and reduced-motion alternatives +6. Run usability and semantic review against the contract +7. Only then implement React components +``` + +The first high-fidelity prototype should be the Root Accord workspace in +Light Ledger mode, with the mobile variant and action-review sheet designed in +the same pass. The first generated hero/onboarding image should remain outside +the core facility screen until the information hierarchy has passed review. + +## Source notes + +- [IBM Plex repository](https://github.com/IBM/plex) — UI-oriented family, + script coverage, and OFL licensing. +- [IBM Design Language typography](https://www.ibm.com/design/language/typography/typeface/) + — official distribution and licensing guidance. +- [Heroicons](https://heroicons.com/) — icon family and React/Vue library. +- [MengTo Skills](https://github.com/MengTo/Skills) — explicit design + constraints, hierarchy, and controlled iteration. +- [`shared-product-contract.md`](./shared-product-contract.md) — product and + authority semantics. +- [`frontend-design-system.md`](./frontend-design-system.md) — prior + component-level design and icon decisions. diff --git a/docs/frontend-design-system.md b/docs/frontend-design-system.md new file mode 100644 index 0000000..4579065 --- /dev/null +++ b/docs/frontend-design-system.md @@ -0,0 +1,563 @@ +# Concord frontend design system + +Status: first React implementation checkpoint. This document defines and now +audits the implemented interface. It remains subordinate to +[`shared-product-contract.md`](./shared-product-contract.md) and +[`frontend-map.md`](./frontend-map.md). + +## Design objective + +Concord should feel like a relationship workspace for real people moving +capital, not like a speculative trading dashboard or a generic DeFi pool. + +The first screen must let a treasury, provider, institution, auditor, or +developer answer six questions without decoding a graph: + +1. What relationship is this? +2. Who participates? +3. What was selected, funded, drawn, and remains available? +4. Which child relationships supplied the current exposure? +5. Why is the next action allowed? +6. What public evidence and lineage support the state? + +The interface is a readable relationship ledger. Visual topology supports the +explanation; it never replaces the explanation. + +## Governing design decisions + +### One product object + +The primary object is the Root Accord workspace. A transaction receipt, +provider quote, or draw is a linked event in that workspace, not the home +screen's organizing concept. + +### Progressive disclosure + +Every screen has two disclosure modes over the same read model: + +- **Guided**: plain-language labels, one permitted next action, short + explanations, and clear warnings. +- **Detailed**: canonical IDs, contract addresses, fee bps, terms commitments, + block/transaction references, FCC evidence, and full lineage. + +Guided mode is the default. Detailed mode is a user-controlled view preference, +not a different data model. + +### State before styling + +State labels must be derived from the observed projection. The UI must never +turn an unobserved value into `0`, a selected allocation into committed capital, +or a submitted wallet request into a confirmed action. + +Use these exact distinctions in copy: + +| Machine state | User-facing label | Required explanation | +|---|---|---| +| `SELECTED` | Selected — funding not observed | The provider was selected by CoFill, but its USDT0 transfer has not been observed. | +| `FUNDED` | Funded | The provider's USDT0 commitment transfer was observed. | +| `ACTIVE` | Active | The child relationship is participating in an active facility. | +| `EXPIRED` | Expired | The relationship is no longer valid for new financial actions. | +| `PARTIAL` observation | Partial data | Some linked state could not be observed from the configured source. | +| `NOT_OBSERVED` | Not observed | Concord has no authoritative observation and is not asserting a value. | + +### Real-world vocabulary first + +Use “facility”, “provider”, “funded”, “draw”, “repay”, and “available” in +headings and actions. Show “Root Accord”, “Child Accord”, “Makkari”, “CoFill”, +and canonical IDs in the relationship spine, detail panels, exports, and API +links. Do not force a new user to know protocol nouns before understanding the +economic relationship. + +## Visual direction + +### Style lane + +**Quiet financial operations / relationship ledger.** + +- Light-first neutral canvas with a dark ink text system. +- Dense enough for institutional work, calm enough for a first-time treasury + user. +- Thin rules and measured containers create structure; they do not become + decoration. +- One proof accent is reserved for verified/observed states. Pending and + blocked states use separate semantic colors plus text and icons. +- No crypto-market candlesticks, floating neon cards, or anonymous “AI” + surfaces. One restrained spectral Root Accord artifact is permitted on the + public landing page as the persistent relationship signature; it does not + enter data-dense workspace views. +- Elevation is restrained: borders and surface contrast carry hierarchy; + shadows are soft and rare. + +This is an interface for accountable capital coordination. The visual language +should communicate precision and calm, not yield or price excitement. + +### Layout constraints + +The desktop workspace content container is `1320px` maximum with responsive horizontal +padding: + +```css +--container-max: 1320px; +--container-pad: clamp(20px, 4vw, 48px); +``` + +Use a 12-column grid on desktop, 8 columns on tablet, and a single readable +column on mobile. The main shell has three levels: + +```text +global context bar + └─ relationship header + ├─ metrics band + ├─ state explanation / next action + ├─ relationship spine + └─ children, activity, evidence +``` + +Subtle one-pixel vertical container guides may appear at the outer content +edges on desktop and major sections. They must align with the real container, +sit behind content, use `pointer-events: none`, and disappear when they harm +mobile readability. Do not draw lines around every nested card. + +### Type hierarchy + +The type system is intentionally restrained: + +| Level | Use | Treatment | +|---|---|---| +| Display | facility name and one primary amount | strong sans, tight leading, never all caps for a sentence | +| Section | “Relationship”, “Children”, “Evidence” | medium/semibold, compact | +| Label | metric labels, state labels, role labels | small, tracked, sentence case unless canonical state | +| Body | explanations and instructions | readable line height, plain language | +| Data | IDs, hashes, addresses, base units | monospace, copyable, never used as marketing typography | + +Do not use more than two font families. The exact font can be chosen during +implementation, but it must prioritize legibility, numeral clarity, and broad +Unicode coverage for international names and organizations. + +### Amounts + +Every amount block has: + +```text +formatted amount + token symbol +human label +network / decimals on demand +exact base-unit value in the detail drawer +``` + +Never infer FXRP and USDT0 decimals from one another. Do not display a local +currency conversion as if it were an onchain value. If a fiat estimate is +added later, it must be explicitly labeled as an estimate with its source and +timestamp. + +## Screen composition + +### 1. Facility list `/` + +Purpose: orient the user without pretending the list is a portfolio analytics +product. + +```text +Concord Network · Wallet +Your facilities [Create Root Accord] +Short explanation of what a facility relationship is. + +[Search] [Role / state filter] [Guided · Detailed] + +Facility name State Committed Drawn Available Open +Treasury working cap Active 600,000 USDT0 400,000 200,000 → +... +``` + +Each row shows observation status and network. If the list is not observed, +show a non-zero loading/unknown state rather than an empty list that implies +there are no facilities. + +### 2. Root Accord workspace `/facilities/:rootAccordId` + +This is the canonical first screen. + +#### Header + +```text +ROOT ACCORD ACTIVE · Observed +Treasury working capital facility +Root ID 0x… [Copy] [Detailed] +FXRP-backed · USDT0 liquidity · expires 12 Aug 2026 + [Next action] +``` + +The primary action is state-derived. Examples: “Prepare FXRP lock”, “Review +selected providers”, “Fund your allocation”, “Prepare draw”, or “Prepare +repayment”. Never use a generic “Continue” button when the protocol knows the +specific next action. + +#### Metrics band + +Use four primary metrics in this order: + +```text +Target Committed Drawn Available +1,000,000 600,000 400,000 200,000 USDT0 +``` + +Under `Committed`, show `600,000 funded` and, when relevant, a separate +`250,000 selected · not funded` figure. The selected figure must not be added +to committed capacity. + +#### State explanation + +A short sentence explains the observed state and the reason for the next +action. Example: + +> This facility is active. Three child relationships have funded 600,000 +> USDT0; 400,000 is currently drawn, leaving 200,000 available. + +If an invariant is not observed or fails, this area becomes a prominent +warning with a link to evidence. It must not be hidden in a developer panel. + +#### Relationship spine + +Use a horizontal stepper on wide screens and a vertical sequence on narrow +screens: + +```text +Root Accord → Makkari → CoFill → Child Accords → Draw → Repayment +``` + +Each node is a compact, expandable relationship card: + +- what this node means in plain language; +- current state; +- observed timestamp/block; +- number of linked records; +- “View details” affordance. + +The Child Accords node expands into provider cards. The Draw node expands into +explicit DrawLegs. Do not draw arbitrary edges between every record; the +causal order is the visual hierarchy. + +#### Child table + +Columns in Guided mode: + +```text +Provider State Funded Drawn Available +Provider A Funded · Active 250,000 250,000 0 +Provider B Selected · funding pending — — — +Provider C Funded · Active 350,000 150,000 200,000 +``` + +Detailed mode adds child ID, fee bps, terms commitment, valid-until, funding +transaction, and source block. Losing quotes and private constraints remain +withheld unless the role is authorized to see them. + +#### Activity and evidence + +Use two adjacent sections rather than a single undifferentiated feed: + +- **Activity**: human-readable lifecycle events ordered by time. +- **Evidence**: source, observation status, transaction/hash links, FCC result + digest, and disclosure boundary. + +An activity item always links back to the relationship node that authorized it. + +### 3. Makkari / CoFill round `/rounds/:roundId` + +This screen explains confidential coordination without exposing confidential +inputs by default. + +```text +SYNDICATION ROUND +Objective Fill 1,000,000 USDT0 +Policy Maximum fee 680 bps +Expiry 12 Aug 2026 +Status Allocation verified + +Makkari session → CoFill result → materialization handoff + +Selected capacity Funded capacity Quotes withheld / visible by role +``` + +Authorized provider view: its own quote and status. Treasury view: accepted +providers, accepted executable capacity, terms commitments, and verification +metadata. Observer view: public result and disclosure notice. No role gets a +visualized “losing quote” by accident. + +### 4. Child Accord `/children/:childAccordId` + +Provider-focused but still rooted in the parent relationship: + +```text +CHILD ACCORD +Provider relationship under Treasury working capital facility +Parent Root Accord → [open] + +Your allocation Funded Drawn Available Valid until +250,000 USDT0 250,000 250,000 0 12 Aug 2026 + +[Funding / exposure timeline] +[Terms and evidence] +``` + +The provider must see a clear boundary between an allocation offer and a +successful funding transfer. + +### 5. Draw `/draws/:drawId` + +The draw screen must make split supply obvious: + +```text +DRAW D-01 400,000 USDT0 +Authorized by Root Accord Active · Observed + +Draw legs +├─ Child Accord A / Provider A 250,000 +└─ Child Accord C / Provider C 150,000 + +Principal remaining · settlement receipt · repayment preparation +``` + +Never flatten the draw into a single provider or a single anonymous transfer. + +### 6. Evidence `/evidence/:resultDigest` + +Evidence is a provenance view, not a claim of private execution. + +Show: + +- result digest and linked round/root IDs; +- verification status and verifier contract/source; +- network, chain ID, block, and transaction references where observed; +- what is public, what is withheld, and what was not observed; +- simulated/development TEE labeling where applicable. + +Do not use a shield icon alone to imply “private” or “secure”. The disclosure +boundary must be written in text. + +## Role and permission behavior + +The role selector is a view of authorized scope, not a cosmetic theme switch. +In production, role context comes from the caller's wallet/organization +authorization. A demo-only role switch must be visibly labeled as a preview. + +| Role | Default landing emphasis | Visible actions | +|---|---|---| +| Treasury | facility lifecycle and available capacity | prepare authorized root, draw, repay, close actions | +| Provider | eligible rounds and own child relationships | submit own quote, fund own child, inspect own exposure | +| Institution/operator | approvals, evidence, multiple facilities | delegated preparation within granted scope | +| Auditor/observer | public state, evidence, lineage | read-only | +| Developer/agent | structured projection and intent details | read and prepare unsigned intents; never sign/broadcast | + +Disabled actions must explain the missing precondition. Do not hide an action +when its existence helps explain the lifecycle; disable it with a reason when +the caller can see the relationship but cannot act. + +## Action review pattern + +Every write uses the same review surface. It is a side panel on desktop and a +full-screen sheet on mobile. + +```text +1. What will happen? + Prepare a 250,000 USDT0 funding transfer for Child Accord … + +2. Why is it allowed? + Selected allocation observed; amount is within the selected capacity. + +3. What will the wallet receive? + Network · chain ID · target contract · value · calldata disclosure + +4. What must still be verified? + Wallet signature, transaction receipt, and indexed state observation. + + [Cancel] [Review in wallet] +``` + +After approval, show `Awaiting signature`, `Submitted`, `Confirmed`, +`Reverted`, or `Not observed`. “Confirmed” requires an observed receipt and +the relevant state read; a wallet callback alone is not confirmation. + +## Heroicons usage rules + +Heroicons is the single icon family for the first frontend. Use the React +library rather than copying arbitrary SVG markup into components. Keep icons +supportive and labeled; the product's meaning comes from text and state. + +### Style and sizing + +- Outline icons for navigation, buttons, metadata, and relationship actions. +- Solid icons only for compact status markers where the adjacent text remains + visible. +- Default control size: `20px`; standard relationship/status size: `24px`; + empty-state illustration maximum: `32px`. +- Use one icon per action. Do not stack multiple decorative icons beside a + short label. +- Icon-only controls require an accessible name and a visible tooltip or + adjacent context; primary actions are always text-labelled. +- Do not use token-specific currency icons. Keep `FXRP` and `USDT0` as text + symbols with asset metadata. + +### Concord icon matrix + +| Meaning | Heroicon | Variant / use | +|---|---|---| +| Facility / relationship | `rectangle-group` | outline, Root Accord header | +| Parent-child lineage | `share` | outline, lineage affordance | +| Provider group | `user-group` | outline, participant summary | +| Confidential boundary | `lock-closed` | outline, Makkari explanation; never privacy proof by itself | +| Allocation / composition | `squares-2x2` | outline, CoFill node | +| Selected / queued | `queue-list` | outline, selected but not funded | +| Funded / committed | `banknotes` | outline, funded capacity label | +| Draw | `arrow-down-tray` | outline, liquidity entering treasury | +| Repayment | `arrow-up-tray` | outline, liquidity returning | +| Verified evidence | `shield-check` | solid/outline with text “Verified” | +| Public receipt | `receipt-percent` | outline, settlement/fee evidence | +| Partial observation | `ellipsis-horizontal-circle` | outline plus “Partial data” | +| Not observed / withheld | `eye-slash` | outline plus explicit text | +| Warning / invariant issue | `exclamation-triangle` | outline plus sentence-level warning | +| Expiry | `clock` | outline, valid-until metadata | +| Frozen / unavailable | `pause-circle` | outline plus reason | +| Copy ID | `document-duplicate` | outline, accessible label “Copy Root Accord ID” | +| External explorer | `arrow-top-right-on-square` | outline, external link | +| Network settings | `server-stack` | outline, network context | +| Wallet boundary | `wallet` | outline, connection/approval context | + +Status meaning is always encoded by icon + label + shape/placement. Color is a +secondary cue only. + +## Responsive behavior + +### Desktop + +- One stable global header for facilities, activity, evidence, network, wallet, + and settings; no permanent left sidebar in the first version. +- Relationship workspace uses the full container; metrics stay above the + fold. +- Relationship spine is horizontal; child table remains tabular. + +### Tablet + +- Collapse left navigation into a top navigation row. +- Keep the metric band in two rows if needed; never shrink numbers below + readable size to preserve one row. +- Relationship spine may wrap to two rows but retains left-to-right causal + order. + +### Mobile + +- One column, with a sticky relationship header containing state and the next + action. +- Metrics become stacked cards with `Available` first when a draw is active. +- Relationship spine becomes a vertical numbered sequence. +- Child rows become provider cards; DrawLegs remain individually visible. +- Action review becomes a full-screen sheet with the final wallet action fixed + to the bottom safe area. +- Never hide evidence or lineage behind a graph-only interaction. + +## Motion + +Motion is functional and bounded: + +- 150–220ms for state changes and panel transitions; +- no perpetual shimmer after data has loaded; +- no animation that changes the apparent amount or state without an explicit + observation update; +- respect `prefers-reduced-motion`; +- use a one-time reveal for the causal spine only when it helps the user read + order, not as a branded spectacle. + +## Accessibility and international use + +- Target WCAG 2.2 AA for the implemented component stack. +- Keyboard access for every disclosure, filter, table action, and wallet step. +- Visible focus indicator independent of the semantic state color. +- Screen-reader labels include relationship type and ID where needed. +- Use locale-aware grouping and dates, but expose the exact base unit and + decimals in details. +- Do not assume USD, English date order, a two-party relationship, or a + borrower/lender vocabulary in generic components. +- Support long organization names, non-Latin names, right-to-left locale + testing, and narrow screens before launch. + +## First design review gate + +Before any frontend implementation, the design review must show static +wireframes or a design prototype for: + +1. an active Root Accord with three children and a two-child Draw; +2. a selected-but-not-funded provider; +3. a partial/not-observed read response; +4. a repayment action review and receipt state; +5. the same facility in Guided and Detailed modes; +6. desktop, tablet, and mobile layouts. + +Acceptance means a reviewer can trace: + +```text +Root Accord → Makkari → CoFill → Child Accord → DrawLeg → Repayment +``` + +without opening a developer console, and can distinguish selected capacity +from funded committed capacity without reading protocol documentation. + +## Design-first prompt card + +Use this as the fixed prompt for future visual prototypes and iterations: + +```text +GOAL +Design the Concord Root Accord relationship workspace for a treasury and its +independent liquidity providers. Make the persistent relationship legible +before showing transaction detail. + +FORMAT +Desktop 1440px first; responsive tablet and mobile variants. +Content max 1120px; safe horizontal padding clamp(20px, 4vw, 48px). + +LAYOUT +Global context → Root Accord header → metrics band → state explanation and +next action → causal relationship spine → child relationships → activity and +evidence. Use a 12-column editorial grid and quiet 1px container guides. + +TYPE +Legible sans for headings/body; monospace only for IDs, hashes, addresses, and +exact base units. Tight headings, readable body copy, tracked small labels. + +COLOR + MATERIAL +Neutral light canvas, dark ink text, restrained borders, one proof accent, +explicit pending and blocked states. No rainbow gradient, no glassmorphism, +no speculative-trading visual language. + +ICONOGRAPHY +Heroicons React outline icons at 20–24px, one icon per action, always paired +with a label. Use the Concord icon matrix; do not invent token icons. + +COPY +Use “Selected — funding not observed”, never “Committed” before USDT0 funding. +Use “Not observed” instead of zero when authoritative state is unavailable. + +CONSTRAINTS +RELATIONSHIP-FIRST · GUIDED-BY-DEFAULT · EVIDENCE-VISIBLE · WALLET-APPROVAL-EXPLICIT + +NEGATIVE PROMPT +No fake dashboard metrics, no generic “Continue”, no flattened provider pool, +no private-transfer claims, no hidden losing quotes, no decorative graph that +cannot explain causal authorization, no text baked into generated imagery. +``` + +## Sources and skill application + +- [Heroicons](https://heroicons.com/) — current catalog/version, MIT license, + and React/Vue library availability. +- [MengTo Skills](https://github.com/MengTo/Skills) — public skill collection + and design-first principle of explicit specs, constraints, hierarchy, and + one-variable iteration. +- [Concord shared product contract](./shared-product-contract.md) — source of + truth for lifecycle, roles, amounts, observations, and intent boundaries. +- [Concord frontend map](./frontend-map.md) — route and component baseline. + +The exact Interface Foundry package and the local MengTo package are not +currently present in the pruned workspace. This document records the design +decisions so the frontend remains reproducible instead of depending on +ephemeral skill files. diff --git a/docs/frontend-map.md b/docs/frontend-map.md new file mode 100644 index 0000000..313345a --- /dev/null +++ b/docs/frontend-map.md @@ -0,0 +1,195 @@ +# Concord frontend map + +This is the frontend implementation map for the first product phase. It is a +map over the shared product contract, not a second protocol design. Full UI +implementation follows the live contract/FCC integration evidence gate. + +## Product shell + +```text +┌──────────────────────────────────────────────────────────────────────┐ +│ Concord Facilities Docs Help Network Wallet │ +├──────────────────────────────────────────────────────────────────────┤ +│ Back to parent · Facilities / Current facility / Current detail │ +├──────────────────────────────────────────────────────────────────────┤ +│ Root Accord · state · FXRP collateral · committed · drawn · available │ +│ expiry · plain-language state · next permitted action │ +├──────────────────────────────────────────────────────────────────────┤ +│ Relationship spine: Root → Makkari → CoFill → children → draw legs │ +├──────────────────────────────────────────────────────────────────────┤ +│ Child Accords · provider · selected · funded · drawn · available │ +├──────────────────────────────────────────────────────────────────────┤ +│ Activity · evidence · action review │ +└──────────────────────────────────────────────────────────────────────┘ +``` + +The first version uses one global header and no permanent desktop sidebar. +Facilities is the sole product-level destination; Docs and Help are supporting +utilities. A simple in-content parent return and breadcrumb state where the +user is. Once inside a facility, stable tabs expose Overview, Funding, +Activity, Evidence, and Lineage. There are no global Next/Previous controls and +no mobile bottom navigation duplicating those tabs. On mobile, the header +collapses into an accessible hamburger drawer and the parent return remains +visible. + +Human-first page titles carry a smaller canonical label such as `ROOT ACCORD`, +`MAKKARI SESSION`, or `DRAW · DRAW LEGS`. Definitions are available in place +through an information control. A dismissible first-use primer, optional +five-step tour, and persistent Help/glossary menu teach the product without +interrupting repeat use. + +The relationship view is the primary screen. A chart or graph is subordinate +to readable relationship cards and exact amounts; the graph is not decoration +and cannot be the only way to understand causality. + +## Routes + +| Route | Purpose | Required contract data | +|---|---|---| +| `/` | Outcome-first product story, wallet entry, and public proof | recorded evidence, network identity | +| `/facilities` | Facility register | accessible/recorded facilities and current state | +| `/facilities/:rootAccordId` | Primary Root Accord workspace | facility, round, children, invariants | +| `/facilities/:rootAccordId/funding` | Formation and funded Child Accords | round, allocation, funded children | +| `/facilities/:rootAccordId/activity` | Causally ordered lifecycle activity | facility events and receipts | +| `/facilities/:rootAccordId/evidence` | Facility verification boundary | FCC and public-chain evidence | +| `/facilities/:rootAccordId/lineage` | Full causal relationship trail | lineage, linked public actions | +| `/rounds/:roundId` | Makkari/CoFill status | round, authorization-scoped evidence | +| `/children/:childAccordId` | Provider relationship view | child, root summary, funding/exposure | +| `/draws/:drawId` | One draw and its explicit legs | draw, child/provider links, settlements | +| `/evidence/:resultDigest` | Verification and disclosure boundary | evidence metadata, source, warning | +| `/settings` | Network/assets and disclosure settings | network config, asset decimals | + +## Wayfinding contract + +The facility workspace has one stable hierarchy: + +```text +Facilities → Facility → {Overview | Funding | Activity | Evidence | Lineage} +``` + +Detail objects return to the section that owns them: rounds and Child Accords +return to Funding, draws return to Activity, and allocation results return to +Evidence. Each detail exposes a breadcrumb and labelled parent return. There is +no forced lifecycle tour in normal navigation; users choose the facility tab +that answers their question. Route changes reset the document to the top so the +destination title and context are never hidden by a previous page's scroll +position. + +## Components + +```text +AppShell +├─ RoleContext +├─ NetworkStatus +├─ WalletBoundary +├─ FacilityList +└─ FacilityWorkspace + ├─ RelationshipHeader + ├─ FacilityMetrics + ├─ StateExplanation + ├─ NextActionCard + ├─ RelationshipTopology + │ ├─ RootAccordNode + │ ├─ MakkariNode + │ ├─ CoFillNode + │ ├─ ChildAccordNode[] + │ └─ DrawLegNode[] + ├─ ChildAccordTable + ├─ ActivityTimeline + ├─ EvidencePanel + └─ ActionReviewModal + ├─ HumanSummary + ├─ Preconditions + ├─ ContractTarget + ├─ WalletRequest + └─ ReceiptResult +``` + +The `ActionReviewModal` never displays “completed” merely because a wallet +request was submitted. Its states are `draft`, `awaiting_signature`, +`submitted`, `confirmed`, `reverted`, and `not_observed`. + +## Role surfaces + +### Treasury + +The treasury sees the facility lifecycle in order: + +1. Create Root Accord. +2. Lock FXRP collateral. +3. Open the Makkari round. +4. Review the verified CoFill allocation. +5. Monitor selected versus funded children. +6. Draw only within available capacity. +7. Repay and verify restored capacity. + +The primary call-to-action is derived from the authoritative root state, not a +generic “continue” button. + +### Provider + +The provider sees eligible rounds, its own confidential quote workflow, and its +own child relationships. A selected card must say “selected, not funded” until +the USDT0 transfer succeeds. Provider data must never disclose losing quotes or +another provider's confidential constraints. + +### Institution/operator + +The institution view adds organization context, approvals, evidence export, +and activity filtering. It uses the same Root Accord, Child Accord, exposure, +and lineage fields as the treasury view. + +### Auditor/observer + +The observer view is read-only and emphasizes public commitments, transfers, +exposure, transaction receipts, and lineage. Private quote fields are visibly +marked `withheld`, not shown as empty or zero. + +### Developer/agent + +The agent surface is a compact API/MCP-oriented view. It can inspect the same +projection, explain why an action is available, and prepare an unsigned intent. +It must show the human approval boundary before handing anything to a wallet. + +## State-to-action map + +| Observed state | Guided explanation | Primary action | +|---|---|---| +| `PROPOSED` | “Your facility exists. Collateral is the next requirement.” | Prepare FXRP lock | +| `SYNDICATING` | “Providers are privately offering capacity.” | Monitor round / authorized quote action | +| `FUNDING` | “Providers were selected; funding is still being transferred.” | Provider funding / monitor | +| `ACTIVE` | “The facility is fully funded.” | Prepare draw | +| Draw outstanding | “This draw is supplied by these child relationships.” | Prepare repayment | +| Repaid | “Exposure fell and capacity returned.” | Review receipt / next draw | +| `CLOSED` | “The relationship is complete.” | View lineage and evidence | +| `EXPIRED` or `FROZEN` | “New financial actions are unavailable.” | View reason and evidence | + +## Global and accessible behavior + +- Use the user's locale for dates and number grouping, while retaining exact + base-unit values in detail and copy-to-clipboard controls. +- Display token symbol, decimals, network, and contract address together when + an amount could be misunderstood. +- Never rely on color alone for state; use text, icons with labels, and status + announcements. +- Support keyboard navigation, reduced motion, readable contrast, zoom, and + narrow screens. The target is WCAG 2.2 AA where the chosen component stack + supports it. +- Use human-readable labels first and canonical IDs on demand. Do not shorten + two different identifiers to the same visible prefix in the same view. +- Surface stale, partial, and not-observed data as explicit states. +- Avoid regional assumptions in currency formatting, date order, language, and + legal or institutional terminology. + +## Frontend implementation boundary + +The eventual React/TypeScript app should consume `@concord-protocol/sdk` and +never call contract methods directly from arbitrary components. Wallet calls +are made from an action boundary after the API/SDK returns an unsigned intent. +This keeps the same semantics across web, CLI, REST, MCP, and institution +integrations. + +The current wallet foundation intentionally stops before that write boundary: +it connects an injected wallet, switches to Coston2, and reads the native +balance. It does not infer a treasury/provider role from an address and does +not submit facility transactions. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..72e2aa8 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,123 @@ +# Getting started + +The runtime foundation is the official Flare FCE scaffold; the Concord +implementation is the Go extension under go/. + +## Local verification + +Use an installed Foundry and Go toolchain, or configure your own toolchain +locations before running these commands: + +~~~bash +forge test +(cd go && GOTOOLCHAIN=local go test ./...) +(cd tools && GOTOOLCHAIN=local go test ./...) +~~~ + +The Solidity flow test covers root creation, FXRP collateral, a verified +allocation, two funded child Accords, a multi-child draw, repayment, restored +capacity, child closure and root closure. The Go tests cover encrypted-action +dispatch, signed quote validation, deterministic ordering, partial final +allocation, invalid signatures and insufficient capacity. + +Generate the instruction-sender binding after changing its ABI: + +~~~bash +./scripts/generate-bindings.sh +~~~ + +## Coston2 asset resolution + +The current target is Coston2, chain ID 114. Resolve and validate live asset +metadata before deployment: + +~~~bash +./scripts/resolve-coston2-assets.sh +~~~ + +The resolver obtains AssetManagerFXRP from Flare's ContractRegistry and then +calls fAsset(). USDT0 is kept in the centralized Coston2 network config and +validated by live symbol, decimals and bytecode because it is not currently a +named entry in the checked-in Flare ContractRegistry package. + +## Deployment inputs + +Concord deployment is intentionally two-phase: the FCC instruction sender is +deployed and registered first, then the economic facility is deployed with +the resulting extension id. Do not deploy the facility with a guessed id. + +The official scaffold lifecycle remains available through pre-build.sh. +It deploys the Concord instruction sender with the configured scaffold +registries, registers the extension, and writes the live values to +config/extension.env: + +~~~bash +export CHAIN=coston2 +export DEPLOYMENT_PRIVATE_KEY=0x... +./scripts/pre-build.sh +source ./config/extension.env +~~~ + +For a standalone Foundry sender deployment, use the explicit sender phase +and complete FCC registration through the official scaffold tooling before +running the facility phase: + +~~~bash +export DEPLOYMENT_OWNER=0x... +export DEPLOYMENT_PRIVATE_KEY=0x... +export TEE_EXTENSION_REGISTRY=0x... +export TEE_MACHINE_REGISTRY=0x... +DEPLOY_PHASE=sender ./scripts/deploy-coston2.sh +~~~ + +After registration, set CONCORD_EXTENSION_ID to the registered extension +id and deploy the Accord/facility contracts: + +~~~bash +export DEPLOYMENT_OWNER=0x... +export DEPLOYMENT_PRIVATE_KEY=0x... +export ALLOCATION_VERIFIER=0x... +export CONCORD_EXTENSION_ID=0x... +DEPLOY_PHASE=facility ./scripts/deploy-coston2.sh +~~~ + +The TEE registry values are normally the Coston2 FlareTeeManager diamond +address used by the official scaffold tooling. The script does not guess +them. Keep private keys in an ignored .env, never in source or compose +defaults. + +## FCC sequence + +Registration and proxy setup follow the current Flare FCE scaffold sequence: + +~~~text +register extension → start official proxy/TEE scaffold → submit encrypted quotes +→ finalize encrypted round → verify result offchain → mark digest → materialize children +~~~ + +The runner writes a signed FCC action-evidence envelope with `-out`. +The verifier rechecks the TEE action signature, proxy-signed TEE identity, +active machine binding, facility extension id, open round, root binding, +target capacity, fee limits, eligible providers, and canonical result digest. +It is read-only unless `-mark` is supplied: + +~~~bash +cd tools +go run ./cmd/verify-allocation -c https://coston2-api.flare.network/ext/C/rpc -facility 0x... -teeRegistry 0x... -result ../evidence/allocation.json -extensionId 0x... -roundId 0x... -rootAccordId 0x... -out ../evidence/allocation-verification.json + +# Only after the local checks pass, with DEPLOYMENT_PRIVATE_KEY set to +# the facility allocationVerifier address: +go run ./cmd/verify-allocation -c https://coston2-api.flare.network/ext/C/rpc -facility 0x... -teeRegistry 0x... -result ../evidence/allocation.json -extensionId 0x... -roundId 0x... -rootAccordId 0x... -mark -out ../evidence/allocation-verification.json +~~~ + +The sender, FCC extension registration, and facility deployment observations are recorded in `docs/CONCORD_STATUS.md`; local tests still do not prove the live lifecycle. See +CONCORD_STATUS.md for the current evidence boundary. + +## Confidentiality mode disclosure + +For the Coston2 development path, `SIMULATED_TEE=true` selects the documented +simulated attestation mode. This is useful for development verification but +must not be described as production hardware-backed confidential execution. +Private provider inputs and allocation computation remain within the intended +confidential boundary; FXRP and USDT0 transfers and required relationship state +remain public where the facility requires them. diff --git a/docs/lineage.md b/docs/lineage.md new file mode 100644 index 0000000..91ff12c --- /dev/null +++ b/docs/lineage.md @@ -0,0 +1,25 @@ +# Lineage + +The relationship graph is queryable through AccordRegistry. + +~~~text +ROOT_ACCORD + └─ MAKKARI_SESSION + └─ COFILL_ALLOCATION + └─ CHILD_ACCORD + └─ DRAW + └─ DRAW_LEG + └─ SETTLEMENT + └─ REPAYMENT + └─ SETTLEMENT +~~~ + +The facility registers nodes only after the corresponding economic boundary is +valid. A draw is registered only after its USDT0 transfer succeeds; every leg +points back to the child relationship that supplied that portion. A repayment +is registered only after the borrower's transfer succeeds and carries the draw +as its parent. + +The getChildren(parentId) query is the minimal graph traversal primitive. +Relationship state remains in CapitalFacility, while AccordRegistry answers +which relationship authorized a derived action. diff --git a/docs/pre-deployment-hardening.md b/docs/pre-deployment-hardening.md new file mode 100644 index 0000000..710f605 --- /dev/null +++ b/docs/pre-deployment-hardening.md @@ -0,0 +1,94 @@ +# Pre-deployment hardening + +Status: active implementation phase + +This document defines the work that can be completed before Concord has live +Coston2 contracts, funded accounts, or a verified FCC result. It is a delivery +and repository gate; it does not change Concord Product Truth or expand the MVP. + +## Protected product boundary + +The MVP remains: + +```text +FXRP-backed Root Accord + → private provider offers through Makkari / FCC + → deterministic CoFill allocation + → Root + Child Accords + → actual USDT0 funding + → active facility + → multi-child draw with DrawLegs + → repayment + → restored capacity and queryable lineage +``` + +Selected capacity is not committed capacity. A child becomes funded only after +the provider USDT0 transfer succeeds. Public settlement and relationship state +remain distinct from confidential provider inputs and FCC evidence. + +## Gates that can run immediately + +### Repository and toolchain + +- GitHub Actions runs the Solidity, Go extension, Go tooling, TypeScript SDK, + shell syntax, dependency-pin, and documentation checks. +- Go is set to 1.25.1, matching the repository modules and official scaffold + baseline. +- The Coston2 asset resolver is available as a manual workflow because it is a + live RPC check, not a deterministic unit test. +- `scripts/coston2-preflight.sh` validates the centralized Coston2 config offline + and can re-check RPC chain ID, public bytecode, and FCC proxy `/info` live. + +### Contract and extension correctness + +- Solidity tests cover Root Accord creation, FXRP collateral, verified and + replay-protected allocation materialization, selected versus funded child + capacity, multi-child draws, DrawLegs, repayment, exposure, closure, expiry, + active-child demotion, malicious ERC-20 callbacks, and balance recovery. +- Go tests cover strict payload decoding, provider signature validation, + expiry and policy rejection, deterministic ordering, partial allocation, and + explicit insufficient-capacity failure. +- The verifier remains an offchain evidence boundary. A local test actor is not + treated as a native FCC proof. + +### Shared control plane + +REST/OpenAPI, the TypeScript SDK, MCP, CLI, and frontend must consume the same +read model and intent rules documented in +[shared-product-contract.md](shared-product-contract.md). Write-capable surfaces +prepare unsigned intents and require explicit user approval. They do not hold +keys, sign, broadcast, or report success before an authoritative receipt is +observed. + +### Product surface preparation + +The frontend may be fully designed during this phase: routes, role journeys, +guided and detailed disclosure modes, state explanations, error states, +lineage presentation, accessibility, and motion. Full implementation against +live state remains downstream of the contract/FCC evidence gate. + +## Deployment-gated work + +The following cannot be truthfully completed by local tests alone: + +- FCC extension registration and a supported proxy/TEE run; +- signed provider offers entering the live Coston2 confidential path; +- verified allocation materialization on deployed Concord contracts; +- actual USDT0 provider funding and root activation; +- a multi-child draw, ERC-20 settlement, repayment, and restored capacity; +- deployment addresses, instruction IDs, transaction hashes, result digests, + and explorer evidence. + +The official Flare FCE scaffold and current Flare documentation remain the +authority for that runtime path. A simulated development TEE must not be +described as production hardware-backed confidential execution. + +## Exit condition for this phase + +This phase is complete when the CI workflow is green, the documentation gate +describes Concord rather than inherited scaffold examples, contract and Go +tests run in a clean environment, SDK compilation succeeds, and the live +evidence checklist is ready to receive Coston2 artifacts. + +It does not constitute the final hackathon completion gate. That still requires +the funded Coston2 causal chain recorded in `docs/CONCORD_STATUS.md`. diff --git a/docs/shared-product-contract.md b/docs/shared-product-contract.md new file mode 100644 index 0000000..149e5bf --- /dev/null +++ b/docs/shared-product-contract.md @@ -0,0 +1,189 @@ +# Concord shared product contract + +Status: implementation contract for the first integration phase. + +This document is the semantic boundary shared by the web interface, REST API, +SDKs, CLI, MCP server, and future agent integrations. It does not create a +second source of truth. Onchain Accord and facility state, plus verified FCC +evidence where applicable, remain authoritative. + +## Canonical relationship + +Concord exposes one persistent economic relationship: + +```text +Root Accord + └─ Makkari session + └─ CoFill allocation + └─ Child Accords + └─ funded commitments + └─ Draw + └─ DrawLegs + └─ Settlement + └─ Repayment +``` + +The transaction is an event in this graph. It is not the canonical product +object. + +## Shared vocabulary + +| Canonical term | Plain-language meaning | Authoritative owner | +|---|---|---| +| Root Accord | The one facility relationship between the treasury and its providers | `CapitalFacility` + `AccordRegistry` | +| Makkari session | The bounded private coordination session for one syndication round | FCC extension and round state | +| CoFill allocation | The deterministic answer to which eligible providers supply which portions | FCC result, then verified digest | +| Child Accord | One provider's independently governed relationship under the root | `CapitalFacility` | +| Committed | Capacity actually funded by a provider's successful USDT0 transfer | `CapitalFacility` | +| Available | Committed capacity minus principal currently drawn | Derived from onchain state | +| Draw leg | The portion of one root draw supplied by one child Accord | `CapitalFacility` + `AccordRegistry` | +| Lineage | The causal relationship trail explaining why an action was allowed | `AccordRegistry` | + +The UI may use the plain-language labels, but it must preserve the canonical +terms in details, URLs, exports, and machine-readable responses. + +## State semantics + +### Root Accord + +```text +PROPOSED → SYNDICATING → FUNDING → ACTIVE → CLOSED + └──────────────→ EXPIRED + └──────────────→ FROZEN (exceptional) +``` + +| State | Meaning | What the interface may offer | +|---|---|---| +| `PROPOSED` | Root exists; collateral has not necessarily been locked | Lock collateral | +| `SYNDICATING` | Collateral is locked and the Makkari round is open | Submit/observe the round; finalize only through the verified flow | +| `FUNDING` | CoFill selected providers; selected capacity is not yet committed | Provider funding | +| `ACTIVE` | Target capacity is fully funded | Draw and observe exposure | +| `CLOSED` | Exposure and commitments are zero; recoverable balances were returned | Read-only history | +| `FROZEN` | Exceptional administrative or safety state | Read-only; no new financial action | +| `EXPIRED` | Validity ended with no outstanding exposure or commitments | Read-only history | + +### Child Accord + +```text +SELECTED → FUNDED → ACTIVE → CLOSED + └──────────────→ EXPIRED + └──────────────→ DEFAULTED (future resolution, not MVP) +``` + +`SELECTED` is an allocation result. It is never presented as funded or +committed capital. `FUNDED` is reached only after the provider's USDT0 +transfer succeeds. `ACTIVE` is reached when the root reaches full funding and +the facility activates the funded children. + +### Amount invariants + +Every projection and every write-preparation response must preserve: + +```text +root.drawnPrincipal <= root.committedCapacity +child.drawnPrincipal <= child.committedCapacity +root.drawnPrincipal == Σ child.drawnPrincipal +root.committedCapacity == Σ funded child commitments +root.availableCapacity = root.committedCapacity - root.drawnPrincipal +``` + +Amounts are transported as decimal strings in API and SDK payloads. A client +must use the asset decimals supplied by the network configuration; it must not +assume that FXRP and USDT0 use the same decimals. + +## Observation and evidence + +Every read response carries an observation envelope: + +- `observed`: authoritative state was read from the configured source; +- `partial`: some related data was unavailable or could not be traversed; +- `not_observed`: the system has no authoritative observation and must not + present a zero value as a fact. + +The envelope also carries network, chain ID, block number when known, source, +and observation time. “No children” and “children not observed” are different +responses. + +FCC evidence is separate from public facility state. A surface may expose +evidence metadata and verification status to an authorized caller, but it must +not expose losing quotes, private provider constraints, or decrypted payloads +as a general API feature. + +## Role model + +Roles change which actions are visible and which private inputs may be +returned. They do not change the underlying economic semantics. + +| Role | Read scope | Write/prepare scope | +|---|---|---| +| Treasury | Root, public round result, children, draws, repayments, lineage, authorized evidence | Root creation, collateral lock, round opening, materialization handoff, draw, repayment, close | +| Provider | Authorized rounds, own quote status, own child Accords, own funding/exposure, public lineage | Own signed quote, own child funding, own close/expiry action | +| Institution/operator | Facilities granted by its organization, evidence and exports allowed by policy | Same actions delegated by an organization; every transaction still requires a signer | +| Auditor/observer | Public relationship state, commitments, exposure, settlements, repayments, lineage, published evidence | None by default | +| Developer/agent | API/MCP scope granted by the caller; public state by default | Prepare unsigned intents; no implicit custody, signing, verifier authority, or broadcast | + +An agent is a delegate. It is not a new participant in the Accord and cannot +become the allocation verifier merely because it can call an API. + +## Layered surfaces + +All surfaces consume this contract through one control plane: + +```text +Contracts + FCC evidence + ↓ +Concord read model / intent builder + ┌────┼──────────┬─────────┐ + ↓ ↓ ↓ ↓ + REST SDK MCP CLI + └────┴──────────┴─────────┘ + ↓ + Frontend +``` + +- REST/OpenAPI is the stable external contract. +- SDKs are typed clients of REST and transaction-intent builders; they do not + reimplement lifecycle rules. +- MCP exposes read resources and safe preparation tools through the same API; + it is not a privileged backdoor. +- The CLI is an operator/developer client of the same endpoints and verifier + commands. It must not silently use a different state model. +- x402, when added, may gate access to API/MCP resources. It cannot bypass + Accord authority or become the facility's USDT0 settlement rail. + +## Transaction intent rules + +A write-capable surface returns an unsigned intent containing: + +- target contract and chain ID; +- calldata and value; +- the human-readable action and affected relationship IDs; +- preconditions read from the current projection; +- warnings for expiry, availability, or authorization assumptions; +- `requiresExplicitApproval: true`. + +The surface does not hold a private key, sign for a user, broadcast a +transaction, or report success before a receipt is observed. The frontend may +hand the intent to a connected wallet; the final result comes from the chain. + +## Frontend contract + +The primary route is `/facilities/:rootAccordId`. It must answer, in this +order: + +1. What relationship is this? +2. Who participates? +3. How much was selected, funded, drawn, and remains available? +4. Which child relationships supplied the current exposure? +5. Why is the next action allowed? +6. What evidence and lineage support the state? + +The interface has two disclosure modes over the same data: + +- Guided mode: plain-language labels, one next action, warnings, and a short + relationship explanation. +- Detailed mode: canonical IDs, contract addresses, fee bps, terms + commitments, block/transaction references, FCC evidence, and lineage. + +No screen may show a synthetic “success” state from a local mock when the +authoritative observation is missing. diff --git a/docs/skills-foundry-pack-ingestion.md b/docs/skills-foundry-pack-ingestion.md new file mode 100644 index 0000000..32f6647 --- /dev/null +++ b/docs/skills-foundry-pack-ingestion.md @@ -0,0 +1,97 @@ +# Concord Foundry Skills Pack Ingestion + +Status: durably staged and loaded on 2026-08-12. + +## Takeover re-ingestion + +The attached `Concord-Foundry-Skills-Pack-2026-08-12.zip` was re-ingested for +the 2026-08-14 Concord takeover. Its outer SHA-256 is unchanged at +`35bb0180d3a95bb3470470f120a9336e92bba2589f0282741d6d4b342f26c474`, and all +six nested bundle checksums below were re-verified. The attached GitHub and +Cloudflare environment files were used only through secret-preserving command +environments; their values were not printed, committed, or copied into this +repository. + +## Source + +- Uploaded name: `Concord-Foundry-Skills-Pack-2026-08-12(1).zip` +- File citation ID: `file_000000000d7881f48366d27a71f127f9` +- Library file ID: `libfile_09b19d4ba3e081918c774f22a18a02d2` +- Original upload path: `/workspace/scratch/9b193e913b16/upload/Concord-Foundry-Skills-Pack-2026-08-12(1).zip` +- Durable archive: `/root/.codex/skills/remote-skills/concord-foundry-skills-pack-2026-08-12-1/Concord-Foundry-Skills-Pack-2026-08-12-1.zip` +- Durable archive SHA-256: `35bb0180d3a95bb3470470f120a9336e92bba2589f0282741d6d4b342f26c474` + +The original upload location is recorded for provenance only. The durable +archive and extracted sources are the working copies; no task step depends on +scratch or `/tmp`. + +## Integrity + +The outer archive checksum file was verified. All six nested archives passed +their recorded SHA-256 checksums: + +| Package | SHA-256 | +| --- | --- | +| `product-foundry-agent-install-bundle-0.4.3.zip` | `0a43f714971cbf07c8b4c47da4babcf7616455dd6c48ef55774ba7448377f948` | +| `Research-Foundry-0.2.0.zip` | `c1fefce6fa19f9db4fecc8081a114d7ee585d152e94264cfa9eefddd86cc3f60` | +| `concord-fcc-0.1.0.zip` | `4d3cd95f372c5f196a5dca911059c0058f844d1e921ba4d5d989f0bea56e5ebc` | +| `experiment-foundry-agent-install-bundle-0.1.0.zip` | `1666edaf8ac6bf7e13ff11feef152c6578eebebf0fc887d87a9f8601558d4b4e` | +| `interface-foundry-0.3.0.zip` | `f4dc5e676567f59131410d80858ad3d0a663d3126aa49b9708e7ce7c724e11c4` | +| `Demo-Foundry-0.5.3.zip` | `e27a650b4ed9d3a9d1bc524b84453d182415c7a740d75071ecc35ec78528e7a3` | + +## Durable extraction + +All package sources are retained beneath: + +`/root/.codex/skills/remote-skills/concord-foundry-skills-pack-2026-08-12-1/` + +Extracted package directories: + +- `product-foundry-agent-install-bundle-0.4.3-extracted/` +- `Research-Foundry-0.2.0-extracted/` +- `concord-fcc-0.1.0-extracted/` +- `Experiment-Foundry-0.1.0-extracted/` +- `interface-foundry-0.3.0-extracted/` +- `Demo-Foundry-0.5.3-extracted/` + +## Loaded order + +The top-level instructions and required direct references were read in the +canonical order: + +1. Product Foundry `0.4.3` — loaded. +2. Research Foundry `0.2.0` — loaded. +3. `concord-fcc` `0.1.0` — loaded; Flare skills remain external dependencies. +4. Experiment Foundry `0.1.0` — loaded from its nested archive. +5. Interface Foundry `0.3.0` — loaded. +6. Demo Foundry `0.5.3` — loaded for later production only. + +## Validation + +- Product, Research, and Experiment personal-skill compatibility validators: passed. +- Demo Foundry personal-skill compatibility validator: passed. +- `concord-fcc` skill validator: passed. +- Interface Foundry: no personal-skill validator was supplied in the package. +- Full Product/Research/Experiment runtime validation is pending because the + environment lacks the declared `jsonschema` dependency. The package was not + mutated to hide that limitation. + +## Recorded package note + +The Demo archive and manifest identify version `0.5.3`, while the header inside +its `SKILL.md` says `0.5.2`. This is retained as an explicit package +contradiction; the archive/manifest identity controls package tracking until +the upstream skill resolves it. + +## Operating boundaries + +- Product Foundry protects Concord Product Truth and requires persistent project state. +- Research Foundry requires source provenance, freshness, uncertainty, and visible contradictions. +- `concord-fcc` requires a fresh official Flare source snapshot for every material Flare/FCC task; its hackathon freshness limit is 12 hours. +- Experiment Foundry governs failure-focused, falsifiable tests and preserves raw/derived/interpretive evidence boundaries. +- Interface Foundry requires evidence-led design questions, accessibility-first semantics, critic/scorecard passes, and progressive disclosure. +- Demo Foundry must not produce final-demo material before the application works and evidence is truthful. + +Official Flare skills and Flare-controlled sources are intentionally not vendored +in this pack and must be refreshed from their current official repositories and +Developer Hub before material Flare implementation decisions. diff --git a/docs/source-snapshot-2026-08-13.yaml b/docs/source-snapshot-2026-08-13.yaml new file mode 100644 index 0000000..951f98f --- /dev/null +++ b/docs/source-snapshot-2026-08-13.yaml @@ -0,0 +1,72 @@ +snapshot: + captured_at_utc: 2026-08-13T19:14:40Z + freshness: FRESH + environment: Coston2 + chain_id: 114 + tee_mode: simulated + hardware_attestation_claim: false + official_skills: + repository: https://github.com/flare-foundation/flare-ai-skills + revision: 0bd60deb977df97455da720df1aaf765480b8141 + consulted: + - flare-general + - flare-fassets + - flare-fcc + - flare-fdc (boundary only; not implemented in the MVP) + developer_hub: + - https://dev.flare.network/fcc/guides/getting-started + - https://dev.flare.network/network/overview + - https://dev.flare.network/support/faqs + - https://dev.flare.network/fxrp/token-interactions/fxrp-address + official_repositories: + - repository: https://github.com/flare-foundation/fce-extension-scaffold + revision: e3f587949069780084e2ced8a53c9419ed05c250 + finding: current scaffold supports Go, Python, and TypeScript implementations; Concord intentionally uses Go. + verified_facts: + - Coston2 uses chain ID 114 and the configured public RPC. + - The official Coston2 FCC development walkthrough uses LOCAL_MODE=false and SIMULATED_TEE=true. + - The walkthrough requires a public HTTPS extension-proxy URL and Coston2 indexer database credentials for the local proxy. + - The current scaffold path includes pre-build, start-services, post-build, and test phases. + - Flare's FAQ identifies Coston2 as the primary dApp testnet and states that FCC is available there while in development. + - FCC development registration requires one active machine in status 2 (PRODUCTION), a fresh availability check, a registered teeId, and a reachable stable public HTTPS extension URL. + - The extension receives provider delivery at the scaffold external proxy port 6664; the Docker host mapping used by this repository is 6674. + - Rotating quick-tunnel URLs are not valid for a registered Coston2 machine; the named tunnel token form is `run --token `. + - The official Coston2 FTDC proxy endpoints are `https://tee-proxy-coston2-1.flare.rocks` and `https://tee-proxy-coston2-2.flare.rocks`; they are normal infrastructure proxies, not Concord's extension endpoint. + - The current DevHub walkthrough specifies the Coston2 indexer endpoint as host `34.38.42.208`, port `3306`, database `indexer`; credentials remain secret inputs. + contradictions: + - id: FCC-QUICK-TUNNEL-SCOPE + statement: The DevHub getting-started walkthrough still documents quick tunnels for development and says to update EXT_PROXY_URL when a quick-tunnel URL changes. + current_operational_constraint: A registered Coston2 machine must use one stable public HTTPS URL; restarting a rotating tunnel strands the onchain machine record and can create a new simulated TEE identity. + resolution: Concord permits quick tunnels only for unregistered smoke tests and requires a stable named-tunnel URL/token for the guarded registration workflow. + - id: FCC-WORKERS-DEV-INGRESS-FALLBACK + statement: The official path requires a stable public HTTPS extension-proxy URL; a named Cloudflare Tunnel is one supported way to provide it. + current_operational_constraint: This account cannot self-assign the Cloudflare Connectivity Directory Admin role required for the VPC/tunnel path, and no custom domain is available. + resolution: Concord uses the stable Workers.dev URL `https://concord-fcc-ingress.microcosm.workers.dev` as a narrow development relay to the disposable Codespace public port. It forwards `/instruction`, `/info`, `/health`, `/metrics`, `/action/status...`, and `/action/result...`; it is not represented as a named tunnel or production ingress. + concord_decisions: + - No product-semantic contradiction was found. + - Simulated TEE evidence remains development-path evidence, not production hardware-backed confidentiality. + - FDC remains an evidence seam and is not added to the MVP. + live_gate_inputs_completed: + - funded Coston2 deployment/proxy accounts + - a stable public HTTPS EXT_PROXY_URL (the current guarded branch uses the Workers.dev relay fallback) + - current extension registration and active TEE-machine evidence for extension 66188 + - signed quote submission, CoFill finalization, allocation verification, and child materialization + - real provider funding, multi-child draw, repayment, and explorer receipts + live_evidence: + extension_id: "0x000000000000000000000000000000000000000000000000000000000001028c" + tee_id: "0xeE39d5e7d1C5043232282e3CC884B41a9Db22c85" + facility: "0xfaff601a18a9fca33378953515aa0f3ef9286ecd" + root_accord_id: "0x6e03af41b0194c5a369a50629474090cfc5b041a712144855e6efb1a574cfddd" + round_id: "0x7323280ede4786463744a755e4cddf9ce2a91be952d05d152679b58b67d176ce" + allocation_digest: "0xf17a292655b898d8b00c9794565972ed838cd3de2812f6fa59d877d6963c88ec" + complete_lifecycle_run: "https://github.com/etvjay/Concord/actions/runs/31734593116" + evidence_artifact: "https://github.com/etvjay/Concord/actions/runs/31734593116/artifacts/9194681063" + final_root_state: ACTIVE + final_committed_capacity: 9000000 + final_drawn_principal: 0 + final_available_capacity: 9000000 + draw_leg_count: 2 + remaining_boundaries: + - production hardware-backed TEE execution is not evidenced; the run used the documented simulated development TEE + - a named Cloudflare Tunnel/custom-domain ingress is not provisioned; the stable Workers.dev relay is a development fallback + - the browser frontend and wallet UX are not the source of the live lifecycle evidence diff --git a/docs/source-snapshot-2026-08-14-deployment.yaml b/docs/source-snapshot-2026-08-14-deployment.yaml new file mode 100644 index 0000000..c3000e6 --- /dev/null +++ b/docs/source-snapshot-2026-08-14-deployment.yaml @@ -0,0 +1,87 @@ +snapshot: + captured_at_utc: 2026-08-14T10:17:34Z + freshness: FRESH_AT_CAPTURE + environment: Coston2 + chain_id: 114 + rpc_url: https://coston2-api.flare.network/ext/C/rpc + concord_extension_id: + decimal: 66188 + bytes32: 0x000000000000000000000000000000000000000000000000000000000001028c + tee_registry: 0x1a9C4A0f9D76c0b1D91d22E24E573a9b377618aE + tee_mode: simulated + hardware_attestation_claim: false + runtime_values: + SIMULATED_TEE: true + MODE: 1 + normal_ftdc_proxy_url: https://tee-proxy-coston2-1.flare.rocks + official_flare_sources: + flare_ai_skills: + url: https://github.com/flare-foundation/flare-ai-skills + revision: 0bd60deb977df97455da720df1aaf765480b8141 + retrieved_at_utc: 2026-08-14T10:17:34Z + fce_extension_scaffold: + url: https://github.com/flare-foundation/fce-extension-scaffold + revision: e3f587949069780084e2ced8a53c9419ed05c250 + retrieved_at_utc: 2026-08-14T10:17:34Z + developer_hub: + - url: https://dev.flare.network/network/overview + retrieved_at_utc: 2026-08-14T10:17:34Z + verified: Coston2 RPC, chain ID, and explorer + - url: https://dev.flare.network/fcc/guides/getting-started + retrieved_at_utc: 2026-08-14T10:17:34Z + verified: simulated Coston2 flow, nested machineData /info fields, indexer DB separation, and normal proxy guidance + repository_contracts: + proxy_dockerfile: /infra/railway/proxy.Dockerfile + tee_dockerfile: /go/Dockerfile + proxy_public_port: 6664 + proxy_private_port: 6663 + tee_private_ports: [5501, 7701, 7702] + redis: private_addon_only + indexer_database: separate_mysql_compatible_private_dependency + historical_machine: + tee_id: 0xeE39d5e7d1C5043232282e3CC884B41a9Db22c85 + registered_url: https://concord-fcc-ingress.microcosm.workers.dev + status: 2 + role: historical_codespace_relay + live_readonly_checks: + retrieved_at_utc: 2026-08-14T10:22:48Z + rpc_eth_chain_id: 0x72 + rpc_chain_id_decimal: 114 + old_workersdev_info_http: 404 + normal_ftdc_proxy_info_http: 200 + normal_ftdc_proxy_info_chain_id: 114 + normal_ftdc_proxy_info_extension_id: 0 + current_registry: + registry: 0x1a9C4A0f9D76c0b1D91d22E24E573a9b377618aE + extension_id: 66188 + active_machine_count: 1 + active_machines: + - tee_id: 0xeE39d5e7d1C5043232282e3CC884B41a9Db22c85 + url: https://concord-fcc-ingress.microcosm.workers.dev + status: 2 + cutover_started: false + deployment_attempt: + vercel: + project: concord-flare + team: jaydearcadians-projects + local_install_build_tests: passed + production_deployment: blocked + blocker: HTTP_403_missing_production_deploy_permission + public_url: not_observed + northflank: + project: not_created_or_observed + stable_proxy_url: not_observed + blocker: no_connected_northflank_api_or_dashboard_in_this_session + cloudflare: + supplied_credentials_ingested: true + tunnel_or_worker_change: not_claimed + blocker: cloudflare_api_path_unavailable_in_this_session + broadcast_boundary: + transactions_broadcast: false + pending_actions: + - allow_or_confirm_tee_version_only_if_not_already_satisfied + - set_governance_only_if_not_already_satisfied + - register_new_simulated_tee_with_command_rRap + - pause_historical_codespace_identity + - dispatch_one_fresh_concord_development_instruction + confirmation_required: true diff --git a/docs/source-snapshot-2026-08-14-live-blocker.yaml b/docs/source-snapshot-2026-08-14-live-blocker.yaml new file mode 100644 index 0000000..4c1fb5f --- /dev/null +++ b/docs/source-snapshot-2026-08-14-live-blocker.yaml @@ -0,0 +1,66 @@ +snapshot: + captured_at_utc: 2026-08-14T10:49:53Z + freshness: LIVE_BLOCKER_CHECK + environment: Coston2 + chain_id: 114 + rpc_url: https://coston2-api.flare.network/ext/C/rpc + concord_extension_id: + decimal: 66188 + bytes32: 0x000000000000000000000000000000000000000000000000000000000001028c + tee_registry: 0x1a9C4A0f9D76c0b1D91d22E24E573a9b377618aE + runtime_values: + SIMULATED_TEE: true + MODE: 1 + normal_ftdc_proxy_url: https://tee-proxy-coston2-1.flare.rocks + sources: + - url: https://dev.flare.network/network/overview + retrieved_at_utc: 2026-08-14T10:17:34Z + verified: Coston2 RPC, chain ID, and explorer + - url: https://dev.flare.network/fcc/guides/getting-started + retrieved_at_utc: 2026-08-14T10:17:34Z + verified: simulated Coston2 flow, nested /info fields, separate indexer DB, and normal proxy guidance + - url: https://coston2-systems-explorer.flare.network/tee/objects?tab=machines&machine_extensionId=66188 + retrieved_at_utc: 2026-08-14T10:49:53Z + http_status: 200 + note: The public Systems Explorer page loaded; the client-rendered machine table was still loading in the raw HTTP response. The Coston2 RPC read remains the registry authority. + - url: https://concord-fcc-ingress.microcosm.workers.dev/info + retrieved_at_utc: 2026-08-14T10:49:53Z + http_status: 404 + note: Historical Workers.dev relay is not serving /info. + historical_machine: + tee_id: 0xeE39d5e7d1C5043232282e3CC884B41a9Db22c85 + registered_url: https://concord-fcc-ingress.microcosm.workers.dev + status: 2 + last_chain_registry_read_at_utc: 2026-08-14T10:22:48Z + codespaces: + source: https://api.github.com/user/codespaces + retrieved_at_utc: 2026-08-14T10:49:53Z + repository: etvjay/Concord + observed: + - state: Shutdown + public_port: absent + - state: Shutdown + public_port: absent + implication: The historical Codespace-backed upstream cannot currently serve the Workers.dev relay. + deployment_attempt: + vercel: + project: concord-flare + team: jaydearcadians-projects + public_production_url: not_observed + blocker: HTTP_403_missing_production_deploy_permission + northflank: + project: not_created_or_observed + stable_proxy_url: not_observed + blocker: short-lived token was pasted into chat and was not consumed; safe env-file or managed-secret injection is required + cloudflare: + worker_change: not_claimed + historical_worker: https://concord-fcc-ingress.microcosm.workers.dev + broadcast_boundary: + transactions_broadcast: false + pending_actions: + - allow_or_confirm_tee_version_only_if_not_already_satisfied + - set_governance_only_if_not_already_satisfied + - register_new_simulated_tee_with_command_rRap + - pause_historical_codespace_identity + - dispatch_one_fresh_concord_development_instruction + confirmation_required: true diff --git a/docs/source-snapshot-2026-08-14.yaml b/docs/source-snapshot-2026-08-14.yaml new file mode 100644 index 0000000..fcf7b23 --- /dev/null +++ b/docs/source-snapshot-2026-08-14.yaml @@ -0,0 +1,58 @@ +snapshot: + captured_at_utc: 2026-08-14T05:09:35Z + freshness: FRESH + environment: Coston2 + chain_id: 114 + tee_mode: simulated + hardware_attestation_claim: false + official_flare_sources: + skills_repository: + url: https://github.com/flare-foundation/flare-ai-skills + revision: 0bd60deb977df97455da720df1aaf765480b8141 + fce_scaffold: + url: https://github.com/flare-foundation/fce-extension-scaffold + revision: e3f587949069780084e2ced8a53c9419ed05c250 + developer_hub: + - https://dev.flare.network/fcc/guides/getting-started + - https://dev.flare.network/network/overview + hosting_sources: + railway: + - https://docs.railway.com/config-as-code/reference + - https://docs.railway.com/networking/private-networking + - https://docs.railway.com/builds/dockerfiles + - https://docs.railway.com/deployments/restart-policy + northflank: + - https://northflank.com/features/application-layer + - https://northflank.com/docs/v1/application/getting-started/build-and-deploy-your-code + - https://northflank.com/docs/v1/application/network/configure-ports + - https://northflank.com/docs/v1/application/observe/configure-health-checks + - https://northflank.com/docs/v1/application/scale/scale-instances + verified_facts: + - The current official FCE scaffold still composes extension-tee, tee-proxy, and Redis; Concord keeps that runtime separation. + - The Coston2 provider-facing extension-proxy external port is 6664 and requires a stable public HTTPS URL. + - SIMULATED_TEE=true remains a supported Coston2 hackathon development path and is not production hardware-backed execution. + - A simulated TEE restart creates a new identity; the new machine must be registered and promoted and the stale identity paused. + - Exactly one active machine per endpoint avoids random dispatch to stale machines. + - Northflank private services resolve as service-name:port inside one project, and public HTTP ports receive managed HTTPS endpoints. + - Northflank sandbox services do not sleep, but Northflank does not position the sandbox as production infrastructure. + - Railway supports Dockerfile configuration-as-code, private service DNS, and stable public domains. + - Railway ALWAYS restart is unavailable on free/trial plans; an always-on claim requires a paid plan and no scale-to-zero behavior. + - Northflank supports health checks, GitOps templates, private services, and Redis addons for the primary long-running hackathon development topology. + current_recorded_machine: + extension_id_decimal: 66188 + tee_id: 0xeE39d5e7d1C5043232282e3CC884B41a9Db22c85 + tee_proxy_id: 0x801470C95f78D0cA444e589aF8Ea0858Ce6d613e + status: 2 + registered_url: https://concord-fcc-ingress.microcosm.workers.dev + availability_now: unavailable_because_disposable_codespace_upstream_is_offline + decision: + primary_host: Northflank long-running combined services plus private Redis addon + fallback_host: Railway paid long-running services + cutover_required: true + reason: Northflank removes the disposable Codespace ingress dependency without scale-to-zero, but a fresh simulated TEE identity must still be deliberately registered and the stale machine paused after any TEE restart. + product_semantics: + contradiction_found: true + contradiction: The current official Developer Hub supports SIMULATED_TEE=true for a live Coston2 development path, while production deployment guidance states that simulated attestation is rejected for production. Concord resolves this by using simulated mode only for the Coston2 development proof and making no hardware-backed or production TEE claim. + mvp_changed: false + fdc_added: false + privacy_claim_expanded: false diff --git a/docs/templates/coston2-evidence.template.json b/docs/templates/coston2-evidence.template.json new file mode 100644 index 0000000..7f20d10 --- /dev/null +++ b/docs/templates/coston2-evidence.template.json @@ -0,0 +1,70 @@ +{ + "status": "not_observed", + "network": { + "name": "coston2", + "chainId": 114, + "rpcUrl": "https://coston2-api.flare.network/ext/C/rpc", + "explorerUrl": "https://coston2-explorer.flare.network", + "observedAt": null + }, + "assets": { + "fxrp": { + "assetManager": null, + "token": null, + "decimals": null, + "resolutionStatus": "not_observed", + "resolverRunUrl": null + }, + "usdt0": { + "token": null, + "symbol": null, + "decimals": null, + "resolutionStatus": "not_observed", + "resolverRunUrl": null + } + }, + "deployment": { + "accordRegistry": null, + "capitalFacility": null, + "instructionSender": null, + "allocationVerifier": null, + "extensionId": null, + "teeRegistry": null, + "activeMachine": null, + "teeMode": "not_observed" + }, + "syndication": { + "rootAccordId": null, + "roundId": null, + "submitQuoteInstructionIds": [], + "finalizeInstructionId": null, + "resultDigest": null, + "selectedChildAccordIds": [], + "fundedChildAccordIds": [] + }, + "lifecycle": { + "rootCreationTx": null, + "collateralDepositTx": null, + "childMaterializationTx": null, + "fundingTransfers": [], + "activationTx": null, + "drawTx": null, + "drawLegs": [], + "repaymentTx": null, + "closureTx": null + }, + "lineage": { + "rootToRound": null, + "roundToAllocation": null, + "allocationToChildren": [], + "drawToLegs": [], + "repaymentToExposure": null + }, + "privacyDisclosure": [ + "No private FXRP transfers are claimed.", + "No private USDT0 transfers are claimed.", + "No fully private EVM execution is claimed.", + "TEE mode must be recorded as simulated development or hardware-backed only when observed." + ], + "notes": [] +} diff --git a/docs/testing-against-coston2.md b/docs/testing-against-coston2.md new file mode 100644 index 0000000..5da3b88 --- /dev/null +++ b/docs/testing-against-coston2.md @@ -0,0 +1,137 @@ +# Testing against a Coston2 deployment + +For exercising an extension that is already deployed and registered on Coston2 — +either on a Confidential Space VM, or locally with a public tunnel. To deploy one +first, see [deployment-steps.md](deployment-steps.md). + +## What you need + +| Need | Note | +|---|---| +| `.env.coston2` | filled in; `use-chain.sh coston2` activates it | +| A funded Coston2 key | sends the test instructions; get C2FLR from the [faucet](https://faucet.flare.network/coston2) | +| `config/coston2/deployed-addresses.json` | the `FlareTeeManager` diamond and friends | +| `config/proxy/extension_proxy.coston2.docker.toml` | gitignored — copy the `.example` and fill in `[db]` | +| A reachable `EXT_PROXY_URL` | the VM's URL, or a tunnel if the proxy runs locally | + +## Confirm the deployment is live + +```bash +curl -s "$EXT_PROXY_URL/info" | jq '{machineData: {extensionId: .machineData.extensionId, codeHash: .machineData.codeHash, platform: .machineData.platform}, teeInfo: {publicKey: .teeInfo.publicKey, chainId: .teeInfo.chainId}}' +``` + +| Field | Expect | +|---|---| +| `extensionId` | matches `EXTENSION_ID` in `config/extension.env` | +| `codeHash` | the hash registered on-chain — `0x194844cf…` means simulated, not real hardware | +| `platform` | `GCP_AMD_SEV` on real hardware, `TEST_PLATFORM` when simulated | + +Then check the on-chain side: + +```bash +cd tools +go run ./cmd/query-tee -ext -rpc "$CHAIN_URL" +go run ./cmd/verify-deploy -a ../config/coston2/deployed-addresses.json -c "$CHAIN_URL" +``` + +`query-tee` lists the TEE machines registered for the extension. **More than one +active machine is a problem** — instructions are load-balanced across them, so a +stale one swallows roughly half your requests. See +[deployment-steps.md](deployment-steps.md). + +## Run the Concord flow + +```bash +./scripts/use-chain.sh coston2 +CONCORD_QUOTES=fixtures/quote-a.json,fixtures/quote-b.json,fixtures/quote-c.json \ +CONCORD_FINALIZE=fixtures/finalize-round.json \ +CONCORD_EVIDENCE_OUT=evidence/fcc-action.json \ +./scripts/test.sh +``` + +The quote files must contain signed Concord `QuoteRequest` payloads. The +finalization file must contain a `FinalizeRoundRequest` bound to the live +extension, root Accord, Makkari round, target, expiry, and eligible providers. +The runner submits each quote through encrypted `SUBMIT_QUOTE` instructions, +then submits `FINALIZE_ROUND` and writes the complete signed FCC evidence +envelope to `CONCORD_EVIDENCE_OUT`. + +For the disposable live operator path, build those files from the current +onchain round without placing them in the repository: + +```bash +mkdir -p "$RUNNER_TEMP/concord-quotes" +(cd go && go run ./cmd/build-quotes \ + -rpc "$CHAIN_URL" \ + -facility "$CAPITAL_FACILITY" \ + -extension-id "$EXTENSION_ID" \ + -round-id "$ROUND_ID" \ + -root-accord-id "$ROOT_ACCORD_ID" \ + -out-dir "$RUNNER_TEMP/concord-quotes" \ + -provider-a "$PROVIDER_A" \ + -provider-b "$PROVIDER_B" \ + -provider-c "$PROVIDER_C") +``` + +`build-quotes` reads the three provider private keys from the environment, +checks the live round and eligibility onchain, and writes mode-0600 quote +payloads plus the finalization payload. It never writes a provider key. + +Verify that envelope against the deployed CapitalFacility before materializing +children: + +```bash +cd tools +go run ./cmd/verify-allocation \ + -c "$CHAIN_URL" \ + -facility "$CAPITAL_FACILITY" \ + -teeRegistry "$TEE_REGISTRY" \ + -result ../evidence/fcc-action.json \ + -extensionId "$EXTENSION_ID" \ + -roundId "$ROUND_ID" \ + -rootAccordId "$ROOT_ACCORD_ID" \ + -out ../evidence/allocation-verification.json +``` + +The verifier is read-only by default. Use `-mark` only after the signed +action, active-machine, facility-binding, and digest checks pass; the caller +must be the facility's configured `allocationVerifier`. + +After that verification, the treasury borrower can materialize the selected +Child Accords in the same bounded step: + +```bash +go run ./cmd/verify-allocation \ + ...same verification flags... \ + -mark -materialize +``` + +`-materialize` requires `-mark`; it first authorizes the verified digest and +then calls `materializeAllocation`. A selected child is still not funded until +its provider's USDT0 transfer succeeds. + +Counters are in memory, so the numbers restart at 1 after any TEE relaunch. + +## Testing a local proxy against Coston2 + +The proxy must be publicly reachable for FTDC data providers to answer it. Start +the tunnel and let the scripts wire the URL in: + +```bash +./scripts/full-setup.sh --chain coston2 --tunnel --test +``` + +That writes the tunnel URL into `.env` as `EXT_PROXY_URL`, so `post-build.sh` and +`test.sh` pick it up. Details in [cloudflared.md](cloudflared.md). + +## If something's blocked + +| Symptom | Cause | +|---|---| +| `pollAction` timeout, `/action/result` 404 | multiple active TEE machines; pause the stale ones | +| `Verification.ChallengeExpired` | re-registration without `-command rRap` | +| `no round` / 404 from the FTDC proxy | proxy signing policy out of sync with the on-chain reward epoch; `register-tee` pre-flights this | +| `signature must be 65 bytes, got 0` | `CHAIN_ID` unset on the node | +| `InvalidTeePublicKeyOrSignature` | node `CHAIN_ID`, proxy `chain_id` and the registry disagree — all three must say 114 | +| Instructions never arrive | `EXT_PROXY_URL` not reachable from outside, or a rotated tunnel URL left stale in `.env` | +| `Extension ID already set.` | `setExtensionId()` is one-shot; a redeploy is the only reset | diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000..4bde431 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,62 @@ +# Testing + +Concord's tests are organized around the causal chain, including failure +behaviour. + +## Solidity + +~~~bash +forge test -vv +~~~ + +Use your installed Foundry toolchain, or set `PATH` and `FOUNDRY_SOLC` to +toolchain locations appropriate to your environment. + +contracts/test/ConcordFlow.t.sol covers: + +- root Accord creation and FXRP collateral locking; +- verifier-gated, root/round/extension-bound allocation materialization; +- duplicate-provider and overfunding constraints; +- selected versus funded child capacity; +- root activation only after actual USDT0 transfers; +- deterministic draw allocation across at least two children; +- explicit DrawLeg and settlement lineage; +- exposure reduction after repayment and restored availability; +- provider capital return and collateral return after exposure is clear; +- malicious ERC-20 callback attempts are blocked by the facility reentrancy guard while the outer transfer remains correct. + +## FCC extension + +~~~bash +(cd go && GOTOOLCHAIN=local go test ./...) +~~~ + +The Go tests cover strict payload decoding, the encrypted-action boundary, +EIP-191 provider signature recovery, expired and invalid quotes, deterministic +fee/address/nonce ordering, partial final allocation, and explicit insufficient +capacity failure. + +## Deployment tooling + +~~~bash +(cd tools && GOTOOLCHAIN=local go test ./...) +~~~ + +This validates deployment support, result parsing, diagnostics and the +generated ConcordInstructionSender binding. A live Coston2 run additionally +requires a funded account, registered extension, reachable official FCC +scaffold, provider accounts, and an independently verified allocation result. + +## Evidence rule + +Passing local tests do not equal a Coston2 deployment. Record contract +addresses, instruction IDs, transaction hashes, result digests and explorer +links in the status record before making a live-flow claim. + +## Failure and evidence matrix + +[Coston2 and lifecycle failure matrix](experiments/coston2-failure-matrix.md) +covers adversarial signatures, expiry, replay, insufficient capacity, selected +versus funded state, active-child expiry demotion, partial repayment, unsigned +intent boundaries, and live-gated FCC/settlement evidence. The matrix is a +release gate, not a claim that the live cases have already been observed. \ No newline at end of file diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..63d8380 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,12 @@ +[profile.default] +src = "contracts" +out = "out" +libs = ["lib"] +# TODO: Once flare-smart-contracts-v2 is published as a package, add it as a dependency +# and restore the remapping: +# remappings = ["flare-smart-contracts-v2=lib/flare-smart-contracts-v2"] +via-ir = true +optimizer = true +optimizer_runs = 200 +solc_version = "0.8.27" +evm_version = "paris" diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..cce8707 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,22 @@ +FROM node:24.19.0-alpine AS build + +WORKDIR /repo/frontend + +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci --ignore-scripts --no-audit --no-fund + +COPY frontend/ ./ +COPY sdk/typescript/ /repo/sdk/typescript/ +COPY config/coston2/concord-deployment.json /repo/config/coston2/concord-deployment.json + +RUN npm run build + +FROM nginx:1.29.4-alpine + +COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /repo/frontend/dist /usr/share/nginx/html + +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget -q -O /dev/null http://127.0.0.1:8080/ || exit 1 diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..324a32b --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,83 @@ +# Concord frontend + +The first institutional frontend checkpoint is implemented in React, +TypeScript, and Vite. It translates the supplied visual reference into +Concord's actual product semantics: a public landing page, facility register, +Root Accord workspace, funding formation, activity, FCC evidence, explicit +lineage, child dossiers, and draw-leg detail. + +The rendered data comes directly from +`config/coston2/concord-deployment.json` and uses the canonical TypeScript SDK +types. It does not invent a second facility model or fake institutional data. +The recorded proof facility contains 9 USDT0 of funded capacity, three active +children, a repaid two-leg draw, and restored capacity. + +## Run locally + +```bash +cd frontend +npm ci --ignore-scripts --no-audit --no-fund +npm run dev +``` + +Use `npm run build` for the TypeScript and production-bundle gate and +`npm test` for semantic/invariant tests. The root-context production image is: + +```bash +docker build -f frontend/Dockerfile -t concord-frontend . +docker run --rm -p 8080:8080 concord-frontend +``` + +The GitHub workflow in `.github/workflows/frontend.yml` reproduces dependency +installation, build, tests, and the container build. + +The semantic map remains in `docs/frontend-map.md`. Visual decisions are in +`docs/frontend-design-system.md` and `docs/frontend-art-direction.md`. +Heroicons is the single interface icon family. Inter Variable is self-hosted +through the npm package; no runtime font CDN is required. + +The public landing page leads with the user outcome—one facility funded by +multiple accountable providers—before progressively disclosing Accord, +Makkari, CoFill, FCC, and Lineage. Its restrained facility object and ambient +motion respect the user's reduced-motion preference. The visual system uses a +flat neutral canvas, crisp container edges, and cobalt only for proof and +action emphasis; it does not rely on card gradients or glass effects. + +The application shell has one global product destination, a plain parent trail, +and stable facility tabs for Overview, Funding, Activity, Evidence, and +Lineage. Canonical terms remain visible beside human-first titles and have +in-place definitions. A dismissible first-use primer, optional five-step tour, +and persistent Help/glossary menu teach the model without blocking experienced +users. Detail routes return to their owning section instead of creating a +navigation loop. + +## Wallet connection + +The app supports injected EVM wallets through Wagmi and Viem. A user can: + +- connect an injected wallet such as MetaMask or Rabby; +- add or switch to Flare Testnet Coston2 through the wallet; +- see the connected address and live C2FLR balance; +- open the official Coston2 faucet for C2FLR, FXRP, and USDT0; and +- disconnect without Concord retaining a private key. + +No WalletConnect project credential is bundled, so the UI does not claim +WalletConnect support. Public evidence remains browsable without connecting. + +## Current interaction boundary + +Read views remain anchored to the recorded Coston2 evidence. The “Prepare draw” +review now reads live capacity from the canonical CapitalFacility, validates the +connected wallet against the recorded treasury borrower, and builds canonical +`draw(bytes32,bytes32,uint256)` calldata locally. It presents the network, +contract, Root Accord, amount, generated Draw ID, zero native value, +preconditions, warnings, and full calldata before a separate “Approve in +wallet” action. Submission is fixed to Coston2 and confirmation is read from the +public receipt; Concord never receives a private key. + +Only the recorded treasury borrower can pass the client-side authority gate, +and the contract independently enforces that authority and available capacity. +The recorded snapshot is not silently rewritten after a new transaction; the +review reports live post-confirmation capacity separately. The FCC label remains +“simulated development TEE”; token settlement and public EVM state are not +represented as private. diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..c9839d0 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,17 @@ + + + + + + + + Concord — One facility. Capital from many providers. + + +
+ + + diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..fafe611 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,17 @@ +server { + listen 8080; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location /assets/ { + try_files $uri =404; + expires 1y; + add_header Cache-Control "public, immutable"; + } + + location / { + try_files $uri $uri/ /index.html; + add_header Cache-Control "no-cache"; + } +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..34bb4fd --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,2907 @@ +{ + "name": "@concord-protocol/frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@concord-protocol/frontend", + "version": "0.1.0", + "dependencies": { + "@fontsource-variable/inter": "5.3.0", + "@heroicons/react": "2.2.0", + "@tanstack/react-query": "5.101.4", + "react": "19.2.8", + "react-dom": "19.2.8", + "react-router-dom": "7.18.2", + "viem": "2.55.15", + "wagmi": "3.7.6" + }, + "devDependencies": { + "@testing-library/jest-dom": "7.0.1", + "@testing-library/react": "16.3.2", + "@types/react": "19.2.18", + "@types/react-dom": "19.2.4", + "@vitejs/plugin-react": "6.0.5", + "jsdom": "30.0.1", + "typescript": "7.0.2", + "vite": "8.2.1", + "vitest": "4.1.10" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.7.tgz", + "integrity": "sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.3.0", + "@csstools/css-color-parser": "^4.1.10", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.5.2" + }, + "engines": { + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-8.3.2.tgz", + "integrity": "sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2" + }, + "engines": { + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz", + "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.0", + "@csstools/css-calc": "^3.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz", + "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@fontsource-variable/inter": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.3.0.tgz", + "integrity": "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@heroicons/react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", + "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16 || ^19.0.0-rc" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.144.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz", + "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz", + "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz", + "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz", + "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz", + "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz", + "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz", + "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz", + "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz", + "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz", + "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz", + "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz", + "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz", + "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz", + "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tanstack/query-core": { + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz", + "integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.4.tgz", + "integrity": "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.101.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-7.0.1.tgz", + "integrity": "sha512-oMDTC3oA+6CXSO2JZnvOI7CA6oVub6kij5ggk9ohwye5slmkwxYDXcPOVxgMw/RQlticjtO0C1RZkR97HgrWMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=22", + "npm": ">=6", + "yarn": ">=1" + }, + "peerDependencies": { + "@testing-library/dom": ">=10 <11", + "vitest": ">= 0.32" + }, + "peerDependenciesMeta": { + "vitest": { + "optional": true + } + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/jsdom": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.0.1.tgz", + "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^6.0.5", + "@asamuzakjp/dom-selector": "^8.3.0", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.7", + "@exodus/bytes": "^1.15.1", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.2", + "undici": "^8.9.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^17.1.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "canvas": "^3.2.3" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mipd": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mipd/-/mipd-0.0.7.tgz", + "integrity": "sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wagmi-dev" + } + ], + "license": "MIT", + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ox": { + "version": "0.14.33", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.33.tgz", + "integrity": "sha512-rooA/4o7bBof4Ge2VH/eovfNPb/AEEYyrNj03wggc55g5HZD8Pjs/OeWhttgjic3dDcqn0r29bDuvQEdTiUemQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/react-router": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz", + "integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz", + "integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz", + "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.144.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.2.4", + "@rolldown/binding-darwin-arm64": "1.2.4", + "@rolldown/binding-darwin-x64": "1.2.4", + "@rolldown/binding-freebsd-x64": "1.2.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.4", + "@rolldown/binding-linux-arm64-gnu": "1.2.4", + "@rolldown/binding-linux-arm64-musl": "1.2.4", + "@rolldown/binding-linux-ppc64-gnu": "1.2.4", + "@rolldown/binding-linux-s390x-gnu": "1.2.4", + "@rolldown/binding-linux-x64-gnu": "1.2.4", + "@rolldown/binding-linux-x64-musl": "1.2.4", + "@rolldown/binding-openharmony-arm64": "1.2.4", + "@rolldown/binding-win32-arm64-msvc": "1.2.4", + "@rolldown/binding-win32-x64-msvc": "1.2.4" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz", + "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.10" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/undici": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz", + "integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/viem": { + "version": "2.55.15", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.55.15.tgz", + "integrity": "sha512-ka9SfSJ3ZfhuUEzTGmufwALRvEPVKM068tF0AwfdRZagqA3yuFa/QoXIvALzr9y47m4Wiisl3yEoYWuFQso6Ng==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.2.3", + "isows": "1.0.7", + "ox": "0.14.33", + "ws": "8.21.0" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vite": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", + "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.25", + "rolldown": "~1.2.1", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/wagmi": { + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/wagmi/-/wagmi-3.7.6.tgz", + "integrity": "sha512-Zm/ppGHV6GYX9by3ERs1KVcNqpMZ4Vdno48Avdz9U+3QCjC5iFatOwruA9IMOl8poL7n/pJGQOcR1xj8N5c69w==", + "license": "MIT", + "dependencies": { + "@wagmi/connectors": "8.1.0", + "@wagmi/core": "3.6.4", + "use-sync-external-store": "1.4.0" + }, + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "@tanstack/react-query": ">=5.0.0", + "react": ">=18", + "typescript": ">=5.9.3", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/wagmi/node_modules/@wagmi/connectors": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@wagmi/connectors/-/connectors-8.1.0.tgz", + "integrity": "sha512-ASlHXq9NC4pp617lalZdlPuPnZUhFeWNEKQfm3ngVSQKd4S2is5XXG4v3EOAhcxdLjSk1AMfGXlHgk5tX2WCZg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "@base-org/account": "^2.5.1", + "@coinbase/wallet-sdk": "^4.3.6", + "@metamask/connect-evm": "^2.1.0", + "@safe-global/safe-apps-provider": "~0.18.6", + "@safe-global/safe-apps-sdk": "^9.1.0", + "@wagmi/core": "3.6.4", + "@walletconnect/ethereum-provider": "^2.21.1", + "accounts": "~0.14", + "typescript": ">=5.9.3", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "@base-org/account": { + "optional": true + }, + "@coinbase/wallet-sdk": { + "optional": true + }, + "@metamask/connect-evm": { + "optional": true + }, + "@safe-global/safe-apps-provider": { + "optional": true + }, + "@safe-global/safe-apps-sdk": { + "optional": true + }, + "@walletconnect/ethereum-provider": { + "optional": true + }, + "accounts": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/wagmi/node_modules/@wagmi/core": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-3.6.4.tgz", + "integrity": "sha512-YERJ+vbFZw3UI6p4KoFU5DkxkqmtzBq5lddpgVAvcu5Tz7kSicKVA3DMCWvjVAFsikNdZYIs39FIkMGWfzpSMQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "5.0.1", + "mipd": "0.0.7", + "zustand": "5.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "@tanstack/query-core": ">=5.0.0", + "accounts": "~0.14", + "typescript": ">=5.9.3", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "@tanstack/query-core": { + "optional": true + }, + "accounts": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/wagmi/node_modules/@wagmi/core/node_modules/zustand": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.0.tgz", + "integrity": "sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.0.tgz", + "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.15.1", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^22.14.0 || >=24.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..8fb9c87 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,34 @@ +{ + "name": "@concord-protocol/frontend", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "tsc -b && vite build", + "preview": "vite preview --host 0.0.0.0", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "@fontsource-variable/inter": "5.3.0", + "@heroicons/react": "2.2.0", + "@tanstack/react-query": "5.101.4", + "react": "19.2.8", + "react-dom": "19.2.8", + "react-router-dom": "7.18.2", + "viem": "2.55.15", + "wagmi": "3.7.6" + }, + "devDependencies": { + "@testing-library/jest-dom": "7.0.1", + "@testing-library/react": "16.3.2", + "@types/react": "19.2.18", + "@types/react-dom": "19.2.4", + "@vitejs/plugin-react": "6.0.5", + "jsdom": "30.0.1", + "typescript": "7.0.2", + "vite": "8.2.1", + "vitest": "4.1.10" + } +} diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx new file mode 100644 index 0000000..0b335c4 --- /dev/null +++ b/frontend/src/App.test.tsx @@ -0,0 +1,121 @@ +import { fireEvent, render, screen, within } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { MemoryRouter } from "react-router-dom"; +import { beforeEach, describe, expect, it } from "vitest"; +import { WagmiProvider } from "wagmi"; +import { App } from "./App"; +import { children, draw, facility, formatToken } from "./data/concord"; +import { wagmiConfig } from "./web3"; + +function renderRoute(path: string) { + const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }); + return render( + + + + + + + , + ); +} + +describe("Concord frontend product semantics", () => { + beforeEach(() => { + window.localStorage.clear(); + }); + + it("presents the relationship as the product on the landing page", () => { + renderRoute("/"); + expect(screen.getByRole("heading", { name: /one facility\.\s*capital from many providers/i })).toBeInTheDocument(); + expect(screen.getByText(/from capital need to reusable capacity/i)).toBeInTheDocument(); + expect(screen.getByRole("heading", { name: /private offers\. public settlement\. no misleading claims/i })).toBeInTheDocument(); + expect(screen.getAllByRole("link", { name: /install wallet/i }).length).toBeGreaterThan(0); + }); + + it("renders the observed Root Accord and distinguishes current exposure from restored capacity", () => { + renderRoute(`/facilities/${facility.id}`); + expect(screen.getByRole("heading", { level: 1, name: "Coston2 syndicated facility" })).toBeInTheDocument(); + expect(screen.getByText(/all 9 usdt0 is available\. nothing is currently owed/i)).toBeInTheDocument(); + expect(screen.getAllByText("ROOT ACCORD").length).toBeGreaterThan(0); + expect(screen.getByLabelText("What is Root Accord?")).toBeInTheDocument(); + expect(screen.getAllByText(/9 USDT0/i).length).toBeGreaterThan(0); + expect(screen.getAllByText(/0 USDT0/i).length).toBeGreaterThan(0); + }); + + it("keeps child commitments and draw legs explicit", () => { + renderRoute(`/draws/${draw.id}`); + expect(screen.getByRole("heading", { name: /4 usdt0 draw/i })).toBeInTheDocument(); + expect(screen.getAllByText("DRAW · DRAW LEGS").length).toBeGreaterThan(0); + expect(screen.getByText("Provider 1")).toBeInTheDocument(); + expect(screen.getByText("Provider 2")).toBeInTheDocument(); + expect(draw.legs).toHaveLength(2); + expect(children).toHaveLength(3); + }); + + it("keeps a draw detail anchored to its parent without duplicating global navigation", () => { + renderRoute(`/draws/${draw.id}`); + expect(screen.getByRole("link", { name: /back to activity/i })).toHaveAttribute("href", `/facilities/${facility.id}/activity`); + expect(within(screen.getByRole("navigation", { name: "Breadcrumb" })).getByText("Activity")).toBeInTheDocument(); + expect(within(screen.getByRole("navigation", { name: "Application navigation" })).getByRole("link", { name: "Facilities" })).toHaveAttribute("aria-current", "page"); + expect(within(screen.getByRole("navigation", { name: "Application navigation" })).queryByRole("link", { name: "Activity" })).not.toBeInTheDocument(); + }); + + it("presents facility sections as stable tabs without repeating the overview header", () => { + document.documentElement.scrollTop = 480; + renderRoute(`/facilities/${facility.id}/funding`); + expect(document.documentElement.scrollTop).toBe(0); + expect(screen.getByRole("heading", { level: 1, name: "Funding" })).toBeInTheDocument(); + expect(screen.getAllByText("MAKKARI SESSION · COFILL ALLOCATION").length).toBeGreaterThan(0); + expect(screen.queryByRole("heading", { level: 1, name: "Coston2 syndicated facility" })).not.toBeInTheDocument(); + expect(screen.getByRole("link", { name: /back to facility overview/i })).toHaveAttribute("href", `/facilities/${facility.id}`); + expect(screen.getAllByRole("link", { name: /activity/i }).some((link) => link.getAttribute("href") === `/facilities/${facility.id}/activity`)).toBe(true); + }); + + it("presents lineage as relationship history, not a forced journey endpoint", () => { + renderRoute(`/facilities/${facility.id}/lineage`); + expect(screen.getByRole("heading", { level: 1, name: "Relationship history" })).toBeInTheDocument(); + expect(screen.getAllByText("LINEAGE").length).toBeGreaterThan(0); + expect(screen.getByRole("link", { name: /back to facility overview/i })).toBeInTheDocument(); + }); + + it("offers an optional tour and glossary without blocking the facility", () => { + renderRoute(`/facilities/${facility.id}`); + expect(screen.getByText("New to Concord?")).toBeInTheDocument(); + + fireEvent.click(screen.getByRole("button", { name: /take the 60-second tour/i })); + expect(screen.getByRole("dialog", { name: /your facility/i })).toBeInTheDocument(); + expect(within(screen.getByRole("dialog", { name: /your facility/i })).getByText("Root Accord")).toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: "Next" })); + expect(screen.getByText("Your current position")).toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: /skip tour/i })); + + fireEvent.click(screen.getByRole("button", { name: "Help" })); + const help = screen.getByRole("dialog", { name: /help and learning/i }); + expect(help).toBeInTheDocument(); + fireEvent.click(screen.getByText("Concord glossary")); + expect(within(help).getByText(/the persistent facility relationship/i)).toBeInTheDocument(); + }); + + it("does not convert an unknown Root Accord into the observed facility", () => { + renderRoute("/facilities/0xnot-observed"); + expect(screen.getByRole("heading", { name: /not in the recorded evidence/i })).toBeInTheDocument(); + expect(screen.queryByText(/all 9 usdt0 is available/i)).not.toBeInTheDocument(); + }); +}); + +describe("recorded Coston2 invariants", () => { + it("derives root commitment and exposure from funded children", () => { + const childCommitment = children.reduce((sum, child) => sum + BigInt(child.committedCapacity), 0n); + const childExposure = children.reduce((sum, child) => sum + BigInt(child.drawnPrincipal), 0n); + expect(childCommitment).toBe(BigInt(facility.committedCapacity)); + expect(childExposure).toBe(BigInt(facility.drawnPrincipal)); + expect(BigInt(facility.availableCapacity)).toBe( + BigInt(facility.committedCapacity) - BigInt(facility.drawnPrincipal), + ); + }); + + it("formats large base-unit amounts without converting through unsafe JavaScript numbers", () => { + expect(formatToken("9007199254740993000000").replace(/\D/g, "")).toBe("9007199254740993"); + }); +}); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..3551d85 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,974 @@ +import { + ArrowDownTrayIcon, + ArrowLeftIcon, + ArrowRightIcon, + ArrowTopRightOnSquareIcon, + ArrowUpTrayIcon, + ArrowsRightLeftIcon, + BanknotesIcon, + Bars3Icon, + BuildingLibraryIcon, + CheckCircleIcon, + ChevronRightIcon, + DocumentDuplicateIcon, + DocumentTextIcon, + EyeSlashIcon, + FunnelIcon, + GlobeAltIcon, + InformationCircleIcon, + LockClosedIcon, + MagnifyingGlassIcon, + QuestionMarkCircleIcon, + RectangleGroupIcon, + ServerStackIcon, + ShareIcon, + ShieldCheckIcon, + SparklesIcon, + Squares2X2Icon, + UserGroupIcon, + XMarkIcon, +} from "@heroicons/react/24/outline"; +import { CheckCircleIcon as CheckCircleSolid } from "@heroicons/react/20/solid"; +import { + type CSSProperties, + type ComponentType, + type PropsWithChildren, + type SVGProps, + useEffect, + useLayoutEffect, + useRef, + useState, +} from "react"; +import { + Link, + NavLink, + Route, + Routes, + useLocation, + useParams, +} from "react-router-dom"; +import { DrawActionReview } from "./components/DrawActionReview"; +import { WalletControl } from "./components/WalletControl"; +import { + activity, + children, + draw, + evidence, + explorerAddress, + explorerTx, + facility, + formatDate, + formatToken, + providerLabels, + round, + shortId, + snapshot, +} from "./data/concord"; + +type IconType = ComponentType>; + +const rootHref = `/facilities/${facility.id}`; + +function Brand({ inverted = false }: { inverted?: boolean }) { + return ( + +