diff --git a/.github/actions/setup-hyperbet/action.yml b/.github/actions/setup-hyperbet/action.yml index 8bc00e2e..3eebf76f 100644 --- a/.github/actions/setup-hyperbet/action.yml +++ b/.github/actions/setup-hyperbet/action.yml @@ -75,6 +75,19 @@ runs: if: ${{ inputs.install-foundry == 'true' }} uses: foundry-rs/foundry-toolchain@v1 + - name: Initialize EVM submodules + if: ${{ inputs.install-foundry == 'true' }} + shell: bash + run: | + set -euo pipefail + if [[ ! -f .gitmodules ]]; then + exit 0 + fi + if [[ -f packages/evm-contracts/lib/solady/src/utils/FixedPointMathLib.sol ]]; then + exit 0 + fi + git submodule update --init --recursive packages/evm-contracts/lib/solady + - name: Cache Solana CLI if: ${{ inputs.install-solana == 'true' }} uses: actions/cache@v5.0.4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05bc270b..81423673 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -223,16 +223,24 @@ jobs: include: - package: hyperbet-solana workspace_target: hyperbet-solana - audit_target: pages:solana + audit_target: pages:legacy build_mode: mainnet-beta solana_cluster: mainnet-beta + keeper_url_var: HYPERBET_SOLANA_KEEPER_URL + keeper_ws_url_var: HYPERBET_SOLANA_KEEPER_WS_URL + keeper_fallback_url: "https://api.hyperbet.win" + keeper_fallback_ws_url: "wss://api.hyperbet.win/ws" avax_chain_id: "" avax_gold_clob_address: "" - package: hyperbet-bsc workspace_target: hyperbet-bsc - audit_target: pages:bsc + audit_target: pages:legacy build_mode: mainnet-beta solana_cluster: mainnet-beta + keeper_url_var: HYPERBET_BSC_KEEPER_URL + keeper_ws_url_var: HYPERBET_BSC_KEEPER_WS_URL + keeper_fallback_url: "https://bsc-api.hyperbet.win" + keeper_fallback_ws_url: "wss://bsc-api.hyperbet.win/ws" avax_chain_id: "" avax_gold_clob_address: "" - package: hyperbet-avax @@ -240,12 +248,16 @@ jobs: audit_target: app:avax build_mode: testnet solana_cluster: testnet + keeper_url_var: HYPERBET_AVAX_KEEPER_URL + keeper_ws_url_var: HYPERBET_AVAX_KEEPER_WS_URL + keeper_fallback_url: "https://avax-api.hyperbet.win" + keeper_fallback_ws_url: "wss://avax-api.hyperbet.win/ws" avax_chain_id: "" avax_gold_clob_address: "" env: CF_PAGES_COMMIT_SHA: ${{ github.sha }} - VITE_GAME_API_URL: https://api.hyperbet.win - VITE_GAME_WS_URL: wss://api.hyperbet.win/ws + VITE_GAME_API_URL: ${{ vars[matrix.keeper_url_var] || matrix.keeper_fallback_url }} + VITE_GAME_WS_URL: ${{ vars[matrix.keeper_ws_url_var] || matrix.keeper_fallback_ws_url }} VITE_SOLANA_CLUSTER: ${{ matrix.solana_cluster }} VITE_USE_GAME_RPC_PROXY: "true" VITE_USE_GAME_EVM_RPC_PROXY: "true" @@ -335,8 +347,8 @@ jobs: needs: shared-validation env: CF_PAGES_COMMIT_SHA: ${{ github.sha }} - VITE_GAME_API_URL: https://api.hyperbet.win - VITE_GAME_WS_URL: wss://api.hyperbet.win/ws + VITE_GAME_API_URL: ${{ vars.HYPERBET_BSC_KEEPER_URL || 'https://bsc-api.hyperbet.win' }} + VITE_GAME_WS_URL: ${{ vars.HYPERBET_BSC_KEEPER_WS_URL || 'wss://bsc-api.hyperbet.win/ws' }} VITE_SOLANA_CLUSTER: mainnet-beta VITE_USE_GAME_RPC_PROXY: "true" VITE_USE_GAME_EVM_RPC_PROXY: "true" diff --git a/.github/workflows/deploy-avax-pages.yml b/.github/workflows/deploy-avax-pages.yml index b2076569..3b7e9cf6 100644 --- a/.github/workflows/deploy-avax-pages.yml +++ b/.github/workflows/deploy-avax-pages.yml @@ -1,17 +1,6 @@ name: Deploy Hyperbet AVAX Pages on: - push: - branches: [main] - paths: - - "packages/hyperbet-avax/app/**" - - "packages/hyperbet-ui/**" - - "scripts/**" - - ".github/actions/setup-hyperbet/**" - - ".github/workflows/deploy-avax-pages.yml" - - "docs/hyperbet-production-deploy.md" - - "package.json" - - "bun.lock" workflow_dispatch: inputs: environment: @@ -154,7 +143,7 @@ jobs: if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.environment }}" = "preview" ]; then BRANCH="preview-${{ github.run_id }}" elif [ "${DEPLOY_TARGET}" = "staging" ]; then - BRANCH="staging" + BRANCH="enoomian/staging" else BRANCH="main" fi diff --git a/.github/workflows/deploy-bsc-pages.yml b/.github/workflows/deploy-bsc-pages.yml index 3c204524..c68f2827 100644 --- a/.github/workflows/deploy-bsc-pages.yml +++ b/.github/workflows/deploy-bsc-pages.yml @@ -1,18 +1,6 @@ -name: Deploy Hyperbet BSC Pages +name: Rollback Legacy Hyperbet BSC Pages on: - push: - branches: [main] - paths: - - "packages/hyperbet-bsc/app/**" - - "packages/hyperbet-ui/**" - - "packages/hyperbet-chain-registry/**" - - "scripts/**" - - ".github/actions/setup-hyperbet/**" - - ".github/workflows/deploy-bsc-pages.yml" - - "docs/hyperbet-production-deploy.md" - - "package.json" - - "bun.lock" workflow_dispatch: inputs: environment: @@ -24,6 +12,11 @@ on: - production - staging - preview + rollback_only: + description: "Confirm this legacy workflow is being used for rollback only" + required: true + default: false + type: boolean permissions: contents: read @@ -34,7 +27,7 @@ concurrency: jobs: deploy: - if: ${{ github.event_name == 'workflow_dispatch' || vars.HYPERBET_BSC_PAGES_DEPLOY_ENABLED == 'true' || (vars.HYPERBET_BSC_PAGES_DEPLOY_ENABLED == '' && vars.HYPERBET_BSC_DEPLOY_ENABLED == 'true') }} + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_only == 'true' }} name: Deploy hyperbet-bsc pages runs-on: ubuntu-latest environment: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'staging' && 'staging' || 'production' }} @@ -120,7 +113,7 @@ jobs: bash scripts/ci-install-verified.sh root hyperbet-bsc-app - name: Audit deploy env - run: node --import tsx scripts/ci-env-audit.ts --target=pages:bsc --deployment=${DEPLOY_TARGET} + run: node --import tsx scripts/ci-env-audit.ts --target=pages:legacy --deployment=${DEPLOY_TARGET} env: VITE_GAME_API_URL: ${{ env.HYPERBET_API_URL }} VITE_GAME_WS_URL: ${{ env.HYPERBET_WS_URL }} @@ -164,7 +157,7 @@ jobs: if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.environment }}" = "preview" ]; then BRANCH="preview-${{ github.run_id }}" elif [ "${DEPLOY_TARGET}" = "staging" ]; then - BRANCH="staging" + BRANCH="enoomian/staging" else BRANCH="main" fi diff --git a/.github/workflows/deploy-hyperbet-keeper.yml b/.github/workflows/deploy-hyperbet-keeper.yml new file mode 100644 index 00000000..07aa1149 --- /dev/null +++ b/.github/workflows/deploy-hyperbet-keeper.yml @@ -0,0 +1,233 @@ +name: Deploy Hyperbet Keeper + +on: + push: + branches: [main] + paths: + - "packages/hyperbet-evm/keeper/**" + - "packages/hyperbet-evm/deployments/**" + - "packages/hyperbet-chain-registry/**" + - "packages/hyperbet-mm-core/**" + - "packages/hyperbet-evm-keeper-core/**" + - "scripts/**" + - ".github/actions/setup-hyperbet/**" + - ".github/workflows/deploy-hyperbet-keeper.yml" + - "docs/hyperbet-production-deploy.md" + - "package.json" + - "bun.lock" + workflow_dispatch: + inputs: + environment: + description: "Deployment target" + required: true + default: production + type: choice + options: + - production + - staging + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + deploy: + name: Deploy hyperbet keeper + runs-on: ubuntu-latest + environment: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'staging' && 'enoomian-staging' || 'production' }} + steps: + - name: Checkout code + uses: actions/checkout@v6.0.2 + with: + fetch-depth: 0 + + - name: Resolve deployment target + run: | + deploy_target="production" + pages_url="${{ vars.HYPERBET_PAGES_URL }}" + keeper_url="${{ vars.HYPERBET_KEEPER_URL }}" + keeper_ws_url="${{ vars.HYPERBET_KEEPER_WS_URL }}" + railway_project_id="${{ vars.HYPERBET_RAILWAY_PROJECT_ID }}" + railway_environment_id="${{ vars.HYPERBET_RAILWAY_PRODUCTION_ENVIRONMENT_ID }}" + railway_service_id="${{ vars.HYPERBET_RAILWAY_KEEPER_SERVICE_ID }}" + enabled_evm_chains="bsc,avax" + avax_rpc_url="https://api.avax.network/ext/bc/C/rpc" + avax_gold_clob_address="${{ vars.HYPERBET_AVAX_GOLD_CLOB_ADDRESS }}" + keeper_status_filter='.ok == true and .proxies.solanaRpc == true and .proxies.bscRpc == true and .proxies.avaxRpc == true' + + if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.environment }}" = "staging" ]; then + deploy_target="staging" + pages_url="${{ vars.ENOOMIAN_HYPERBET_PAGES_URL }}" + keeper_url="${{ vars.ENOOMIAN_HYPERBET_KEEPER_URL }}" + keeper_ws_url="${{ vars.ENOOMIAN_HYPERBET_KEEPER_WS_URL }}" + railway_project_id="${{ vars.ENOOMIAN_RAILWAY_PROJECT_ID }}" + railway_environment_id="${{ vars.ENOOMIAN_RAILWAY_ENVIRONMENT_ID }}" + railway_service_id="${{ vars.ENOOMIAN_HYPERBET_KEEPER_SERVICE_ID }}" + enabled_evm_chains="bsc" + avax_rpc_url="" + avax_gold_clob_address="" + keeper_status_filter='.ok == true and .proxies.solanaRpc == true and .proxies.bscRpc == true' + fi + + if [ -z "${pages_url}" ] || [ -z "${keeper_url}" ] || [ -z "${keeper_ws_url}" ] || [ -z "${railway_project_id}" ] || [ -z "${railway_environment_id}" ] || [ -z "${railway_service_id}" ]; then + echo "Missing Hyperbet keeper configuration for ${deploy_target}" >&2 + exit 1 + fi + + echo "DEPLOY_TARGET=${deploy_target}" >> "${GITHUB_ENV}" + echo "HYPERBET_PAGES_URL=${pages_url}" >> "${GITHUB_ENV}" + echo "HYPERBET_KEEPER_URL=${keeper_url}" >> "${GITHUB_ENV}" + echo "HYPERBET_KEEPER_WS_URL=${keeper_ws_url}" >> "${GITHUB_ENV}" + echo "RAILWAY_PROJECT_ID=${railway_project_id}" >> "${GITHUB_ENV}" + echo "RAILWAY_ENVIRONMENT_ID=${railway_environment_id}" >> "${GITHUB_ENV}" + echo "RAILWAY_KEEPER_SERVICE_ID=${railway_service_id}" >> "${GITHUB_ENV}" + echo "HYPERBET_ENABLED_EVM_CHAINS=${enabled_evm_chains}" >> "${GITHUB_ENV}" + echo "HYPERBET_AVAX_RPC_URL=${avax_rpc_url}" >> "${GITHUB_ENV}" + echo "HYPERBET_AVAX_GOLD_CLOB_ADDRESS=${avax_gold_clob_address}" >> "${GITHUB_ENV}" + echo "KEEPER_STATUS_FILTER=${keeper_status_filter}" >> "${GITHUB_ENV}" + + - name: Setup Hyperbet toolchain + uses: ./.github/actions/setup-hyperbet + + - name: Detect keeper source changes + id: keeper_changes + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "Manual dispatch requested; forcing keeper deploy." + echo "keeper_source_changed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + base_ref="${{ github.event.before }}" + if [ -z "$base_ref" ] || [ "$base_ref" = "0000000000000000000000000000000000000000" ] || ! git cat-file -e "${base_ref}^{commit}" 2>/dev/null; then + base_ref="$(git rev-parse HEAD^ 2>/dev/null || true)" + fi + + if [ -n "$base_ref" ]; then + changed_files="$(git diff --name-only "$base_ref" "${{ github.sha }}")" + else + changed_files="$(git show --pretty='' --name-only "${{ github.sha }}")" + fi + printf '%s\n' "$changed_files" + + keeper_change_pattern='^(packages/hyperbet-evm/keeper/|packages/hyperbet-evm/deployments/|packages/hyperbet-chain-registry/|packages/hyperbet-mm-core/|packages/hyperbet-evm-keeper-core/|scripts/|\.github/actions/setup-hyperbet/)' + keeper_source_changed=false + if printf '%s\n' "$changed_files" | grep -Eq "${keeper_change_pattern}"; then + keeper_source_changed=true + fi + + echo "keeper_source_changed=${keeper_source_changed}" >> "$GITHUB_OUTPUT" + + - name: Install dependencies + run: bash scripts/ci-install-verified.sh root hyperbet-evm-keeper + + - name: Audit deploy env + run: node --import tsx scripts/ci-env-audit.ts --target=keeper:unified --deployment=${DEPLOY_TARGET} + env: + ENOOMIAN_HYPERBET_PAGES_URL: ${{ env.HYPERBET_PAGES_URL }} + ENOOMIAN_HYPERBET_KEEPER_URL: ${{ env.HYPERBET_KEEPER_URL }} + ENOOMIAN_HYPERBET_KEEPER_WS_URL: ${{ env.HYPERBET_KEEPER_WS_URL }} + CI_AUDIT_REQUIRE_RUNTIME: "true" + HYPERBET_KEEPER_URL: ${{ env.HYPERBET_KEEPER_URL }} + RAILWAY_PROJECT_ID: ${{ env.RAILWAY_PROJECT_ID }} + RAILWAY_ENVIRONMENT_ID: ${{ env.RAILWAY_ENVIRONMENT_ID }} + RAILWAY_KEEPER_SERVICE_ID: ${{ env.RAILWAY_KEEPER_SERVICE_ID }} + SOLANA_RPC_URL: https://api.mainnet-beta.solana.com + BSC_RPC_URL: https://bsc-dataseed.binance.org + BSC_GOLD_CLOB_ADDRESS: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'staging' && (vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS != '' && vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS || vars.HYPERBET_BSC_STAGING_GOLD_CLOB_ADDRESS) || vars.HYPERBET_BSC_GOLD_CLOB_ADDRESS }} + AVAX_RPC_URL: ${{ env.HYPERBET_AVAX_RPC_URL }} + AVAX_GOLD_CLOB_ADDRESS: ${{ env.HYPERBET_AVAX_GOLD_CLOB_ADDRESS }} + EVM_KEEPER_CHAINS: ${{ env.HYPERBET_ENABLED_EVM_CHAINS }} + + - name: Typecheck keeper + run: bunx tsc --noEmit -p packages/hyperbet-evm/keeper/tsconfig.json + + - name: Smoke test keeper service + run: | + PORT=18080 \ + ENABLE_KEEPER_BOT=false \ + EVM_KEEPER_CHAINS="${HYPERBET_ENABLED_EVM_CHAINS}" \ + SOLANA_RPC_URL=https://api.mainnet-beta.solana.com \ + BSC_RPC_URL=https://bsc-dataseed.binance.org \ + AVAX_RPC_URL="${HYPERBET_AVAX_RPC_URL}" \ + BSC_GOLD_CLOB_ADDRESS=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'staging' && (vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS != '' && vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS || vars.HYPERBET_BSC_STAGING_GOLD_CLOB_ADDRESS) || vars.HYPERBET_BSC_GOLD_CLOB_ADDRESS }} \ + AVAX_GOLD_CLOB_ADDRESS="${HYPERBET_AVAX_GOLD_CLOB_ADDRESS}" \ + bun --bun packages/hyperbet-evm/keeper/src/service.ts >/tmp/hyperbet-keeper.log 2>&1 & + KEEPER_PID=$! + trap 'kill "$KEEPER_PID" >/dev/null 2>&1 || true' EXIT + + for attempt in $(seq 1 20); do + if curl -fsSL http://127.0.0.1:18080/status | jq -e '.ok == true and .proxies != null' >/dev/null; then + exit 0 + fi + sleep 1 + done + + cat /tmp/hyperbet-keeper.log + exit 1 + + - name: Install Railway CLI + if: steps.keeper_changes.outputs.keeper_source_changed == 'true' + run: bun add -g @railway/cli + + - name: Write Railway keeper context + if: steps.keeper_changes.outputs.keeper_source_changed == 'true' + working-directory: packages/hyperbet-evm/keeper + run: | + keeper_path="$(pwd)" + mkdir -p "${HOME}/.railway" + cat > "${HOME}/.railway/config.json" </dev/null; then + break + fi + sleep 15 + done + + curl -fsSL "${HYPERBET_KEEPER_URL}/status" | jq -e '.ok == true' + curl -fsSL "${HYPERBET_KEEPER_URL}/api/arena/prediction-markets/active" | jq -e '.duel != null and .markets != null' + curl -fsSL "${HYPERBET_KEEPER_URL}/api/arena/prediction-markets/overview" | jq -e '.duel != null and .markets != null' + curl -fsSL "${HYPERBET_KEEPER_URL}/api/keeper/bot-health" | jq -e '.ok == true and .markets != null' diff --git a/.github/workflows/deploy-hyperbet-pages.yml b/.github/workflows/deploy-hyperbet-pages.yml new file mode 100644 index 00000000..53b38aa8 --- /dev/null +++ b/.github/workflows/deploy-hyperbet-pages.yml @@ -0,0 +1,237 @@ +name: Deploy Hyperbet Pages + +on: + push: + branches: [main] + paths: + - "packages/hyperbet-evm/app/**" + - "packages/hyperbet-ui/**" + - "packages/hyperbet-chain-registry/**" + - "scripts/**" + - ".github/actions/setup-hyperbet/**" + - ".github/workflows/deploy-hyperbet-pages.yml" + - "docs/hyperbet-production-deploy.md" + - "package.json" + - "bun.lock" + workflow_dispatch: + inputs: + environment: + description: "Deployment target" + required: true + default: production + type: choice + options: + - production + - staging + - preview + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + CLOUDFLARE_ACCOUNT_ID: ${{ vars.HYPERBET_CLOUDFLARE_ACCOUNT_ID != '' && vars.HYPERBET_CLOUDFLARE_ACCOUNT_ID || '50ad2052bbc6ca528d6993a689b419a4' }} + +jobs: + deploy: + name: Deploy hyperbet pages + runs-on: ubuntu-latest + environment: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'staging' && 'enoomian-staging' || 'production' }} + steps: + - name: Checkout code + uses: actions/checkout@v6.0.2 + + - name: Resolve deployment target + run: | + deploy_target="production" + pages_project_name="${{ vars.HYPERBET_PAGES_PROJECT_NAME }}" + pages_expected_url="${{ vars.HYPERBET_PAGES_PRODUCTION_URL }}" + api_url="${{ vars.HYPERBET_KEEPER_URL }}" + ws_url="${{ vars.HYPERBET_KEEPER_WS_URL }}" + bsc_chain_id="56" + bsc_clob_address="${{ vars.HYPERBET_BSC_GOLD_CLOB_ADDRESS != '' && vars.HYPERBET_BSC_GOLD_CLOB_ADDRESS || '0x443C09B1E7bb7bA3392b02500772B185654A6F33' }}" + base_chain_id="${{ vars.HYPERBET_BASE_CHAIN_ID != '' && vars.HYPERBET_BASE_CHAIN_ID || '8453' }}" + base_clob_address="${{ vars.HYPERBET_BASE_GOLD_CLOB_ADDRESS != '' && vars.HYPERBET_BASE_GOLD_CLOB_ADDRESS || '0xb8c66D6895Bafd1B0027F2c0865865043064437C' }}" + avax_chain_id="${{ vars.HYPERBET_AVAX_CHAIN_ID }}" + avax_clob_address="${{ vars.HYPERBET_AVAX_GOLD_CLOB_ADDRESS }}" + + if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.environment }}" = "staging" ]; then + deploy_target="staging" + pages_project_name="${{ vars.ENOOMIAN_HYPERBET_PAGES_PROJECT_NAME }}" + pages_expected_url="${{ vars.ENOOMIAN_HYPERBET_PAGES_URL }}" + api_url="${{ vars.ENOOMIAN_HYPERBET_KEEPER_URL }}" + ws_url="${{ vars.ENOOMIAN_HYPERBET_KEEPER_WS_URL }}" + bsc_chain_id="${{ vars.ENOOMIAN_BSC_CHAIN_ID != '' && vars.ENOOMIAN_BSC_CHAIN_ID || vars.HYPERBET_BSC_STAGING_CHAIN_ID }}" + bsc_clob_address="${{ vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS != '' && vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS || vars.HYPERBET_BSC_STAGING_GOLD_CLOB_ADDRESS }}" + base_chain_id="" + base_clob_address="" + avax_chain_id="" + avax_clob_address="" + fi + + if [ -z "${pages_project_name}" ]; then + echo "Missing Hyperbet Pages project name for ${deploy_target}" >&2 + exit 1 + fi + + if [ "${{ github.event_name }}" != "workflow_dispatch" ] || [ "${{ inputs.environment }}" != "preview" ]; then + if [ -z "${pages_expected_url}" ] || [ -z "${api_url}" ] || [ -z "${ws_url}" ]; then + echo "Missing Hyperbet Pages URL configuration for ${deploy_target}" >&2 + exit 1 + fi + fi + + if [ -z "${bsc_chain_id}" ] || [ -z "${bsc_clob_address}" ]; then + echo "Missing BSC chain configuration for ${deploy_target}" >&2 + exit 1 + fi + + echo "DEPLOY_TARGET=${deploy_target}" >> "${GITHUB_ENV}" + echo "PAGES_PROJECT_NAME=${pages_project_name}" >> "${GITHUB_ENV}" + echo "PAGES_EXPECTED_URL=${pages_expected_url}" >> "${GITHUB_ENV}" + echo "HYPERBET_API_URL=${api_url}" >> "${GITHUB_ENV}" + echo "HYPERBET_WS_URL=${ws_url}" >> "${GITHUB_ENV}" + echo "HYPERBET_BSC_CHAIN_ID=${bsc_chain_id}" >> "${GITHUB_ENV}" + echo "HYPERBET_BSC_GOLD_CLOB_ADDRESS=${bsc_clob_address}" >> "${GITHUB_ENV}" + echo "HYPERBET_BASE_CHAIN_ID=${base_chain_id}" >> "${GITHUB_ENV}" + echo "HYPERBET_BASE_GOLD_CLOB_ADDRESS=${base_clob_address}" >> "${GITHUB_ENV}" + echo "HYPERBET_AVAX_CHAIN_ID=${avax_chain_id}" >> "${GITHUB_ENV}" + echo "HYPERBET_AVAX_GOLD_CLOB_ADDRESS=${avax_clob_address}" >> "${GITHUB_ENV}" + + - name: Configure Wrangler auth + env: + CLOUDFLARE_API_TOKEN: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'staging' && secrets.ENOOMIAN_CLOUDFLARE_API_TOKEN || secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_WRANGLER_CONFIG_B64: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'staging' && secrets.ENOOMIAN_CLOUDFLARE_WRANGLER_CONFIG_B64 || secrets.CLOUDFLARE_WRANGLER_CONFIG_B64 }} + run: | + if [ -n "${CLOUDFLARE_API_TOKEN}" ]; then + echo "CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}" >> "${GITHUB_ENV}" + exit 0 + fi + + if [ -z "${CLOUDFLARE_WRANGLER_CONFIG_B64}" ]; then + echo "Missing Cloudflare deploy credentials" >&2 + exit 1 + fi + + mkdir -p "${HOME}/.wrangler/config" + printf '%s' "${CLOUDFLARE_WRANGLER_CONFIG_B64}" | base64 --decode > "${HOME}/.wrangler/config/default.toml" + + - name: Setup Hyperbet toolchain + uses: ./.github/actions/setup-hyperbet + + - name: Install dependencies + run: bash scripts/ci-install-verified.sh root hyperbet-evm-app + + - name: Audit deploy env + run: node --import tsx scripts/ci-env-audit.ts --target=pages:unified --deployment=${DEPLOY_TARGET} + env: + ENOOMIAN_HYPERBET_PAGES_URL: ${{ env.PAGES_EXPECTED_URL }} + ENOOMIAN_HYPERBET_KEEPER_URL: ${{ env.HYPERBET_API_URL }} + ENOOMIAN_HYPERBET_KEEPER_WS_URL: ${{ env.HYPERBET_WS_URL }} + VITE_GAME_API_URL: ${{ env.HYPERBET_API_URL }} + VITE_GAME_WS_URL: ${{ env.HYPERBET_WS_URL }} + VITE_SOLANA_CLUSTER: mainnet-beta + VITE_USE_GAME_RPC_PROXY: "true" + VITE_USE_GAME_EVM_RPC_PROXY: "true" + VITE_BSC_CHAIN_ID: ${{ env.HYPERBET_BSC_CHAIN_ID }} + VITE_BSC_GOLD_CLOB_ADDRESS: ${{ env.HYPERBET_BSC_GOLD_CLOB_ADDRESS }} + VITE_BASE_CHAIN_ID: ${{ env.HYPERBET_BASE_CHAIN_ID }} + VITE_BASE_GOLD_CLOB_ADDRESS: ${{ env.HYPERBET_BASE_GOLD_CLOB_ADDRESS }} + VITE_AVAX_CHAIN_ID: ${{ env.HYPERBET_AVAX_CHAIN_ID }} + VITE_AVAX_GOLD_CLOB_ADDRESS: ${{ env.HYPERBET_AVAX_GOLD_CLOB_ADDRESS }} + + - name: Build production app + run: bun run --cwd packages/hyperbet-evm/app build --mode mainnet-beta + env: + CF_PAGES_COMMIT_SHA: ${{ github.sha }} + VITE_GAME_API_URL: ${{ env.HYPERBET_API_URL }} + VITE_GAME_WS_URL: ${{ env.HYPERBET_WS_URL }} + VITE_SOLANA_CLUSTER: mainnet-beta + VITE_USE_GAME_RPC_PROXY: "true" + VITE_USE_GAME_EVM_RPC_PROXY: "true" + VITE_BSC_CHAIN_ID: ${{ env.HYPERBET_BSC_CHAIN_ID }} + VITE_BSC_GOLD_CLOB_ADDRESS: ${{ env.HYPERBET_BSC_GOLD_CLOB_ADDRESS }} + VITE_BASE_CHAIN_ID: ${{ env.HYPERBET_BASE_CHAIN_ID }} + VITE_BASE_GOLD_CLOB_ADDRESS: ${{ env.HYPERBET_BASE_GOLD_CLOB_ADDRESS }} + VITE_AVAX_CHAIN_ID: ${{ env.HYPERBET_AVAX_CHAIN_ID }} + VITE_AVAX_GOLD_CLOB_ADDRESS: ${{ env.HYPERBET_AVAX_GOLD_CLOB_ADDRESS }} + + - name: Verify dist hygiene + run: | + test -f packages/hyperbet-evm/app/dist/build-info.json + if find packages/hyperbet-evm/app/dist -name '*.map' | grep -q .; then + echo "Unexpected source maps in production dist" + exit 1 + fi + if grep -R -n "api-key=" packages/hyperbet-evm/app/dist; then + echo "Provider key leak detected in dist" + exit 1 + fi + + - name: Deploy to Cloudflare Pages + working-directory: packages/hyperbet-evm/app + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.environment }}" = "preview" ]; then + BRANCH="preview-${{ github.run_id }}" + elif [ "${DEPLOY_TARGET}" = "staging" ]; then + BRANCH="enoomian/staging" + else + BRANCH="main" + fi + + COMMIT_MSG=$(git log -1 --pretty=%s | tr -d '"' | cut -c1-100) + + npx wrangler@4.72.0 pages deploy dist \ + --project-name="${PAGES_PROJECT_NAME}" \ + --branch="${BRANCH}" \ + --commit-hash="${GITHUB_SHA}" \ + --commit-message="${COMMIT_MSG}" \ + --commit-dirty=true \ + --skip-caching + + - name: Resolve deployed Pages URL + id: deployment_url + if: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.environment != 'preview' }} + run: | + pages_environment="production" + if [ "${DEPLOY_TARGET}" != "production" ]; then + pages_environment="preview" + fi + + deployed_url="$(npx wrangler@4.72.0 pages deployment list \ + --project-name "${PAGES_PROJECT_NAME}" \ + --environment "${pages_environment}" \ + --json | jq -r --arg source "${GITHUB_SHA::7}" 'map(select(.Source == $source)) | .[0].Deployment // empty')" + + if [ -z "${deployed_url}" ]; then + echo "Unable to resolve deployed Pages URL for ${PAGES_PROJECT_NAME}" >&2 + exit 1 + fi + + echo "deployed_url=${deployed_url}" >> "${GITHUB_OUTPUT}" + + - name: Verify production build metadata + if: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.environment != 'preview' }} + run: | + deployed_url="${{ steps.deployment_url.outputs.deployed_url }}" + for attempt in $(seq 1 20); do + payload="$(curl -fsSL "${deployed_url}/build-info.json" || true)" + if [ -n "$payload" ] && echo "$payload" | jq -e --arg sha "$GITHUB_SHA" '.commitHash == $sha' >/dev/null; then + echo "build-info.json matches ${GITHUB_SHA} at ${deployed_url}" + break + fi + sleep 15 + done + curl -fsSL "${deployed_url}/build-info.json" | jq -e --arg sha "$GITHUB_SHA" '.commitHash == $sha' + + if [ -n "${PAGES_EXPECTED_URL}" ] && [ "${PAGES_EXPECTED_URL}" != "${deployed_url}" ]; then + custom_payload="$(curl -fsSL "${PAGES_EXPECTED_URL}/build-info.json" || true)" + if [ -n "${custom_payload}" ] && echo "${custom_payload}" | jq -e --arg sha "$GITHUB_SHA" '.commitHash == $sha' >/dev/null; then + echo "custom domain build-info.json matches ${GITHUB_SHA}" + else + echo "warning: ${PAGES_EXPECTED_URL}/build-info.json is not serving ${GITHUB_SHA} yet" >&2 + fi + fi diff --git a/.github/workflows/deploy-solana-pages.yml b/.github/workflows/deploy-solana-pages.yml index 4347e50c..57587fea 100644 --- a/.github/workflows/deploy-solana-pages.yml +++ b/.github/workflows/deploy-solana-pages.yml @@ -1,18 +1,6 @@ -name: Deploy Hyperbet Solana Pages +name: Rollback Legacy Hyperbet Solana Pages on: - push: - branches: [main] - paths: - - "packages/hyperbet-solana/app/**" - - "packages/hyperbet-ui/**" - - "packages/hyperbet-chain-registry/**" - - "scripts/**" - - ".github/actions/setup-hyperbet/**" - - ".github/workflows/deploy-solana-pages.yml" - - "docs/hyperbet-production-deploy.md" - - "package.json" - - "bun.lock" workflow_dispatch: inputs: environment: @@ -24,6 +12,11 @@ on: - production - staging - preview + rollback_only: + description: "Confirm this legacy workflow is being used for rollback only" + required: true + default: false + type: boolean permissions: contents: read @@ -39,6 +32,7 @@ env: jobs: deploy: + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.rollback_only == 'true' }} name: Deploy hyperbet-solana pages runs-on: ubuntu-latest environment: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'staging' && 'staging' || 'production' }} @@ -125,7 +119,7 @@ jobs: bash scripts/ci-install-verified.sh root hyperbet-solana-app - name: Audit deploy env - run: node --import tsx scripts/ci-env-audit.ts --target=pages:solana --deployment=${DEPLOY_TARGET} + run: node --import tsx scripts/ci-env-audit.ts --target=pages:legacy --deployment=${DEPLOY_TARGET} env: VITE_GAME_API_URL: ${{ env.HYPERBET_API_URL }} VITE_GAME_WS_URL: ${{ env.HYPERBET_WS_URL }} @@ -169,7 +163,7 @@ jobs: if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.environment }}" = "preview" ]; then BRANCH="preview-${{ github.run_id }}" elif [ "${DEPLOY_TARGET}" = "staging" ]; then - BRANCH="staging" + BRANCH="enoomian/staging" else BRANCH="main" fi diff --git a/.github/workflows/pm-soak.yml b/.github/workflows/pm-soak.yml index cf3ec574..8184b8c8 100644 --- a/.github/workflows/pm-soak.yml +++ b/.github/workflows/pm-soak.yml @@ -19,7 +19,7 @@ on: chains: description: "Comma-separated chain set" required: true - default: solana,bsc,avax + default: unified type: string screenshots: description: "Capture staged page screenshots" @@ -41,34 +41,37 @@ jobs: timeout-minutes: 180 permissions: contents: read - environment: staging + environment: enoomian-staging env: + HYPERBET_ACCEPTANCE_DEPLOYMENT: staging + ENOOMIAN_HYPERBET_PAGES_URL: ${{ vars.ENOOMIAN_HYPERBET_PAGES_URL }} + ENOOMIAN_HYPERBET_KEEPER_URL: ${{ vars.ENOOMIAN_HYPERBET_KEEPER_URL }} + ENOOMIAN_HYPERBET_KEEPER_WS_URL: ${{ vars.ENOOMIAN_HYPERBET_KEEPER_WS_URL }} + ENOOMIAN_RAILWAY_PROJECT_ID: ${{ vars.ENOOMIAN_RAILWAY_PROJECT_ID }} + ENOOMIAN_RAILWAY_ENVIRONMENT_ID: ${{ vars.ENOOMIAN_RAILWAY_ENVIRONMENT_ID }} + ENOOMIAN_HYPERBET_KEEPER_SERVICE_ID: ${{ vars.ENOOMIAN_HYPERBET_KEEPER_SERVICE_ID }} + ENOOMIAN_SOLANA_RPC_URL: ${{ vars.ENOOMIAN_SOLANA_RPC_URL }} + ENOOMIAN_SOLANA_CLUSTER: ${{ vars.ENOOMIAN_SOLANA_CLUSTER }} + ENOOMIAN_BSC_RPC_URL: ${{ vars.ENOOMIAN_BSC_RPC_URL }} + ENOOMIAN_BSC_CHAIN_ID: ${{ vars.ENOOMIAN_BSC_CHAIN_ID != '' && vars.ENOOMIAN_BSC_CHAIN_ID || vars.HYPERBET_BSC_STAGING_CHAIN_ID }} + ENOOMIAN_BSC_GOLD_CLOB_ADDRESS: ${{ vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS != '' && vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS || vars.HYPERBET_BSC_STAGING_GOLD_CLOB_ADDRESS }} HYPERBET_STAGED_PROOF_DUEL_ID: ${{ vars.HYPERBET_STAGED_PROOF_DUEL_ID }} HYPERBET_STAGED_PROOF_DUEL_KEY: ${{ vars.HYPERBET_STAGED_PROOF_DUEL_KEY }} - HYPERBET_SOLANA_PAGES_STAGING_URL: ${{ vars.HYPERBET_SOLANA_PAGES_STAGING_URL }} - HYPERBET_SOLANA_KEEPER_STAGING_URL: ${{ vars.HYPERBET_SOLANA_KEEPER_STAGING_URL }} - HYPERBET_SOLANA_KEEPER_STAGING_WS_URL: ${{ vars.HYPERBET_SOLANA_KEEPER_STAGING_WS_URL }} - HYPERBET_SOLANA_STAGING_CLUSTER: ${{ vars.HYPERBET_SOLANA_STAGING_CLUSTER }} - HYPERBET_SOLANA_STAGING_RPC_URL: ${{ vars.HYPERBET_SOLANA_STAGING_RPC_URL }} + HYPERBET_SOLANA_KEEPER_STAGING_URL: ${{ vars.ENOOMIAN_HYPERBET_SOLANA_KEEPER_URL }} + HYPERBET_SOLANA_KEEPER_STAGING_WS_URL: ${{ vars.ENOOMIAN_HYPERBET_SOLANA_KEEPER_WS_URL }} + HYPERBET_SOLANA_STAGING_CLUSTER: ${{ vars.ENOOMIAN_SOLANA_CLUSTER }} + HYPERBET_SOLANA_STAGING_RPC_URL: ${{ vars.ENOOMIAN_SOLANA_RPC_URL }} HYPERBET_SOLANA_STAGING_FIGHT_ORACLE_PROGRAM_ID: ${{ vars.HYPERBET_SOLANA_STAGING_FIGHT_ORACLE_PROGRAM_ID }} HYPERBET_SOLANA_STAGING_GOLD_CLOB_PROGRAM_ID: ${{ vars.HYPERBET_SOLANA_STAGING_GOLD_CLOB_PROGRAM_ID }} HYPERBET_SOLANA_STAGING_GOLD_AMM_PROGRAM_ID: ${{ vars.HYPERBET_SOLANA_STAGING_GOLD_AMM_PROGRAM_ID }} HYPERBET_SOLANA_STAGING_GOLD_PERPS_PROGRAM_ID: ${{ vars.HYPERBET_SOLANA_STAGING_GOLD_PERPS_PROGRAM_ID }} - HYPERBET_SOLANA_STAGING_CANARY_KEYPAIR: ${{ secrets.HYPERBET_SOLANA_STAGING_CANARY_KEYPAIR }} - HYPERBET_BSC_PAGES_STAGING_URL: ${{ vars.HYPERBET_BSC_PAGES_STAGING_URL }} - HYPERBET_BSC_KEEPER_STAGING_URL: ${{ vars.HYPERBET_BSC_KEEPER_STAGING_URL }} - HYPERBET_BSC_KEEPER_STAGING_WS_URL: ${{ vars.HYPERBET_BSC_KEEPER_STAGING_WS_URL }} - HYPERBET_BSC_STAGING_RPC_URL: ${{ vars.HYPERBET_BSC_STAGING_RPC_URL }} - HYPERBET_BSC_STAGING_GOLD_CLOB_ADDRESS: ${{ vars.HYPERBET_BSC_STAGING_GOLD_CLOB_ADDRESS }} - HYPERBET_BSC_STAGING_CANARY_PRIVATE_KEY: ${{ secrets.HYPERBET_BSC_STAGING_CANARY_PRIVATE_KEY }} - HYPERBET_BSC_STAGING_STREAM_PUBLISH_KEY: ${{ secrets.HYPERBET_BSC_STAGING_STREAM_PUBLISH_KEY }} - HYPERBET_AVAX_PAGES_STAGING_URL: ${{ vars.HYPERBET_AVAX_PAGES_STAGING_URL }} - HYPERBET_AVAX_KEEPER_STAGING_URL: ${{ vars.HYPERBET_AVAX_KEEPER_STAGING_URL }} - HYPERBET_AVAX_KEEPER_STAGING_WS_URL: ${{ vars.HYPERBET_AVAX_KEEPER_STAGING_WS_URL }} - HYPERBET_AVAX_STAGING_RPC_URL: ${{ vars.HYPERBET_AVAX_STAGING_RPC_URL }} - HYPERBET_AVAX_STAGING_GOLD_CLOB_ADDRESS: ${{ vars.HYPERBET_AVAX_STAGING_GOLD_CLOB_ADDRESS }} - HYPERBET_AVAX_STAGING_CANARY_PRIVATE_KEY: ${{ secrets.HYPERBET_AVAX_STAGING_CANARY_PRIVATE_KEY }} - HYPERBET_AVAX_STAGING_STREAM_PUBLISH_KEY: ${{ secrets.HYPERBET_AVAX_STAGING_STREAM_PUBLISH_KEY }} + HYPERBET_SOLANA_STAGING_CANARY_KEYPAIR: ${{ secrets.ENOOMIAN_HYPERBET_SOLANA_CANARY_KEYPAIR }} + HYPERBET_BSC_KEEPER_STAGING_URL: ${{ vars.ENOOMIAN_HYPERBET_BSC_KEEPER_URL }} + HYPERBET_BSC_KEEPER_STAGING_WS_URL: ${{ vars.ENOOMIAN_HYPERBET_BSC_KEEPER_WS_URL }} + HYPERBET_BSC_STAGING_RPC_URL: ${{ vars.ENOOMIAN_BSC_RPC_URL }} + HYPERBET_BSC_STAGING_GOLD_CLOB_ADDRESS: ${{ vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS != '' && vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS || vars.HYPERBET_BSC_STAGING_GOLD_CLOB_ADDRESS }} + HYPERBET_BSC_STAGING_CANARY_PRIVATE_KEY: ${{ secrets.ENOOMIAN_HYPERBET_BSC_CANARY_PRIVATE_KEY }} + HYPERBET_BSC_STAGING_STREAM_PUBLISH_KEY: ${{ secrets.ENOOMIAN_HYPERBET_BSC_STREAM_PUBLISH_KEY }} steps: - name: Checkout code uses: actions/checkout@v6.0.2 @@ -103,7 +106,7 @@ jobs: run: bunx playwright install chromium - name: Run staged read-only preflight - run: node --import tsx scripts/staged-live-proof.ts --mode=read-only --target=all + run: node --import tsx scripts/staged-live-proof.ts --mode=read-only --target=unified - name: Run staged PM soak env: diff --git a/.github/workflows/prediction-market-gates.yml b/.github/workflows/prediction-market-gates.yml index cbcb272a..25ea1dfc 100644 --- a/.github/workflows/prediction-market-gates.yml +++ b/.github/workflows/prediction-market-gates.yml @@ -2,7 +2,7 @@ name: Prediction Market Gates on: push: - branches: [main, staging, develop, hackathon] + branches: [main, staging, develop, hackathon, enoomian/staging] paths: - "packages/hyperbet-evm/**" - "packages/hyperbet-deployments/**" @@ -21,7 +21,7 @@ on: - "package.json" - "bun.lock" pull_request: - branches: [main, staging, develop, hackathon] + branches: [main, staging, develop, hackathon, enoomian/staging] paths: - "packages/hyperbet-evm/**" - "packages/hyperbet-deployments/**" @@ -234,12 +234,12 @@ jobs: if-no-files-found: ignore cross-chain-e2e: - name: Cross-Chain E2E (${{ matrix.chain }}) + name: Unified Bets E2E (${{ matrix.chain }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: - chain: [solana, bsc, avax] + chain: [solana, bsc] steps: - name: Checkout code uses: actions/checkout@v6.0.2 @@ -258,24 +258,42 @@ jobs: run: bun run dev:bootstrap - name: Install Playwright browser - run: bunx playwright install --with-deps chromium - - - name: AVAX gate posture note - if: matrix.chain == 'avax' - run: | - echo "::warning title=AVAX Gate posture::AVAX launch gate is not promoted to required status until chain-registry production mainnet fields are canonical and staged read-only/canary-write proof artifacts are reviewed." - echo "Evidence requirement for AVAX completion: reviewed proof bundle from both `bun run staged:proof -- --mode=read-only --target=avax` and `bun run staged:proof -- --mode=canary-write --target=avax`." - echo "Required AVAX staged proof inputs/secrets: HYPERBET_AVAX_STAGING_RPC_URL, HYPERBET_AVAX_STAGING_CHAIN_ID, HYPERBET_AVAX_STAGING_REPORTER_PRIVATE_KEY, HYPERBET_AVAX_STAGING_CANARY_PRIVATE_KEY, HYPERBET_AVAX_STAGING_ADMIN_PRIVATE_KEY, HYPERBET_AVAX_STAGING_MARKET_OPERATOR_PRIVATE_KEY, HYPERBET_AVAX_STAGING_DUEL_ORACLE_ADDRESS, HYPERBET_AVAX_STAGING_GOLD_CLOB_ADDRESS, HYPERBET_AVAX_STAGING_GOLD_AMM_ROUTER_ADDRESS, HYPERBET_AVAX_STAGING_MUSD_TOKEN_ADDRESS, HYPERBET_AVAX_STAGING_GOLD_TOKEN_ADDRESS, HYPERBET_AVAX_STAGING_SKILL_ORACLE_ADDRESS, HYPERBET_AVAX_STAGING_PERP_ENGINE_ADDRESS, HYPERBET_AVAX_STAGING_STREAM_PUBLISH_KEY, HYPERBET_AVAX_RAILWAY_STAGING_PROJECT_ID, HYPERBET_AVAX_RAILWAY_STAGING_ENVIRONMENT_ID, HYPERBET_AVAX_RAILWAY_STAGING_KEEPER_SERVICE_ID" + # Use the workspace-pinned Playwright rather than `bunx`, which + # resolves the latest CLI from npm and downloads a browser bundle + # (e.g. v1217) that doesn't match the pinned `@playwright/test` + # runner (v1208). That skew caused the Apr 23 Cross-Chain / Unified + # Bets E2E regression. + working-directory: packages/hyperbet-${{ matrix.chain }}/app + run: ./node_modules/.bin/playwright install --with-deps chromium + + - name: Audit unified page env + env: + # Enoomian staging targets are public URLs already baked into the + # deployed bets-page bundle. Falling back to literals here lets the + # gate run green on branches that don't have project-level + # vars.ENOOMIAN_* populated (e.g. enoomian/staging). When project + # vars are set on other branches, they win via the `||` short-circuit. + ENOOMIAN_HYPERBET_PAGES_URL: ${{ vars.ENOOMIAN_HYPERBET_PAGES_URL || 'https://hyperbet-enoomian-staging.pages.dev' }} + ENOOMIAN_HYPERBET_KEEPER_URL: ${{ vars.ENOOMIAN_HYPERBET_KEEPER_URL || 'https://hyperbet-keeper-staging-production.up.railway.app' }} + ENOOMIAN_HYPERBET_KEEPER_WS_URL: ${{ vars.ENOOMIAN_HYPERBET_KEEPER_WS_URL || 'wss://hyperbet-keeper-staging-production.up.railway.app/ws' }} + VITE_GAME_API_URL: ${{ vars.ENOOMIAN_HYPERBET_KEEPER_URL || 'https://hyperbet-keeper-staging-production.up.railway.app' }} + VITE_GAME_WS_URL: ${{ vars.ENOOMIAN_HYPERBET_KEEPER_WS_URL || 'wss://hyperbet-keeper-staging-production.up.railway.app/ws' }} + VITE_SOLANA_CLUSTER: mainnet-beta + VITE_USE_GAME_RPC_PROXY: "true" + VITE_USE_GAME_EVM_RPC_PROXY: "true" + VITE_BSC_CHAIN_ID: ${{ vars.ENOOMIAN_BSC_CHAIN_ID != '' && vars.ENOOMIAN_BSC_CHAIN_ID || vars.HYPERBET_BSC_STAGING_CHAIN_ID || '97' }} + VITE_BSC_GOLD_CLOB_ADDRESS: ${{ vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS != '' && vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS || vars.HYPERBET_BSC_STAGING_GOLD_CLOB_ADDRESS || '0xb7b2833875A17d5E5401C310C694Bb75a21a2582' }} + run: bun run ci:env -- --target=pages:unified --deployment=staging - name: Run Gate 10 local E2E - run: node --import tsx scripts/ci-gate-e2e.ts --chain=${{ matrix.chain }} + run: bun run ci:gate:e2e:${{ matrix.chain }} - name: Upload E2E artifacts if: always() uses: actions/upload-artifact@v7.0.0 with: name: cross-chain-e2e-${{ matrix.chain }} - path: .ci-artifacts/e2e-${{ matrix.chain }} + path: .ci-artifacts/e2e-unified-${{ matrix.chain }} if-no-files-found: ignore base-add-chain-smoke: @@ -294,7 +312,7 @@ jobs: - name: Install dependencies run: | - bash scripts/ci-install-verified.sh root hyperbet-bsc-app market-maker-bot + bash scripts/ci-install-verified.sh root hyperbet-evm-app market-maker-bot - name: Audit shared env run: node --import tsx scripts/ci-env-audit.ts --target=ci-shared diff --git a/.github/workflows/staged-live-proof.yml b/.github/workflows/staged-live-proof.yml index d12e3780..0689be1d 100644 --- a/.github/workflows/staged-live-proof.yml +++ b/.github/workflows/staged-live-proof.yml @@ -14,13 +14,12 @@ on: target: description: "Proof target" required: true - default: all + default: unified type: choice options: - - all + - unified - solana - bsc - - avax permissions: contents: read @@ -33,58 +32,50 @@ jobs: staged-live-proof: name: Staged Live Proof runs-on: ubuntu-latest - environment: staging + environment: enoomian-staging env: + HYPERBET_ACCEPTANCE_DEPLOYMENT: staging + ENOOMIAN_HYPERBET_PAGES_URL: ${{ vars.ENOOMIAN_HYPERBET_PAGES_URL }} + ENOOMIAN_HYPERBET_SOLANA_PAGES_URL: ${{ vars.ENOOMIAN_HYPERBET_SOLANA_PAGES_URL }} + ENOOMIAN_HYPERBET_BSC_PAGES_URL: ${{ vars.ENOOMIAN_HYPERBET_BSC_PAGES_URL }} + ENOOMIAN_HYPERBET_KEEPER_URL: ${{ vars.ENOOMIAN_HYPERBET_KEEPER_URL }} + ENOOMIAN_HYPERBET_KEEPER_WS_URL: ${{ vars.ENOOMIAN_HYPERBET_KEEPER_WS_URL }} + ENOOMIAN_RAILWAY_PROJECT_ID: ${{ vars.ENOOMIAN_RAILWAY_PROJECT_ID }} + ENOOMIAN_RAILWAY_ENVIRONMENT_ID: ${{ vars.ENOOMIAN_RAILWAY_ENVIRONMENT_ID }} + ENOOMIAN_HYPERBET_KEEPER_SERVICE_ID: ${{ vars.ENOOMIAN_HYPERBET_KEEPER_SERVICE_ID }} + ENOOMIAN_SOLANA_RPC_URL: ${{ vars.ENOOMIAN_SOLANA_RPC_URL }} + ENOOMIAN_SOLANA_CLUSTER: ${{ vars.ENOOMIAN_SOLANA_CLUSTER }} + ENOOMIAN_BSC_RPC_URL: ${{ vars.ENOOMIAN_BSC_RPC_URL }} + ENOOMIAN_BSC_CHAIN_ID: ${{ vars.ENOOMIAN_BSC_CHAIN_ID != '' && vars.ENOOMIAN_BSC_CHAIN_ID || vars.HYPERBET_BSC_STAGING_CHAIN_ID }} + ENOOMIAN_BSC_GOLD_CLOB_ADDRESS: ${{ vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS != '' && vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS || vars.HYPERBET_BSC_STAGING_GOLD_CLOB_ADDRESS }} HYPERBET_STAGED_PROOF_DUEL_ID: ${{ vars.HYPERBET_STAGED_PROOF_DUEL_ID }} HYPERBET_STAGED_PROOF_DUEL_KEY: ${{ vars.HYPERBET_STAGED_PROOF_DUEL_KEY }} - HYPERBET_SOLANA_PAGES_STAGING_URL: ${{ vars.HYPERBET_SOLANA_PAGES_STAGING_URL }} - HYPERBET_SOLANA_KEEPER_STAGING_URL: ${{ vars.HYPERBET_SOLANA_KEEPER_STAGING_URL }} - HYPERBET_SOLANA_KEEPER_STAGING_WS_URL: ${{ vars.HYPERBET_SOLANA_KEEPER_STAGING_WS_URL }} - HYPERBET_SOLANA_STAGING_CLUSTER: ${{ vars.HYPERBET_SOLANA_STAGING_CLUSTER }} - HYPERBET_SOLANA_STAGING_RPC_URL: ${{ vars.HYPERBET_SOLANA_STAGING_RPC_URL }} + HYPERBET_SOLANA_KEEPER_STAGING_URL: ${{ vars.ENOOMIAN_HYPERBET_SOLANA_KEEPER_URL }} + HYPERBET_SOLANA_KEEPER_STAGING_WS_URL: ${{ vars.ENOOMIAN_HYPERBET_SOLANA_KEEPER_WS_URL }} + HYPERBET_SOLANA_STAGING_CLUSTER: ${{ vars.ENOOMIAN_SOLANA_CLUSTER }} + HYPERBET_SOLANA_STAGING_RPC_URL: ${{ vars.ENOOMIAN_SOLANA_RPC_URL }} HYPERBET_SOLANA_STAGING_FIGHT_ORACLE_PROGRAM_ID: ${{ vars.HYPERBET_SOLANA_STAGING_FIGHT_ORACLE_PROGRAM_ID }} HYPERBET_SOLANA_STAGING_GOLD_CLOB_PROGRAM_ID: ${{ vars.HYPERBET_SOLANA_STAGING_GOLD_CLOB_PROGRAM_ID }} HYPERBET_SOLANA_STAGING_GOLD_AMM_PROGRAM_ID: ${{ vars.HYPERBET_SOLANA_STAGING_GOLD_AMM_PROGRAM_ID }} HYPERBET_SOLANA_STAGING_GOLD_PERPS_PROGRAM_ID: ${{ vars.HYPERBET_SOLANA_STAGING_GOLD_PERPS_PROGRAM_ID }} - HYPERBET_SOLANA_STAGING_STREAM_PUBLISH_KEY: ${{ secrets.HYPERBET_SOLANA_STAGING_STREAM_PUBLISH_KEY }} - HYPERBET_SOLANA_STAGING_ORACLE_AUTHORITY_KEYPAIR: ${{ secrets.HYPERBET_SOLANA_STAGING_ORACLE_AUTHORITY_KEYPAIR }} - HYPERBET_SOLANA_STAGING_CANARY_KEYPAIR: ${{ secrets.HYPERBET_SOLANA_STAGING_CANARY_KEYPAIR }} - HYPERBET_BSC_PAGES_STAGING_URL: ${{ vars.HYPERBET_BSC_PAGES_STAGING_URL }} - HYPERBET_BSC_KEEPER_STAGING_URL: ${{ vars.HYPERBET_BSC_KEEPER_STAGING_URL }} - HYPERBET_BSC_KEEPER_STAGING_WS_URL: ${{ vars.HYPERBET_BSC_KEEPER_STAGING_WS_URL }} - HYPERBET_BSC_STAGING_RPC_URL: ${{ vars.HYPERBET_BSC_STAGING_RPC_URL }} - HYPERBET_BSC_STAGING_DUEL_ORACLE_ADDRESS: ${{ vars.HYPERBET_BSC_STAGING_DUEL_ORACLE_ADDRESS }} - HYPERBET_BSC_STAGING_GOLD_CLOB_ADDRESS: ${{ vars.HYPERBET_BSC_STAGING_GOLD_CLOB_ADDRESS }} + HYPERBET_SOLANA_STAGING_STREAM_PUBLISH_KEY: ${{ secrets.ENOOMIAN_HYPERBET_SOLANA_STREAM_PUBLISH_KEY }} + HYPERBET_SOLANA_STAGING_ORACLE_AUTHORITY_KEYPAIR: ${{ secrets.ENOOMIAN_HYPERBET_SOLANA_ORACLE_AUTHORITY_KEYPAIR }} + HYPERBET_SOLANA_STAGING_CANARY_KEYPAIR: ${{ secrets.ENOOMIAN_HYPERBET_SOLANA_CANARY_KEYPAIR }} + HYPERBET_BSC_KEEPER_STAGING_URL: ${{ vars.ENOOMIAN_HYPERBET_BSC_KEEPER_URL }} + HYPERBET_BSC_KEEPER_STAGING_WS_URL: ${{ vars.ENOOMIAN_HYPERBET_BSC_KEEPER_WS_URL }} + HYPERBET_BSC_STAGING_RPC_URL: ${{ vars.ENOOMIAN_BSC_RPC_URL }} + HYPERBET_BSC_STAGING_DUEL_ORACLE_ADDRESS: ${{ vars.ENOOMIAN_BSC_DUEL_ORACLE_ADDRESS }} + HYPERBET_BSC_STAGING_GOLD_CLOB_ADDRESS: ${{ vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS != '' && vars.ENOOMIAN_BSC_GOLD_CLOB_ADDRESS || vars.HYPERBET_BSC_STAGING_GOLD_CLOB_ADDRESS }} HYPERBET_BSC_STAGING_GOLD_AMM_ROUTER_ADDRESS: ${{ vars.HYPERBET_BSC_STAGING_GOLD_AMM_ROUTER_ADDRESS }} HYPERBET_BSC_STAGING_MUSD_TOKEN_ADDRESS: ${{ vars.HYPERBET_BSC_STAGING_MUSD_TOKEN_ADDRESS }} HYPERBET_BSC_STAGING_GOLD_TOKEN_ADDRESS: ${{ vars.HYPERBET_BSC_STAGING_GOLD_TOKEN_ADDRESS }} HYPERBET_BSC_STAGING_SKILL_ORACLE_ADDRESS: ${{ vars.HYPERBET_BSC_STAGING_SKILL_ORACLE_ADDRESS }} HYPERBET_BSC_STAGING_PERP_ENGINE_ADDRESS: ${{ vars.HYPERBET_BSC_STAGING_PERP_ENGINE_ADDRESS }} - HYPERBET_BSC_STAGING_STREAM_PUBLISH_KEY: ${{ secrets.HYPERBET_BSC_STAGING_STREAM_PUBLISH_KEY }} + HYPERBET_BSC_STAGING_STREAM_PUBLISH_KEY: ${{ secrets.ENOOMIAN_HYPERBET_BSC_STREAM_PUBLISH_KEY }} HYPERBET_BSC_STAGING_REPORTER_PRIVATE_KEY: ${{ secrets.HYPERBET_BSC_STAGING_REPORTER_PRIVATE_KEY }} - HYPERBET_BSC_STAGING_CANARY_PRIVATE_KEY: ${{ secrets.HYPERBET_BSC_STAGING_CANARY_PRIVATE_KEY }} + HYPERBET_BSC_STAGING_CANARY_PRIVATE_KEY: ${{ secrets.ENOOMIAN_HYPERBET_BSC_CANARY_PRIVATE_KEY }} HYPERBET_BSC_STAGING_ADMIN_PRIVATE_KEY: ${{ secrets.HYPERBET_BSC_STAGING_ADMIN_PRIVATE_KEY }} HYPERBET_BSC_STAGING_MARKET_OPERATOR_PRIVATE_KEY: ${{ secrets.HYPERBET_BSC_STAGING_MARKET_OPERATOR_PRIVATE_KEY }} - HYPERBET_AVAX_PAGES_STAGING_URL: ${{ vars.HYPERBET_AVAX_PAGES_STAGING_URL }} - HYPERBET_AVAX_KEEPER_STAGING_URL: ${{ vars.HYPERBET_AVAX_KEEPER_STAGING_URL }} - HYPERBET_AVAX_KEEPER_STAGING_WS_URL: ${{ vars.HYPERBET_AVAX_KEEPER_STAGING_WS_URL }} - HYPERBET_AVAX_STAGING_RPC_URL: ${{ vars.HYPERBET_AVAX_STAGING_RPC_URL }} - HYPERBET_AVAX_STAGING_CHAIN_ID: ${{ vars.HYPERBET_AVAX_STAGING_CHAIN_ID }} - HYPERBET_AVAX_STAGING_DUEL_ORACLE_ADDRESS: ${{ vars.HYPERBET_AVAX_STAGING_DUEL_ORACLE_ADDRESS }} - HYPERBET_AVAX_STAGING_GOLD_CLOB_ADDRESS: ${{ vars.HYPERBET_AVAX_STAGING_GOLD_CLOB_ADDRESS }} - HYPERBET_AVAX_STAGING_GOLD_AMM_ROUTER_ADDRESS: ${{ vars.HYPERBET_AVAX_STAGING_GOLD_AMM_ROUTER_ADDRESS }} - HYPERBET_AVAX_STAGING_MUSD_TOKEN_ADDRESS: ${{ vars.HYPERBET_AVAX_STAGING_MUSD_TOKEN_ADDRESS }} - HYPERBET_AVAX_STAGING_GOLD_TOKEN_ADDRESS: ${{ vars.HYPERBET_AVAX_STAGING_GOLD_TOKEN_ADDRESS }} - HYPERBET_AVAX_STAGING_SKILL_ORACLE_ADDRESS: ${{ vars.HYPERBET_AVAX_STAGING_SKILL_ORACLE_ADDRESS }} - HYPERBET_AVAX_STAGING_PERP_ENGINE_ADDRESS: ${{ vars.HYPERBET_AVAX_STAGING_PERP_ENGINE_ADDRESS }} - HYPERBET_AVAX_STAGING_STREAM_PUBLISH_KEY: ${{ secrets.HYPERBET_AVAX_STAGING_STREAM_PUBLISH_KEY }} - HYPERBET_AVAX_STAGING_REPORTER_PRIVATE_KEY: ${{ secrets.HYPERBET_AVAX_STAGING_REPORTER_PRIVATE_KEY }} - HYPERBET_AVAX_STAGING_CANARY_PRIVATE_KEY: ${{ secrets.HYPERBET_AVAX_STAGING_CANARY_PRIVATE_KEY }} - HYPERBET_AVAX_STAGING_ADMIN_PRIVATE_KEY: ${{ secrets.HYPERBET_AVAX_STAGING_ADMIN_PRIVATE_KEY }} - HYPERBET_AVAX_STAGING_MARKET_OPERATOR_PRIVATE_KEY: ${{ secrets.HYPERBET_AVAX_STAGING_MARKET_OPERATOR_PRIVATE_KEY }} - HYPERBET_AVAX_RAILWAY_STAGING_PROJECT_ID: ${{ vars.HYPERBET_AVAX_RAILWAY_STAGING_PROJECT_ID }} - HYPERBET_AVAX_RAILWAY_STAGING_ENVIRONMENT_ID: ${{ vars.HYPERBET_AVAX_RAILWAY_STAGING_ENVIRONMENT_ID }} - HYPERBET_AVAX_RAILWAY_STAGING_KEEPER_SERVICE_ID: ${{ vars.HYPERBET_AVAX_RAILWAY_STAGING_KEEPER_SERVICE_ID }} steps: - name: Checkout code uses: actions/checkout@v6.0.2 @@ -100,6 +91,13 @@ jobs: - name: Install dependencies run: bash scripts/ci-install-verified.sh root + - name: Validate staging proof target + run: | + if [ "${{ inputs.mode }}" = "canary-write" ] && [ "${{ inputs.target }}" = "unified" ]; then + echo "Unified proof is read-only only; run canary writes with --target=solana or --target=bsc" >&2 + exit 1 + fi + - name: Run staged live proof run: node --import tsx scripts/staged-live-proof.ts --mode=${{ inputs.mode }} --target=${{ inputs.target }} diff --git a/.gitignore b/.gitignore index ca9046f4..4b948ba7 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,4 @@ poetry.lock packages/market-maker-bot/simulations/ docs/plans/ output/ +tmp/enoomian-staging/ diff --git a/bun.lock b/bun.lock index 181874a6..777f0a34 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "@hyperbet/monorepo", @@ -242,7 +243,6 @@ "dependencies": { "@noble/curves": "^2.0.1", "@rainbow-me/rainbowkit": "^2.2.10", - "@tanstack/react-query": "^5.90.21", "@wagmi/core": "^3.4.0", "hls.js": "^1.6.15", "lightweight-charts": "^5.1.0", @@ -311,6 +311,7 @@ "ethers": "^6.13.4", }, "devDependencies": { + "@biomejs/biome": "^2.4.11", "@types/bn.js": "^5.2.0", "@types/node": "^22.9.0", "tsup": "^8.3.5", @@ -419,6 +420,9 @@ "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "@rainbow-me/rainbowkit": "^2.2.10", + "@solana/client": "latest", + "@solana/kit": "latest", + "@solana/react-hooks": "latest", "@solana/spl-token": "0.4.14", "@solana/wallet-adapter-base": "0.9.27", "@solana/wallet-adapter-phantom": "0.9.28", @@ -641,6 +645,24 @@ "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + "@biomejs/biome": ["@biomejs/biome@2.4.12", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.12", "@biomejs/cli-darwin-x64": "2.4.12", "@biomejs/cli-linux-arm64": "2.4.12", "@biomejs/cli-linux-arm64-musl": "2.4.12", "@biomejs/cli-linux-x64": "2.4.12", "@biomejs/cli-linux-x64-musl": "2.4.12", "@biomejs/cli-win32-arm64": "2.4.12", "@biomejs/cli-win32-x64": "2.4.12" }, "bin": { "biome": "bin/biome" } }, "sha512-Rro7adQl3NLq/zJCIL98eElXKI8eEiBtoeu5TbXF/U3qbjuSc7Jb5rjUbeHHcquDWeSf3HnGP7XI5qGrlRk/pA=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BnMU4Pc3ciEVteVpZ0BK33MLr7X57F5w1dwDLDn+/iy/yTrA4Q/N2yftidFtsA4vrDh0FMXDpacNV/Tl3fbmng=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-x9uJ0bI1rJsWICp3VH8w/5PnAVD3A7SqzDpbrfoUQX1QyWrK5jSU4fRLo/wSgGeplCivbxBRKmt5Xq4/nWvq8A=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-tOwuCuZZtKi1jVzbk/5nXmIsziOB6yqN8c9r9QM0EJYPU6DpQWf11uBOSCfFKKM4H3d9ZoarvlgMfbcuD051Pw=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-FhfpkAAlKL6kwvcVap0Hgp4AhZmtd3YImg0kK1jd7C/aSoh4SfsB2f++yG1rU0lr8Y5MCFJrcSkmssiL9Xnnig=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.12", "", { "os": "linux", "cpu": "x64" }, "sha512-8pFeAnLU9QdW9jCIslB/v82bI0lhBmz2ZAKc8pVMFPO0t0wAHsoEkrUQUbMkIorTRIjbqyNZHA3lEXavsPWYSw=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.12", "", { "os": "linux", "cpu": "x64" }, "sha512-dwTIgZrGutzhkQCuvHynCkyW6hJxUuyZqKKO0YNfaS2GUoRO+tOvxXZqZB6SkWAOdfZTzwaw8IEdUnIkHKHoew=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-B0DLnx0vA9ya/3v7XyCaP+/lCpnbWbMOfUFFve+xb5OxyYvdHaS55YsSddr228Y+JAFk58agCuZTsqNiw2a6ig=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.12", "", { "os": "win32", "cpu": "x64" }, "sha512-yMckRzTyZ83hkk8iDFWswqSdU8tvZxspJKnYNh7JZr/zhZNOlzH13k4ecboU6MurKExCe2HUkH75pGI/O2JwGA=="], + "@codama/cli": ["@codama/cli@1.5.0", "", { "dependencies": { "@codama/nodes": "1.5.1", "@codama/visitors": "1.5.1", "@codama/visitors-core": "1.5.1", "commander": "^14.0.2", "picocolors": "^1.1.1", "prompts": "^2.4.2" }, "bin": { "codama": "bin/cli.cjs" } }, "sha512-+q62IvEA6o7ji/mcnGgAvyPWyiFx3cojVGrFNG8NSm0zFXrBk1lT3n/qg+2Ag8C8aHwno9boXgDTxV+P5VCDYw=="], "@codama/errors": ["@codama/errors@1.5.1", "", { "dependencies": { "@codama/node-types": "1.5.1", "commander": "^14.0.2", "picocolors": "^1.1.1" }, "bin": { "errors": "bin/cli.cjs" } }, "sha512-kdLk/OSLBt03DoViRU1Xr0M7NZ7J/CSqaXV8fooF9qMRGPRJdgUeW2VkCGlLXDQSaIALrls3HkHmKRKbqqjSOA=="], @@ -1239,11 +1261,11 @@ "@solana-program/token-2022": ["@solana-program/token-2022@0.7.0", "", { "peerDependencies": { "@solana/kit": "^5.0", "@solana/sysvars": "^4.0" } }, "sha512-ByQdTdbgyhjGf9JklqGRf3u0nbQF5Hbn8Wb2Ir0LZHCgo8lG+2PmBN8UvNY6ONVYb7CjLbApgghvBAEQK1aagg=="], - "@solana/accounts": ["@solana/accounts@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/rpc-spec": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h3zQFjwZjmy+YxgTGOEna6g74Tsn4hTBaBCslwPT4QjqWhywe2JrM2Ab0ANfJcj7g/xrHF5QJ/FnUIcyUTeVfQ=="], + "@solana/accounts": ["@solana/accounts@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/rpc-spec": "6.8.0", "@solana/rpc-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rXjFYVopaEw1H2PTBQbRjKr+0i4EFuBEhRT5E0dI4cMaabSb4KKypC2gaf47+6cjU3hMlM1AcsyIs72/MqAVBw=="], - "@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + "@solana/addresses": ["@solana/addresses@6.8.0", "", { "dependencies": { "@solana/assertions": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/nominal-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-xVlA0DNX1LVfTueVsbhxDDoqr1VxeXvgJEh2GcIN/vcJPhY3GE3AYtjTbJJmTDgPrzOccI0t6ElVb1gelJH/PQ=="], - "@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@solana/assertions": ["@solana/assertions@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-OU6prCq39fSvGL8xY1C/9vhghasvAkMiRlituzJxzJpZRfpVRrwhzLd6P5NPAPoQ28qKcenA50kFdw9+ZyneJQ=="], "@solana/buffer-layout": ["@solana/buffer-layout@4.0.1", "", { "dependencies": { "buffer": "~6.0.3" } }, "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA=="], @@ -1251,7 +1273,7 @@ "@solana/client": ["@solana/client@1.7.0", "", { "dependencies": { "@solana-program/address-lookup-table": "^0.10.0", "@solana-program/compute-budget": "^0.11.0", "@solana-program/stake": "^0.5.0", "@solana-program/system": "^0.10.0", "@solana-program/token": "^0.9.0", "@solana-program/token-2022": "^0.7.0", "@solana/codecs-strings": "^5.0.0", "@solana/kit": "^5.0.0", "@solana/transaction-confirmation": "^5.0.0", "@solana/transactions": "^5.0.0", "@solana/wallet-standard-features": "^1.3.0", "@wallet-standard/app": "^1.0.1", "@wallet-standard/base": "^1.1.0", "@wallet-standard/errors": "^0.1.1", "@wallet-standard/features": "^1.0.3", "bs58": "^6.0.0", "zustand": "^5.0.0" }, "peerDependencies": { "@solana/connector": "^0.2.3", "typescript": ">=5.3.3" }, "optionalPeers": ["@solana/connector"] }, "sha512-92QgeS2PlyCissESrP5XqzMU2IcyUOA5PYr5dqOIyDf/GaDep+WexTgmTGybgCsfmfbJAo2JsrpE9nEUYrJNlg=="], - "@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + "@solana/codecs": ["@solana/codecs@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/options": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-qCSAaw1qszeQflavkIM7c21qJ3BHReP/qgDelZbhsEXpZc852CCZM00FOIWuxePr6X+JjSNqJquxwdDSoZe7Bw=="], "@solana/codecs-core": ["@solana/codecs-core@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw=="], @@ -1265,15 +1287,15 @@ "@solana/connector": ["@solana/connector@0.2.4", "", { "dependencies": { "@solana-mobile/wallet-standard-mobile": "^0.4.3", "@solana/addresses": "^5.0.0", "@solana/codecs": "^5.0.0", "@solana/keys": "^5.0.0", "@solana/kit": "^5.0.0", "@solana/signers": "^5.0.0", "@solana/transaction-messages": "^5.0.0", "@solana/transactions": "^5.0.0", "@solana/webcrypto-ed25519-polyfill": "^4.0.0", "@wallet-standard/app": "^1.1.0", "@wallet-standard/base": "^1.1.0", "@wallet-standard/features": "^1.1.0", "@wallet-ui/core": "^2.1.0", "zod": "^4.0.0" }, "peerDependencies": { "@solana/keychain": "^0.2.1", "@solana/keychain-aws-kms": "^0.2.1", "@solana/keychain-fireblocks": "^0.2.1", "@solana/keychain-privy": "^0.2.1", "@solana/keychain-turnkey": "^0.2.1", "@solana/keychain-vault": "^0.2.1", "@solana/web3.js": "^1.0.0", "@walletconnect/universal-provider": "^2.0.0", "react": ">=18.0.0" }, "optionalPeers": ["@solana/keychain", "@solana/keychain-fireblocks", "@solana/keychain-privy", "@solana/web3.js", "@walletconnect/universal-provider", "react"] }, "sha512-klxVTjgmhdEhzzBt+UEFHgTwpEs2wxECbuQCSjrKAbKOtncS7VFaosva0OwOoyfMkl2aQ7uerUZXW+x5FTOpZQ=="], - "@solana/errors": ["@solana/errors@6.5.0", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.3" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-XPc0I8Ck6vgx8Uu+LVLewx/1RWDkXkY3lU+1aN1kmbrPAQWbX4Txk7GPmuIIFpyys8o5aKocYfNxJOPKvfaQhg=="], + "@solana/errors": ["@solana/errors@6.8.0", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.3" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-HRTrLgTn0c99GKz4v4IKgz2+6soaRY1mh2tLW4sk1Fe4Zzv85Q6ZLK1mXrVGL73z1apyHDrr9/Sd/9ZhUsUvpA=="], - "@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5ATQDwBVZMoenX5KS23uFswtaAGoaZB9TthzUXle3tkU3tOfgQTuEWEoqEBYc7ct0sK6LtyE1XXT/NP5YvAkkQ=="], + "@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lZa3Qnsn+9ew6rHTXkPc+uqSa3i+AWqSBhV6oYxxBc+smvuxovItU4TPIs30cTfA7lAP+j+oYAQtUDu2dLy0hA=="], - "@solana/functional": ["@solana/functional@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-/KYgY7ZpBJfkN8+qlIvxuBpxv32U9jHXIOOJh3U5xk8Ncsa9Ex5VwbU9NkOf43MJjoIamsP0vARCHjcqJwe5JQ=="], + "@solana/functional": ["@solana/functional@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-oMSAD/8w9ujx7OplvwRWwHHFnaaxi/Xrji1XH3xAB+gzxupUpBbOmgxQ+e84x+9VN8QWk5aU3L7gmCqdTAR6OA=="], - "@solana/instruction-plans": ["@solana/instruction-plans@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/promises": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-zp2asevpyMwvhajHYM1aruYpO+xf3LSwHEI2FK6E2hddYZaEhuBy+bz+NZ1ixCyfx3iXcq7MamlFQc2ySHDyUQ=="], + "@solana/instruction-plans": ["@solana/instruction-plans@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/instructions": "6.8.0", "@solana/keys": "6.8.0", "@solana/promises": "6.8.0", "@solana/transaction-messages": "6.8.0", "@solana/transactions": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-osAsY8ozqohrcTcHlG1EmO3i9flc0eESMIy9akTHyVvqk915gZgkaTmt4IjcYSwBGt7i+Rh8TmLj27RrTpCKvg=="], - "@solana/instructions": ["@solana/instructions@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-2mQP/1qqr5PCfaVMzs9KofBjpyS7J1sBV6PidGoX9Dg5/4UgwJJ+7yfCVQPn37l1nKCShm4I+pQAy5vbmrxJmA=="], + "@solana/instructions": ["@solana/instructions@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-dTtykhS9IeN3npCfnd7wSS6KmKAh54+g90JRtLYy5/31L2Zvunf3AJz2QUk58vgsAGZ5fuoiMyhCxRJm4rHUBQ=="], "@solana/keychain-aws-kms": ["@solana/keychain-aws-kms@0.2.1", "", { "dependencies": { "@aws-sdk/client-kms": "^3.700.0", "@solana/addresses": "^5.0.0", "@solana/codecs-strings": "^5.0.0", "@solana/keychain-core": "0.2.1", "@solana/keys": "^5.0.0", "@solana/signers": "^5.0.0", "@solana/transactions": "^5.0.0" } }, "sha512-nKbpxRSE+zu+y8ZywJGAbwjxbjtLzbQR35Q5wQ1HWTvM4ZCfLzVqlkX8GYFT3eeWCi+JX4VXJdHfOFofl9D/GA=="], @@ -1283,53 +1305,53 @@ "@solana/keychain-vault": ["@solana/keychain-vault@0.2.1", "", { "dependencies": { "@solana/addresses": "^5.0.0", "@solana/codecs-core": "^5.0.0", "@solana/codecs-strings": "^5.0.0", "@solana/keychain-core": "0.2.1", "@solana/keys": "^5.0.0", "@solana/signers": "^5.0.0", "@solana/transactions": "^5.0.0" } }, "sha512-W9ykOYqDqG3GBF5sbJUnSvLZXyicTK3atoQZMl7zTORxr+N9vwPAvmPbtLgDS2GKoZUG0RZWFeJgAaNq14Avaw=="], - "@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + "@solana/keys": ["@solana/keys@6.8.0", "", { "dependencies": { "@solana/assertions": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/nominal-types": "6.8.0", "@solana/promises": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wo8CnbrVfCP1Jbsb3ElMej/3dmMrl4ArPhI1mDcqIIz/O4j4HmxZYbn2BCWtnV9V/LPM638EMO2r1x6GzDNrPA=="], - "@solana/kit": ["@solana/kit@6.5.0", "", { "dependencies": { "@solana/accounts": "6.5.0", "@solana/addresses": "6.5.0", "@solana/codecs": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instruction-plans": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/offchain-messages": "6.5.0", "@solana/plugin-core": "6.5.0", "@solana/plugin-interfaces": "6.5.0", "@solana/program-client-core": "6.5.0", "@solana/programs": "6.5.0", "@solana/rpc": "6.5.0", "@solana/rpc-api": "6.5.0", "@solana/rpc-parsed-types": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-subscriptions": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/signers": "6.5.0", "@solana/sysvars": "6.5.0", "@solana/transaction-confirmation": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-4ysrtqMRd7CTYRv179gQq4kbw9zMsJCLhWjiyOmLZ4co4ld3L654D8ykW7yqWE5PJwF0hzEfheE7oBscO37nvw=="], + "@solana/kit": ["@solana/kit@6.8.0", "", { "dependencies": { "@solana/accounts": "6.8.0", "@solana/addresses": "6.8.0", "@solana/codecs": "6.8.0", "@solana/errors": "6.8.0", "@solana/functional": "6.8.0", "@solana/instruction-plans": "6.8.0", "@solana/instructions": "6.8.0", "@solana/keys": "6.8.0", "@solana/offchain-messages": "6.8.0", "@solana/plugin-core": "6.8.0", "@solana/plugin-interfaces": "6.8.0", "@solana/program-client-core": "6.8.0", "@solana/programs": "6.8.0", "@solana/rpc": "6.8.0", "@solana/rpc-api": "6.8.0", "@solana/rpc-parsed-types": "6.8.0", "@solana/rpc-spec-types": "6.8.0", "@solana/rpc-subscriptions": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/signers": "6.8.0", "@solana/subscribable": "6.8.0", "@solana/sysvars": "6.8.0", "@solana/transaction-confirmation": "6.8.0", "@solana/transaction-messages": "6.8.0", "@solana/transactions": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-+McC1aCgcUBdM7Cd7U6k2ZHJ9OKCy5mzpb0XWrhkrgsFxT0QoRr0AcWJc85o6tIDfG6Jz7vVhbS3l8ugYz2Vzw=="], "@solana/nominal-types": ["@solana/nominal-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-I1ImR+kfrLFxN5z22UDiTWLdRZeKtU0J/pkWkO8qm/8WxveiwdIv4hooi8pb6JnlR4mSrWhq0pCIOxDYrL9GIQ=="], - "@solana/offchain-messages": ["@solana/offchain-messages@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-IYuidJCwfXg5xlh3rkflkA1fbTKWTsip8MdI+znvXm87grfqOYCTd6t/SKiV4BhLl/65Tn0wB/zvZ1cmzJqa1w=="], + "@solana/offchain-messages": ["@solana/offchain-messages@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/keys": "6.8.0", "@solana/nominal-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HoniTs2uoCHGicD0dTTJ3YBhLZC9URxdXXUf0CHalLFwAidF9iNuB8dsuKk16Euu68L4/ERKKGfyC0QobBvahw=="], - "@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + "@solana/options": ["@solana/options@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-T5441HHeucFaLtaMAJQJl79T7mX007oAFPunpPebBphRvCXGv+qQwQvqa4HkYct6Jf2O0aKLBL9GSe/kfdCk9A=="], - "@solana/plugin-core": ["@solana/plugin-core@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-L6N69oNQOAqljH4GnLTaxpwJB0nibW9DrybHZxpGWshyv6b/EvwvkDVRKj5bNqtCG+HRZUHnEhLi1UgZVNkjpQ=="], + "@solana/plugin-core": ["@solana/plugin-core@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kdqFIhQvJP2BDUsMOIbor35esj8u78SO33Xv0Wmo+uTRg6yKONKVK53ghw235pWrinOT4f0VnVe6MN6ciYiQVA=="], - "@solana/plugin-interfaces": ["@solana/plugin-interfaces@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/instruction-plans": "6.5.0", "@solana/keys": "6.5.0", "@solana/rpc-spec": "6.5.0", "@solana/rpc-subscriptions-spec": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/signers": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-/ZlybbMaR7P4ySersOe1huioMADWze0AzsHbzgkpt5dJUv2tz5cpaKdu7TEVQkUZAFhLdqXQULNGqAU5neOgzg=="], + "@solana/plugin-interfaces": ["@solana/plugin-interfaces@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/instruction-plans": "6.8.0", "@solana/keys": "6.8.0", "@solana/rpc-spec": "6.8.0", "@solana/rpc-subscriptions-spec": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/signers": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-4olaMKGUVA7wG6BBWM5A31bQsUWBlfcL1pjhq6ZTqVEJ7vshHXGwHVlWYXYyYn9ixozGDpGSl553yaRY9jQwWw=="], - "@solana/program-client-core": ["@solana/program-client-core@6.5.0", "", { "dependencies": { "@solana/accounts": "6.5.0", "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0", "@solana/instruction-plans": "6.5.0", "@solana/instructions": "6.5.0", "@solana/plugin-interfaces": "6.5.0", "@solana/rpc-api": "6.5.0", "@solana/signers": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eUz1xSeDKySGIjToAryPmlESdj8KX0Np7R+Pjt+kSFGw5Jgmn/Inh4o8luoeEnf5XwbvSPVb4aHpIsDyoUVbIg=="], + "@solana/program-client-core": ["@solana/program-client-core@6.8.0", "", { "dependencies": { "@solana/accounts": "6.8.0", "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0", "@solana/instruction-plans": "6.8.0", "@solana/instructions": "6.8.0", "@solana/plugin-interfaces": "6.8.0", "@solana/rpc-api": "6.8.0", "@solana/signers": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eOZtEnwl+vdiy9x/rFF89NDtnvt+Q3H04A/0u4GoHnt+fFkQG3JS+ChWG9c77izmpmRuz5C1GptOPDGNDnIUgQ=="], - "@solana/programs": ["@solana/programs@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-srn3nEROBxCnBpVz/bvLkVln1BZtk3bS3nuReu3yaeOLkKl8b0h1Zp0YmXVyXHzdMcYahsTvKKLR1ZtLZEyEPA=="], + "@solana/programs": ["@solana/programs@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-8hSKGfPTLX9Sm7KGV/UtiGCeSzptT/9vcjbodE+ZGHKFefo5vES4UAW+qD01LjL7IumGtMJvnfhCWt81qT/jbQ=="], "@solana/promises": ["@solana/promises@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-T9lfuUYkGykJmppEcssNiCf6yiYQxJkhiLPP+pyAc2z84/7r3UVIb2tNJk4A9sucS66pzJnVHZKcZVGUUp6wzA=="], "@solana/react-hooks": ["@solana/react-hooks@1.4.1", "", { "dependencies": { "@solana/addresses": "^5.0.0", "@solana/client": "1.7.0", "@solana/codecs-core": "^5.0.0", "@solana/errors": "^5.0.0", "@solana/keys": "^5.0.0", "@solana/kit": "^5.0.0", "@solana/promises": "^5.0.0", "@solana/signers": "^5.0.0", "@solana/transaction-messages": "^5.0.0", "@solana/transactions": "^5.0.0", "swr": "^2.3.6", "zustand": "^5.0.0" }, "peerDependencies": { "react": ">=18" } }, "sha512-XOfDewMUeVdjuYCp527ZlFaVCe8yRs3oq18c1ERQ36ZEkKtgABAsQ10b9/UsGxIsXp6VO4cEzPFlITh+tlyt8Q=="], - "@solana/rpc": ["@solana/rpc@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/fast-stable-stringify": "6.5.0", "@solana/functional": "6.5.0", "@solana/rpc-api": "6.5.0", "@solana/rpc-spec": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-transformers": "6.5.0", "@solana/rpc-transport-http": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lGj7ZMVOR3Rf16aByXD6ghrMqw3G8rAMuWCHU4uMKES5M5VLqNv6o71bSyoTxVMGrmYdbALOvCbFMFINAxtoBg=="], + "@solana/rpc": ["@solana/rpc@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/fast-stable-stringify": "6.8.0", "@solana/functional": "6.8.0", "@solana/rpc-api": "6.8.0", "@solana/rpc-spec": "6.8.0", "@solana/rpc-spec-types": "6.8.0", "@solana/rpc-transformers": "6.8.0", "@solana/rpc-transport-http": "6.8.0", "@solana/rpc-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-+jW4n9TDmBttY3bO3PdUo54GAnwFrd7UJsyfXoMgl/lWGQq5uddYDgnzQLtHOBP5zKslkR8h0RKkic0GZhMZrQ=="], - "@solana/rpc-api": ["@solana/rpc-api@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/keys": "6.5.0", "@solana/rpc-parsed-types": "6.5.0", "@solana/rpc-spec": "6.5.0", "@solana/rpc-transformers": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-b+kftroO8vZFzLHj7Nk/uATS3HOlBUsUqdGg3eTQrW1pFgkyq5yIoEYHeFF7ApUN/SJLTK86U8ofCaXabd2SXA=="], + "@solana/rpc-api": ["@solana/rpc-api@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/keys": "6.8.0", "@solana/rpc-parsed-types": "6.8.0", "@solana/rpc-spec": "6.8.0", "@solana/rpc-transformers": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/transaction-messages": "6.8.0", "@solana/transactions": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-v8ZKWgPtKbF6HeJcfC4ciwI8mwDCizBtRLYYjjHOu+9S9IJYyefQzsQxL5P8OjJPpI4gFauT6gsjQLo76BoojA=="], - "@solana/rpc-parsed-types": ["@solana/rpc-parsed-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-129c8meL6CxRg56/HfhkFOpwYteQH9Rt0wyXOXZQx3a3FNpcJLd4JdPvxDsLBE3EupEkXLGVku/1bGKz+F2J+g=="], + "@solana/rpc-parsed-types": ["@solana/rpc-parsed-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-jYddZviBSUYbuUKqvNthet7KbJVI7me6xfRH2znv1SjIpmvhSPJcGN5QrlHVOasHdzEWSpvZa5VYDfnqH3aYvA=="], - "@solana/rpc-spec": ["@solana/rpc-spec@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/rpc-spec-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k4O7Kg0QfVyjUqQovL+WZJ1iuPzq0jiUDcWYgvzFjYVxQDVOIZmAol7yTvLEL4maVmf0tNFDsrDaB6t75MKRZA=="], + "@solana/rpc-spec": ["@solana/rpc-spec@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/rpc-spec-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kE5uOspxCVFJKNUu73hlebGiAFosjfYXbbTXAbGKfksPzy84u1oJFC2IVIobLRnqUCw1x7oJcvfnX00Zs0Itpg=="], - "@solana/rpc-spec-types": ["@solana/rpc-spec-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-XasJp+sOW6PLfNoalzoLnm+j3LEZF8XOQmSrOqv9AGrGxQckkuOf6iXZucWTqeNKdstsOpU28BN2B6qOavfRzQ=="], + "@solana/rpc-spec-types": ["@solana/rpc-spec-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebCWgiQbIgFOehU7PdRFmYCzda3Azc/qa2Y3P8gexSHSsDAO27VwS4E05XSY+a7cIL5MYmvUa1vpDynl1Rkakw=="], - "@solana/rpc-subscriptions": ["@solana/rpc-subscriptions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/fast-stable-stringify": "6.5.0", "@solana/functional": "6.5.0", "@solana/promises": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-subscriptions-api": "6.5.0", "@solana/rpc-subscriptions-channel-websocket": "6.5.0", "@solana/rpc-subscriptions-spec": "6.5.0", "@solana/rpc-transformers": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/subscribable": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-EenogPQw9Iy8VUj8anu7xoBnPk7gu1J6sAi4MTVlNVz02sNjdUBJoSS0PRJZuhSM1ktPTtHrNwqlXP8TxPR7jg=="], + "@solana/rpc-subscriptions": ["@solana/rpc-subscriptions@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/fast-stable-stringify": "6.8.0", "@solana/functional": "6.8.0", "@solana/promises": "6.8.0", "@solana/rpc-spec-types": "6.8.0", "@solana/rpc-subscriptions-api": "6.8.0", "@solana/rpc-subscriptions-channel-websocket": "6.8.0", "@solana/rpc-subscriptions-spec": "6.8.0", "@solana/rpc-transformers": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/subscribable": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-9CotreNZmKAP2z07FY1I7TPPvylKLFF5p4mujB5ZFMHQPp5JVQFVCmMIhSj5voZHAeYx7jdwJ2Kf0RDeClqJzA=="], - "@solana/rpc-subscriptions-api": ["@solana/rpc-subscriptions-api@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/keys": "6.5.0", "@solana/rpc-subscriptions-spec": "6.5.0", "@solana/rpc-transformers": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-smqNjT2C5Vf9nWGIwiYOLOP744gRWKi2i2g0i3ZVdsfoouvB0d/WTQ2bbWq47MrdV8FSuGnjAOM3dRIwYmYOWw=="], + "@solana/rpc-subscriptions-api": ["@solana/rpc-subscriptions-api@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/keys": "6.8.0", "@solana/rpc-subscriptions-spec": "6.8.0", "@solana/rpc-transformers": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/transaction-messages": "6.8.0", "@solana/transactions": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-cPJOsydyoqkztW3msEH09wPDYqxJcMvO6DBlvrboq6wGu1UjeP66w2eApzQ8POoQHxhyw+CfEXl1Gbu6kKwuMQ=="], - "@solana/rpc-subscriptions-channel-websocket": ["@solana/rpc-subscriptions-channel-websocket@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/rpc-subscriptions-spec": "6.5.0", "@solana/subscribable": "6.5.0", "ws": "^8.19.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-xRKH3ZwIoV9Zua9Gp0RR0eL8lXNgx+iNIkE3F0ROlOzI48lt4lRJ7jLrHQCN3raVtkatFVuEyZ7e9eLHK9zhAw=="], + "@solana/rpc-subscriptions-channel-websocket": ["@solana/rpc-subscriptions-channel-websocket@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/functional": "6.8.0", "@solana/rpc-subscriptions-spec": "6.8.0", "@solana/subscribable": "6.8.0", "ws": "^8.19.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-c3PpkorYwhAz1iuUfM5sLpZQi8xtZFGbaPbaPRELVeDjFSRzoa12KFnuQs4i9fbVbLy5Cnt1t23tf0bL2snZCQ=="], - "@solana/rpc-subscriptions-spec": ["@solana/rpc-subscriptions-spec@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/promises": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/subscribable": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Mi8g9rNS2lG7lyNkDhOVfQVfDC7hXKgH+BlI5qKGk+8cfyU7VDq6tVjDysu6kBWGOPHZxyCvcL6+xW/EkdVoAg=="], + "@solana/rpc-subscriptions-spec": ["@solana/rpc-subscriptions-spec@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/promises": "6.8.0", "@solana/rpc-spec-types": "6.8.0", "@solana/subscribable": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-+t4L5q9qE6IVfunW3n1amA/3EswJr64pVqRF7234vCUuVUz4PgYfbqtEBV3KkA1o0NwEHHM3pXuofT63nBb8Bg=="], - "@solana/rpc-transformers": ["@solana/rpc-transformers@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kS0d+LuuSLfsod2cm2xp0mNj65PL1aomwu6VKtubmsdESwPXHIaI9XrpkPCBuhNSz1SwVp4OkfK5O/VOOHYHSw=="], + "@solana/rpc-transformers": ["@solana/rpc-transformers@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/functional": "6.8.0", "@solana/nominal-types": "6.8.0", "@solana/rpc-spec-types": "6.8.0", "@solana/rpc-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-GzcFkllym7eXbw7grdE41MCb15CjkibrXtr7EFsf4d6LD9DRvzFj2ZRYywS2FB2ibVP0LUXXGk3vmtkZJjfajA=="], - "@solana/rpc-transport-http": ["@solana/rpc-transport-http@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/rpc-spec": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "undici-types": "^7.22.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-A3qgDGiUIHdtAfc2OyazlQa7IvRh+xyl0dmzaZlz4rY7Oc7Xk8jmXtaKGkgXihLyAK3oVSqSz5gn9yEfx55eXA=="], + "@solana/rpc-transport-http": ["@solana/rpc-transport-http@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0", "@solana/rpc-spec": "6.8.0", "@solana/rpc-spec-types": "6.8.0", "undici-types": "^8.0.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-jw/L0q2motGcx7yo6KvkKJd2HGVg9gvViXatFloLl1XmHbkwE7+97YYmG17WRuM5xauzI/UGYOXNW7cEB+Uaxw=="], - "@solana/rpc-types": ["@solana/rpc-types@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-hxts27+Z2VNv4IjXGcXkqbj/MgrN9Xtw/4iE1qZk68T2OAb5vA4b8LHchsOHmHvrzZfo8XDvB9mModCdM3JPsQ=="], + "@solana/rpc-types": ["@solana/rpc-types@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/nominal-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-vACMV9VR2JsZGDcgaMOFN/dwLK57CsE+erassxxtF12sSPXJooz+Vu1vyY2Yp2EkCc7mDf7BNkTKvSXajbt+Qw=="], - "@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], + "@solana/signers": ["@solana/signers@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0", "@solana/instructions": "6.8.0", "@solana/keys": "6.8.0", "@solana/nominal-types": "6.8.0", "@solana/offchain-messages": "6.8.0", "@solana/transaction-messages": "6.8.0", "@solana/transactions": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-7E1cAXBLOcz9kmHhzWdu5m3UJlJzxfwOl8irOMLJI6NnKB2EmU0B0h4I+Mlfs9w8Bfj0WQpUei21ammbNBq39g=="], "@solana/spl-token": ["@solana/spl-token@0.4.14", "", { "dependencies": { "@solana/buffer-layout": "^4.0.0", "@solana/buffer-layout-utils": "^0.2.0", "@solana/spl-token-group": "^0.0.7", "@solana/spl-token-metadata": "^0.1.6", "buffer": "^6.0.3" }, "peerDependencies": { "@solana/web3.js": "^1.95.5" } }, "sha512-u09zr96UBpX4U685MnvQsNzlvw9TiY005hk1vJmJr7gMJldoPG1eYU5/wNEyOA5lkMLiR/gOi9SFD4MefOYEsA=="], @@ -1337,13 +1359,13 @@ "@solana/spl-token-metadata": ["@solana/spl-token-metadata@0.1.6", "", { "dependencies": { "@solana/codecs": "2.0.0-rc.1" }, "peerDependencies": { "@solana/web3.js": "^1.95.3" } }, "sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA=="], - "@solana/subscribable": ["@solana/subscribable@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Jmy2NYmQN68FsQzKJ5CY3qrxXBJdb5qtJKp8B4byPPO5liKNIsC59HpT0Tq8MCNSfBMmOkWF2rrVot2/g1iB1A=="], + "@solana/subscribable": ["@solana/subscribable@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-yj41Q97MiWrOmLj1iRFobvTdtU6H5wz5BlH5FHJg9lyapy1YQyaYF37MZx4LiUj4Ww0V3ReluIZTWWDBOJ53Jg=="], - "@solana/sysvars": ["@solana/sysvars@6.5.0", "", { "dependencies": { "@solana/accounts": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iLSS5qj0MWNiGH1LN1E4jhGsXH9D3tWSjwaB6zK9LjhLdVYcPfkosBkj7s0EHHrH03QlwiuFdU0Y2kH8Jcp8kw=="], + "@solana/sysvars": ["@solana/sysvars@6.8.0", "", { "dependencies": { "@solana/accounts": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0", "@solana/rpc-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-pwfMpMNL6MSmm07eHQYdTdRdzmPOd+EuVCCaNLSYdWGpYcocVJiaLiNWRV3cXA5wPj/ZFkoUGtc1bo0v7H50lw=="], "@solana/transaction-confirmation": ["@solana/transaction-confirmation@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/promises": "5.5.1", "@solana/rpc": "5.5.1", "@solana/rpc-subscriptions": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-j4mKlYPHEyu+OD7MBt3jRoX4ScFgkhZC6H65on4Fux6LMScgivPJlwnKoZMnsgxFgWds0pl+BYzSiALDsXlYtw=="], - "@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], + "@solana/transaction-messages": ["@solana/transaction-messages@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0", "@solana/functional": "6.8.0", "@solana/instructions": "6.8.0", "@solana/nominal-types": "6.8.0", "@solana/rpc-types": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-jsJu9mAcN1x7onKOeC4WEvYP04UVcnkOYu/9bMe+S9jqjL+3DMy9kFZpV5FBl+TPuTNJrtOqc6Gc28hUWyyp1A=="], "@solana/transactions": ["@solana/transactions@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/transaction-messages": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-8hHtDxtqalZ157pnx6p8k10D7J/KY/biLzfgh9R09VNLLY3Fqi7kJvJCr7M2ik3oRll56pxhraAGCC9yIT6eOA=="], @@ -3543,6 +3565,8 @@ "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "@codama/nodes-from-anchor/@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + "@codama/renderers-js/@solana/codecs-strings": ["@solana/codecs-strings@6.1.0", "", { "dependencies": { "@solana/codecs-core": "6.1.0", "@solana/codecs-numbers": "6.1.0", "@solana/errors": "6.1.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-pRH5uAn4VCFUs2rYiDITyWsRnpvs3Uh/nhSc6OSP/kusghcCcCJcUzHBIjT4x08MVacXmGUlSLe/9qPQO+QK3Q=="], "@codama/renderers-js/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], @@ -3791,15 +3815,15 @@ "@solana-program/token-2022/@solana/sysvars": ["@solana/sysvars@6.3.0", "", { "dependencies": { "@solana/accounts": "6.3.0", "@solana/codecs-core": "6.3.0", "@solana/codecs-data-structures": "6.3.0", "@solana/codecs-numbers": "6.3.0", "@solana/errors": "6.3.0", "@solana/rpc-types": "6.3.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kUYY766Ct9HSmLiXWZqKkkZsiH5BDLm7FtejY7eCG5OBOrT/v0cCRsxFQhQvozwJK//exQWhsWhX9Vdcc1ZbxQ=="], - "@solana/accounts/@solana/addresses": ["@solana/addresses@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iD4/u3CWchQcPofbwzteaE9RnFJSoi654Rnhru5fOu6U2XOte3+7t50d6OxdxQ109ho2LqZyVtyCo2Wb7u1aJQ=="], + "@solana/accounts/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/accounts/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/accounts/@solana/codecs-strings": ["@solana/codecs-strings@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-Rpk5NVhbKYcPnE7wz3IpTp0GVNVs0IYKdmyzByiimgPTiII8eb8ay4wQiYHGHrpYh62hD14Qy3GiGDFgipRKqA=="], - "@solana/accounts/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/addresses/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/addresses/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + "@solana/addresses/@solana/codecs-strings": ["@solana/codecs-strings@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-Rpk5NVhbKYcPnE7wz3IpTp0GVNVs0IYKdmyzByiimgPTiII8eb8ay4wQiYHGHrpYh62hD14Qy3GiGDFgipRKqA=="], - "@solana/assertions/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + "@solana/addresses/@solana/nominal-types": ["@solana/nominal-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-mLmHr92pM4mEfe49GUmZ5Ry0RMqtMuFQqZYnxQqhDKMcl+Wtt820ezxYgwPhqcMxRzfqaQSO3ZxpSB0RlLBa/Q=="], "@solana/client/@solana-program/compute-budget": ["@solana-program/compute-budget@0.11.0", "", { "peerDependencies": { "@solana/kit": "^5.0" } }, "sha512-7f1ePqB/eURkTwTOO9TNIdUXZcyrZoX3Uy2hNo7cXMfNhPFWp9AVgIyRNBc2jf15sdUa9gNpW+PfP2iV8AYAaw=="], @@ -3807,7 +3831,13 @@ "@solana/client/@solana/kit": ["@solana/kit@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/addresses": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instruction-plans": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/plugin-core": "5.5.1", "@solana/programs": "5.5.1", "@solana/rpc": "5.5.1", "@solana/rpc-api": "5.5.1", "@solana/rpc-parsed-types": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-subscriptions": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/signers": "5.5.1", "@solana/sysvars": "5.5.1", "@solana/transaction-confirmation": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ=="], - "@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana/codecs/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], + + "@solana/codecs/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lHr0F+nNwgm9c+tWQX398yzYh1qDi7QSCJpY9MQ2azW4FfY2IyPSo7bqzTaWNnJh9pmJx3ZI6jHfXBnLD5k/SQ=="], + + "@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], + + "@solana/codecs/@solana/codecs-strings": ["@solana/codecs-strings@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-Rpk5NVhbKYcPnE7wz3IpTp0GVNVs0IYKdmyzByiimgPTiII8eb8ay4wQiYHGHrpYh62hD14Qy3GiGDFgipRKqA=="], "@solana/codecs-core/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], @@ -3823,147 +3853,165 @@ "@solana/codecs-strings/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + "@solana/compat/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + "@solana/compat/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], "@solana/compat/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + "@solana/compat/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + + "@solana/connector/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + + "@solana/connector/@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + + "@solana/connector/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + "@solana/connector/@solana/kit": ["@solana/kit@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/addresses": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instruction-plans": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/plugin-core": "5.5.1", "@solana/programs": "5.5.1", "@solana/rpc": "5.5.1", "@solana/rpc-api": "5.5.1", "@solana/rpc-parsed-types": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-subscriptions": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/signers": "5.5.1", "@solana/sysvars": "5.5.1", "@solana/transaction-confirmation": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ=="], - "@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "@solana/connector/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], + + "@solana/connector/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], - "@solana/instruction-plans/@solana/keys": ["@solana/keys@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-CN5jmodX9j5CZKrWLM5XGaRlrLl/Ebl4vgqDXrnwC2NiSfUslLsthuORMuVUTDqkzBX/jd/tgVXFRH2NYNzREQ=="], + "@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "@solana/instruction-plans/@solana/promises": ["@solana/promises@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n5rsA3YwOO2nUst6ghuVw6RSnuZQYqevqBKqVYbw11Z4XezsoQ6hb78opW3J9YNYapw9wLWy6tEfUsJjY+xtGw=="], + "@solana/instruction-plans/@solana/promises": ["@solana/promises@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kIypZG83ZbADbrAq9/LS7LuWlVxlgJSzIpic75+9IuAfC3k5/KSus8LrvggBkCzfAyIslrUh70iz4JcnzUZrOw=="], - "@solana/instruction-plans/@solana/transaction-messages": ["@solana/transaction-messages@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ueXkm5xaRlqYBFAlABhaCKK/DuzIYSot0FybwSDeOQCDy2hvU9Zda16Iwa1n56M0fG+XUvFJz2woG3u9DhQh1g=="], + "@solana/instruction-plans/@solana/transactions": ["@solana/transactions@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/functional": "6.8.0", "@solana/instructions": "6.8.0", "@solana/keys": "6.8.0", "@solana/nominal-types": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/transaction-messages": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Q46m+o3C1yL2EIZBAP5B8ou2VZwHN9wTi+muIS6/giCKO3jwUtnTEbWcZEDMj2vxUb7P2WfwTluZb/VAWxlx7Q=="], - "@solana/instruction-plans/@solana/transactions": ["@solana/transactions@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-b3eJrrGmwpk64VLHjOrmXKAahPpba42WX/FqSUn4WRXPoQjga7Mb57yp+EaRVeQfjszKCkF+13yu+ni6iv2NFQ=="], + "@solana/instructions/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/instructions/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/keychain-aws-kms/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], - "@solana/keys/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + "@solana/keychain-aws-kms/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], - "@solana/kit/@solana/addresses": ["@solana/addresses@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iD4/u3CWchQcPofbwzteaE9RnFJSoi654Rnhru5fOu6U2XOte3+7t50d6OxdxQ109ho2LqZyVtyCo2Wb7u1aJQ=="], + "@solana/keychain-aws-kms/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], - "@solana/kit/@solana/codecs": ["@solana/codecs@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/options": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-WfqMqUXk4jcCJQ9nfKqjDcCJN2Pt8/AKe/E78z8OcblFGVJnTzcu2yZpE2gsqM+DJyCVKdQmOY+NS8Uckk5e5w=="], + "@solana/keychain-core/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], - "@solana/kit/@solana/keys": ["@solana/keys@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-CN5jmodX9j5CZKrWLM5XGaRlrLl/Ebl4vgqDXrnwC2NiSfUslLsthuORMuVUTDqkzBX/jd/tgVXFRH2NYNzREQ=="], + "@solana/keychain-core/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], - "@solana/kit/@solana/signers": ["@solana/signers@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/offchain-messages": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-AL75/DyDUhc+QQ+VGZT7aRwJNzIUTWvmLNXQRlCVhLRuyroXzZEL2WJBs8xOwbZXjY8weacfYT7UNM8qK6ucDg=="], + "@solana/keychain-core/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], - "@solana/kit/@solana/transaction-confirmation": ["@solana/transaction-confirmation@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/keys": "6.5.0", "@solana/promises": "6.5.0", "@solana/rpc": "6.5.0", "@solana/rpc-subscriptions": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-hfdRBq4toZj7DRMgBN3F0VtJpmTAEtcVTTDZoiszoSpSVa2cAvFth6KypIqASVFZyi9t4FKolLP8ASd3/39UQg=="], + "@solana/keychain-turnkey/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], - "@solana/kit/@solana/transaction-messages": ["@solana/transaction-messages@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ueXkm5xaRlqYBFAlABhaCKK/DuzIYSot0FybwSDeOQCDy2hvU9Zda16Iwa1n56M0fG+XUvFJz2woG3u9DhQh1g=="], + "@solana/keychain-turnkey/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], - "@solana/kit/@solana/transactions": ["@solana/transactions@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-b3eJrrGmwpk64VLHjOrmXKAahPpba42WX/FqSUn4WRXPoQjga7Mb57yp+EaRVeQfjszKCkF+13yu+ni6iv2NFQ=="], + "@solana/keychain-turnkey/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], - "@solana/offchain-messages/@solana/addresses": ["@solana/addresses@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iD4/u3CWchQcPofbwzteaE9RnFJSoi654Rnhru5fOu6U2XOte3+7t50d6OxdxQ109ho2LqZyVtyCo2Wb7u1aJQ=="], + "@solana/keychain-vault/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], - "@solana/offchain-messages/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/keychain-vault/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], - "@solana/offchain-messages/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + "@solana/keychain-vault/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], - "@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/keys/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/offchain-messages/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/keys/@solana/codecs-strings": ["@solana/codecs-strings@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-Rpk5NVhbKYcPnE7wz3IpTp0GVNVs0IYKdmyzByiimgPTiII8eb8ay4wQiYHGHrpYh62hD14Qy3GiGDFgipRKqA=="], - "@solana/offchain-messages/@solana/keys": ["@solana/keys@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-CN5jmodX9j5CZKrWLM5XGaRlrLl/Ebl4vgqDXrnwC2NiSfUslLsthuORMuVUTDqkzBX/jd/tgVXFRH2NYNzREQ=="], + "@solana/keys/@solana/nominal-types": ["@solana/nominal-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-mLmHr92pM4mEfe49GUmZ5Ry0RMqtMuFQqZYnxQqhDKMcl+Wtt820ezxYgwPhqcMxRzfqaQSO3ZxpSB0RlLBa/Q=="], - "@solana/offchain-messages/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/keys/@solana/promises": ["@solana/promises@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kIypZG83ZbADbrAq9/LS7LuWlVxlgJSzIpic75+9IuAfC3k5/KSus8LrvggBkCzfAyIslrUh70iz4JcnzUZrOw=="], - "@solana/options/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana/kit/@solana/transaction-confirmation": ["@solana/transaction-confirmation@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/keys": "6.8.0", "@solana/promises": "6.8.0", "@solana/rpc": "6.8.0", "@solana/rpc-subscriptions": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/transaction-messages": "6.8.0", "@solana/transactions": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-R6rj8y/+kZqYJr8FR/fWxgi3Pw3eCiacUyjCPTVtdVe6i+hIiBApTGLzXrSRJmAMdpZrjYBZU1cG8C6oAb+B2A=="], - "@solana/options/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + "@solana/kit/@solana/transactions": ["@solana/transactions@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/functional": "6.8.0", "@solana/instructions": "6.8.0", "@solana/keys": "6.8.0", "@solana/nominal-types": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/transaction-messages": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Q46m+o3C1yL2EIZBAP5B8ou2VZwHN9wTi+muIS6/giCKO3jwUtnTEbWcZEDMj2vxUb7P2WfwTluZb/VAWxlx7Q=="], - "@solana/plugin-interfaces/@solana/addresses": ["@solana/addresses@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iD4/u3CWchQcPofbwzteaE9RnFJSoi654Rnhru5fOu6U2XOte3+7t50d6OxdxQ109ho2LqZyVtyCo2Wb7u1aJQ=="], + "@solana/offchain-messages/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/plugin-interfaces/@solana/keys": ["@solana/keys@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-CN5jmodX9j5CZKrWLM5XGaRlrLl/Ebl4vgqDXrnwC2NiSfUslLsthuORMuVUTDqkzBX/jd/tgVXFRH2NYNzREQ=="], + "@solana/offchain-messages/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lHr0F+nNwgm9c+tWQX398yzYh1qDi7QSCJpY9MQ2azW4FfY2IyPSo7bqzTaWNnJh9pmJx3ZI6jHfXBnLD5k/SQ=="], - "@solana/plugin-interfaces/@solana/signers": ["@solana/signers@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/offchain-messages": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-AL75/DyDUhc+QQ+VGZT7aRwJNzIUTWvmLNXQRlCVhLRuyroXzZEL2WJBs8xOwbZXjY8weacfYT7UNM8qK6ucDg=="], + "@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], - "@solana/program-client-core/@solana/addresses": ["@solana/addresses@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iD4/u3CWchQcPofbwzteaE9RnFJSoi654Rnhru5fOu6U2XOte3+7t50d6OxdxQ109ho2LqZyVtyCo2Wb7u1aJQ=="], + "@solana/offchain-messages/@solana/codecs-strings": ["@solana/codecs-strings@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-Rpk5NVhbKYcPnE7wz3IpTp0GVNVs0IYKdmyzByiimgPTiII8eb8ay4wQiYHGHrpYh62hD14Qy3GiGDFgipRKqA=="], - "@solana/program-client-core/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/offchain-messages/@solana/nominal-types": ["@solana/nominal-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-mLmHr92pM4mEfe49GUmZ5Ry0RMqtMuFQqZYnxQqhDKMcl+Wtt820ezxYgwPhqcMxRzfqaQSO3ZxpSB0RlLBa/Q=="], - "@solana/program-client-core/@solana/signers": ["@solana/signers@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/offchain-messages": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-AL75/DyDUhc+QQ+VGZT7aRwJNzIUTWvmLNXQRlCVhLRuyroXzZEL2WJBs8xOwbZXjY8weacfYT7UNM8qK6ucDg=="], + "@solana/options/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/programs/@solana/addresses": ["@solana/addresses@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iD4/u3CWchQcPofbwzteaE9RnFJSoi654Rnhru5fOu6U2XOte3+7t50d6OxdxQ109ho2LqZyVtyCo2Wb7u1aJQ=="], + "@solana/options/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lHr0F+nNwgm9c+tWQX398yzYh1qDi7QSCJpY9MQ2azW4FfY2IyPSo7bqzTaWNnJh9pmJx3ZI6jHfXBnLD5k/SQ=="], - "@solana/react-hooks/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + "@solana/options/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], - "@solana/react-hooks/@solana/kit": ["@solana/kit@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/addresses": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instruction-plans": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/plugin-core": "5.5.1", "@solana/programs": "5.5.1", "@solana/rpc": "5.5.1", "@solana/rpc-api": "5.5.1", "@solana/rpc-parsed-types": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-subscriptions": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/signers": "5.5.1", "@solana/sysvars": "5.5.1", "@solana/transaction-confirmation": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ=="], + "@solana/options/@solana/codecs-strings": ["@solana/codecs-strings@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-Rpk5NVhbKYcPnE7wz3IpTp0GVNVs0IYKdmyzByiimgPTiII8eb8ay4wQiYHGHrpYh62hD14Qy3GiGDFgipRKqA=="], - "@solana/rpc-api/@solana/addresses": ["@solana/addresses@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iD4/u3CWchQcPofbwzteaE9RnFJSoi654Rnhru5fOu6U2XOte3+7t50d6OxdxQ109ho2LqZyVtyCo2Wb7u1aJQ=="], + "@solana/program-client-core/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/rpc-api/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/react-hooks/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], - "@solana/rpc-api/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/react-hooks/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/rpc-api/@solana/keys": ["@solana/keys@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-CN5jmodX9j5CZKrWLM5XGaRlrLl/Ebl4vgqDXrnwC2NiSfUslLsthuORMuVUTDqkzBX/jd/tgVXFRH2NYNzREQ=="], + "@solana/react-hooks/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], - "@solana/rpc-api/@solana/transaction-messages": ["@solana/transaction-messages@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ueXkm5xaRlqYBFAlABhaCKK/DuzIYSot0FybwSDeOQCDy2hvU9Zda16Iwa1n56M0fG+XUvFJz2woG3u9DhQh1g=="], + "@solana/react-hooks/@solana/kit": ["@solana/kit@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/addresses": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instruction-plans": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/plugin-core": "5.5.1", "@solana/programs": "5.5.1", "@solana/rpc": "5.5.1", "@solana/rpc-api": "5.5.1", "@solana/rpc-parsed-types": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-subscriptions": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/signers": "5.5.1", "@solana/sysvars": "5.5.1", "@solana/transaction-confirmation": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ=="], - "@solana/rpc-api/@solana/transactions": ["@solana/transactions@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-b3eJrrGmwpk64VLHjOrmXKAahPpba42WX/FqSUn4WRXPoQjga7Mb57yp+EaRVeQfjszKCkF+13yu+ni6iv2NFQ=="], + "@solana/react-hooks/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], - "@solana/rpc-subscriptions/@solana/promises": ["@solana/promises@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n5rsA3YwOO2nUst6ghuVw6RSnuZQYqevqBKqVYbw11Z4XezsoQ6hb78opW3J9YNYapw9wLWy6tEfUsJjY+xtGw=="], + "@solana/react-hooks/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], - "@solana/rpc-subscriptions-api/@solana/addresses": ["@solana/addresses@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iD4/u3CWchQcPofbwzteaE9RnFJSoi654Rnhru5fOu6U2XOte3+7t50d6OxdxQ109ho2LqZyVtyCo2Wb7u1aJQ=="], + "@solana/rpc-api/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/rpc-subscriptions-api/@solana/keys": ["@solana/keys@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-CN5jmodX9j5CZKrWLM5XGaRlrLl/Ebl4vgqDXrnwC2NiSfUslLsthuORMuVUTDqkzBX/jd/tgVXFRH2NYNzREQ=="], + "@solana/rpc-api/@solana/codecs-strings": ["@solana/codecs-strings@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-Rpk5NVhbKYcPnE7wz3IpTp0GVNVs0IYKdmyzByiimgPTiII8eb8ay4wQiYHGHrpYh62hD14Qy3GiGDFgipRKqA=="], - "@solana/rpc-subscriptions-api/@solana/transaction-messages": ["@solana/transaction-messages@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ueXkm5xaRlqYBFAlABhaCKK/DuzIYSot0FybwSDeOQCDy2hvU9Zda16Iwa1n56M0fG+XUvFJz2woG3u9DhQh1g=="], + "@solana/rpc-api/@solana/transactions": ["@solana/transactions@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/functional": "6.8.0", "@solana/instructions": "6.8.0", "@solana/keys": "6.8.0", "@solana/nominal-types": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/transaction-messages": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Q46m+o3C1yL2EIZBAP5B8ou2VZwHN9wTi+muIS6/giCKO3jwUtnTEbWcZEDMj2vxUb7P2WfwTluZb/VAWxlx7Q=="], - "@solana/rpc-subscriptions-api/@solana/transactions": ["@solana/transactions@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-b3eJrrGmwpk64VLHjOrmXKAahPpba42WX/FqSUn4WRXPoQjga7Mb57yp+EaRVeQfjszKCkF+13yu+ni6iv2NFQ=="], + "@solana/rpc-subscriptions/@solana/promises": ["@solana/promises@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kIypZG83ZbADbrAq9/LS7LuWlVxlgJSzIpic75+9IuAfC3k5/KSus8LrvggBkCzfAyIslrUh70iz4JcnzUZrOw=="], - "@solana/rpc-subscriptions-spec/@solana/promises": ["@solana/promises@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n5rsA3YwOO2nUst6ghuVw6RSnuZQYqevqBKqVYbw11Z4XezsoQ6hb78opW3J9YNYapw9wLWy6tEfUsJjY+xtGw=="], + "@solana/rpc-subscriptions-api/@solana/transactions": ["@solana/transactions@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/functional": "6.8.0", "@solana/instructions": "6.8.0", "@solana/keys": "6.8.0", "@solana/nominal-types": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/transaction-messages": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Q46m+o3C1yL2EIZBAP5B8ou2VZwHN9wTi+muIS6/giCKO3jwUtnTEbWcZEDMj2vxUb7P2WfwTluZb/VAWxlx7Q=="], - "@solana/rpc-transformers/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/rpc-subscriptions-spec/@solana/promises": ["@solana/promises@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kIypZG83ZbADbrAq9/LS7LuWlVxlgJSzIpic75+9IuAfC3k5/KSus8LrvggBkCzfAyIslrUh70iz4JcnzUZrOw=="], - "@solana/rpc-transport-http/undici-types": ["undici-types@7.22.0", "", {}, "sha512-RKZvifiL60xdsIuC80UY0dq8Z7DbJUV8/l2hOVbyZAxBzEeQU4Z58+4ZzJ6WN2Lidi9KzT5EbiGX+PI/UGYuRw=="], + "@solana/rpc-transformers/@solana/nominal-types": ["@solana/nominal-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-mLmHr92pM4mEfe49GUmZ5Ry0RMqtMuFQqZYnxQqhDKMcl+Wtt820ezxYgwPhqcMxRzfqaQSO3ZxpSB0RlLBa/Q=="], - "@solana/rpc-types/@solana/addresses": ["@solana/addresses@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iD4/u3CWchQcPofbwzteaE9RnFJSoi654Rnhru5fOu6U2XOte3+7t50d6OxdxQ109ho2LqZyVtyCo2Wb7u1aJQ=="], + "@solana/rpc-transport-http/undici-types": ["undici-types@8.1.0", "", {}, "sha512-JlLXdMmH4kxyn2JPtGK/cajzKY7F15OKYG8sO5HfkIC1AC09sLUeptGFKjnMWnprDQ2EwzYDO3kgzkK3aaoHCA=="], - "@solana/rpc-types/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/rpc-types/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], - "@solana/rpc-types/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/rpc-types/@solana/codecs-strings": ["@solana/codecs-strings@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-Rpk5NVhbKYcPnE7wz3IpTp0GVNVs0IYKdmyzByiimgPTiII8eb8ay4wQiYHGHrpYh62hD14Qy3GiGDFgipRKqA=="], - "@solana/rpc-types/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/rpc-types/@solana/nominal-types": ["@solana/nominal-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-mLmHr92pM4mEfe49GUmZ5Ry0RMqtMuFQqZYnxQqhDKMcl+Wtt820ezxYgwPhqcMxRzfqaQSO3ZxpSB0RlLBa/Q=="], - "@solana/signers/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + "@solana/signers/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/signers/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + "@solana/signers/@solana/nominal-types": ["@solana/nominal-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-mLmHr92pM4mEfe49GUmZ5Ry0RMqtMuFQqZYnxQqhDKMcl+Wtt820ezxYgwPhqcMxRzfqaQSO3ZxpSB0RlLBa/Q=="], - "@solana/signers/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], + "@solana/signers/@solana/transactions": ["@solana/transactions@6.8.0", "", { "dependencies": { "@solana/addresses": "6.8.0", "@solana/codecs-core": "6.8.0", "@solana/codecs-data-structures": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/codecs-strings": "6.8.0", "@solana/errors": "6.8.0", "@solana/functional": "6.8.0", "@solana/instructions": "6.8.0", "@solana/keys": "6.8.0", "@solana/nominal-types": "6.8.0", "@solana/rpc-types": "6.8.0", "@solana/transaction-messages": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Q46m+o3C1yL2EIZBAP5B8ou2VZwHN9wTi+muIS6/giCKO3jwUtnTEbWcZEDMj2vxUb7P2WfwTluZb/VAWxlx7Q=="], "@solana/spl-token-group/@solana/codecs": ["@solana/codecs@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/codecs-data-structures": "2.0.0-rc.1", "@solana/codecs-numbers": "2.0.0-rc.1", "@solana/codecs-strings": "2.0.0-rc.1", "@solana/options": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ=="], "@solana/spl-token-metadata/@solana/codecs": ["@solana/codecs@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/codecs-data-structures": "2.0.0-rc.1", "@solana/codecs-numbers": "2.0.0-rc.1", "@solana/codecs-strings": "2.0.0-rc.1", "@solana/options": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ=="], - "@solana/sysvars/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/sysvars/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/sysvars/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + "@solana/sysvars/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lHr0F+nNwgm9c+tWQX398yzYh1qDi7QSCJpY9MQ2azW4FfY2IyPSo7bqzTaWNnJh9pmJx3ZI6jHfXBnLD5k/SQ=="], - "@solana/sysvars/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/sysvars/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], + + "@solana/transaction-confirmation/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], "@solana/transaction-confirmation/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + "@solana/transaction-confirmation/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + "@solana/transaction-confirmation/@solana/rpc": ["@solana/rpc@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/fast-stable-stringify": "5.5.1", "@solana/functional": "5.5.1", "@solana/rpc-api": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-transformers": "5.5.1", "@solana/rpc-transport-http": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ku8zTUMrkCWci66PRIBC+1mXepEnZH/q1f3ck0kJZ95a06bOTl5KU7HeXWtskkyefzARJ5zvCs54AD5nxjQJ+A=="], "@solana/transaction-confirmation/@solana/rpc-subscriptions": ["@solana/rpc-subscriptions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/fast-stable-stringify": "5.5.1", "@solana/functional": "5.5.1", "@solana/promises": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-subscriptions-api": "5.5.1", "@solana/rpc-subscriptions-channel-websocket": "5.5.1", "@solana/rpc-subscriptions-spec": "5.5.1", "@solana/rpc-transformers": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/subscribable": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-CTMy5bt/6mDh4tc6vUJms9EcuZj3xvK0/xq8IQ90rhkpYvate91RjBP+egvjgSayUg9yucU9vNuUpEjz4spM7w=="], "@solana/transaction-confirmation/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], - "@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana/transaction-confirmation/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], - "@solana/transaction-messages/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + "@solana/transaction-messages/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/transaction-messages/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], + "@solana/transaction-messages/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lHr0F+nNwgm9c+tWQX398yzYh1qDi7QSCJpY9MQ2azW4FfY2IyPSo7bqzTaWNnJh9pmJx3ZI6jHfXBnLD5k/SQ=="], - "@solana/transaction-messages/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + "@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], - "@solana/transaction-messages/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], + "@solana/transaction-messages/@solana/nominal-types": ["@solana/nominal-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-mLmHr92pM4mEfe49GUmZ5Ry0RMqtMuFQqZYnxQqhDKMcl+Wtt820ezxYgwPhqcMxRzfqaQSO3ZxpSB0RlLBa/Q=="], + + "@solana/transactions/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], "@solana/transactions/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], @@ -3973,14 +4021,20 @@ "@solana/transactions/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + "@solana/transactions/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + "@solana/transactions/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], + "@solana/transactions/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], + "@solana/wallet-adapter-base/eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], "@solana/wallet-adapter-unsafe-burner/@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], "@solana/wallet-standard-util/@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], + "@solana/web3-compat/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + "@solana/web3-compat/@solana/kit": ["@solana/kit@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/addresses": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instruction-plans": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/plugin-core": "5.5.1", "@solana/programs": "5.5.1", "@solana/rpc": "5.5.1", "@solana/rpc-api": "5.5.1", "@solana/rpc-parsed-types": "5.5.1", "@solana/rpc-spec-types": "5.5.1", "@solana/rpc-subscriptions": "5.5.1", "@solana/rpc-types": "5.5.1", "@solana/signers": "5.5.1", "@solana/sysvars": "5.5.1", "@solana/transaction-confirmation": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ=="], "@solana/web3.js/@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], @@ -4217,8 +4271,12 @@ "hyperbet-solana-app/@eslint/js": ["@eslint/js@9.39.4", "", {}, "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw=="], + "hyperbet-solana-app/@solana/kit": ["@solana/kit@6.5.0", "", { "dependencies": { "@solana/accounts": "6.5.0", "@solana/addresses": "6.5.0", "@solana/codecs": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instruction-plans": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/offchain-messages": "6.5.0", "@solana/plugin-core": "6.5.0", "@solana/plugin-interfaces": "6.5.0", "@solana/program-client-core": "6.5.0", "@solana/programs": "6.5.0", "@solana/rpc": "6.5.0", "@solana/rpc-api": "6.5.0", "@solana/rpc-parsed-types": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-subscriptions": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/signers": "6.5.0", "@solana/sysvars": "6.5.0", "@solana/transaction-confirmation": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-4ysrtqMRd7CTYRv179gQq4kbw9zMsJCLhWjiyOmLZ4co4ld3L654D8ykW7yqWE5PJwF0hzEfheE7oBscO37nvw=="], + "hyperbet-solana-app/eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="], + "hyperbet-solana-keeper/@solana/kit": ["@solana/kit@6.5.0", "", { "dependencies": { "@solana/accounts": "6.5.0", "@solana/addresses": "6.5.0", "@solana/codecs": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instruction-plans": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/offchain-messages": "6.5.0", "@solana/plugin-core": "6.5.0", "@solana/plugin-interfaces": "6.5.0", "@solana/program-client-core": "6.5.0", "@solana/programs": "6.5.0", "@solana/rpc": "6.5.0", "@solana/rpc-api": "6.5.0", "@solana/rpc-parsed-types": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-subscriptions": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/signers": "6.5.0", "@solana/sysvars": "6.5.0", "@solana/transaction-confirmation": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-4ysrtqMRd7CTYRv179gQq4kbw9zMsJCLhWjiyOmLZ4co4ld3L654D8ykW7yqWE5PJwF0hzEfheE7oBscO37nvw=="], + "hyperbet-solana-keeper/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], "hyperbet-solana-keeper/dotenv": ["dotenv@16.5.0", "", {}, "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg=="], @@ -4425,6 +4483,10 @@ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + "@codama/nodes-from-anchor/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@codama/nodes-from-anchor/@solana/codecs/@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + "@codama/renderers-js/@solana/codecs-strings/@solana/codecs-core": ["@solana/codecs-core@6.1.0", "", { "dependencies": { "@solana/errors": "6.1.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5rNnDOOm2GRFMJbd9imYCPNvGOrQ+TZ53NCkFFWbbB7f+L9KkLeuuAsDMFN1lCziJFlymvN785YtDnMeWj2W+g=="], "@codama/renderers-js/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.1.0", "", { "dependencies": { "@solana/codecs-core": "6.1.0", "@solana/errors": "6.1.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YPQwwl6LE3igH23ah+d8kgpyE5xFcPbuwhxCDsLWqY/ESrvO/0YQSbsgIXahbhZxN59ZC4uq1LnHhBNbpCSVQg=="], @@ -4511,6 +4573,8 @@ "@macalinao/coda-visitors/@solana/sysvars/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], + "@macalinao/coda-visitors/@solana/sysvars/@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + "@macalinao/coda-visitors/@solana/sysvars/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], "@macalinao/coda-visitors/@solana/sysvars/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], @@ -4557,6 +4621,10 @@ "@solana-program/address-lookup-table/@solana/kit/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], + "@solana-program/address-lookup-table/@solana/kit/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + + "@solana-program/address-lookup-table/@solana/kit/@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + "@solana-program/address-lookup-table/@solana/kit/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], "@solana-program/address-lookup-table/@solana/kit/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], @@ -4565,6 +4633,8 @@ "@solana-program/address-lookup-table/@solana/kit/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + "@solana-program/address-lookup-table/@solana/kit/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + "@solana-program/address-lookup-table/@solana/kit/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], "@solana-program/address-lookup-table/@solana/kit/@solana/plugin-core": ["@solana/plugin-core@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-VUZl30lDQFJeiSyNfzU1EjYt2QZvoBFKEwjn1lilUJw7KgqD5z7mbV7diJhT+dLFs36i0OsjXvq5kSygn8YJ3A=="], @@ -4583,10 +4653,18 @@ "@solana-program/address-lookup-table/@solana/kit/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], + "@solana-program/address-lookup-table/@solana/kit/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], + "@solana-program/address-lookup-table/@solana/kit/@solana/sysvars": ["@solana/sysvars@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA=="], + "@solana-program/address-lookup-table/@solana/kit/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], + "@solana-program/memo/@solana/kit/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], + "@solana-program/memo/@solana/kit/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + + "@solana-program/memo/@solana/kit/@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + "@solana-program/memo/@solana/kit/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], "@solana-program/memo/@solana/kit/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], @@ -4595,6 +4673,8 @@ "@solana-program/memo/@solana/kit/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + "@solana-program/memo/@solana/kit/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + "@solana-program/memo/@solana/kit/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], "@solana-program/memo/@solana/kit/@solana/plugin-core": ["@solana/plugin-core@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-VUZl30lDQFJeiSyNfzU1EjYt2QZvoBFKEwjn1lilUJw7KgqD5z7mbV7diJhT+dLFs36i0OsjXvq5kSygn8YJ3A=="], @@ -4613,10 +4693,18 @@ "@solana-program/memo/@solana/kit/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], + "@solana-program/memo/@solana/kit/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], + "@solana-program/memo/@solana/kit/@solana/sysvars": ["@solana/sysvars@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA=="], + "@solana-program/memo/@solana/kit/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], + "@solana-program/stake/@solana/kit/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], + "@solana-program/stake/@solana/kit/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + + "@solana-program/stake/@solana/kit/@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + "@solana-program/stake/@solana/kit/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], "@solana-program/stake/@solana/kit/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], @@ -4625,6 +4713,8 @@ "@solana-program/stake/@solana/kit/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + "@solana-program/stake/@solana/kit/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + "@solana-program/stake/@solana/kit/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], "@solana-program/stake/@solana/kit/@solana/plugin-core": ["@solana/plugin-core@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-VUZl30lDQFJeiSyNfzU1EjYt2QZvoBFKEwjn1lilUJw7KgqD5z7mbV7diJhT+dLFs36i0OsjXvq5kSygn8YJ3A=="], @@ -4643,10 +4733,18 @@ "@solana-program/stake/@solana/kit/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], + "@solana-program/stake/@solana/kit/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], + "@solana-program/stake/@solana/kit/@solana/sysvars": ["@solana/sysvars@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA=="], + "@solana-program/stake/@solana/kit/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], + "@solana-program/token-2022/@solana/kit/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], + "@solana-program/token-2022/@solana/kit/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + + "@solana-program/token-2022/@solana/kit/@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + "@solana-program/token-2022/@solana/kit/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], "@solana-program/token-2022/@solana/kit/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], @@ -4655,6 +4753,8 @@ "@solana-program/token-2022/@solana/kit/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + "@solana-program/token-2022/@solana/kit/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + "@solana-program/token-2022/@solana/kit/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], "@solana-program/token-2022/@solana/kit/@solana/plugin-core": ["@solana/plugin-core@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-VUZl30lDQFJeiSyNfzU1EjYt2QZvoBFKEwjn1lilUJw7KgqD5z7mbV7diJhT+dLFs36i0OsjXvq5kSygn8YJ3A=="], @@ -4673,8 +4773,12 @@ "@solana-program/token-2022/@solana/kit/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], + "@solana-program/token-2022/@solana/kit/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], + "@solana-program/token-2022/@solana/kit/@solana/sysvars": ["@solana/sysvars@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA=="], + "@solana-program/token-2022/@solana/kit/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], + "@solana-program/token-2022/@solana/sysvars/@solana/accounts": ["@solana/accounts@6.3.0", "", { "dependencies": { "@solana/addresses": "6.3.0", "@solana/codecs-core": "6.3.0", "@solana/codecs-strings": "6.3.0", "@solana/errors": "6.3.0", "@solana/rpc-spec": "6.3.0", "@solana/rpc-types": "6.3.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ojd1Wz/xBveE8in4GiNEE4vd5/QbIXyCHKSAPh+ggA/iGNFv+cqFHF1EwKCXkI4FkCU7eS9JzGeh2ZuJo3bNYg=="], "@solana-program/token-2022/@solana/sysvars/@solana/codecs-core": ["@solana/codecs-core@6.3.0", "", { "dependencies": { "@solana/errors": "6.3.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lBrGfCa9971IInFWoh1y4a3Z824f/Rl1J/A6jKxXAu8rWLWtQFzuijE/fPlpBcxiwRh7xJOLfQc3+1YAlhd43w=="], @@ -4689,6 +4793,10 @@ "@solana-program/token/@solana/kit/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], + "@solana-program/token/@solana/kit/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + + "@solana-program/token/@solana/kit/@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + "@solana-program/token/@solana/kit/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], "@solana-program/token/@solana/kit/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], @@ -4697,6 +4805,8 @@ "@solana-program/token/@solana/kit/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + "@solana-program/token/@solana/kit/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + "@solana-program/token/@solana/kit/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], "@solana-program/token/@solana/kit/@solana/plugin-core": ["@solana/plugin-core@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-VUZl30lDQFJeiSyNfzU1EjYt2QZvoBFKEwjn1lilUJw7KgqD5z7mbV7diJhT+dLFs36i0OsjXvq5kSygn8YJ3A=="], @@ -4715,23 +4825,21 @@ "@solana-program/token/@solana/kit/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], - "@solana-program/token/@solana/kit/@solana/sysvars": ["@solana/sysvars@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA=="], - - "@solana/accounts/@solana/addresses/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + "@solana-program/token/@solana/kit/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], - "@solana/accounts/@solana/addresses/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana-program/token/@solana/kit/@solana/sysvars": ["@solana/sysvars@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA=="], - "@solana/accounts/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana-program/token/@solana/kit/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], - "@solana/addresses/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "@solana/accounts/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], - "@solana/addresses/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana/addresses/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], - "@solana/assertions/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "@solana/client/@solana/kit/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], - "@solana/assertions/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana/client/@solana/kit/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], - "@solana/client/@solana/kit/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], + "@solana/client/@solana/kit/@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], "@solana/client/@solana/kit/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], @@ -4741,6 +4849,8 @@ "@solana/client/@solana/kit/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + "@solana/client/@solana/kit/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + "@solana/client/@solana/kit/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], "@solana/client/@solana/kit/@solana/plugin-core": ["@solana/plugin-core@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-VUZl30lDQFJeiSyNfzU1EjYt2QZvoBFKEwjn1lilUJw7KgqD5z7mbV7diJhT+dLFs36i0OsjXvq5kSygn8YJ3A=="], @@ -4759,8 +4869,12 @@ "@solana/client/@solana/kit/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], + "@solana/client/@solana/kit/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], + "@solana/client/@solana/kit/@solana/sysvars": ["@solana/sysvars@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA=="], + "@solana/client/@solana/kit/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], + "@solana/codecs-core/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@solana/codecs-core/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], @@ -4775,12 +4889,26 @@ "@solana/codecs-strings/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - "@solana/codecs/@solana/codecs-numbers/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + "@solana/compat/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], "@solana/compat/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@solana/compat/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana/compat/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + + "@solana/connector/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + + "@solana/connector/@solana/addresses/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + + "@solana/connector/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@solana/connector/@solana/codecs/@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + + "@solana/connector/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + + "@solana/connector/@solana/keys/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + "@solana/connector/@solana/kit/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], "@solana/connector/@solana/kit/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], @@ -4811,152 +4939,124 @@ "@solana/connector/@solana/kit/@solana/sysvars": ["@solana/sysvars@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA=="], - "@solana/instruction-plans/@solana/keys/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], - - "@solana/instruction-plans/@solana/keys/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], - - "@solana/instruction-plans/@solana/keys/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], - - "@solana/instruction-plans/@solana/keys/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/connector/@solana/signers/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/instruction-plans/@solana/transaction-messages/@solana/addresses": ["@solana/addresses@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iD4/u3CWchQcPofbwzteaE9RnFJSoi654Rnhru5fOu6U2XOte3+7t50d6OxdxQ109ho2LqZyVtyCo2Wb7u1aJQ=="], + "@solana/connector/@solana/signers/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], - "@solana/instruction-plans/@solana/transaction-messages/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/connector/@solana/signers/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], - "@solana/instruction-plans/@solana/transaction-messages/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + "@solana/connector/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], - "@solana/instruction-plans/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/connector/@solana/transaction-messages/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/instruction-plans/@solana/transaction-messages/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/connector/@solana/transaction-messages/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], - "@solana/instruction-plans/@solana/transactions/@solana/addresses": ["@solana/addresses@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iD4/u3CWchQcPofbwzteaE9RnFJSoi654Rnhru5fOu6U2XOte3+7t50d6OxdxQ109ho2LqZyVtyCo2Wb7u1aJQ=="], + "@solana/connector/@solana/transaction-messages/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], - "@solana/instruction-plans/@solana/transactions/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/connector/@solana/transaction-messages/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], - "@solana/instruction-plans/@solana/transactions/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + "@solana/instruction-plans/@solana/transactions/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/instruction-plans/@solana/transactions/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/instruction-plans/@solana/transactions/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lHr0F+nNwgm9c+tWQX398yzYh1qDi7QSCJpY9MQ2azW4FfY2IyPSo7bqzTaWNnJh9pmJx3ZI6jHfXBnLD5k/SQ=="], - "@solana/instruction-plans/@solana/transactions/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/instruction-plans/@solana/transactions/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], - "@solana/instruction-plans/@solana/transactions/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/instruction-plans/@solana/transactions/@solana/codecs-strings": ["@solana/codecs-strings@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-Rpk5NVhbKYcPnE7wz3IpTp0GVNVs0IYKdmyzByiimgPTiII8eb8ay4wQiYHGHrpYh62hD14Qy3GiGDFgipRKqA=="], - "@solana/keys/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "@solana/instruction-plans/@solana/transactions/@solana/nominal-types": ["@solana/nominal-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-mLmHr92pM4mEfe49GUmZ5Ry0RMqtMuFQqZYnxQqhDKMcl+Wtt820ezxYgwPhqcMxRzfqaQSO3ZxpSB0RlLBa/Q=="], - "@solana/keys/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana/keychain-aws-kms/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], - "@solana/kit/@solana/addresses/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + "@solana/keychain-aws-kms/@solana/addresses/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/kit/@solana/addresses/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/keychain-aws-kms/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], - "@solana/kit/@solana/addresses/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/keychain-aws-kms/@solana/keys/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/kit/@solana/addresses/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/keychain-aws-kms/@solana/signers/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/kit/@solana/codecs/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/keychain-aws-kms/@solana/signers/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], - "@solana/kit/@solana/codecs/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + "@solana/keychain-aws-kms/@solana/signers/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], - "@solana/kit/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/keychain-aws-kms/@solana/signers/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], - "@solana/kit/@solana/codecs/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/keychain-core/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], - "@solana/kit/@solana/codecs/@solana/options": ["@solana/options@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-jdZjSKGCQpsMFK+3CiUEI7W9iGsndi46R4Abk66ULNLDoMsjvfqNy8kqktm0TN0++EX8dKEecpFwxFaA4VlY5g=="], + "@solana/keychain-core/@solana/addresses/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/kit/@solana/keys/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + "@solana/keychain-core/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], - "@solana/kit/@solana/keys/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/keychain-core/@solana/keys/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/kit/@solana/keys/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/keychain-core/@solana/signers/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/kit/@solana/keys/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/keychain-core/@solana/signers/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], - "@solana/kit/@solana/signers/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/keychain-core/@solana/signers/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], - "@solana/kit/@solana/signers/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/keychain-core/@solana/signers/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], - "@solana/kit/@solana/transaction-confirmation/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/keychain-turnkey/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], - "@solana/kit/@solana/transaction-confirmation/@solana/promises": ["@solana/promises@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n5rsA3YwOO2nUst6ghuVw6RSnuZQYqevqBKqVYbw11Z4XezsoQ6hb78opW3J9YNYapw9wLWy6tEfUsJjY+xtGw=="], + "@solana/keychain-turnkey/@solana/addresses/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/kit/@solana/transaction-messages/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/keychain-turnkey/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], - "@solana/kit/@solana/transaction-messages/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + "@solana/keychain-turnkey/@solana/keys/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/kit/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/keychain-turnkey/@solana/signers/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/kit/@solana/transaction-messages/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/keychain-turnkey/@solana/signers/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], - "@solana/kit/@solana/transactions/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/keychain-turnkey/@solana/signers/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], - "@solana/kit/@solana/transactions/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + "@solana/keychain-turnkey/@solana/signers/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], - "@solana/kit/@solana/transactions/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/keychain-vault/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], - "@solana/kit/@solana/transactions/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/keychain-vault/@solana/addresses/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/kit/@solana/transactions/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/keychain-vault/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], - "@solana/offchain-messages/@solana/addresses/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + "@solana/keychain-vault/@solana/keys/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/offchain-messages/@solana/keys/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + "@solana/keychain-vault/@solana/signers/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/options/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "@solana/keychain-vault/@solana/signers/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], - "@solana/options/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana/keychain-vault/@solana/signers/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], - "@solana/plugin-interfaces/@solana/addresses/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + "@solana/keychain-vault/@solana/signers/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], - "@solana/plugin-interfaces/@solana/addresses/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/keys/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], - "@solana/plugin-interfaces/@solana/addresses/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/kit/@solana/transaction-confirmation/@solana/codecs-strings": ["@solana/codecs-strings@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-Rpk5NVhbKYcPnE7wz3IpTp0GVNVs0IYKdmyzByiimgPTiII8eb8ay4wQiYHGHrpYh62hD14Qy3GiGDFgipRKqA=="], - "@solana/plugin-interfaces/@solana/addresses/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/kit/@solana/transaction-confirmation/@solana/promises": ["@solana/promises@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kIypZG83ZbADbrAq9/LS7LuWlVxlgJSzIpic75+9IuAfC3k5/KSus8LrvggBkCzfAyIslrUh70iz4JcnzUZrOw=="], - "@solana/plugin-interfaces/@solana/keys/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + "@solana/kit/@solana/transactions/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/plugin-interfaces/@solana/keys/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/kit/@solana/transactions/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lHr0F+nNwgm9c+tWQX398yzYh1qDi7QSCJpY9MQ2azW4FfY2IyPSo7bqzTaWNnJh9pmJx3ZI6jHfXBnLD5k/SQ=="], - "@solana/plugin-interfaces/@solana/keys/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/kit/@solana/transactions/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], - "@solana/plugin-interfaces/@solana/keys/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/kit/@solana/transactions/@solana/codecs-strings": ["@solana/codecs-strings@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-Rpk5NVhbKYcPnE7wz3IpTp0GVNVs0IYKdmyzByiimgPTiII8eb8ay4wQiYHGHrpYh62hD14Qy3GiGDFgipRKqA=="], - "@solana/plugin-interfaces/@solana/signers/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/kit/@solana/transactions/@solana/nominal-types": ["@solana/nominal-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-mLmHr92pM4mEfe49GUmZ5Ry0RMqtMuFQqZYnxQqhDKMcl+Wtt820ezxYgwPhqcMxRzfqaQSO3ZxpSB0RlLBa/Q=="], - "@solana/plugin-interfaces/@solana/signers/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], - - "@solana/plugin-interfaces/@solana/signers/@solana/transaction-messages": ["@solana/transaction-messages@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ueXkm5xaRlqYBFAlABhaCKK/DuzIYSot0FybwSDeOQCDy2hvU9Zda16Iwa1n56M0fG+XUvFJz2woG3u9DhQh1g=="], - - "@solana/plugin-interfaces/@solana/signers/@solana/transactions": ["@solana/transactions@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-b3eJrrGmwpk64VLHjOrmXKAahPpba42WX/FqSUn4WRXPoQjga7Mb57yp+EaRVeQfjszKCkF+13yu+ni6iv2NFQ=="], - - "@solana/program-client-core/@solana/addresses/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], - - "@solana/program-client-core/@solana/addresses/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], - - "@solana/program-client-core/@solana/addresses/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], - - "@solana/program-client-core/@solana/signers/@solana/keys": ["@solana/keys@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-CN5jmodX9j5CZKrWLM5XGaRlrLl/Ebl4vgqDXrnwC2NiSfUslLsthuORMuVUTDqkzBX/jd/tgVXFRH2NYNzREQ=="], - - "@solana/program-client-core/@solana/signers/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], - - "@solana/program-client-core/@solana/signers/@solana/transaction-messages": ["@solana/transaction-messages@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ueXkm5xaRlqYBFAlABhaCKK/DuzIYSot0FybwSDeOQCDy2hvU9Zda16Iwa1n56M0fG+XUvFJz2woG3u9DhQh1g=="], - - "@solana/program-client-core/@solana/signers/@solana/transactions": ["@solana/transactions@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-b3eJrrGmwpk64VLHjOrmXKAahPpba42WX/FqSUn4WRXPoQjga7Mb57yp+EaRVeQfjszKCkF+13yu+ni6iv2NFQ=="], - - "@solana/programs/@solana/addresses/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], - - "@solana/programs/@solana/addresses/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], - - "@solana/programs/@solana/addresses/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], - - "@solana/programs/@solana/addresses/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/react-hooks/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], "@solana/react-hooks/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@solana/react-hooks/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana/react-hooks/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@solana/react-hooks/@solana/kit/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], + "@solana/react-hooks/@solana/kit/@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + "@solana/react-hooks/@solana/kit/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], "@solana/react-hooks/@solana/kit/@solana/instruction-plans": ["@solana/instruction-plans@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/promises": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-7z3CB7YMcFKuVvgcnNY8bY6IsZ8LG61Iytbz7HpNVGX2u1RthOs1tRW8luTzSG1MPL0Ox7afyAVMYeFqSPHnaQ=="], @@ -4983,69 +5083,41 @@ "@solana/react-hooks/@solana/kit/@solana/sysvars": ["@solana/sysvars@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA=="], - "@solana/rpc-api/@solana/addresses/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], - - "@solana/rpc-api/@solana/addresses/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], - - "@solana/rpc-api/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], - - "@solana/rpc-api/@solana/keys/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], - - "@solana/rpc-api/@solana/keys/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], - - "@solana/rpc-api/@solana/transaction-messages/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], - - "@solana/rpc-api/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], - - "@solana/rpc-api/@solana/transaction-messages/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], - - "@solana/rpc-api/@solana/transactions/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], - - "@solana/rpc-api/@solana/transactions/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], - - "@solana/rpc-api/@solana/transactions/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], - - "@solana/rpc-subscriptions-api/@solana/addresses/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], - - "@solana/rpc-subscriptions-api/@solana/addresses/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/react-hooks/@solana/signers/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], - "@solana/rpc-subscriptions-api/@solana/addresses/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/react-hooks/@solana/signers/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], - "@solana/rpc-subscriptions-api/@solana/addresses/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/react-hooks/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], - "@solana/rpc-subscriptions-api/@solana/keys/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + "@solana/react-hooks/@solana/transaction-messages/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], - "@solana/rpc-subscriptions-api/@solana/keys/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/react-hooks/@solana/transaction-messages/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], - "@solana/rpc-subscriptions-api/@solana/keys/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/react-hooks/@solana/transaction-messages/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], - "@solana/rpc-subscriptions-api/@solana/keys/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/rpc-api/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], - "@solana/rpc-subscriptions-api/@solana/transaction-messages/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/rpc-api/@solana/transactions/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lHr0F+nNwgm9c+tWQX398yzYh1qDi7QSCJpY9MQ2azW4FfY2IyPSo7bqzTaWNnJh9pmJx3ZI6jHfXBnLD5k/SQ=="], - "@solana/rpc-subscriptions-api/@solana/transaction-messages/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + "@solana/rpc-api/@solana/transactions/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], - "@solana/rpc-subscriptions-api/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/rpc-api/@solana/transactions/@solana/nominal-types": ["@solana/nominal-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-mLmHr92pM4mEfe49GUmZ5Ry0RMqtMuFQqZYnxQqhDKMcl+Wtt820ezxYgwPhqcMxRzfqaQSO3ZxpSB0RlLBa/Q=="], - "@solana/rpc-subscriptions-api/@solana/transaction-messages/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/rpc-subscriptions-api/@solana/transactions/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], - "@solana/rpc-subscriptions-api/@solana/transactions/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/rpc-subscriptions-api/@solana/transactions/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lHr0F+nNwgm9c+tWQX398yzYh1qDi7QSCJpY9MQ2azW4FfY2IyPSo7bqzTaWNnJh9pmJx3ZI6jHfXBnLD5k/SQ=="], - "@solana/rpc-subscriptions-api/@solana/transactions/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + "@solana/rpc-subscriptions-api/@solana/transactions/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], - "@solana/rpc-subscriptions-api/@solana/transactions/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/rpc-subscriptions-api/@solana/transactions/@solana/codecs-strings": ["@solana/codecs-strings@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-Rpk5NVhbKYcPnE7wz3IpTp0GVNVs0IYKdmyzByiimgPTiII8eb8ay4wQiYHGHrpYh62hD14Qy3GiGDFgipRKqA=="], - "@solana/rpc-subscriptions-api/@solana/transactions/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/rpc-subscriptions-api/@solana/transactions/@solana/nominal-types": ["@solana/nominal-types@6.8.0", "", { "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-mLmHr92pM4mEfe49GUmZ5Ry0RMqtMuFQqZYnxQqhDKMcl+Wtt820ezxYgwPhqcMxRzfqaQSO3ZxpSB0RlLBa/Q=="], - "@solana/rpc-subscriptions-api/@solana/transactions/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "@solana/signers/@solana/transactions/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lHr0F+nNwgm9c+tWQX398yzYh1qDi7QSCJpY9MQ2azW4FfY2IyPSo7bqzTaWNnJh9pmJx3ZI6jHfXBnLD5k/SQ=="], - "@solana/rpc-types/@solana/addresses/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + "@solana/signers/@solana/transactions/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], - "@solana/signers/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - - "@solana/signers/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - - "@solana/signers/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana/signers/@solana/transactions/@solana/codecs-strings": ["@solana/codecs-strings@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/codecs-numbers": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-Rpk5NVhbKYcPnE7wz3IpTp0GVNVs0IYKdmyzByiimgPTiII8eb8ay4wQiYHGHrpYh62hD14Qy3GiGDFgipRKqA=="], "@solana/spl-token-group/@solana/codecs/@solana/codecs-core": ["@solana/codecs-core@2.0.0-rc.1", "", { "dependencies": { "@solana/errors": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ=="], @@ -5067,10 +5139,14 @@ "@solana/spl-token-metadata/@solana/codecs/@solana/options": ["@solana/options@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/codecs-data-structures": "2.0.0-rc.1", "@solana/codecs-numbers": "2.0.0-rc.1", "@solana/codecs-strings": "2.0.0-rc.1", "@solana/errors": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA=="], + "@solana/transaction-confirmation/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@solana/transaction-confirmation/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@solana/transaction-confirmation/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana/transaction-confirmation/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@solana/transaction-confirmation/@solana/rpc/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw=="], "@solana/transaction-confirmation/@solana/rpc/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], @@ -5103,20 +5179,32 @@ "@solana/transaction-confirmation/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], - "@solana/transaction-messages/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "@solana/transaction-confirmation/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], - "@solana/transaction-messages/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana/transaction-confirmation/@solana/transaction-messages/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], + + "@solana/transaction-confirmation/@solana/transaction-messages/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + + "@solana/transactions/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], "@solana/transactions/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@solana/transactions/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana/transactions/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@solana/wallet-adapter-unsafe-burner/@noble/curves/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], "@solana/wallet-standard-util/@noble/curves/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@solana/web3-compat/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + + "@solana/web3-compat/@solana/addresses/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + "@solana/web3-compat/@solana/kit/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], + "@solana/web3-compat/@solana/kit/@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + "@solana/web3-compat/@solana/kit/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], "@solana/web3-compat/@solana/kit/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], @@ -5125,6 +5213,8 @@ "@solana/web3-compat/@solana/kit/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + "@solana/web3-compat/@solana/kit/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + "@solana/web3-compat/@solana/kit/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], "@solana/web3-compat/@solana/kit/@solana/plugin-core": ["@solana/plugin-core@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-VUZl30lDQFJeiSyNfzU1EjYt2QZvoBFKEwjn1lilUJw7KgqD5z7mbV7diJhT+dLFs36i0OsjXvq5kSygn8YJ3A=="], @@ -5143,8 +5233,12 @@ "@solana/web3-compat/@solana/kit/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], + "@solana/web3-compat/@solana/kit/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], + "@solana/web3-compat/@solana/kit/@solana/sysvars": ["@solana/sysvars@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA=="], + "@solana/web3-compat/@solana/kit/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], + "@solana/web3.js/bs58/base-x": ["base-x@3.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA=="], "@solflare-wallet/metamask-sdk/bs58/base-x": ["base-x@4.0.1", "", {}, "sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw=="], @@ -5325,6 +5419,10 @@ "@wallet-ui/core/@solana/kit/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], + "@wallet-ui/core/@solana/kit/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + + "@wallet-ui/core/@solana/kit/@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + "@wallet-ui/core/@solana/kit/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], "@wallet-ui/core/@solana/kit/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], @@ -5333,6 +5431,8 @@ "@wallet-ui/core/@solana/kit/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + "@wallet-ui/core/@solana/kit/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + "@wallet-ui/core/@solana/kit/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], "@wallet-ui/core/@solana/kit/@solana/plugin-core": ["@solana/plugin-core@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-VUZl30lDQFJeiSyNfzU1EjYt2QZvoBFKEwjn1lilUJw7KgqD5z7mbV7diJhT+dLFs36i0OsjXvq5kSygn8YJ3A=="], @@ -5351,8 +5451,12 @@ "@wallet-ui/core/@solana/kit/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], + "@wallet-ui/core/@solana/kit/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], + "@wallet-ui/core/@solana/kit/@solana/sysvars": ["@solana/sysvars@5.5.1", "", { "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA=="], + "@wallet-ui/core/@solana/kit/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], + "borsh/bs58/base-x": ["base-x@3.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA=="], "browserify-sign/readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], @@ -5381,6 +5485,54 @@ "hyperbet-evm-keeper/tsx/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "hyperbet-solana-app/@solana/kit/@solana/accounts": ["@solana/accounts@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/rpc-spec": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h3zQFjwZjmy+YxgTGOEna6g74Tsn4hTBaBCslwPT4QjqWhywe2JrM2Ab0ANfJcj7g/xrHF5QJ/FnUIcyUTeVfQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/addresses": ["@solana/addresses@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iD4/u3CWchQcPofbwzteaE9RnFJSoi654Rnhru5fOu6U2XOte3+7t50d6OxdxQ109ho2LqZyVtyCo2Wb7u1aJQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/codecs": ["@solana/codecs@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/options": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-WfqMqUXk4jcCJQ9nfKqjDcCJN2Pt8/AKe/E78z8OcblFGVJnTzcu2yZpE2gsqM+DJyCVKdQmOY+NS8Uckk5e5w=="], + + "hyperbet-solana-app/@solana/kit/@solana/errors": ["@solana/errors@6.5.0", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.3" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-XPc0I8Ck6vgx8Uu+LVLewx/1RWDkXkY3lU+1aN1kmbrPAQWbX4Txk7GPmuIIFpyys8o5aKocYfNxJOPKvfaQhg=="], + + "hyperbet-solana-app/@solana/kit/@solana/functional": ["@solana/functional@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-/KYgY7ZpBJfkN8+qlIvxuBpxv32U9jHXIOOJh3U5xk8Ncsa9Ex5VwbU9NkOf43MJjoIamsP0vARCHjcqJwe5JQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/instruction-plans": ["@solana/instruction-plans@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/promises": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-zp2asevpyMwvhajHYM1aruYpO+xf3LSwHEI2FK6E2hddYZaEhuBy+bz+NZ1ixCyfx3iXcq7MamlFQc2ySHDyUQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/instructions": ["@solana/instructions@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-2mQP/1qqr5PCfaVMzs9KofBjpyS7J1sBV6PidGoX9Dg5/4UgwJJ+7yfCVQPn37l1nKCShm4I+pQAy5vbmrxJmA=="], + + "hyperbet-solana-app/@solana/kit/@solana/keys": ["@solana/keys@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-CN5jmodX9j5CZKrWLM5XGaRlrLl/Ebl4vgqDXrnwC2NiSfUslLsthuORMuVUTDqkzBX/jd/tgVXFRH2NYNzREQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/offchain-messages": ["@solana/offchain-messages@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-IYuidJCwfXg5xlh3rkflkA1fbTKWTsip8MdI+znvXm87grfqOYCTd6t/SKiV4BhLl/65Tn0wB/zvZ1cmzJqa1w=="], + + "hyperbet-solana-app/@solana/kit/@solana/plugin-core": ["@solana/plugin-core@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-L6N69oNQOAqljH4GnLTaxpwJB0nibW9DrybHZxpGWshyv6b/EvwvkDVRKj5bNqtCG+HRZUHnEhLi1UgZVNkjpQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/plugin-interfaces": ["@solana/plugin-interfaces@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/instruction-plans": "6.5.0", "@solana/keys": "6.5.0", "@solana/rpc-spec": "6.5.0", "@solana/rpc-subscriptions-spec": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/signers": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-/ZlybbMaR7P4ySersOe1huioMADWze0AzsHbzgkpt5dJUv2tz5cpaKdu7TEVQkUZAFhLdqXQULNGqAU5neOgzg=="], + + "hyperbet-solana-app/@solana/kit/@solana/program-client-core": ["@solana/program-client-core@6.5.0", "", { "dependencies": { "@solana/accounts": "6.5.0", "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0", "@solana/instruction-plans": "6.5.0", "@solana/instructions": "6.5.0", "@solana/plugin-interfaces": "6.5.0", "@solana/rpc-api": "6.5.0", "@solana/signers": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eUz1xSeDKySGIjToAryPmlESdj8KX0Np7R+Pjt+kSFGw5Jgmn/Inh4o8luoeEnf5XwbvSPVb4aHpIsDyoUVbIg=="], + + "hyperbet-solana-app/@solana/kit/@solana/programs": ["@solana/programs@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-srn3nEROBxCnBpVz/bvLkVln1BZtk3bS3nuReu3yaeOLkKl8b0h1Zp0YmXVyXHzdMcYahsTvKKLR1ZtLZEyEPA=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc": ["@solana/rpc@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/fast-stable-stringify": "6.5.0", "@solana/functional": "6.5.0", "@solana/rpc-api": "6.5.0", "@solana/rpc-spec": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-transformers": "6.5.0", "@solana/rpc-transport-http": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lGj7ZMVOR3Rf16aByXD6ghrMqw3G8rAMuWCHU4uMKES5M5VLqNv6o71bSyoTxVMGrmYdbALOvCbFMFINAxtoBg=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-api": ["@solana/rpc-api@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/keys": "6.5.0", "@solana/rpc-parsed-types": "6.5.0", "@solana/rpc-spec": "6.5.0", "@solana/rpc-transformers": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-b+kftroO8vZFzLHj7Nk/uATS3HOlBUsUqdGg3eTQrW1pFgkyq5yIoEYHeFF7ApUN/SJLTK86U8ofCaXabd2SXA=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-parsed-types": ["@solana/rpc-parsed-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-129c8meL6CxRg56/HfhkFOpwYteQH9Rt0wyXOXZQx3a3FNpcJLd4JdPvxDsLBE3EupEkXLGVku/1bGKz+F2J+g=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-spec-types": ["@solana/rpc-spec-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-XasJp+sOW6PLfNoalzoLnm+j3LEZF8XOQmSrOqv9AGrGxQckkuOf6iXZucWTqeNKdstsOpU28BN2B6qOavfRzQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-subscriptions": ["@solana/rpc-subscriptions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/fast-stable-stringify": "6.5.0", "@solana/functional": "6.5.0", "@solana/promises": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-subscriptions-api": "6.5.0", "@solana/rpc-subscriptions-channel-websocket": "6.5.0", "@solana/rpc-subscriptions-spec": "6.5.0", "@solana/rpc-transformers": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/subscribable": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-EenogPQw9Iy8VUj8anu7xoBnPk7gu1J6sAi4MTVlNVz02sNjdUBJoSS0PRJZuhSM1ktPTtHrNwqlXP8TxPR7jg=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-types": ["@solana/rpc-types@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-hxts27+Z2VNv4IjXGcXkqbj/MgrN9Xtw/4iE1qZk68T2OAb5vA4b8LHchsOHmHvrzZfo8XDvB9mModCdM3JPsQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/signers": ["@solana/signers@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/offchain-messages": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-AL75/DyDUhc+QQ+VGZT7aRwJNzIUTWvmLNXQRlCVhLRuyroXzZEL2WJBs8xOwbZXjY8weacfYT7UNM8qK6ucDg=="], + + "hyperbet-solana-app/@solana/kit/@solana/sysvars": ["@solana/sysvars@6.5.0", "", { "dependencies": { "@solana/accounts": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iLSS5qj0MWNiGH1LN1E4jhGsXH9D3tWSjwaB6zK9LjhLdVYcPfkosBkj7s0EHHrH03QlwiuFdU0Y2kH8Jcp8kw=="], + + "hyperbet-solana-app/@solana/kit/@solana/transaction-confirmation": ["@solana/transaction-confirmation@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/keys": "6.5.0", "@solana/promises": "6.5.0", "@solana/rpc": "6.5.0", "@solana/rpc-subscriptions": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-hfdRBq4toZj7DRMgBN3F0VtJpmTAEtcVTTDZoiszoSpSVa2cAvFth6KypIqASVFZyi9t4FKolLP8ASd3/39UQg=="], + + "hyperbet-solana-app/@solana/kit/@solana/transaction-messages": ["@solana/transaction-messages@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ueXkm5xaRlqYBFAlABhaCKK/DuzIYSot0FybwSDeOQCDy2hvU9Zda16Iwa1n56M0fG+XUvFJz2woG3u9DhQh1g=="], + + "hyperbet-solana-app/@solana/kit/@solana/transactions": ["@solana/transactions@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-b3eJrrGmwpk64VLHjOrmXKAahPpba42WX/FqSUn4WRXPoQjga7Mb57yp+EaRVeQfjszKCkF+13yu+ni6iv2NFQ=="], + "hyperbet-solana-app/eslint/@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], "hyperbet-solana-app/eslint/@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], @@ -5397,6 +5549,54 @@ "hyperbet-solana-app/eslint/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "hyperbet-solana-keeper/@solana/kit/@solana/accounts": ["@solana/accounts@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/rpc-spec": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h3zQFjwZjmy+YxgTGOEna6g74Tsn4hTBaBCslwPT4QjqWhywe2JrM2Ab0ANfJcj7g/xrHF5QJ/FnUIcyUTeVfQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/addresses": ["@solana/addresses@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iD4/u3CWchQcPofbwzteaE9RnFJSoi654Rnhru5fOu6U2XOte3+7t50d6OxdxQ109ho2LqZyVtyCo2Wb7u1aJQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/codecs": ["@solana/codecs@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/options": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-WfqMqUXk4jcCJQ9nfKqjDcCJN2Pt8/AKe/E78z8OcblFGVJnTzcu2yZpE2gsqM+DJyCVKdQmOY+NS8Uckk5e5w=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/errors": ["@solana/errors@6.5.0", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.3" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-XPc0I8Ck6vgx8Uu+LVLewx/1RWDkXkY3lU+1aN1kmbrPAQWbX4Txk7GPmuIIFpyys8o5aKocYfNxJOPKvfaQhg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/functional": ["@solana/functional@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-/KYgY7ZpBJfkN8+qlIvxuBpxv32U9jHXIOOJh3U5xk8Ncsa9Ex5VwbU9NkOf43MJjoIamsP0vARCHjcqJwe5JQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/instruction-plans": ["@solana/instruction-plans@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/promises": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-zp2asevpyMwvhajHYM1aruYpO+xf3LSwHEI2FK6E2hddYZaEhuBy+bz+NZ1ixCyfx3iXcq7MamlFQc2ySHDyUQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/instructions": ["@solana/instructions@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-2mQP/1qqr5PCfaVMzs9KofBjpyS7J1sBV6PidGoX9Dg5/4UgwJJ+7yfCVQPn37l1nKCShm4I+pQAy5vbmrxJmA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/keys": ["@solana/keys@6.5.0", "", { "dependencies": { "@solana/assertions": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-CN5jmodX9j5CZKrWLM5XGaRlrLl/Ebl4vgqDXrnwC2NiSfUslLsthuORMuVUTDqkzBX/jd/tgVXFRH2NYNzREQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/offchain-messages": ["@solana/offchain-messages@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-IYuidJCwfXg5xlh3rkflkA1fbTKWTsip8MdI+znvXm87grfqOYCTd6t/SKiV4BhLl/65Tn0wB/zvZ1cmzJqa1w=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/plugin-core": ["@solana/plugin-core@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-L6N69oNQOAqljH4GnLTaxpwJB0nibW9DrybHZxpGWshyv6b/EvwvkDVRKj5bNqtCG+HRZUHnEhLi1UgZVNkjpQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/plugin-interfaces": ["@solana/plugin-interfaces@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/instruction-plans": "6.5.0", "@solana/keys": "6.5.0", "@solana/rpc-spec": "6.5.0", "@solana/rpc-subscriptions-spec": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/signers": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-/ZlybbMaR7P4ySersOe1huioMADWze0AzsHbzgkpt5dJUv2tz5cpaKdu7TEVQkUZAFhLdqXQULNGqAU5neOgzg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/program-client-core": ["@solana/program-client-core@6.5.0", "", { "dependencies": { "@solana/accounts": "6.5.0", "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0", "@solana/instruction-plans": "6.5.0", "@solana/instructions": "6.5.0", "@solana/plugin-interfaces": "6.5.0", "@solana/rpc-api": "6.5.0", "@solana/signers": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eUz1xSeDKySGIjToAryPmlESdj8KX0Np7R+Pjt+kSFGw5Jgmn/Inh4o8luoeEnf5XwbvSPVb4aHpIsDyoUVbIg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/programs": ["@solana/programs@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-srn3nEROBxCnBpVz/bvLkVln1BZtk3bS3nuReu3yaeOLkKl8b0h1Zp0YmXVyXHzdMcYahsTvKKLR1ZtLZEyEPA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc": ["@solana/rpc@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/fast-stable-stringify": "6.5.0", "@solana/functional": "6.5.0", "@solana/rpc-api": "6.5.0", "@solana/rpc-spec": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-transformers": "6.5.0", "@solana/rpc-transport-http": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-lGj7ZMVOR3Rf16aByXD6ghrMqw3G8rAMuWCHU4uMKES5M5VLqNv6o71bSyoTxVMGrmYdbALOvCbFMFINAxtoBg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-api": ["@solana/rpc-api@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/keys": "6.5.0", "@solana/rpc-parsed-types": "6.5.0", "@solana/rpc-spec": "6.5.0", "@solana/rpc-transformers": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-b+kftroO8vZFzLHj7Nk/uATS3HOlBUsUqdGg3eTQrW1pFgkyq5yIoEYHeFF7ApUN/SJLTK86U8ofCaXabd2SXA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-parsed-types": ["@solana/rpc-parsed-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-129c8meL6CxRg56/HfhkFOpwYteQH9Rt0wyXOXZQx3a3FNpcJLd4JdPvxDsLBE3EupEkXLGVku/1bGKz+F2J+g=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-spec-types": ["@solana/rpc-spec-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-XasJp+sOW6PLfNoalzoLnm+j3LEZF8XOQmSrOqv9AGrGxQckkuOf6iXZucWTqeNKdstsOpU28BN2B6qOavfRzQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-subscriptions": ["@solana/rpc-subscriptions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/fast-stable-stringify": "6.5.0", "@solana/functional": "6.5.0", "@solana/promises": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-subscriptions-api": "6.5.0", "@solana/rpc-subscriptions-channel-websocket": "6.5.0", "@solana/rpc-subscriptions-spec": "6.5.0", "@solana/rpc-transformers": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/subscribable": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-EenogPQw9Iy8VUj8anu7xoBnPk7gu1J6sAi4MTVlNVz02sNjdUBJoSS0PRJZuhSM1ktPTtHrNwqlXP8TxPR7jg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-types": ["@solana/rpc-types@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/nominal-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-hxts27+Z2VNv4IjXGcXkqbj/MgrN9Xtw/4iE1qZk68T2OAb5vA4b8LHchsOHmHvrzZfo8XDvB9mModCdM3JPsQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/signers": ["@solana/signers@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/offchain-messages": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-AL75/DyDUhc+QQ+VGZT7aRwJNzIUTWvmLNXQRlCVhLRuyroXzZEL2WJBs8xOwbZXjY8weacfYT7UNM8qK6ucDg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/sysvars": ["@solana/sysvars@6.5.0", "", { "dependencies": { "@solana/accounts": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-iLSS5qj0MWNiGH1LN1E4jhGsXH9D3tWSjwaB6zK9LjhLdVYcPfkosBkj7s0EHHrH03QlwiuFdU0Y2kH8Jcp8kw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transaction-confirmation": ["@solana/transaction-confirmation@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/keys": "6.5.0", "@solana/promises": "6.5.0", "@solana/rpc": "6.5.0", "@solana/rpc-subscriptions": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-hfdRBq4toZj7DRMgBN3F0VtJpmTAEtcVTTDZoiszoSpSVa2cAvFth6KypIqASVFZyi9t4FKolLP8ASd3/39UQg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transaction-messages": ["@solana/transaction-messages@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ueXkm5xaRlqYBFAlABhaCKK/DuzIYSot0FybwSDeOQCDy2hvU9Zda16Iwa1n56M0fG+XUvFJz2woG3u9DhQh1g=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transactions": ["@solana/transactions@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/instructions": "6.5.0", "@solana/keys": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-b3eJrrGmwpk64VLHjOrmXKAahPpba42WX/FqSUn4WRXPoQjga7Mb57yp+EaRVeQfjszKCkF+13yu+ni6iv2NFQ=="], + "hyperbet-solana-keeper/tsx/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "jayson/@types/ws/@types/node": ["@types/node@25.3.5", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA=="], @@ -5503,6 +5703,10 @@ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + "@codama/nodes-from-anchor/@solana/codecs/@solana/codecs-numbers/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + + "@codama/nodes-from-anchor/@solana/codecs/@solana/options/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + "@codama/renderers-js/@solana/codecs-strings/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@eslint/eslintrc/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -5525,6 +5729,10 @@ "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], + "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + + "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], @@ -5533,6 +5741,8 @@ "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/plugin-core": ["@solana/plugin-core@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-VUZl30lDQFJeiSyNfzU1EjYt2QZvoBFKEwjn1lilUJw7KgqD5z7mbV7diJhT+dLFs36i0OsjXvq5kSygn8YJ3A=="], @@ -5551,8 +5761,16 @@ "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], + "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], + + "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/accounts": ["@solana/accounts@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/rpc-spec": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ=="], + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/codecs": ["@solana/codecs@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/options": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g=="], + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], @@ -5561,6 +5779,8 @@ "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/instructions": ["@solana/instructions@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ=="], + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/keys": ["@solana/keys@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg=="], + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/offchain-messages": ["@solana/offchain-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw=="], "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/plugin-core": ["@solana/plugin-core@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-VUZl30lDQFJeiSyNfzU1EjYt2QZvoBFKEwjn1lilUJw7KgqD5z7mbV7diJhT+dLFs36i0OsjXvq5kSygn8YJ3A=="], @@ -5579,12 +5799,24 @@ "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/signers": ["@solana/signers@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1", "@solana/instructions": "5.5.1", "@solana/keys": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/offchain-messages": "5.5.1", "@solana/transaction-messages": "5.5.1", "@solana/transactions": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ=="], + + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/transaction-messages": ["@solana/transaction-messages@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/errors": "5.5.1", "@solana/functional": "5.5.1", "@solana/instructions": "5.5.1", "@solana/nominal-types": "5.5.1", "@solana/rpc-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ=="], + + "@macalinao/coda-visitors/@solana/sysvars/@solana/accounts/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + "@macalinao/coda-visitors/@solana/sysvars/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + "@macalinao/coda-visitors/@solana/sysvars/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@macalinao/coda-visitors/@solana/sysvars/@solana/codecs/@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + "@macalinao/coda-visitors/@solana/sysvars/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@macalinao/coda-visitors/@solana/sysvars/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@macalinao/coda-visitors/@solana/sysvars/@solana/rpc-types/@solana/addresses": ["@solana/addresses@5.5.1", "", { "dependencies": { "@solana/assertions": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA=="], + "@macalinao/coda-visitors/@solana/sysvars/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/sign-client/@walletconnect/core": ["@walletconnect/core@2.19.1", "", { "dependencies": { "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/jsonrpc-ws-connection": "1.0.16", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "@walletconnect/relay-api": "1.0.11", "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.19.1", "@walletconnect/utils": "2.19.1", "@walletconnect/window-getters": "1.0.1", "es-toolkit": "1.33.0", "events": "3.3.0", "uint8arrays": "3.1.0" } }, "sha512-rMvpZS0tQXR/ivzOxN1GkHvw3jRRMlI/jRX5g7ZteLgg2L0ZcANsFvAU5IxILxIKcIkTCloF9TcfloKVbK3qmw=="], @@ -5627,10 +5859,18 @@ "@solana-program/address-lookup-table/@solana/kit/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + "@solana-program/address-lookup-table/@solana/kit/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + + "@solana-program/address-lookup-table/@solana/kit/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@solana-program/address-lookup-table/@solana/kit/@solana/codecs/@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + "@solana-program/address-lookup-table/@solana/kit/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@solana-program/address-lookup-table/@solana/kit/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana-program/address-lookup-table/@solana/kit/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@solana-program/address-lookup-table/@solana/kit/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], "@solana-program/address-lookup-table/@solana/kit/@solana/rpc/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw=="], @@ -5659,12 +5899,22 @@ "@solana-program/address-lookup-table/@solana/kit/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana-program/address-lookup-table/@solana/kit/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana-program/memo/@solana/kit/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + "@solana-program/memo/@solana/kit/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + + "@solana-program/memo/@solana/kit/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@solana-program/memo/@solana/kit/@solana/codecs/@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + "@solana-program/memo/@solana/kit/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@solana-program/memo/@solana/kit/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana-program/memo/@solana/kit/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@solana-program/memo/@solana/kit/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], "@solana-program/memo/@solana/kit/@solana/rpc/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw=="], @@ -5693,12 +5943,22 @@ "@solana-program/memo/@solana/kit/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana-program/memo/@solana/kit/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana-program/stake/@solana/kit/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + "@solana-program/stake/@solana/kit/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + + "@solana-program/stake/@solana/kit/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@solana-program/stake/@solana/kit/@solana/codecs/@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + "@solana-program/stake/@solana/kit/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@solana-program/stake/@solana/kit/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana-program/stake/@solana/kit/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@solana-program/stake/@solana/kit/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], "@solana-program/stake/@solana/kit/@solana/rpc/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw=="], @@ -5727,12 +5987,22 @@ "@solana-program/stake/@solana/kit/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana-program/stake/@solana/kit/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana-program/token-2022/@solana/kit/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + "@solana-program/token-2022/@solana/kit/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + + "@solana-program/token-2022/@solana/kit/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@solana-program/token-2022/@solana/kit/@solana/codecs/@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + "@solana-program/token-2022/@solana/kit/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@solana-program/token-2022/@solana/kit/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana-program/token-2022/@solana/kit/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@solana-program/token-2022/@solana/kit/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], "@solana-program/token-2022/@solana/kit/@solana/rpc/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw=="], @@ -5761,6 +6031,8 @@ "@solana-program/token-2022/@solana/kit/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana-program/token-2022/@solana/kit/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana-program/token-2022/@solana/sysvars/@solana/accounts/@solana/addresses": ["@solana/addresses@6.3.0", "", { "dependencies": { "@solana/assertions": "6.3.0", "@solana/codecs-core": "6.3.0", "@solana/codecs-strings": "6.3.0", "@solana/errors": "6.3.0", "@solana/nominal-types": "6.3.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-if/HpMyoRf4+6SPBjn7UB9LKLb3fDuJn1I502FAjpXHLY13NT7JGqPPiRZuXR3U4NkZTXw6zYksO3RPIFDaWVQ=="], "@solana-program/token-2022/@solana/sysvars/@solana/accounts/@solana/codecs-strings": ["@solana/codecs-strings@6.3.0", "", { "dependencies": { "@solana/codecs-core": "6.3.0", "@solana/codecs-numbers": "6.3.0", "@solana/errors": "6.3.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-Y2zu90M+iLLGNfuGS1B+G69exd9z8Xka6iyAXm4r2BebVl+wd0vjAtfcua/KGcwKT89bxrtjS3Oerc5rtxBeWg=="], @@ -5777,10 +6049,18 @@ "@solana-program/token/@solana/kit/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + "@solana-program/token/@solana/kit/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + + "@solana-program/token/@solana/kit/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@solana-program/token/@solana/kit/@solana/codecs/@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + "@solana-program/token/@solana/kit/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@solana-program/token/@solana/kit/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana-program/token/@solana/kit/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@solana-program/token/@solana/kit/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], "@solana-program/token/@solana/kit/@solana/rpc/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw=="], @@ -5809,12 +6089,22 @@ "@solana-program/token/@solana/kit/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana-program/token/@solana/kit/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana/client/@solana/kit/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + "@solana/client/@solana/kit/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + + "@solana/client/@solana/kit/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@solana/client/@solana/kit/@solana/codecs/@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + "@solana/client/@solana/kit/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@solana/client/@solana/kit/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana/client/@solana/kit/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@solana/client/@solana/kit/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], "@solana/client/@solana/kit/@solana/rpc/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw=="], @@ -5843,9 +6133,19 @@ "@solana/client/@solana/kit/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], - "@solana/codecs/@solana/codecs-numbers/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "@solana/client/@solana/kit/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@solana/connector/@solana/addresses/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/connector/@solana/addresses/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + + "@solana/connector/@solana/codecs/@solana/codecs-numbers/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], - "@solana/codecs/@solana/codecs-numbers/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana/connector/@solana/codecs/@solana/options/@solana/errors": ["@solana/errors@5.5.1", "", { "dependencies": { "chalk": "5.6.2", "commander": "14.0.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "errors": "bin/cli.mjs" } }, "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg=="], + + "@solana/connector/@solana/keys/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/connector/@solana/keys/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], "@solana/connector/@solana/kit/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], @@ -5881,56 +6181,106 @@ "@solana/connector/@solana/kit/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], - "@solana/instruction-plans/@solana/keys/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/connector/@solana/signers/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/connector/@solana/signers/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + + "@solana/connector/@solana/signers/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@solana/connector/@solana/transaction-messages/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/connector/@solana/transaction-messages/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + + "@solana/keychain-aws-kms/@solana/addresses/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/keychain-aws-kms/@solana/addresses/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + + "@solana/keychain-aws-kms/@solana/keys/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/keychain-aws-kms/@solana/keys/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + + "@solana/keychain-aws-kms/@solana/signers/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/keychain-aws-kms/@solana/signers/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + + "@solana/keychain-aws-kms/@solana/signers/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@solana/keychain-aws-kms/@solana/signers/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@solana/keychain-aws-kms/@solana/signers/@solana/transaction-messages/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], + + "@solana/keychain-aws-kms/@solana/signers/@solana/transaction-messages/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], + + "@solana/keychain-core/@solana/addresses/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/keychain-core/@solana/addresses/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + + "@solana/keychain-core/@solana/keys/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/keychain-core/@solana/keys/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - "@solana/instruction-plans/@solana/transaction-messages/@solana/addresses/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + "@solana/keychain-core/@solana/signers/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "@solana/instruction-plans/@solana/transaction-messages/@solana/addresses/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/keychain-core/@solana/signers/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - "@solana/instruction-plans/@solana/transactions/@solana/addresses/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + "@solana/keychain-core/@solana/signers/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], - "@solana/kit/@solana/addresses/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/keychain-core/@solana/signers/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], - "@solana/kit/@solana/keys/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/keychain-core/@solana/signers/@solana/transaction-messages/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], - "@solana/kit/@solana/transaction-confirmation/@solana/codecs-strings/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + "@solana/keychain-core/@solana/signers/@solana/transaction-messages/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], - "@solana/kit/@solana/transaction-confirmation/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/keychain-turnkey/@solana/addresses/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "@solana/plugin-interfaces/@solana/addresses/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/keychain-turnkey/@solana/addresses/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - "@solana/plugin-interfaces/@solana/keys/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/keychain-turnkey/@solana/keys/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "@solana/plugin-interfaces/@solana/signers/@solana/transaction-messages/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + "@solana/keychain-turnkey/@solana/keys/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - "@solana/plugin-interfaces/@solana/signers/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/keychain-turnkey/@solana/signers/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "@solana/plugin-interfaces/@solana/signers/@solana/transactions/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + "@solana/keychain-turnkey/@solana/signers/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - "@solana/plugin-interfaces/@solana/signers/@solana/transactions/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/keychain-turnkey/@solana/signers/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], - "@solana/plugin-interfaces/@solana/signers/@solana/transactions/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/keychain-turnkey/@solana/signers/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], - "@solana/program-client-core/@solana/addresses/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/keychain-turnkey/@solana/signers/@solana/transaction-messages/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], - "@solana/program-client-core/@solana/signers/@solana/keys/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + "@solana/keychain-turnkey/@solana/signers/@solana/transaction-messages/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], - "@solana/program-client-core/@solana/signers/@solana/keys/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/keychain-vault/@solana/addresses/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "@solana/program-client-core/@solana/signers/@solana/transaction-messages/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + "@solana/keychain-vault/@solana/addresses/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - "@solana/program-client-core/@solana/signers/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/keychain-vault/@solana/keys/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "@solana/program-client-core/@solana/signers/@solana/transactions/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + "@solana/keychain-vault/@solana/keys/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - "@solana/program-client-core/@solana/signers/@solana/transactions/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/keychain-vault/@solana/signers/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "@solana/program-client-core/@solana/signers/@solana/transactions/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + "@solana/keychain-vault/@solana/signers/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - "@solana/programs/@solana/addresses/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/keychain-vault/@solana/signers/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@solana/keychain-vault/@solana/signers/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@solana/keychain-vault/@solana/signers/@solana/transaction-messages/@solana/functional": ["@solana/functional@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w=="], + + "@solana/keychain-vault/@solana/signers/@solana/transaction-messages/@solana/rpc-types": ["@solana/rpc-types@5.5.1", "", { "dependencies": { "@solana/addresses": "5.5.1", "@solana/codecs-core": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1", "@solana/nominal-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA=="], + + "@solana/kit/@solana/transaction-confirmation/@solana/codecs-strings/@solana/codecs-core": ["@solana/codecs-core@6.8.0", "", { "dependencies": { "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-udFO8TrvzgROonwX3rY3E2SG675RehILNb4ZYcKlf1mL7vkDJ9bEJnBxi87AEwl8RWZFTl+MhT0MmrJnbpvdug=="], + + "@solana/kit/@solana/transaction-confirmation/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.8.0", "", { "dependencies": { "@solana/codecs-core": "6.8.0", "@solana/errors": "6.8.0" }, "peerDependencies": { "typescript": ">=5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-ebf4f1D19EAe0uhdUYOCEYnn5+EellsBxbJ42tM2yYEoIBVz5FoBBC0gSsq+UTNbQHFa7XagyBT3LewxXttiTQ=="], "@solana/react-hooks/@solana/kit/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + "@solana/react-hooks/@solana/kit/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@solana/react-hooks/@solana/kit/@solana/codecs/@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + "@solana/react-hooks/@solana/kit/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], "@solana/react-hooks/@solana/kit/@solana/rpc/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw=="], @@ -5959,9 +6309,7 @@ "@solana/react-hooks/@solana/kit/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], - "@solana/rpc-subscriptions-api/@solana/addresses/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], - - "@solana/rpc-subscriptions-api/@solana/keys/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/react-hooks/@solana/signers/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], "@solana/spl-token-group/@solana/codecs/@solana/codecs-core/@solana/errors": ["@solana/errors@2.0.0-rc.1", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.1.0" }, "peerDependencies": { "typescript": ">=5" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ=="], @@ -5987,12 +6335,22 @@ "@solana/transaction-confirmation/@solana/rpc/@solana/rpc-transport-http/undici-types": ["undici-types@7.22.0", "", {}, "sha512-RKZvifiL60xdsIuC80UY0dq8Z7DbJUV8/l2hOVbyZAxBzEeQU4Z58+4ZzJ6WN2Lidi9KzT5EbiGX+PI/UGYuRw=="], + "@solana/web3-compat/@solana/addresses/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/web3-compat/@solana/addresses/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana/web3-compat/@solana/kit/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + "@solana/web3-compat/@solana/kit/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@solana/web3-compat/@solana/kit/@solana/codecs/@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + "@solana/web3-compat/@solana/kit/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@solana/web3-compat/@solana/kit/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@solana/web3-compat/@solana/kit/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@solana/web3-compat/@solana/kit/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], "@solana/web3-compat/@solana/kit/@solana/rpc/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw=="], @@ -6021,6 +6379,8 @@ "@solana/web3-compat/@solana/kit/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@solana/web3-compat/@solana/kit/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@storybook/addon-interactions/@storybook/test/@vitest/expect/@vitest/utils": ["@vitest/utils@2.0.5", "", { "dependencies": { "@vitest/pretty-format": "2.0.5", "estree-walker": "^3.0.3", "loupe": "^3.1.1", "tinyrainbow": "^1.2.0" } }, "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ=="], "@storybook/addon-interactions/@storybook/test/@vitest/expect/chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], @@ -6227,10 +6587,18 @@ "@wallet-ui/core/@solana/kit/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + "@wallet-ui/core/@solana/kit/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + + "@wallet-ui/core/@solana/kit/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@wallet-ui/core/@solana/kit/@solana/codecs/@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + "@wallet-ui/core/@solana/kit/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@wallet-ui/core/@solana/kit/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@wallet-ui/core/@solana/kit/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@wallet-ui/core/@solana/kit/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], "@wallet-ui/core/@solana/kit/@solana/rpc/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw=="], @@ -6259,6 +6627,8 @@ "@wallet-ui/core/@solana/kit/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@wallet-ui/core/@solana/kit/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "hyperbet-avax-keeper/tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], "hyperbet-avax-keeper/tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], @@ -6415,10 +6785,258 @@ "hyperbet-evm-keeper/tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "hyperbet-solana-app/@solana/kit/@solana/accounts/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-app/@solana/kit/@solana/accounts/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/rpc-spec-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k4O7Kg0QfVyjUqQovL+WZJ1iuPzq0jiUDcWYgvzFjYVxQDVOIZmAol7yTvLEL4maVmf0tNFDsrDaB6t75MKRZA=="], + + "hyperbet-solana-app/@solana/kit/@solana/addresses/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + + "hyperbet-solana-app/@solana/kit/@solana/addresses/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-app/@solana/kit/@solana/addresses/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/addresses/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/codecs/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-app/@solana/kit/@solana/codecs/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + + "hyperbet-solana-app/@solana/kit/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-app/@solana/kit/@solana/codecs/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/codecs/@solana/options": ["@solana/options@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-jdZjSKGCQpsMFK+3CiUEI7W9iGsndi46R4Abk66ULNLDoMsjvfqNy8kqktm0TN0++EX8dKEecpFwxFaA4VlY5g=="], + + "hyperbet-solana-app/@solana/kit/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "hyperbet-solana-app/@solana/kit/@solana/instruction-plans/@solana/promises": ["@solana/promises@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n5rsA3YwOO2nUst6ghuVw6RSnuZQYqevqBKqVYbw11Z4XezsoQ6hb78opW3J9YNYapw9wLWy6tEfUsJjY+xtGw=="], + + "hyperbet-solana-app/@solana/kit/@solana/instructions/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-app/@solana/kit/@solana/keys/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + + "hyperbet-solana-app/@solana/kit/@solana/keys/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-app/@solana/kit/@solana/keys/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/keys/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/offchain-messages/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-app/@solana/kit/@solana/offchain-messages/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + + "hyperbet-solana-app/@solana/kit/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-app/@solana/kit/@solana/offchain-messages/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/offchain-messages/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/plugin-interfaces/@solana/rpc-spec": ["@solana/rpc-spec@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/rpc-spec-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k4O7Kg0QfVyjUqQovL+WZJ1iuPzq0jiUDcWYgvzFjYVxQDVOIZmAol7yTvLEL4maVmf0tNFDsrDaB6t75MKRZA=="], + + "hyperbet-solana-app/@solana/kit/@solana/plugin-interfaces/@solana/rpc-subscriptions-spec": ["@solana/rpc-subscriptions-spec@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/promises": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/subscribable": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Mi8g9rNS2lG7lyNkDhOVfQVfDC7hXKgH+BlI5qKGk+8cfyU7VDq6tVjDysu6kBWGOPHZxyCvcL6+xW/EkdVoAg=="], + + "hyperbet-solana-app/@solana/kit/@solana/program-client-core/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5ATQDwBVZMoenX5KS23uFswtaAGoaZB9TthzUXle3tkU3tOfgQTuEWEoqEBYc7ct0sK6LtyE1XXT/NP5YvAkkQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc/@solana/rpc-spec": ["@solana/rpc-spec@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/rpc-spec-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k4O7Kg0QfVyjUqQovL+WZJ1iuPzq0jiUDcWYgvzFjYVxQDVOIZmAol7yTvLEL4maVmf0tNFDsrDaB6t75MKRZA=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc/@solana/rpc-transformers": ["@solana/rpc-transformers@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kS0d+LuuSLfsod2cm2xp0mNj65PL1aomwu6VKtubmsdESwPXHIaI9XrpkPCBuhNSz1SwVp4OkfK5O/VOOHYHSw=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc/@solana/rpc-transport-http": ["@solana/rpc-transport-http@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/rpc-spec": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "undici-types": "^7.22.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-A3qgDGiUIHdtAfc2OyazlQa7IvRh+xyl0dmzaZlz4rY7Oc7Xk8jmXtaKGkgXihLyAK3oVSqSz5gn9yEfx55eXA=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-api/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-api/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-api/@solana/rpc-spec": ["@solana/rpc-spec@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/rpc-spec-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k4O7Kg0QfVyjUqQovL+WZJ1iuPzq0jiUDcWYgvzFjYVxQDVOIZmAol7yTvLEL4maVmf0tNFDsrDaB6t75MKRZA=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-api/@solana/rpc-transformers": ["@solana/rpc-transformers@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kS0d+LuuSLfsod2cm2xp0mNj65PL1aomwu6VKtubmsdESwPXHIaI9XrpkPCBuhNSz1SwVp4OkfK5O/VOOHYHSw=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-subscriptions/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5ATQDwBVZMoenX5KS23uFswtaAGoaZB9TthzUXle3tkU3tOfgQTuEWEoqEBYc7ct0sK6LtyE1XXT/NP5YvAkkQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-subscriptions/@solana/promises": ["@solana/promises@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n5rsA3YwOO2nUst6ghuVw6RSnuZQYqevqBKqVYbw11Z4XezsoQ6hb78opW3J9YNYapw9wLWy6tEfUsJjY+xtGw=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-subscriptions-api": ["@solana/rpc-subscriptions-api@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/keys": "6.5.0", "@solana/rpc-subscriptions-spec": "6.5.0", "@solana/rpc-transformers": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-smqNjT2C5Vf9nWGIwiYOLOP744gRWKi2i2g0i3ZVdsfoouvB0d/WTQ2bbWq47MrdV8FSuGnjAOM3dRIwYmYOWw=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-subscriptions-channel-websocket": ["@solana/rpc-subscriptions-channel-websocket@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/rpc-subscriptions-spec": "6.5.0", "@solana/subscribable": "6.5.0", "ws": "^8.19.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-xRKH3ZwIoV9Zua9Gp0RR0eL8lXNgx+iNIkE3F0ROlOzI48lt4lRJ7jLrHQCN3raVtkatFVuEyZ7e9eLHK9zhAw=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-subscriptions-spec": ["@solana/rpc-subscriptions-spec@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/promises": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/subscribable": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Mi8g9rNS2lG7lyNkDhOVfQVfDC7hXKgH+BlI5qKGk+8cfyU7VDq6tVjDysu6kBWGOPHZxyCvcL6+xW/EkdVoAg=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-transformers": ["@solana/rpc-transformers@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kS0d+LuuSLfsod2cm2xp0mNj65PL1aomwu6VKtubmsdESwPXHIaI9XrpkPCBuhNSz1SwVp4OkfK5O/VOOHYHSw=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-subscriptions/@solana/subscribable": ["@solana/subscribable@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Jmy2NYmQN68FsQzKJ5CY3qrxXBJdb5qtJKp8B4byPPO5liKNIsC59HpT0Tq8MCNSfBMmOkWF2rrVot2/g1iB1A=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-types/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-types/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-types/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/signers/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-app/@solana/kit/@solana/signers/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/sysvars/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-app/@solana/kit/@solana/sysvars/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + + "hyperbet-solana-app/@solana/kit/@solana/sysvars/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-app/@solana/kit/@solana/transaction-confirmation/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/transaction-confirmation/@solana/promises": ["@solana/promises@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n5rsA3YwOO2nUst6ghuVw6RSnuZQYqevqBKqVYbw11Z4XezsoQ6hb78opW3J9YNYapw9wLWy6tEfUsJjY+xtGw=="], + + "hyperbet-solana-app/@solana/kit/@solana/transaction-messages/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-app/@solana/kit/@solana/transaction-messages/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + + "hyperbet-solana-app/@solana/kit/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-app/@solana/kit/@solana/transaction-messages/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/transactions/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-app/@solana/kit/@solana/transactions/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + + "hyperbet-solana-app/@solana/kit/@solana/transactions/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-app/@solana/kit/@solana/transactions/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/transactions/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "hyperbet-solana-app/eslint/@eslint/config-array/@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], "hyperbet-solana-app/eslint/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + "hyperbet-solana-keeper/@solana/kit/@solana/accounts/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/accounts/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/rpc-spec-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k4O7Kg0QfVyjUqQovL+WZJ1iuPzq0jiUDcWYgvzFjYVxQDVOIZmAol7yTvLEL4maVmf0tNFDsrDaB6t75MKRZA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/addresses/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/addresses/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/addresses/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/addresses/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/codecs/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/codecs/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/codecs/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/codecs/@solana/options": ["@solana/options@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-data-structures": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/codecs-strings": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-jdZjSKGCQpsMFK+3CiUEI7W9iGsndi46R4Abk66ULNLDoMsjvfqNy8kqktm0TN0++EX8dKEecpFwxFaA4VlY5g=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/instruction-plans/@solana/promises": ["@solana/promises@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n5rsA3YwOO2nUst6ghuVw6RSnuZQYqevqBKqVYbw11Z4XezsoQ6hb78opW3J9YNYapw9wLWy6tEfUsJjY+xtGw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/instructions/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/keys/@solana/assertions": ["@solana/assertions@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rEAf40TtC9r6EtJFLe39WID4xnTNT6hdOVRfD1xDzmIQdVOyGgIbJGt2FAuB/uQDKLWneWMnvGDBim+K61Bljw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/keys/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/keys/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/keys/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/offchain-messages/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/offchain-messages/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/offchain-messages/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/offchain-messages/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/plugin-interfaces/@solana/rpc-spec": ["@solana/rpc-spec@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/rpc-spec-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k4O7Kg0QfVyjUqQovL+WZJ1iuPzq0jiUDcWYgvzFjYVxQDVOIZmAol7yTvLEL4maVmf0tNFDsrDaB6t75MKRZA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/plugin-interfaces/@solana/rpc-subscriptions-spec": ["@solana/rpc-subscriptions-spec@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/promises": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/subscribable": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Mi8g9rNS2lG7lyNkDhOVfQVfDC7hXKgH+BlI5qKGk+8cfyU7VDq6tVjDysu6kBWGOPHZxyCvcL6+xW/EkdVoAg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/program-client-core/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5ATQDwBVZMoenX5KS23uFswtaAGoaZB9TthzUXle3tkU3tOfgQTuEWEoqEBYc7ct0sK6LtyE1XXT/NP5YvAkkQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc/@solana/rpc-spec": ["@solana/rpc-spec@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/rpc-spec-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k4O7Kg0QfVyjUqQovL+WZJ1iuPzq0jiUDcWYgvzFjYVxQDVOIZmAol7yTvLEL4maVmf0tNFDsrDaB6t75MKRZA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc/@solana/rpc-transformers": ["@solana/rpc-transformers@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kS0d+LuuSLfsod2cm2xp0mNj65PL1aomwu6VKtubmsdESwPXHIaI9XrpkPCBuhNSz1SwVp4OkfK5O/VOOHYHSw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc/@solana/rpc-transport-http": ["@solana/rpc-transport-http@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/rpc-spec": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "undici-types": "^7.22.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-A3qgDGiUIHdtAfc2OyazlQa7IvRh+xyl0dmzaZlz4rY7Oc7Xk8jmXtaKGkgXihLyAK3oVSqSz5gn9yEfx55eXA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-api/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-api/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-api/@solana/rpc-spec": ["@solana/rpc-spec@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/rpc-spec-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-k4O7Kg0QfVyjUqQovL+WZJ1iuPzq0jiUDcWYgvzFjYVxQDVOIZmAol7yTvLEL4maVmf0tNFDsrDaB6t75MKRZA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-api/@solana/rpc-transformers": ["@solana/rpc-transformers@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kS0d+LuuSLfsod2cm2xp0mNj65PL1aomwu6VKtubmsdESwPXHIaI9XrpkPCBuhNSz1SwVp4OkfK5O/VOOHYHSw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-subscriptions/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-5ATQDwBVZMoenX5KS23uFswtaAGoaZB9TthzUXle3tkU3tOfgQTuEWEoqEBYc7ct0sK6LtyE1XXT/NP5YvAkkQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-subscriptions/@solana/promises": ["@solana/promises@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n5rsA3YwOO2nUst6ghuVw6RSnuZQYqevqBKqVYbw11Z4XezsoQ6hb78opW3J9YNYapw9wLWy6tEfUsJjY+xtGw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-subscriptions-api": ["@solana/rpc-subscriptions-api@6.5.0", "", { "dependencies": { "@solana/addresses": "6.5.0", "@solana/keys": "6.5.0", "@solana/rpc-subscriptions-spec": "6.5.0", "@solana/rpc-transformers": "6.5.0", "@solana/rpc-types": "6.5.0", "@solana/transaction-messages": "6.5.0", "@solana/transactions": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-smqNjT2C5Vf9nWGIwiYOLOP744gRWKi2i2g0i3ZVdsfoouvB0d/WTQ2bbWq47MrdV8FSuGnjAOM3dRIwYmYOWw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-subscriptions-channel-websocket": ["@solana/rpc-subscriptions-channel-websocket@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/rpc-subscriptions-spec": "6.5.0", "@solana/subscribable": "6.5.0", "ws": "^8.19.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-xRKH3ZwIoV9Zua9Gp0RR0eL8lXNgx+iNIkE3F0ROlOzI48lt4lRJ7jLrHQCN3raVtkatFVuEyZ7e9eLHK9zhAw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-subscriptions-spec": ["@solana/rpc-subscriptions-spec@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/promises": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/subscribable": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Mi8g9rNS2lG7lyNkDhOVfQVfDC7hXKgH+BlI5qKGk+8cfyU7VDq6tVjDysu6kBWGOPHZxyCvcL6+xW/EkdVoAg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-transformers": ["@solana/rpc-transformers@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0", "@solana/functional": "6.5.0", "@solana/nominal-types": "6.5.0", "@solana/rpc-spec-types": "6.5.0", "@solana/rpc-types": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-kS0d+LuuSLfsod2cm2xp0mNj65PL1aomwu6VKtubmsdESwPXHIaI9XrpkPCBuhNSz1SwVp4OkfK5O/VOOHYHSw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-subscriptions/@solana/subscribable": ["@solana/subscribable@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Jmy2NYmQN68FsQzKJ5CY3qrxXBJdb5qtJKp8B4byPPO5liKNIsC59HpT0Tq8MCNSfBMmOkWF2rrVot2/g1iB1A=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-types/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-types/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-types/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/signers/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/signers/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/sysvars/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/sysvars/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/sysvars/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transaction-confirmation/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transaction-confirmation/@solana/promises": ["@solana/promises@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n5rsA3YwOO2nUst6ghuVw6RSnuZQYqevqBKqVYbw11Z4XezsoQ6hb78opW3J9YNYapw9wLWy6tEfUsJjY+xtGw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transaction-messages/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transaction-messages/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transaction-messages/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transactions/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transactions/@solana/codecs-data-structures": ["@solana/codecs-data-structures@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Rxi5zVJ1YA+E6FoSQ7RHP+3DF4U7ski0mJ3H5CsYQP24QLRlBqWB3X6m2n9GHT5O3s49UR0sqeF4oyq0lF8bKw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transactions/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transactions/@solana/codecs-strings": ["@solana/codecs-strings@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/codecs-numbers": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": "^5.0.0" }, "optionalPeers": ["fastestsmallesttextencoderdecoder", "typescript"] }, "sha512-9TuQQxumA9gWJeJzbv1GUg0+o0nZp204EijX3efR+lgBOKbkU7W0UWp33ygAZ+RvWE+kTs48ePoYoJ7UHpyxkQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transactions/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + "hyperbet-solana-keeper/tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], "hyperbet-solana-keeper/tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], @@ -6543,6 +7161,14 @@ "vite-node/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], + "@codama/nodes-from-anchor/@solana/codecs/@solana/codecs-numbers/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@codama/nodes-from-anchor/@solana/codecs/@solana/codecs-numbers/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + + "@codama/nodes-from-anchor/@solana/codecs/@solana/options/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@codama/nodes-from-anchor/@solana/codecs/@solana/options/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@hyperbet/sdk/vitest/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], "@hyperbet/sdk/vitest/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], @@ -6595,10 +7221,18 @@ "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + + "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/codecs/@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/rpc/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw=="], @@ -6627,12 +7261,22 @@ "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@macalinao/coda-visitors/@solana-program/system/@solana/kit/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/accounts/@solana/rpc-spec": ["@solana/rpc-spec@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1", "@solana/rpc-spec-types": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q=="], + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/codecs/@solana/options": ["@solana/options@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/codecs-data-structures": "5.5.1", "@solana/codecs-numbers": "5.5.1", "@solana/codecs-strings": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A=="], + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/keys/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/offchain-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/rpc/@solana/fast-stable-stringify": ["@solana/fast-stable-stringify@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw=="], @@ -6661,8 +7305,14 @@ "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/rpc-types/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + "@macalinao/coda-visitors/@solana-program/token-2022/@solana/kit/@solana/transaction-messages/@solana/codecs-numbers": ["@solana/codecs-numbers@5.5.1", "", { "dependencies": { "@solana/codecs-core": "5.5.1", "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw=="], + + "@macalinao/coda-visitors/@solana/sysvars/@solana/accounts/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@macalinao/coda-visitors/@solana/sysvars/@solana/accounts/@solana/rpc-spec/@solana/rpc-spec-types": ["@solana/rpc-spec-types@5.5.1", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-6OFKtRpIEJQs8Jb2C4OO8KyP2h2Hy1MFhatMAoXA+0Ik8S3H+CicIuMZvGZ91mIu/tXicuOOsNNLu3HAkrakrw=="], + "@macalinao/coda-visitors/@solana/sysvars/@solana/rpc-types/@solana/addresses/@solana/assertions": ["@solana/assertions@5.5.1", "", { "dependencies": { "@solana/errors": "5.5.1" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q=="], + "@reown/appkit-ui/qrcode/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], "@reown/appkit-ui/qrcode/yargs/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], @@ -6689,9 +7339,15 @@ "@solana/client/@solana/kit/@solana/rpc/@solana/rpc-transport-http/undici-types": ["undici-types@7.22.0", "", {}, "sha512-RKZvifiL60xdsIuC80UY0dq8Z7DbJUV8/l2hOVbyZAxBzEeQU4Z58+4ZzJ6WN2Lidi9KzT5EbiGX+PI/UGYuRw=="], - "@solana/connector/@solana/kit/@solana/rpc/@solana/rpc-transport-http/undici-types": ["undici-types@7.22.0", "", {}, "sha512-RKZvifiL60xdsIuC80UY0dq8Z7DbJUV8/l2hOVbyZAxBzEeQU4Z58+4ZzJ6WN2Lidi9KzT5EbiGX+PI/UGYuRw=="], + "@solana/connector/@solana/codecs/@solana/codecs-numbers/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/connector/@solana/codecs/@solana/codecs-numbers/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + + "@solana/connector/@solana/codecs/@solana/options/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "@solana/program-client-core/@solana/signers/@solana/keys/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "@solana/connector/@solana/codecs/@solana/options/@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + + "@solana/connector/@solana/kit/@solana/rpc/@solana/rpc-transport-http/undici-types": ["undici-types@7.22.0", "", {}, "sha512-RKZvifiL60xdsIuC80UY0dq8Z7DbJUV8/l2hOVbyZAxBzEeQU4Z58+4ZzJ6WN2Lidi9KzT5EbiGX+PI/UGYuRw=="], "@solana/react-hooks/@solana/kit/@solana/rpc/@solana/rpc-transport-http/undici-types": ["undici-types@7.22.0", "", {}, "sha512-RKZvifiL60xdsIuC80UY0dq8Z7DbJUV8/l2hOVbyZAxBzEeQU4Z58+4ZzJ6WN2Lidi9KzT5EbiGX+PI/UGYuRw=="], @@ -6813,8 +7469,56 @@ "@wallet-ui/core/@solana/kit/@solana/rpc/@solana/rpc-transport-http/undici-types": ["undici-types@7.22.0", "", {}, "sha512-RKZvifiL60xdsIuC80UY0dq8Z7DbJUV8/l2hOVbyZAxBzEeQU4Z58+4ZzJ6WN2Lidi9KzT5EbiGX+PI/UGYuRw=="], + "hyperbet-solana-app/@solana/kit/@solana/accounts/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-app/@solana/kit/@solana/addresses/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-app/@solana/kit/@solana/keys/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-app/@solana/kit/@solana/plugin-interfaces/@solana/rpc-subscriptions-spec/@solana/promises": ["@solana/promises@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n5rsA3YwOO2nUst6ghuVw6RSnuZQYqevqBKqVYbw11Z4XezsoQ6hb78opW3J9YNYapw9wLWy6tEfUsJjY+xtGw=="], + + "hyperbet-solana-app/@solana/kit/@solana/plugin-interfaces/@solana/rpc-subscriptions-spec/@solana/subscribable": ["@solana/subscribable@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Jmy2NYmQN68FsQzKJ5CY3qrxXBJdb5qtJKp8B4byPPO5liKNIsC59HpT0Tq8MCNSfBMmOkWF2rrVot2/g1iB1A=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-api/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-api/@solana/rpc-transformers/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-transformers/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc/@solana/rpc-transformers/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-app/@solana/kit/@solana/rpc/@solana/rpc-transport-http/undici-types": ["undici-types@7.22.0", "", {}, "sha512-RKZvifiL60xdsIuC80UY0dq8Z7DbJUV8/l2hOVbyZAxBzEeQU4Z58+4ZzJ6WN2Lidi9KzT5EbiGX+PI/UGYuRw=="], + + "hyperbet-solana-app/@solana/kit/@solana/transaction-confirmation/@solana/codecs-strings/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-app/@solana/kit/@solana/transaction-confirmation/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "hyperbet-solana-app/eslint/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "hyperbet-solana-keeper/@solana/kit/@solana/accounts/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/addresses/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/keys/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/plugin-interfaces/@solana/rpc-subscriptions-spec/@solana/promises": ["@solana/promises@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n5rsA3YwOO2nUst6ghuVw6RSnuZQYqevqBKqVYbw11Z4XezsoQ6hb78opW3J9YNYapw9wLWy6tEfUsJjY+xtGw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/plugin-interfaces/@solana/rpc-subscriptions-spec/@solana/subscribable": ["@solana/subscribable@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Jmy2NYmQN68FsQzKJ5CY3qrxXBJdb5qtJKp8B4byPPO5liKNIsC59HpT0Tq8MCNSfBMmOkWF2rrVot2/g1iB1A=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-api/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-api/@solana/rpc-transformers/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc-subscriptions/@solana/rpc-transformers/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc/@solana/rpc-transformers/@solana/nominal-types": ["@solana/nominal-types@6.5.0", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-HngIM2nlaDPXk0EDX0PklFqpjGDKuOFnlEKS0bfr2F9CorFwiNhNjhb9lPH+FdgsogD1wJ8wgLMMk1LZWn5kgQ=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/rpc/@solana/rpc-transport-http/undici-types": ["undici-types@7.22.0", "", {}, "sha512-RKZvifiL60xdsIuC80UY0dq8Z7DbJUV8/l2hOVbyZAxBzEeQU4Z58+4ZzJ6WN2Lidi9KzT5EbiGX+PI/UGYuRw=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transaction-confirmation/@solana/codecs-strings/@solana/codecs-core": ["@solana/codecs-core@6.5.0", "", { "dependencies": { "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-Wb+YUj7vUKz5CxqZkrkugtQjxOP2fkMKnffySRlAmVAkpRnQvBY/2eP3VJAKTgDD4ru9xHSIQSpDu09hC/cQZg=="], + + "hyperbet-solana-keeper/@solana/kit/@solana/transaction-confirmation/@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@6.5.0", "", { "dependencies": { "@solana/codecs-core": "6.5.0", "@solana/errors": "6.5.0" }, "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-gU/7eYqD+zl2Kwzo7ctt7YHaxF+c3RX164F+iU4X02dwq8DGVcypp+kmEF1QaO6OiShtdryTxhL+JJmEBjhdfA=="], + "qrcode/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "qrcode/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], diff --git a/docs/enoomian-evm-standardization-decisions.md b/docs/enoomian-evm-standardization-decisions.md index 1234b18d..eb25dbe4 100644 --- a/docs/enoomian-evm-standardization-decisions.md +++ b/docs/enoomian-evm-standardization-decisions.md @@ -2,6 +2,8 @@ > **TL;DR:** Historical decision log for the `enoomian/prediction-market-sprint-base` EVM standardization work. Status: closed. Useful as architecture history, but not the current branch or launch authority. Current release truth lives in the launch execution plan, freeze tracker, and chain registry. +> Current open-work ownership now lives in [`docs/release/tracking-document-map.md`](release/tracking-document-map.md) and [`docs/release/github-project-production-backlog.md`](release/github-project-production-backlog.md). + This document records the authoritative keep/adapt/reject decisions for the `hyperbet-evm-parity-sweep` assimilation work as it has been standardized onto the local `enoomian/prediction-market-sprint-base`. diff --git a/docs/enoomian-next-phase-gates.md b/docs/enoomian-next-phase-gates.md index 0729a9d4..9b5e82a4 100644 --- a/docs/enoomian-next-phase-gates.md +++ b/docs/enoomian-next-phase-gates.md @@ -2,6 +2,8 @@ > **TL;DR:** Historical planning artifact from the `enoomian/prediction-market-sprint-base` multi-branch gate model. Status: closed. Do not use this as the current launch execution plan. The current source of truth is [`docs/release/pm-launch-execution-plan.md`](release/pm-launch-execution-plan.md) plus [`docs/release/prediction-market-launch-freeze-tracker.md`](release/prediction-market-launch-freeze-tracker.md). +> Current open-work ownership now lives in [`docs/release/tracking-document-map.md`](release/tracking-document-map.md) and [`docs/release/github-project-production-backlog.md`](release/github-project-production-backlog.md). + This document defines the post-sprint launch-completion work for `enoomian/prediction-market-sprint-base`. diff --git a/docs/enoomian-prediction-market-sprint.md b/docs/enoomian-prediction-market-sprint.md index 5aa0bac2..d75f4bc4 100644 --- a/docs/enoomian-prediction-market-sprint.md +++ b/docs/enoomian-prediction-market-sprint.md @@ -2,6 +2,8 @@ > **TL;DR:** Historical sprint tracker for `enoomian/prediction-market-sprint-base`. Status: closed. Keep this as branch history only. It is not the current phase-1 launch closeout plan. Use [`docs/release/pm-launch-execution-plan.md`](release/pm-launch-execution-plan.md) and [`docs/release/prediction-market-launch-freeze-tracker.md`](release/prediction-market-launch-freeze-tracker.md) for current status. +> Current open-work ownership now lives in [`docs/release/tracking-document-map.md`](release/tracking-document-map.md) and [`docs/release/github-project-production-backlog.md`](release/github-project-production-backlog.md). + This is the living gate tracker for the `enoomian/prediction-market-sprint-base` branch. Update this document every time the sprint base branch is pushed, or when the @@ -524,8 +526,7 @@ Delivered: Targeted verification: - `node --import tsx scripts/ci-env-audit.ts --target=ci-shared --json` -- `VITE_GAME_API_URL=https://api.hyperbet.win VITE_GAME_WS_URL=wss://api.hyperbet.win/ws VITE_SOLANA_CLUSTER=mainnet-beta VITE_USE_GAME_RPC_PROXY=true VITE_USE_GAME_EVM_RPC_PROXY=true VITE_BSC_CHAIN_ID=56 VITE_BSC_GOLD_CLOB_ADDRESS=0x443C09B1E7bb7bA3392b02500772B185654A6F33 VITE_BASE_CHAIN_ID=8453 VITE_BASE_GOLD_CLOB_ADDRESS=0xb8c66D6895Bafd1B0027F2c0865865043064437C node --import tsx scripts/ci-env-audit.ts --target=pages:solana --json` -- `VITE_GAME_API_URL=https://api.hyperbet.win VITE_GAME_WS_URL=wss://api.hyperbet.win/ws VITE_SOLANA_CLUSTER=mainnet-beta VITE_USE_GAME_RPC_PROXY=true VITE_USE_GAME_EVM_RPC_PROXY=true VITE_BSC_CHAIN_ID=56 VITE_BSC_GOLD_CLOB_ADDRESS=0x443C09B1E7bb7bA3392b02500772B185654A6F33 VITE_BASE_CHAIN_ID=8453 VITE_BASE_GOLD_CLOB_ADDRESS=0xb8c66D6895Bafd1B0027F2c0865865043064437C node --import tsx scripts/ci-env-audit.ts --target=pages:bsc --json` +- `VITE_GAME_API_URL=https://api.hyperbet.win VITE_GAME_WS_URL=wss://api.hyperbet.win/ws VITE_SOLANA_CLUSTER=mainnet-beta VITE_USE_GAME_RPC_PROXY=true VITE_USE_GAME_EVM_RPC_PROXY=true VITE_BSC_CHAIN_ID=56 VITE_BSC_GOLD_CLOB_ADDRESS=0x443C09B1E7bb7bA3392b02500772B185654A6F33 VITE_BASE_CHAIN_ID=8453 VITE_BASE_GOLD_CLOB_ADDRESS=0xb8c66D6895Bafd1B0027F2c0865865043064437C node --import tsx scripts/ci-env-audit.ts --target=pages:legacy --json` - `CI_AUDIT_REQUIRE_RUNTIME=true HYPERBET_KEEPER_URL=https://api.hyperbet.win RAILWAY_PROJECT_ID=test RAILWAY_PRODUCTION_ENVIRONMENT_ID=test RAILWAY_KEEPER_SERVICE_ID=test SOLANA_RPC_URL=https://api.mainnet-beta.solana.com node --import tsx scripts/ci-env-audit.ts --target=keeper:solana --json` - `CI_AUDIT_REQUIRE_RUNTIME=true HYPERBET_KEEPER_URL=https://bsc-api.hyperbet.win RAILWAY_PROJECT_ID=test RAILWAY_PRODUCTION_ENVIRONMENT_ID=test RAILWAY_KEEPER_SERVICE_ID=test BSC_RPC_URL=https://bsc-dataseed.binance.org node --import tsx scripts/ci-env-audit.ts --target=keeper:bsc --json` - `CI_AUDIT_REQUIRE_RUNTIME=true HYPERBET_KEEPER_URL=https://avax-api.hyperbet.win RAILWAY_PROJECT_ID=test RAILWAY_PRODUCTION_ENVIRONMENT_ID=test RAILWAY_KEEPER_SERVICE_ID=test AVAX_RPC_URL=https://api.avax.network/ext/bc/C/rpc node --import tsx scripts/ci-env-audit.ts --target=keeper:avax --json` diff --git a/docs/enoomian-stream-channel-architecture.md b/docs/enoomian-stream-channel-architecture.md new file mode 100644 index 00000000..6cb79e7f --- /dev/null +++ b/docs/enoomian-stream-channel-architecture.md @@ -0,0 +1,422 @@ +# Enoomian Stream Channel Architecture + +Date: April 8, 2026 +Status: Proposed target-state architecture and rollout plan +Scope: Hyperscapes source and delivery plane, Hyperbet keeper authority plane, Hyperbet player and betting consumers + +## Executive Summary + +Enoomian staging should operate as one stable broadcast channel per environment, not as one live stream resource per duel. + +The current staging recovery proved the right shape: + +- source capture and render truth can remain healthy even when a delivery provider fails +- the authority layer can expose stable `sourceRuntime`, `deliveryHealth`, `publicReadiness`, and `canonicalDestination` independently +- bet pages do not need to care whether canonical playback is currently served by Cloudflare or self-hosted HLS + +The long-term design should therefore be: + +- one stable canonical channel per environment +- one provider adapter layer beneath that channel +- duel boundaries represented as application metadata, not transport resets +- per-duel archives or clips derived from the stable channel after the fact +- automatic provider failover, with the client contract remaining unchanged + +For staging today, self-hosted HLS on the GPU host is the working canonical rail. Cloudflare ingest remains a provider-specific investigation track and should not block the broader architecture. + +## Current Findings + +### What is healthy + +- The GPU host source worker can render and encode continuously. +- The local HLS output on the GPU host is healthy. +- Betting bootstrap and keeper state can now reflect canonical playback truth correctly. +- The player stack can handle canonical playback from either Cloudflare or self-hosted HLS. + +### What is not resolved + +- Cloudflare ingest from the GPU host remains unreliable or rejected. +- We do not yet have a proven Cloudflare-side reason code for the failure. +- Staging recovered only after promoting self-hosted HLS as the canonical rail. + +### Architectural implication + +This is now clearly a delivery-provider problem, not a product-state problem. The system should be designed so provider failure degrades canonical delivery provider selection, not duel/session authority. + +## Decision + +### Adopt this model + +Use one stable canonical channel per environment and reuse it across many duels and many broadcasts. + +### Do not adopt this model + +Do not create and rotate a new Cloudflare live input for every duel. + +### Why + +- Stable playback URLs are better for betting clients, embeds, player caches, and authority state. +- Reusing a stable channel reduces secret churn, resource churn, cleanup churn, and race conditions. +- Duel transitions should not require transport teardown and re-bootstrap. +- Recording and replay concerns are downstream media concerns, not reasons to recreate the live transport resource. + +## Target-State System Model + +### 1. Channel Layer + +Each environment owns one canonical broadcast channel: + +- `enoomian-staging` +- `enoomian-production` + +The channel is the public contract consumed by: + +- keeper bootstrap +- keeper event feeds +- bet pages +- spectator players + +The channel contract exposes: + +- `channel.id` +- `channel.mode` +- `channel.activeDuelId` +- `channel.activeDuelKey` +- `channel.publicPlaybackUrl` +- `channel.publicReadiness` +- `channel.canonicalDestinationId` +- `channel.fallbackDestinationId` +- `channel.destinations` + +### 2. Source Plane + +The source plane is responsible only for producing video reliably. + +Responsibilities: + +- launch Chromium against the stream page +- capture compositor frames +- encode video and audio +- emit source diagnostics +- maintain local HLS output when configured + +Source-plane truth is represented by: + +- `sourceRuntime` +- `rendererHealth` +- frame metrics +- capture diagnostics + +Source truth must remain separate from provider truth. + +### 3. Delivery Adapter Plane + +The delivery adapter plane converts source output into one or more destinations. + +Providers: + +- `cloudflare_stream` +- `self_hls` + +Transport examples: + +- `srt` +- `rtmps` +- `hls` +- `llhls` + +The adapter plane owns: + +- provider-specific config +- ingest URL construction +- playback probe logic +- provider-specific lifecycle checks +- failover decisions + +Clients must not derive behavior from ingest transport. Clients care only about canonical playback. + +### 4. Authority Plane + +The authority plane merges source truth and delivery truth into stable client-facing truth. + +It must expose, at minimum: + +- `rendererHealth` +- `sourceRuntime` +- `deliveryHealth` +- `publicReadiness` +- `canonicalDestination` +- `fallbackDestination` + +Rules: + +- `sourceRuntime` answers "is the source worker alive and producing frames?" +- `deliveryHealth` answers "is the selected canonical provider producing playable output?" +- `publicReadiness` answers "should betting clients treat the stream as live and usable?" +- `canonicalDestination` answers "what playback rail is authoritative right now?" + +### 5. Consumer Plane + +Consumers include: + +- Solana keeper +- BSC keeper +- Hyperbet UI +- HLS embed/player + +Consumers must: + +- trust `canonicalDestination` and `publicReadiness` +- avoid reverse-engineering transport state +- avoid inventing parallel authority logic + +## Duel Lifecycle Model + +Duels are logical segments inside one stable channel, not separate transport instances. + +Each duel should create: + +- a duel metadata record +- a duel timing window +- a duel replay index entry +- optional duel clip jobs + +Each duel should not create: + +- a new live input resource +- a new public playback URL +- a new player bootstrap shape + +### Archive model + +Recommended archive approach: + +- live channel remains continuous +- duel boundaries are marked by metadata +- per-duel clips or recordings are cut downstream +- retention applies to clips and archives, not to the channel identity + +This allows: + +- stable betting playback +- replay extraction by duel +- simpler operations + +## Failover Model + +### Canonical provider policy + +For each environment there is one canonical provider at a time. + +Candidate providers: + +- primary: Cloudflare Stream +- fallback: self-hosted HLS + +For staging, until Cloudflare is proven reliable, reverse that order: + +- primary: self-hosted HLS +- secondary research provider: Cloudflare Stream + +### Failover trigger + +Fail over when the canonical provider is not producing fresh playable output within threshold. + +Signals: + +- playback manifest probe failure +- provider lifecycle disconnected state +- repeated ingest write failures +- stale media sequence + +### Failover guarantee + +When provider failover occurs: + +- `publicReadiness` should remain true if fallback is healthy +- `canonicalDestination` should change provider and playback URL +- duel/session state must not reset +- keepers must continue emitting one stable authority contract + +### Failback policy + +Do not immediately fail back on one successful probe. + +Require: + +- provider healthy for soak window +- consecutive successful probes +- no fresh fatal sender errors + +## Cloudflare Position In The Architecture + +Cloudflare should be treated as a delivery provider, not as the architecture itself. + +It remains valuable for: + +- managed public delivery +- LL-HLS playback +- clip and recording workflows +- CDN edge distribution + +It should not be allowed to control: + +- duel truth +- source truth +- keeper authority shape +- betting session continuity + +## Provider Contract Requirements + +Every provider implementation must satisfy the same normalized destination contract: + +- `id` +- `name` +- `role` +- `provider` +- `transport` +- `playbackUrl` +- `ingestUrl` +- `connected` +- `transportHealthy` +- `playbackReady` +- `manifestStatus` +- `lastError` +- `updatedAt` + +Normalization requirements: + +- provider-specific states must be mapped into this contract +- public routes must expose normalized truth, not raw provider internals +- raw provider telemetry may remain available for diagnostics only + +## Configuration Model + +### Desired env shape + +Provider config should be typed and separated: + +- channel-level settings +- provider-level settings +- source-level settings + +Examples: + +- `STREAM_DELIVERY_MODE` +- `STREAM_DELIVERY_PROVIDER` +- `STREAM_PLAYBACK_URL` +- `STREAM_PLAYBACK_HLS_URL` +- `STREAM_PLAYBACK_LLHLS_URL` +- `STREAM_INGEST_TRANSPORT` +- `STREAM_INGEST_SRT_URL` +- `STREAM_INGEST_SRT_STREAM_ID` +- `STREAM_INGEST_SRT_PASSPHRASE` +- `STREAM_INGEST_RTMPS_URL` +- `STREAM_INGEST_STREAM_KEY` + +### Configuration rules + +- Keep input identifiers separate from shared secrets. +- Do not build provider secrets by concatenating values inside env files. +- Keep one personal-staging env source of truth for enoomian staging. +- Do not let official GitHub staging envs leak into the enoomian personal-staging workflow. + +## Observability Requirements + +### Minimum required metrics + +- source frame cadence +- encode cadence +- dropped frames +- worker heartbeat age +- render tick freshness +- visual change age +- HLS manifest updated-at +- media sequence advancement +- provider playback probe status +- first and last fatal write error + +### Minimum required state probes + +- source runtime route +- capture status route +- canonical playback probe +- keeper `/api/streaming/state` +- betting bootstrap `/api/internal/bet-sync/state` + +### Required dashboards later + +- canonical provider health +- source runtime health +- failover events +- duel-to-archive clip completion + +## Rollout Plan + +### Phase 0: Lock in current staging recovery + +- Keep enoomian staging on personal self-hosted HLS. +- Treat self-HLS as canonical for staging until Cloudflare is proven reliable. +- Do not revert staging back to Cloudflare just because the code path exists. + +### Phase 1: Normalize canonical truth everywhere + +- Remove remaining route assumptions that canonical means external Cloudflare. +- Make `streaming.ts` fully self-HLS aware in canonical status derivation. +- Ensure all public and internal routes agree when self-HLS is canonical. + +### Phase 2: Formalize provider failover + +- Add a provider selector state machine. +- Support explicit primary and fallback ordering by environment. +- Add failover and failback soak thresholds. + +### Phase 3: Archive and clip architecture + +- Define duel-boundary metadata emission +- create per-duel archive jobs +- create replay lookup by duel ID and duel key + +### Phase 4: Cloudflare provider investigation + +- restore working personal-account Cloudflare API auth +- inspect live input lifecycle and failure telemetry +- create a fresh test live input +- rerun control publishes from: + - GPU host + - second machine + - OBS +- upgrade host FFmpeg and rerun controls + +### Phase 5: Production readiness + +- keep Cloudflare only if it passes soak and failover tests +- otherwise ship production with self-HLS fallback mandatory + +## Acceptance Criteria + +The architecture is complete when all of the following are true: + +- betting clients remain live through provider swaps +- duel transitions do not recreate the public playback contract +- one canonical channel supports many duels +- per-duel replay artifacts exist without per-duel live input churn +- `sourceRuntime`, `deliveryHealth`, and `publicReadiness` remain distinct +- staging and production envs can choose different provider priority safely + +## Non-Goals + +This plan does not require: + +- per-duel live input creation +- deleting old live inputs after two newer duels +- client awareness of provider-specific transport +- perfect Cloudflare diagnosis before finishing the architecture + +## Immediate Next Actions + +1. Keep enoomian staging on self-HLS until Cloudflare is debugged. +2. Patch any remaining route-level canonical status that still assumes Cloudflare. +3. Add a formal provider-priority config and failover state machine. +4. Open a focused Cloudflare reliability investigation using the personal account only. +5. Design duel archive and clip outputs as downstream media products, not live transport resources. diff --git a/docs/hyperbet-production-deploy.md b/docs/hyperbet-production-deploy.md index fb630d87..bc325412 100644 --- a/docs/hyperbet-production-deploy.md +++ b/docs/hyperbet-production-deploy.md @@ -1,274 +1,159 @@ -# Hyperbet Production Deploy (Cloudflare + Railway) - -> **TL;DR:** This is the production and staged topology for the phase-1 launch product. Launch-blocking chains are `Solana`, `BSC`, and `AVAX`; `Base` remains a non-blocking add-chain lane. The deployment topology is in place, but as of 2026-03-25 real staged proof and soak remain blocked because GitHub does not yet have a provisioned `staging` environment or the required `HYPERBET_*_STAGING_*` vars and secrets. - -This is the recommended production topology for the Hyperbet stack in this repo. - -Operator runbooks are in [docs/runbooks/README.md](runbooks/README.md). - -- Primary frontend (`packages/hyperbet-solana/app`): Cloudflare Pages (`hyperbet.win`) -- Secondary frontends (`packages/hyperbet-bsc/app`, `packages/hyperbet-avax/app`): dedicated Pages project or subdomain per chain -- Primary betting API (`packages/hyperbet-solana/keeper`): Railway -- Secondary betting APIs (`packages/hyperbet-bsc/keeper`, `packages/hyperbet-avax/keeper`): dedicated Railway services if you split by chain -- Live duel/stream source (`packages/server` or Vast duel stack): separate upstream that the keeper polls -- DDoS/WAF/edge cache: Cloudflare proxy in front of the betting API -- Contracts/state: Solana + EVM (configured by env vars below, proxied server-side) - -Production rollout is still blocked until canonical launch-chain deployment -truth exists in the shared chain registry for `Solana`, `BSC`, and `AVAX`, -staged proof artifacts are captured for the target environment, and the real -governance and operator wallets are provisioned. - -## Staging Rail - -The repo also supports a manual staging rail for Solana, BSC, and AVAX without -changing the production topology: - -- staged Solana Pages + staged Solana keeper -- staged BSC Pages + staged BSC keeper -- staged AVAX Pages + staged AVAX keeper -- external staged duel/stream source - -Manual staging deploys use the same workflows as production through -`workflow_dispatch`: - -- `Deploy Hyperbet Solana Pages` -- `Deploy Hyperbet Solana Keeper` -- `Deploy Hyperbet BSC Pages` -- `Deploy Hyperbet BSC Keeper` -- `Deploy Hyperbet AVAX Pages` -- `Deploy Hyperbet AVAX Keeper` - -Select `environment=staging` when dispatching the relevant workflow. - -Current audited GitHub state: - -- repo-level deploy and testnet secrets exist -- no GitHub `staging` environment exists yet -- no `HYPERBET_*_STAGING_*` vars or secrets are provisioned yet -- BSC/AVAX auto-deploy flags can now be split per surface with `HYPERBET_*_PAGES_DEPLOY_ENABLED` and `HYPERBET_*_KEEPER_DEPLOY_ENABLED`; the older shared `HYPERBET_*_DEPLOY_ENABLED` vars remain as a fallback - -That means the code path is ready, but staged proof and staged soak remain -operationally blocked until staging is provisioned. - -Required staging vars are: - -- `HYPERBET_SOLANA_PAGES_STAGING_PROJECT_NAME` -- `HYPERBET_SOLANA_PAGES_STAGING_URL` -- `HYPERBET_SOLANA_KEEPER_STAGING_URL` -- `HYPERBET_SOLANA_KEEPER_STAGING_WS_URL` -- `HYPERBET_SOLANA_RAILWAY_STAGING_PROJECT_ID` -- `HYPERBET_SOLANA_RAILWAY_STAGING_ENVIRONMENT_ID` -- `HYPERBET_SOLANA_RAILWAY_STAGING_KEEPER_SERVICE_ID` -- `HYPERBET_BSC_PAGES_STAGING_PROJECT_NAME` -- `HYPERBET_BSC_PAGES_STAGING_URL` -- `HYPERBET_BSC_KEEPER_STAGING_URL` -- `HYPERBET_BSC_KEEPER_STAGING_WS_URL` -- `HYPERBET_BSC_RAILWAY_STAGING_PROJECT_ID` -- `HYPERBET_BSC_RAILWAY_STAGING_ENVIRONMENT_ID` -- `HYPERBET_BSC_RAILWAY_STAGING_KEEPER_SERVICE_ID` -- `HYPERBET_AVAX_PAGES_STAGING_PROJECT_NAME` -- `HYPERBET_AVAX_PAGES_STAGING_URL` -- `HYPERBET_AVAX_KEEPER_STAGING_URL` -- `HYPERBET_AVAX_KEEPER_STAGING_WS_URL` -- `HYPERBET_AVAX_RAILWAY_STAGING_PROJECT_ID` -- `HYPERBET_AVAX_RAILWAY_STAGING_ENVIRONMENT_ID` -- `HYPERBET_AVAX_RAILWAY_STAGING_KEEPER_SERVICE_ID` -- `HYPERBET_AVAX_STAGING_CHAIN_ID` -- `HYPERBET_AVAX_STAGING_GOLD_CLOB_ADDRESS` - -Required staged proof vars/secrets used by the launch-scope proof rail: - -- `HYPERBET_STAGED_PROOF_DUEL_ID` -- `HYPERBET_STAGED_PROOF_DUEL_KEY` -- `HYPERBET_SOLANA_STAGING_CLUSTER` -- `HYPERBET_SOLANA_STAGING_RPC_URL` -- `HYPERBET_SOLANA_STAGING_GOLD_CLOB_PROGRAM_ID` -- `HYPERBET_SOLANA_STAGING_GOLD_AMM_PROGRAM_ID` -- `HYPERBET_SOLANA_STAGING_GOLD_PERPS_PROGRAM_ID` -- `HYPERBET_SOLANA_STAGING_STREAM_PUBLISH_KEY` -- `HYPERBET_SOLANA_STAGING_ORACLE_AUTHORITY_KEYPAIR` -- `HYPERBET_SOLANA_STAGING_CANARY_KEYPAIR` -- `HYPERBET_BSC_STAGING_RPC_URL` -- `HYPERBET_BSC_STAGING_REPORTER_PRIVATE_KEY` -- `HYPERBET_BSC_STAGING_CANARY_PRIVATE_KEY` -- `HYPERBET_BSC_STAGING_ADMIN_PRIVATE_KEY` -- `HYPERBET_BSC_STAGING_MARKET_OPERATOR_PRIVATE_KEY` -- `HYPERBET_BSC_STAGING_DUEL_ORACLE_ADDRESS` -- `HYPERBET_BSC_STAGING_GOLD_CLOB_ADDRESS` -- `HYPERBET_BSC_STAGING_GOLD_AMM_ROUTER_ADDRESS` -- `HYPERBET_BSC_STAGING_MUSD_TOKEN_ADDRESS` -- `HYPERBET_BSC_STAGING_GOLD_TOKEN_ADDRESS` -- `HYPERBET_BSC_STAGING_SKILL_ORACLE_ADDRESS` -- `HYPERBET_BSC_STAGING_PERP_ENGINE_ADDRESS` -- `HYPERBET_BSC_STAGING_STREAM_PUBLISH_KEY` -- `HYPERBET_AVAX_STAGING_RPC_URL` -- `HYPERBET_AVAX_STAGING_REPORTER_PRIVATE_KEY` -- `HYPERBET_AVAX_STAGING_CANARY_PRIVATE_KEY` -- `HYPERBET_AVAX_STAGING_ADMIN_PRIVATE_KEY` -- `HYPERBET_AVAX_STAGING_MARKET_OPERATOR_PRIVATE_KEY` -- `HYPERBET_AVAX_STAGING_DUEL_ORACLE_ADDRESS` -- `HYPERBET_AVAX_STAGING_GOLD_CLOB_ADDRESS` -- `HYPERBET_AVAX_STAGING_GOLD_AMM_ROUTER_ADDRESS` -- `HYPERBET_AVAX_STAGING_MUSD_TOKEN_ADDRESS` -- `HYPERBET_AVAX_STAGING_GOLD_TOKEN_ADDRESS` -- `HYPERBET_AVAX_STAGING_SKILL_ORACLE_ADDRESS` -- `HYPERBET_AVAX_STAGING_PERP_ENGINE_ADDRESS` -- `HYPERBET_AVAX_STAGING_STREAM_PUBLISH_KEY` -- `HYPERBET_AVAX_RAILWAY_STAGING_PROJECT_ID` -- `HYPERBET_AVAX_RAILWAY_STAGING_ENVIRONMENT_ID` -- `HYPERBET_AVAX_RAILWAY_STAGING_KEEPER_SERVICE_ID` - -AVAX rollout remains blocked until canonical deployment truth exists in the shared chain registry and the effective AVAX wallet/signer set is in place. The staging/prod rail is present so proof and release packaging can use one consistent contract once those addresses are committed. - -## 1) Deploy the keeper to Railway - -From repo root, deploy the keeper service path: - -```bash -railway up packages/hyperbet-solana --path-as-root -s gold-betting-keeper -``` - -Use `packages/hyperbet-solana/railway.json`. - -Set these Railway variables at minimum: - -- `NODE_ENV=production` -- `PORT=8080` (or let Railway inject its port if you proxy through the service domain) -- `STREAM_STATE_SOURCE_URL=https://your-stream-source.example/api/streaming/state` -- `STREAM_STATE_SOURCE_BEARER_TOKEN=...` if the upstream streaming state is protected -- `ARENA_EXTERNAL_BET_WRITE_KEY=...` -- `STREAM_PUBLISH_KEY=...` if you use `/api/streaming/state/publish` -- `SOLANA_CLUSTER=mainnet-beta` -- `SOLANA_RPC_URL=...` -- `BSC_RPC_URL=...` -- `BSC_GOLD_CLOB_ADDRESS=...` -- `BASE_RPC_URL=...` -- `BASE_GOLD_CLOB_ADDRESS=...` -- `AVAX_RPC_URL=...` for AVAX keeper/runtime support after canonical registry values exist -- `BIRDEYE_API_KEY=...` if token-price proxying is enabled - -Persistence: - -- The keeper defaults to a local SQLite file (`KEEPER_DB_PATH=./keeper.sqlite`). -- On Railway that file is ephemeral unless you attach a persistent volume or move the keeper state to an external database. -- Do not treat points history, referrals, or oracle history as durable unless persistence is configured explicitly. - -Notes: - -- The keeper serves the Pages app's read/write betting APIs. It is not the same process as the Hyperscape duel server. -- The keeper also proxies Solana and EVM JSON-RPC for the public app. Keep provider-keyed RPC URLs on Railway, not in Cloudflare Pages build vars. -- The keeper now keeps a short in-memory cache for read-only RPC and Birdeye proxy traffic. Tune it with `RPC_PROXY_CACHE_MAX_ENTRIES`, `RPC_PROXY_CACHE_MAX_PAYLOAD_BYTES`, and `BIRDEYE_PRICE_CACHE_TTL_MS` if needed. -- The keeper will return boot fallback duel data until `STREAM_STATE_SOURCE_URL` is set and the upstream duel server responds. -- The autonomous keeper bot also needs a funded signer wallet on Solana to create/resolve markets in production. - -## 2) Deploy the live duel server / stream source - -This can be the Railway `hyperscape` service or the Vast.ai duel stack. It must expose: - -- `/api/streaming/state` -- `/api/streaming/duel-context` -- `/api/streaming/rtmp/status` -- `/live/stream.m3u8` - -If you run the Vast.ai stack, verify it before pointing the keeper at it: - -```bash -./scripts/check-streaming-status.sh http://127.0.0.1:5555 -``` - -## 3) Put the betting API behind Cloudflare - -1. Create `api.yourdomain.com` in Cloudflare DNS and point it to the keeper Railway target. -2. Enable Cloudflare proxy (orange cloud) for `api.yourdomain.com`. -3. Add WAF rate-limit rules: -- `POST /api/arena/bet/record-external` -- `POST /api/arena/deposit/ingest` -- `/api/arena/points/*` -4. Keep the direct Railway URL private if you introduce a public API domain. - -## 4) Deploy frontend to Cloudflare Pages - -Project root: - -- `packages/hyperbet-solana/app` - -Build/output: - -- Build command: `bun install && bun run build --mode mainnet-beta` -- Output directory: `dist` +# Hyperbet Deploy Topology -Frontend env vars (Cloudflare Pages): +This document defines the intended deploy model after the multicast broadcast +refactor. -- `VITE_GAME_API_URL=https://api.yourdomain.com` -- `VITE_GAME_WS_URL=wss://api.yourdomain.com/ws` if the keeper exposes websocket features you use -- `VITE_SOLANA_CLUSTER=mainnet-beta` (or testnet/devnet) -- `VITE_USE_GAME_RPC_PROXY=true` -- `VITE_USE_GAME_EVM_RPC_PROXY=true` -- `VITE_BSC_GOLD_CLOB_ADDRESS` / `VITE_BASE_GOLD_CLOB_ADDRESS` -- `VITE_BSC_GOLD_TOKEN_ADDRESS` / `VITE_BASE_GOLD_TOKEN_ADDRESS` -- `VITE_STREAM_SOURCES=https://your-hls-or-embed-source,...` +## Product Topology -Do not set provider-keyed values in any `VITE_*RPC_URL` variable for production builds. The betting app build fails intentionally if a public RPC URL looks like a Helius / Alchemy / Infura / QuickNode / dRPC secret endpoint. +- Frontends: Cloudflare Pages per chain surface +- Keepers: Railway per chain surface +- Duel and broadcast authority: Hyperscapes +- Canonical betting destination: Cloudflare Stream +- Emergency fallback destination: self-hosted HLS from the Hyperscapes GPU host +- Mirror destinations: Twitch, Kick, YouTube, and custom restream outputs -Do not treat `packages/hyperbet-avax/deployments/contracts.json` as production deployment truth. The shared chain registry is the canonical production source, and AVAX rollout must stay blocked until that registry is populated with real addresses. +The product operates as one always-on channel. Duel transitions change the +content carried by the channel, not the identity of the channel itself. -Cloudflare Pages headers/SPA rules are already added in: +## Ownership Model -- `packages/hyperbet-solana/app/public/_headers` -- `packages/hyperbet-solana/app/public/_redirects` +Hyperscapes is the sole writer of stream truth. It authors: -Deployment metadata: +- `channel` +- `channel.publicReadiness` +- `channel.destinations` +- `canonicalDestination` +- `fallbackDestination` -- `build-info.json` is emitted into `dist/` on every build and should be served with `Cache-Control: no-store`. -## 5) Verify production +Keepers and frontends consume that contract unchanged. They do not reconstruct +canonical playback truth from local `STREAM_PLAYBACK_*` env vars, and they do +not promote fallback rails on their own. -Health: +Keepers may still poll renderer-health endpoints for additive diagnostics, but +those probes are not authoritative for canonical playback routing. -- `https://api.yourdomain.com/status` -- `https://bet.yourdomain.com` -- `https://api.yourdomain.com/api/streaming/state` -- `https://api.yourdomain.com/api/streaming/duel-context` -- `https://api.yourdomain.com/api/perps/markets` -- `https://api.yourdomain.com/api/proxy/evm/rpc?chain=bsc` (POST JSON-RPC smoke test) -- `https://bet.yourdomain.com/build-info.json` +## Session Contract Hyperbet Consumes -Repo-backed checks from repo root: +Hyperbet surfaces should read the server-authored v2 session/feed contract: -```bash -./scripts/check-streaming-status.sh https://your-stream-source.example -bun run --cwd packages/hyperbet-solana build:mainnet -``` +- `channel.id` +- `channel.mode` +- `channel.presentationDelayMs` +- `channel.canonicalDestinationId` +- `channel.fallbackDestinationId` +- `channel.destinations[]` +- `channel.publicPlaybackUrl` +- `channel.publicReadiness` +- `canonicalDestination` +- `fallbackDestination` -## 6) Run staged live proof +Legacy `delivery` and `deliveryHealth` fields remain compatibility shims during +the migration window only. New work should not depend on them. -Use the manual `Staged Live Proof` workflow or the repo wrapper: +## Destination Roles -```bash -bun run staged:proof -- --mode=read-only --target=all -bun run staged:proof -- --mode=canary-write --target=solana -bun run staged:proof -- --mode=canary-write --target=bsc -bun run staged:proof -- --mode=canary-write --target=avax -``` +- `canonical` + - the only destination that controls betting-page playback readiness + - currently Cloudflare Stream +- `fallback` + - emergency recovery rail + - currently self-hosted HLS on the GPU host +- `mirror` + - promotional and distribution outputs + - Twitch, Kick, YouTube, custom -The proof wrapper captures: +Mirror failures must not change canonical betting readiness. A broken Twitch or +Kick output is an isolated mirror problem, not a betting-page routing event. -- Pages `build-info.json` -- keeper `/status` -- `/api/arena/prediction-markets/active` -- `/api/keeper/bot-health` -- stream-state and duel-context payloads -- Solana and BSC proxy proof -- Solana, BSC, and AVAX canary tx hashes/signatures when `mode=canary-write` -- `verify:chains` output -- AVAX staging env-audit output +## Env Policy -This is a manual operator proof rail. It should not be treated as complete -until a real staged run passes end to end and the artifacts are reviewed. +These env families belong to the Hyperscapes source server and bridge runtime: -## 7) Security notes +- `STREAM_DELIVERY_MODE` +- `STREAM_DELIVERY_PROVIDER` +- `STREAM_INGEST_*` +- `STREAM_PLAYBACK_*` -- Do not expose `ARENA_EXTERNAL_BET_WRITE_KEY` in public frontend env vars. -- Do not ship provider-keyed RPC URLs in public frontend env vars. Keep them on Railway and let the keeper proxy them. -- Rotate all secrets before production if they were ever committed/shared. -- Keep `DISABLE_RATE_LIMIT` unset in production. +They exist to bootstrap the encoder and destinations. They are not a source of +canonical playback truth for keepers or frontends. + +Keepers may use: + +- `STREAM_RENDERER_HEALTH_URL` +- `STREAM_RENDERER_HEALTH_BEARER_TOKEN` + +Only for renderer diagnostics and stale-source detection. + +## Frontend Playback Policy + +All betting surfaces must follow the same routing rules: + +1. Mount canonical playback only when `channel.publicReadiness.ready === true`. +2. Treat missing canonical readiness as not ready. +3. Do not auto-fail over in the browser from canonical to fallback unless the + server explicitly permits or promotes fallback. +4. Keep browser/player reconnect state diagnostic-only relative to bet truth. +5. Never let mirror health change the betting page. + +Low-latency player tuning can remain consistent across surfaces, but those +player settings do not decide authoritative stream truth. + +## Rollout Order + +Production and staging rollouts must respect the control-plane ownership model: + +1. Deploy Hyperscapes server schema v2 with both new and legacy fields. +2. Deploy keepers so they consume the server-authored `channel` contract and + stop reconstructing delivery truth from env. +3. Deploy Pages frontends so they prefer `channel.publicReadiness` and + canonical destination state. +4. Remove remaining legacy `delivery`/`deliveryHealth` consumers only after all + surfaces are on v2. + +Do not roll frontends or keepers onto a mixed contract that assumes v2 fields +before the server emits them. + +## Verification Gates + +Before promoting a rollout: + +1. The Hyperscapes feed emits `channel`, `publicReadiness`, + `canonicalDestination`, and `fallbackDestination`. +2. The canonical destination is ready only when both transport health and + public playback readiness are good. +3. A mirror failure leaves canonical betting readiness unchanged. +4. Duel transitions do not restart the channel identity or force player remount + churn. +5. The betting page never shows browser-local buffering as market truth. +6. Soak tests show no sustained rebuild loop and no canonical/fallback drift. + +## Operator Runbooks + +### Canonical Destination Down + +- Expect `channel.publicReadiness.ready === false`. +- Expect a canonical-destination-specific reason such as `manifest_missing`, + `manifest_stale`, or `destination_disconnected`. +- Keepers and frontends should surface the unavailable state; they should not + synthesize readiness from a leftover playback URL. +- Restore the canonical destination at the source server or bridge layer. + +### Mirror Destination Down + +- Expect only that mirror destination entry to degrade. +- Do not treat this as a betting outage if canonical readiness remains true. +- Restore the affected mirror without touching canonical playback routing. + +### Fallback Manifest Stale + +- Expect the fallback destination entry to degrade independently. +- This is important operationally, but it does not demote canonical betting + playback while canonical readiness is still true. +- Repair the fallback rail so emergency recovery remains available. + +### Source Healthy But Canonical Public Playback Not Ready + +- Expect renderer/capture health to stay green while `publicReadiness` is false. +- Treat this as a distribution-plane problem, not a renderer problem. +- Inspect canonical destination transport health and the public playback probe + before touching duel or renderer systems. diff --git a/docs/prediction-market-release-prep.md b/docs/prediction-market-release-prep.md index 26432ca3..0fef8fc7 100644 --- a/docs/prediction-market-release-prep.md +++ b/docs/prediction-market-release-prep.md @@ -1,14 +1,23 @@ # Prediction Market Release Prep -> **TL;DR:** As of 2026-03-25, the repo is materially stronger and now carries full-product phase-1 rails for `PM + internal AMM + perps` across `Solana devnet`, `BSC testnet`, and `AVAX Fuji`, but the release train is still not deploy-only. The remaining blockers are truthful launch-chain mainnet registry values, staged environment provisioning, shared testnet token/address inputs, governance/evidence closeout, and the frozen audit packet plus external audit/remediation cycle. +> **TL;DR:** As of 2026-03-25, the repo is materially stronger and now carries full-product phase-1 rails for `PM + internal AMM + perps` plus the surrounding app shell and account surfaces that users actually touch. The active production-readiness gate is `Solana devnet + BSC testnet`; AVAX Fuji evidence is preserved but isolated and non-blocking. The remaining blockers are truthful active-scope registry values, staged environment provisioning, shared testnet token/address inputs, app-shell and account-surface closure, governance/evidence closeout, and the frozen audit packet plus external audit/remediation cycle. This document is the reviewer-facing release summary for the current launch closeout train on `audit/develop-pm-hardening`. +Detailed implementation work is tracked in: + +- [GitHub Project Production Backlog](release/github-project-production-backlog.md) +- [Runtime Integration Readiness Matrix](release/runtime-integration-readiness-matrix.md) +- [Tracking Document Map](release/tracking-document-map.md) + ## Phase-1 Product Definition -- Launch chains: `Solana`, `BSC`, `AVAX` +- Active production-readiness chains: `Solana`, `BSC` +- Preserved but isolated follow-on lane: `AVAX` - Non-blocking add-chain lane: `Base` +- Implementation target: shared `EVM` runtime plus `SVM`, with `BSC` as the + current active EVM proving wrapper - User-facing surfaces: - `PM/CLOB duels` - `perps/models` @@ -19,7 +28,11 @@ closeout train on `audit/develop-pm-hardening`. - PM-core hardening is merged for oracle finality, order semantics, governance freezes, and protocol guardrails. -- AMM settlement truth is now oracle-only on both EVM and Solana. +- repo artifact policy is enforced in CI, and the previously flagged tracked + Solana deploy artifacts are no longer present in the tracked tree. +- AMM settlement implementation is materially stronger than before, but the + production settlement model still needs an explicit freeze between + oracle-driven and challenge-window paths. - Solana perps pause remains callable after freeze. - Solana full-product deploy, init, freeze, and verify paths now include `lvr_amm`. @@ -34,16 +47,23 @@ closeout train on `audit/develop-pm-hardening`. ## What Is Still Blocking -### 1. Launch-chain canonical truth is incomplete +Detailed execution tickets for every blocker below live in the canonical +backlog. This document is the summary view, not the issue owner. + +### 1. Active launch-chain canonical truth is incomplete -Strict launch-chain mainnet canonical truth is still incomplete, even though the +Strict active-scope canonical truth is still incomplete, even though the develop-side Stage-A closeout gate now validates the real PR contract for -`Solana devnet + BSC testnet` while AVAX is intentionally deferred: +`Solana devnet + BSC testnet` while AVAX is intentionally isolated: +- active-scope launch constants still imply `avax` in places that should now + reflect the parked-chain decision +- default feature flags still understate the intended active product surface - `solana`: `goldAmmMarketProgramId` - `bsc`: `goldAmmRouterAddress`, `mUsdTokenAddress`, `goldTokenAddress`, `skillOracleAddress`, `perpEngineAddress` -- `avax`: PM-core plus AMM and perps canonical fields +- `avax`: PM-core plus AMM and perps canonical fields remain preserved + follow-on work and are not blocking the current production-readiness path Those values must still come from final mainnet deployment receipts only before any true launch promotion to `main` or `staging`. @@ -61,13 +81,11 @@ That means GitHub staged proof and staged soak cannot run honestly yet. ### 3. Local Stage-A is only partially unblocked -Local non-mainnet deploy and verification are now feasible, but the full BSC -and AVAX AMM/perps rehearsal still needs shared token/address inputs: +Local non-mainnet deploy and verification are now feasible, but the active BSC +AMM/perps rehearsal still needs shared token/address inputs: - `BSC_TESTNET_MUSD_TOKEN_ADDRESS` -- `AVAX_FUJI_MUSD_TOKEN_ADDRESS` - `BSC_TESTNET_GOLD_TOKEN_ADDRESS` -- `AVAX_FUJI_GOLD_TOKEN_ADDRESS` - optional perps margin token addresses when margin is not the GOLD token ### 4. Governance and audit evidence are not complete @@ -82,6 +100,17 @@ the final package still needs: - final RC freeze manifest - external audit findings and remediation output +### 5. AMM settlement truth, coordinated full-product smoke, and full app-shell closure are not yet closed + +- the EVM AMM still exposes both `settleMarket()` and `settleFromOracle()` +- the Solana AMM settlement path still carries compatibility-oriented optional + account handling that needs either tighter validation or an audit-grade + rationale +- PM has the strongest live evidence today; coordinated staged smoke and + evidence for the full active product destination still need to be frozen +- wallet/account, points/referral, and broader app-shell product claims still + need one frozen acceptance and support contract for the active runtimes + ## Gold Asset Boundary Gold is currently tracked as a separate architecture concern, not a phase-1 @@ -103,10 +132,16 @@ see: Reviewers should verify that the repo now reflects these truths consistently: -- phase-1 launch scope is `Solana + BSC + AVAX` +- active production-readiness scope is `Solana + BSC` +- AVAX is preserved but isolated and non-blocking - `Base` is non-blocking -- launch proof is `PM + perps + AMM`, not PM-only +- `BSC` is the active EVM wrapper, not the exclusive EVM implementation target +- launch destination is `PM + perps + internal AMM`, not PM-only +- active launch scope includes the application shell and account surfaces + around those products, not only the underlying market engines - stage/testnet proof does not equal mainnet canonical truth +- AMM settlement-model closure and coordinated full-product smoke are still + open blockers - launch remains blocked on registry truth, staged env provisioning, and audit evidence diff --git a/docs/release/external-audit-package-checklist.md b/docs/release/external-audit-package-checklist.md index 2fcf3e5c..19dac1ea 100644 --- a/docs/release/external-audit-package-checklist.md +++ b/docs/release/external-audit-package-checklist.md @@ -1,11 +1,20 @@ # External Audit Package Checklist -> **TL;DR:** The audit package is no longer just PM-core plus AVAX notes. It now has to prove the full phase-1 launch product: `PM/CLOB duels + perps/models + internal AMM` across `Solana`, `BSC`, and `AVAX`, with truthful launch-chain registry values, staged proof and soak evidence, governance transfer receipts, and the frozen RC manifest. +> **TL;DR:** The audit package is no longer just PM-core plus EVM notes. It now has to prove the full phase-1 launch product: `PM/CLOB duels + perps/models + internal AMM` plus the surrounding app-shell, wallet/account, and rewards surfaces across the active production-readiness scope of `Solana` and `BSC`, with truthful active-scope registry values, staged proof and soak evidence, governance transfer receipts, and the frozen RC manifest. AVAX evidence remains preserved follow-on material and is non-blocking unless the lane is reactivated. Use this checklist with the candidate memo in [release-memo-template.md](release-memo-template.md) and the evidence index in [launch-ops-evidence-index.md](launch-ops-evidence-index.md). +Detailed implementation work is tracked in: + +- [GitHub Project Production Backlog](github-project-production-backlog.md) +- [Runtime Integration Readiness Matrix](runtime-integration-readiness-matrix.md) +- [Tracking Document Map](tracking-document-map.md) + +This checklist owns audit-handoff packet completeness only. It does not own the +underlying implementation backlog. + ## Scope And Freeze - [x] Launch scope documented in @@ -26,11 +35,15 @@ Use this checklist with the candidate memo in - [x] Soak workflow and runbook linked: [../../.github/workflows/pm-soak.yml](../../.github/workflows/pm-soak.yml), [../runbooks/pm-confidence-soak.md](../runbooks/pm-confidence-soak.md) -- [ ] Local Stage-A deploy and verify artifacts attached for Solana devnet, BSC - testnet, and AVAX Fuji +- [ ] Local Stage-A deploy and verify artifacts attached for Solana devnet and + BSC testnet +- [ ] Preserved AVAX Fuji artifacts attached only if the AVAX lane is being + reactivated or explicitly reviewed - [ ] Read-only staged proof artifact bundle attached - [ ] Canary-write staged proof artifact bundle attached with `pm`, `perps`, and `amm` sub-results per chain +- [ ] Full app-shell acceptance evidence attached for wallet/account, + claims/positions, and points/referral surfaces - [ ] Staged soak artifact bundle attached - [ ] `verify-chains.json` attached and green @@ -44,7 +57,7 @@ Use this checklist with the candidate memo in - [ ] BSC canonical PM, AMM, perps, and governance fields committed from mainnet deployment evidence - [ ] AVAX canonical PM, AMM, perps, and governance fields committed from - mainnet deployment evidence + mainnet deployment evidence only if the AVAX lane is explicitly reactivated ## Governance And Emergency Controls @@ -59,8 +72,10 @@ Use this checklist with the candidate memo in - [ ] GitHub `staging` environment created - [ ] Required `HYPERBET_*_STAGING_*` vars and secrets loaded -- [ ] Shared BSC and AVAX testnet token addresses recorded in +- [ ] Shared BSC testnet token addresses recorded in [testnet-operations-ledger.md](testnet-operations-ledger.md) +- [ ] Shared AVAX token addresses recorded only if the AVAX lane is explicitly + reactivated - [ ] Canary, admin, operator, and reporter wallets funded for staged proof and staged soak @@ -78,3 +93,4 @@ Use this checklist with the candidate memo in - [ ] Residual-risk register attached: [residual-risk-register.md](residual-risk-register.md) - [ ] Final findings ledger and accepted risks attached +- [ ] Product-claim statement attached for wallet/account and rewards durability diff --git a/docs/release/github-project-production-backlog.md b/docs/release/github-project-production-backlog.md new file mode 100644 index 00000000..75a428f5 --- /dev/null +++ b/docs/release/github-project-production-backlog.md @@ -0,0 +1,1650 @@ +# GitHub Project Production Backlog + +> **TL;DR:** This is the canonical issue-seed backlog for Hyperbet's full launch path. It covers the full product destination of `PM + perps + internal AMM`, the shared application shell and account surfaces around those products, uses `BSC + Solana` as the active production-readiness gate, and keeps `AVAX` visible as a parked follow-on epic rather than an active blocker. The implementation target is runtime-family based: shared `EVM` plus `SVM`, with `BSC` acting as the current active EVM wrapper. + +Detailed document ownership is defined in +[tracking-document-map.md](tracking-document-map.md). + +The canonical end-to-end runtime status view lives in +[runtime-integration-readiness-matrix.md](runtime-integration-readiness-matrix.md). + +## Scope Contract + +- full product destination: `PM + perps + internal AMM` +- active production-readiness gate: `BSC + Solana` +- parked follow-on epic: `AVAX` +- `AMM` remains internal infrastructure, not a required retail browser surface +- implementation target: shared `EVM` runtime plus `SVM` + +## Chain Generality Contract + +Interpret every ticket in this document with the following default: + +- `BSC` is the current active EVM proving wrapper, not the exclusive EVM + implementation target +- unless a ticket explicitly says `wrapper-specific`, `deployment-specific`, or + `governance/evidence-only`, EVM work should land in shared EVM packages, + registry-driven config, and reusable runtime/app surfaces +- `Solana` owns the active `SVM` lane +- future `Base` onboarding and any later `AVAX` reactivation should be able to + consume the same shared EVM path rather than require BSC-only rewrites +- if an issue is created from this backlog, it should be labeled or fielded as + one of: `evm-shared`, `svm`, `cross-runtime`, or `wrapper-specific` + +## How To Use This Backlog + +- each `##` section is an epic candidate +- each `###` section is an issue candidate +- copy the title directly into GitHub +- keep the acceptance criteria unless scope changes materially +- if work is off-repo or operational, keep it on the same board with an owner + +Suggested label families: + +- `priority:P0`, `priority:P1`, `priority:P2` +- `scope:active`, `scope:parked`, `scope:off-repo` +- `blocker:launch`, `blocker:quality`, `blocker:parked` +- `type:bug`, `type:feature`, `type:improvement`, `type:ci`, + `type:security`, `type:ops`, `type:docs` +- `runtime:evm-shared`, `runtime:svm`, `runtime:cross-runtime`, + `runtime:wrapper-specific` + +Suggested project fields: + +- `Status`: `Inbox`, `Needs Decision`, `Ready`, `In Progress`, `In Review`, + `In QA`, `Blocked`, `Done` +- `Workflow`: mirrored compatibility field with the same queue values until it + is retired deliberately +- `Priority`: `P0`, `P1`, `P2` +- `Scope`: `active`, `parked`, `off-repo` +- `Runtime Applicability`: `evm-shared`, `svm`, `cross-runtime`, + `wrapper-specific` +- `Work Type`: `bug`, `feature`, `improvement`, `ci`, `security`, `ops`, + `docs` +- `Parent issue` +- `Sub-issues progress` +- `Epic` +- `Blocker Class`: `launch-blocking`, `quality-blocking`, `parked`, + `non-blocking` +- `Evidence Required`: `yes`, `no` +- `Owner` + +Suggested Kanban status options: + +- `Inbox` +- `Needs Decision` +- `Ready` +- `In Progress` +- `In Review` +- `In QA` +- `Blocked` +- `Done` + +Suggested project views: + +- `All Work` +- `Launch Blockers` +- `Current Queue` +- `Parked` +- `Off-Repo` +- `By Runtime` + +Queue model: + +- do not use dated sprints or time promises in the canonical backlog +- `Status` is the board-driving queue field used for Kanban grouping and + day-to-day execution +- `Workflow` mirrors `Status` for compatibility until the board model is + simplified deliberately +- sequence only by `Status`, `Priority`, `Dependencies`, `Scope`, + `Runtime Applicability`, and `Blocker Class` +- if a lightweight queue field is needed, use `Current`, `Next`, `Later`, and + `Parked` + +## GitHub Project Conversion Contract + +- create one GitHub Project v2 named `Hyperbet Sprint` +- keep active, parked, and off-repo work on the same board +- create one umbrella issue per epic and one execution issue per `PROD-*` + ticket +- every umbrella issue must use native GitHub sub-issues as the authoritative + child-membership graph +- every `PROD-*` issue must have the correct native GitHub parent issue +- umbrella issue bodies must not be used as pseudo-sub-issue lists +- `Parent issue` and `Sub-issues progress` are required parts of the project + structure, not optional add-ons +- split broad active `P0` tickets into child execution issues before import + when they are too large to act on as single issues +- do not create new execution issues during hierarchy-alignment passes unless a + later explicit planning pass decides to split oversized tickets +- every created issue must carry explicit `Runtime applicability`, even when + the markdown entry relied on inference from the `Chain Generality Contract` + +## Already Landed + +Do not reopen these unless a concrete regression is found: + +- Stage-A browser-to-chain acceptance +- real local Hyperscapes duel lane +- direct canary and verify rails +- current release, freeze, threat-model, and deploy documentation +- explicit distinction between browser surfaces and internal AMM operations +- repo artifact-policy CI enforcement and removal of the previously flagged + tracked Solana deploy artifacts from the tracked tree +- develop-era launch trackers now preserved as historical snapshots rather than + live blocker owners + +## Reconciliation Notes + +This backlog is the reconciled canonical set after comparing: + +- the current repo state +- the canonical release-tracking docs +- the corrected local issue pack and corrected probe audit +- package-level application and SDK truth across `hyperbet-ui`, + `hyperbet-sdk`, `hyperbet-bsc`, `hyperbet-solana`, `hyperbet-evm`, and + `market-maker-bot` + +Important reconciliation outcomes: + +- earlier repo-hygiene items around tracked Solana deploy artifacts and artifact + policy are already closed in the current repo state and therefore remain in + `Already Landed`, not as open tickets +- the corrected probe did surface real open gaps that were previously tracked + too generically; those are now represented by `PROD-001A`, `PROD-007A`, + `PROD-015B`, and `PROD-015C` +- integration and runtime tickets below were sharpened so the canonical backlog + now explicitly owns event-feed promotion, durable checkpoints, idempotent + replay, coordinated staged smoke, and AMM settlement-model closure +- the final review pass also surfaced missing full-app work around wallet and + account surfaces, points/referrals, app-shell acceptance, shared keeper + convergence, orchestration explicitness, and market-maker integration; those + are now owned by `PROD-047` through `PROD-054` + +## Ticket Schema + +Every issue in this document uses the same required fields: + +- `ID` +- `Title` +- `Type` +- `Priority` +- `Scope` +- `Area` +- `Description` +- `Acceptance criteria` +- `Dependencies` +- `Source docs` +- `Suggested owner` +- `Blocker class` +- `Runtime applicability` + +When issue text below predates an explicit runtime line, infer it from the +`Chain Generality Contract` during GitHub issue conversion and make it explicit +on the created issue and project item. + +## Epic 1: Chain Truth And Deployment Integrity + +### PROD-001 Populate canonical active launch-chain registry truth from final receipts + +- ID: `PROD-001` +- Title: `Populate canonical active launch-chain registry truth from final receipts` +- Type: `feature` +- Priority: `P0` +- Scope: `active` +- Area: `registry` +- Description: Commit truthful production registry values for the active launch + chains, `BSC` and `Solana`, using final deployment receipts only, while + preserving registry shape and config semantics that future EVM chains can + reuse without forking the runtime contract. +- Acceptance criteria: + - `packages/hyperbet-chain-registry/src/index.ts` contains complete launch + values for `BSC` and `Solana` + - no active-scope registry placeholders remain + - registry gate passes in strict active-launch mode +- Dependencies: final deployment receipts, explorer verification +- Source docs: `launch-ops-evidence-index.md`, + `prediction-market-launch-freeze-tracker.md`, + `prediction-market-release-prep.md` +- Suggested owner: `protocol` +- Blocker class: `launch-blocking` + +### PROD-001A Align active launch constants and feature-flag truth with the current scope contract + +- ID: `PROD-001A` +- Title: `Align active launch constants and feature-flag truth with the current scope contract` +- Type: `bug` +- Priority: `P0` +- Scope: `active` +- Area: `registry` +- Description: Eliminate scope drift between the canonical registry, launch + ordering constants, and runtime feature truth so the repo stops implying an + active launch posture that no longer matches `BSC + Solana`. +- Acceptance criteria: + - `BETTING_LAUNCH_EVM_CHAIN_ORDER` reflects the active launch gate and no + longer implies parked `AVAX` + - PM, perps, and internal AMM enablement has one documented canonical source + of truth for active environments + - registry tests, runtime env resolution, and active release docs agree on + the same feature and chain-scope contract +- Dependencies: `PROD-001` +- Source docs: `prediction-market-release-prep.md`, + `production-readiness-audit-2026-03-29.md` +- Suggested owner: `protocol` +- Blocker class: `launch-blocking` + +### PROD-002 Freeze and publish a launch manifest from final receipts + +- ID: `PROD-002` +- Title: `Freeze and publish a launch manifest from final receipts` +- Type: `docs` +- Priority: `P0` +- Scope: `active` +- Area: `release` +- Description: Materialize a frozen launch manifest with addresses, + authorities, deployment versions, explorer links, and receipt hashes. +- Acceptance criteria: + - frozen manifest exists under `docs/release/manifests/` + - every launch-critical address maps back to a receipt + - manifest matches chain registry and verification outputs +- Dependencies: `PROD-001` +- Source docs: `pm-launch-execution-plan.md`, + `external-audit-package-checklist.md` +- Suggested owner: `release` +- Blocker class: `launch-blocking` + +### PROD-003 Close non-mainnet receipt packaging and evidence indexing + +- ID: `PROD-003` +- Title: `Close non-mainnet receipt packaging and evidence indexing` +- Type: `docs` +- Priority: `P1` +- Scope: `active` +- Area: `evidence` +- Description: Convert the current Stage-A evidence into a stable attachment set + that reviewers and operators can consume without hunting through artifact + folders. +- Acceptance criteria: + - deploy, init, freeze, and verify outputs are grouped by chain + - receipt index links work from repo docs + - browser/on-chain evidence can be traced to receipts +- Dependencies: none +- Source docs: `stage-a-browser-acceptance-matrix.md`, + `stage-a-promotion-execution-ledger.md`, + `launch-ops-evidence-index.md` +- Suggested owner: `release` +- Blocker class: `quality-blocking` + +### PROD-004 Replace SDK and app placeholder deployment constants with registry-driven config + +- ID: `PROD-004` +- Title: `Replace SDK and app placeholder deployment constants with registry-driven config` +- Type: `improvement` +- Priority: `P1` +- Scope: `active` +- Runtime applicability: `cross-runtime` +- Area: `sdk` +- Description: Remove placeholder addresses and hardcoded defaults from public + SDK and app entrypoints so runtime config resolves from the canonical + registry or frozen manifest instead of drifting into chain-specific + placeholders such as the current SDK default addresses. +- Acceptance criteria: + - no placeholder deployment constants remain in active SDK/app entrypoints, + including `packages/hyperbet-sdk/src/index.ts` + - BSC and Solana config resolution is tested and EVM config semantics remain + reusable for future wrappers + - docs point consumers to the registry or launch manifest +- Dependencies: `PROD-001` +- Source docs: `launch-ops-evidence-index.md`, + `system-design-alignment.md`, + `production-readiness-audit-2026-03-29.md` +- Suggested owner: `sdk` +- Blocker class: `quality-blocking` + +## Epic 2: Staged Environment, CI, Proof, And Soak + +### PROD-005 Provision the GitHub staging environment and staged secrets contract + +- ID: `PROD-005` +- Title: `Provision the GitHub staging environment and staged secrets contract` +- Type: `ci` +- Priority: `P0` +- Scope: `active` +- Area: `infra` +- Description: Create the real GitHub `staging` environment and provision the + staged Pages, Railway, RPC, signer, and publish secrets required by staged + proof and staged soak. +- Acceptance criteria: + - GitHub `staging` environment exists + - required `HYPERBET_*_STAGING_*` vars and secrets are loaded + - env audit passes without placeholder or shadow-truth failures +- Dependencies: infrastructure ownership, secret custody +- Source docs: `hyperbet-production-deploy.md`, + `launch-ops-evidence-index.md`, + `external-audit-package-checklist.md` +- Suggested owner: `infra` +- Blocker class: `launch-blocking` + +### PROD-006 Capture staged proof artifacts for PM, perps/models, and internal AMM + +- ID: `PROD-006` +- Title: `Capture staged proof artifacts for PM, perps/models, and internal AMM` +- Type: `ci` +- Priority: `P0` +- Scope: `active` +- Area: `validation` +- Description: Execute staged proof in read-only and canary-write modes and + archive the full artifact bundle. +- Acceptance criteria: + - staged proof produces chain-by-chain `pm`, `perps`, and `amm` results + - `verify-chains.json` is attached and green + - artifact bundle is linked from evidence and audit docs +- Dependencies: `PROD-005` +- Source docs: `launch-ops-evidence-index.md`, + `external-audit-package-checklist.md` +- Suggested owner: `qa` +- Blocker class: `launch-blocking` + +### PROD-007 Capture authoritative staged soak evidence + +- ID: `PROD-007` +- Title: `Capture authoritative staged soak evidence` +- Type: `ci` +- Priority: `P0` +- Scope: `active` +- Area: `validation` +- Description: Run the staged soak rail against the provisioned environment and + archive the output with a reviewer-facing summary. +- Acceptance criteria: + - real staged soak artifacts exist under `.ci-artifacts/pm-soak` + - summary and screenshots are linked from release docs + - incidents are resolved or explicitly accepted +- Dependencies: `PROD-005` +- Source docs: `launch-ops-evidence-index.md`, + `external-audit-package-checklist.md` +- Suggested owner: `qa` +- Blocker class: `launch-blocking` + +### PROD-007A Run coordinated full-product staged smoke and publish one immutable evidence bundle + +- ID: `PROD-007A` +- Title: `Run coordinated full-product staged smoke and publish one immutable evidence bundle` +- Type: `ci` +- Priority: `P0` +- Scope: `active` +- Area: `validation` +- Description: Prove the coordinated loop of game feed, keeper ingestion, app + interaction, on-chain posting, result pickup, and recovery in one staged + environment for every enabled product surface, then publish one candidate + evidence bundle that reviewers can download directly. +- Acceptance criteria: + - one staged run proves the coordinated PM loop across the active launch + chains + - perps staged smoke is captured on the active EVM lane without baking in + BSC-only assumptions that would block later EVM onboardings + - internal AMM smoke is captured according to the final AMM scope decision + - one immutable evidence bundle contains browser artifacts, tx hashes, + receipts, checkpoints, and run summaries for the candidate +- Dependencies: `PROD-006`, `PROD-007`, `PROD-014A`, `PROD-015`, + `PROD-015A`, `PROD-015B` +- Source docs: `runtime-integration-readiness-matrix.md`, + `launch-ops-evidence-index.md`, + `prediction-market-release-prep.md` +- Suggested owner: `qa` +- Blocker class: `launch-blocking` + +### PROD-008 Rehearse production deploy and rollback as a controlled ceremony + +- ID: `PROD-008` +- Title: `Rehearse production deploy and rollback as a controlled ceremony` +- Type: `ops` +- Priority: `P1` +- Scope: `active` +- Area: `deploy` +- Description: Turn the production deploy guide into an executed ceremony + rehearsal that covers deploy, health verification, rollback trigger, and + rollback validation. +- Acceptance criteria: + - deployment checklist is executed against a non-mainnet environment + - rollback path is written and tested + - operator ownership is documented +- Dependencies: `PROD-006`, `PROD-007` +- Source docs: `hyperbet-production-deploy.md` +- Suggested owner: `infra` +- Blocker class: `quality-blocking` + +## Epic 3: Governance, Key Management, And Custody + +### PROD-009 Finish multisig, timelock, and upgrade-authority custody for the active launch chains + +- ID: `PROD-009` +- Title: `Finish multisig, timelock, and upgrade-authority custody for the active launch chains` +- Type: `ops` +- Priority: `P0` +- Scope: `active` +- Area: `governance` +- Description: Complete the real governance custody model assumed by the threat + model for `BSC` and `Solana`. +- Acceptance criteria: + - BSC timelock and multisig custody is live and recorded + - Solana upgrade authority is transferred from the deployer + - chain registry and governance docs reference the same owners +- Dependencies: final deployment addresses +- Source docs: `threat-model.md`, + `contract-privileged-surface-inventory.md`, + `pm-launch-execution-plan.md` +- Suggested owner: `protocol-ops` +- Blocker class: `launch-blocking` + +### PROD-010 Execute and record final freeze transactions for every privileged surface + +- ID: `PROD-010` +- Title: `Execute and record final freeze transactions for every privileged surface` +- Type: `ops` +- Priority: `P0` +- Scope: `active` +- Area: `governance` +- Description: Run the final freeze steps for oracle, market, perps, and AMM + surfaces and record the tx hashes in the evidence bundle. +- Acceptance criteria: + - all freeze tx hashes are captured and linked + - release docs no longer refer to freeze as pending + - privileged-surface inventory is updated with final evidence +- Dependencies: `PROD-009` +- Source docs: `prediction-market-launch-freeze-tracker.md`, + `contract-privileged-surface-inventory.md` +- Suggested owner: `protocol-ops` +- Blocker class: `launch-blocking` + +### PROD-011 Rotate and retire historical deploy and test keys + +- ID: `PROD-011` +- Title: `Rotate and retire historical deploy and test keys` +- Type: `security` +- Priority: `P0` +- Scope: `active` +- Area: `key-management` +- Description: Treat any keys used during prior testnet, branch, or local + proving phases as exposed and replace them with final production custody. +- Acceptance criteria: + - historical keys are marked retired + - active signer inventory is published + - key-rotation completion is attached to the audit packet +- Dependencies: `PROD-009` +- Source docs: `external-audit-package-checklist.md`, + `signer-policy-and-key-rotation.md` +- Suggested owner: `security` +- Blocker class: `launch-blocking` + +### PROD-012 Rehearse emergency governance actions with named owners + +- ID: `PROD-012` +- Title: `Rehearse emergency governance actions with named owners` +- Type: `ops` +- Priority: `P1` +- Scope: `active` +- Area: `governance` +- Description: Run pause, cancel, and role-recovery drills against the final + custody model so governance is operationally usable. +- Acceptance criteria: + - emergency drill report exists + - owner list and escalation chain are documented + - runbooks reflect final custody addresses and steps +- Dependencies: `PROD-009`, `PROD-010` +- Source docs: `prediction-market-governance-and-emergency-controls.md` +- Suggested owner: `protocol-ops` +- Blocker class: `quality-blocking` + +## Epic 4: Hyperscapes Integration And Runtime Contract Stability + +### PROD-013 Converge BSC onto the canonical EVM runtime and keeper core + +- ID: `PROD-013` +- Title: `Converge BSC onto the canonical EVM runtime and keeper core` +- Type: `improvement` +- Priority: `P1` +- Scope: `active` +- Runtime applicability: `evm-shared` +- Area: `runtime` +- Description: Reduce chain-wrapper drift so BSC remains only the active EVM + proving shell over one canonical EVM runtime and shared keeper core, rather + than becoming the place where EVM-specific business logic accumulates. +- Acceptance criteria: + - chain-specific business logic is minimized in the BSC wrapper + - shared runtime modules own common behavior + - future EVM chains can onboard against the same shared runtime with only + documented wrapper/config differences + - convergence docs match reality +- Dependencies: none +- Source docs: `system-design-alignment.md` +- Suggested owner: `protocol` +- Blocker class: `quality-blocking` + +### PROD-014 Version and contract-test the Hyperscapes to Hyperbet integration boundary + +- ID: `PROD-014` +- Title: `Version and contract-test the Hyperscapes to Hyperbet integration boundary` +- Type: `feature` +- Priority: `P0` +- Scope: `active` +- Area: `integration` +- Description: Formalize the upstream duel lifecycle contract so releases can + pin to a schema and catch breaking changes before runtime. +- Acceptance criteria: + - one canonical upstream contract is identified explicitly + - the contract covers `schemaVersion`, `sourceEpoch`, `seq`, timing fields, + winner mapping, and reset/replay semantics + - contract tests cover duel lifecycle, timing, result mapping, and resets +- Dependencies: active Hyperscapes contract access +- Source docs: `production-readiness-audit-2026-03-29.md`, + `prediction-market-test-flow.md`, + `hyperscapes-local-pm-integration.md` +- Suggested owner: `integration` +- Blocker class: `launch-blocking` + +### PROD-014A Canonicalize keeper ingestion onto the versioned Hyperscapes betting feed + +- ID: `PROD-014A` +- Title: `Canonicalize keeper ingestion onto the versioned Hyperscapes betting feed` +- Type: `bug` +- Priority: `P0` +- Scope: `active` +- Runtime applicability: `cross-runtime` +- Area: `keeper` +- Description: Stop treating the loose raw `/api/streaming/state` payload as a + production contract where a richer betting-feed contract already exists. +- Acceptance criteria: + - BSC and Solana consume the canonical `/api/internal/bet-sync/state` and + `/api/internal/bet-sync/events` contract, or a documented compatibility + adapter with equivalent semantics + - env examples and deploy docs prefer the canonical feed contract + - local and staged runners no longer depend on implicit feed-shape knowledge + and expose the consumer checkpoint boundary explicitly + - the canonical feed path is described as shared runtime contract rather than + a single-wrapper behavior +- Dependencies: `PROD-014` +- Source docs: `production-readiness-audit-2026-03-29.md`, + `hyperscapes-local-pm-integration.md` +- Suggested owner: `integration` +- Blocker class: `launch-blocking` + +### PROD-014B Make feed parsing fail closed on schema drift, replay mismatch, and source resets + +- ID: `PROD-014B` +- Title: `Make feed parsing fail closed on schema drift, replay mismatch, and source resets` +- Type: `security` +- Priority: `P0` +- Scope: `active` +- Runtime applicability: `cross-runtime` +- Area: `keeper` +- Description: Harden keeper feed parsing so contract drift or replay + corruption surfaces as an explicit integration error instead of silently + becoming synthesized state. +- Acceptance criteria: + - production-mode parsers reject missing required sequencing or version fields + - source-epoch changes, replay windows, and reset events are tracked and + surfaced as integration errors when contracts diverge + - missing `seq`, `emittedAt`, or equivalent fields are not silently fabricated + - the failure model is consistent across the active EVM and SVM keepers +- Dependencies: `PROD-014` +- Source docs: `production-readiness-audit-2026-03-29.md`, + `hyperscapes-local-pm-integration.md` +- Suggested owner: `integration` +- Blocker class: `launch-blocking` + +### PROD-015 Close remaining EVM models/perps browser deltas + +- ID: `PROD-015` +- Title: `Close remaining EVM models/perps browser deltas` +- Type: `feature` +- Priority: `P0` +- Scope: `active` +- Runtime applicability: `evm-shared` +- Area: `ui` +- Description: Productionize the user-facing EVM `models/agents` surface so the + product makes a precise claim about what users can do on the active EVM lane, + with BSC as the current proving wrapper rather than a hard-coded product + boundary. +- Acceptance criteria: + - intended active-EVM user actions are documented and tested + - browser-to-chain evidence exists for the supported models/perps actions, + including receipts or tx hashes + - stale-oracle, paused-market, and insufficient-margin UI handling is + exercised and evidenced for the supported flows + - the supported user actions are described as shared EVM product behavior, + with wrapper-specific differences called out separately + - unsupported behaviors are explicit in product copy and docs + - the resulting implementation does not require BSC-only logic where a shared + EVM path is intended + - browser acceptance evidence reflects the final active product claim +- Dependencies: product decision on EVM models/perps scope +- Source docs: `stage-a-browser-acceptance-matrix.md`, + `prediction-market-release-prep.md` +- Suggested owner: `app` +- Blocker class: `launch-blocking` + +### PROD-015A Close active-scope internal AMM and liquidity runtime dependencies + +- ID: `PROD-015A` +- Title: `Close active-scope internal AMM and liquidity runtime dependencies` +- Type: `feature` +- Priority: `P0` +- Scope: `active` +- Runtime applicability: `evm-shared` +- Area: `market-making` +- Description: Close the remaining launch-path gaps for the internal AMM and + active EVM liquidity dependencies, using BSC as the current proving wrapper + without turning AMM into a retail UI scope item or baking BSC-only logic into + the shared EVM path. +- Acceptance criteria: + - required active-EVM token and router inputs are finalized, with current BSC + proving inputs documented explicitly + - internal AMM runtime dependencies are documented and testable + - internal-only AMM operator expectations are explicit in docs, staged smoke, + and release evidence + - staged proof and launch docs treat AMM as internal but production-critical + - the dependency contract is reusable for future EVM wrappers without + reopening BSC-only assumptions +- Dependencies: `PROD-001`, `PROD-005`, `PROD-006`, `PROD-015B` +- Source docs: `prediction-market-release-prep.md`, + `launch-ops-evidence-index.md`, + `runtime-integration-readiness-matrix.md` +- Suggested owner: `protocol` +- Blocker class: `launch-blocking` + +### PROD-015B Decide and freeze the production AMM settlement model + +- ID: `PROD-015B` +- Title: `Decide and freeze the production AMM settlement model` +- Type: `feature` +- Priority: `P0` +- Scope: `active` +- Area: `market-making` +- Description: The EVM AMM still exposes both challenge-window settlement and + oracle-driven settlement. Freeze the production truth so auditors and + operators do not have to infer whether AMM is oracle-only or dual-mode. +- Acceptance criteria: + - the production AMM settlement truth is explicit and documented + - if oracle-only, proposer/challenge settlement is disabled or tightly + guarded for production markets + - if dual-mode, the governing rules, tests, and runbooks are exhaustive and + audit-grade +- Dependencies: product and protocol signoff +- Source docs: `prediction-market-release-prep.md`, + `production-readiness-audit-2026-03-29.md`, + `pm-launch-execution-plan.md` +- Suggested owner: `protocol` +- Blocker class: `launch-blocking` + +### PROD-015C Harden Solana AMM settlement account typing and auditability + +- ID: `PROD-015C` +- Title: `Harden Solana AMM settlement account typing and auditability` +- Type: `security` +- Priority: `P1` +- Scope: `active` +- Area: `market-making` +- Description: Make the Solana AMM settlement path defensible to auditors by + either tightening optional account typing or documenting and exhaustively + testing the compatibility posture. +- Acceptance criteria: + - settlement accounts are strongly constrained or the compatibility exception + is documented explicitly + - negative tests cover wrong owner, wrong PDA, and incompatible optional + account cases + - the audit packet explains the final Solana AMM settlement validation model +- Dependencies: `PROD-015B` +- Source docs: `prediction-market-release-prep.md`, + `production-readiness-audit-2026-03-29.md` +- Suggested owner: `solana-protocol` +- Blocker class: `quality-blocking` + +### PROD-016 Stabilize public API and SDK contracts against chain truth + +- ID: `PROD-016` +- Title: `Stabilize public API and SDK contracts against chain truth` +- Type: `improvement` +- Priority: `P1` +- Scope: `active` +- Runtime applicability: `cross-runtime` +- Area: `api` +- Description: Publish one stable contract for keeper APIs, stream APIs, chain + addressing, SDK entrypoints, and application-level config resolution so + consumers stop relying on incidental implementation details. +- Acceptance criteria: + - API contract doc exists + - SDKs consume canonical config paths + - app shells consume the same contract for addresses, feature flags, and + runtime capabilities + - compatibility policy is documented for breaking changes +- Dependencies: `PROD-001`, `PROD-014` +- Source docs: `system-design-alignment.md`, + `tracking-document-map.md` +- Suggested owner: `sdk` +- Blocker class: `quality-blocking` + +## Epic 5: Application Shell, Account Surfaces, And Runtime Convergence + +### PROD-047 Productionize shared wallet and account surfaces across the active runtimes + +- ID: `PROD-047` +- Title: `Productionize shared wallet and account surfaces across the active runtimes` +- Type: `feature` +- Priority: `P1` +- Scope: `active` +- Runtime applicability: `cross-runtime` +- Area: `app-shell` +- Description: Make wallet connect/select, account state, claimability, + positions, and account-facing copy behave as one deliberate Hyperbet product + across the active runtimes rather than a set of partially chain-specific + surfaces. +- Acceptance criteria: + - shared wallet and account surfaces are documented for the active `EVM` and + `SVM` lanes + - launch-critical account actions such as connect, view positions, and claim + state are covered by acceptance evidence + - chain-specific differences are explicit in copy and docs rather than + emerging from wrapper drift +- Dependencies: `PROD-016` +- Source docs: `system-design-alignment.md`, + `stage-a-browser-acceptance-matrix.md`, + `production-readiness-audit-2026-03-29.md` +- Suggested owner: `app` +- Blocker class: `quality-blocking` + +### PROD-048 Productionize points, referrals, and rewards surfaces as first-class product scope + +- ID: `PROD-048` +- Title: `Productionize points, referrals, and rewards surfaces as first-class product scope` +- Type: `feature` +- Priority: `P1` +- Scope: `active` +- Runtime applicability: `cross-runtime` +- Area: `rewards` +- Description: Treat points, referrals, leaderboard, invite redeem, and wallet + linking as explicit product surfaces with clear durability, auth, and support + expectations rather than incidental add-ons. +- Acceptance criteria: + - points, referrals, and rewards product claims are documented explicitly + - durable versus cached or reconstructable rewards state is stated in docs + - browser evidence exists for the active account and referral flows the + product claims to support + - unsupported or operator-mediated rewards behaviors are explicit in product + copy and support docs +- Dependencies: `PROD-021`, `PROD-047`, `PROD-050` +- Source docs: `production-readiness-audit-2026-03-29.md`, + `stage-a-browser-acceptance-matrix.md`, + `hyperbet-production-deploy.md` +- Suggested owner: `app` +- Blocker class: `quality-blocking` + +### PROD-049 Freeze the active app-shell acceptance contract for the full Hyperbet surface + +- ID: `PROD-049` +- Title: `Freeze the active app-shell acceptance contract for the full Hyperbet surface` +- Type: `feature` +- Priority: `P0` +- Scope: `active` +- Runtime applicability: `cross-runtime` +- Area: `acceptance` +- Description: Define and prove the user-facing Hyperbet shell beyond pure + market lifecycle flows so launch claims cover the full active application: + navigation, drawers, tabs, models/agents, claims, positions, account panels, + and explicit unsupported-state messaging. +- Acceptance criteria: + - the active application shell contract is documented in one canonical place + - every visible active-scope app shell surface maps to acceptance evidence or + an explicit unsupported-state statement + - app-shell acceptance is expressed as shared product truth, not as wrapper + folklore +- Dependencies: `PROD-015`, `PROD-047`, `PROD-048` +- Source docs: `stage-a-browser-acceptance-matrix.md`, + `runtime-integration-readiness-matrix.md`, + `prediction-market-release-prep.md` +- Suggested owner: `app` +- Blocker class: `launch-blocking` + +### PROD-050 Add durable user-account and rewards reconciliation tooling + +- ID: `PROD-050` +- Title: `Add durable user-account and rewards reconciliation tooling` +- Type: `feature` +- Priority: `P1` +- Scope: `active` +- Runtime applicability: `cross-runtime` +- Area: `ops-tooling` +- Description: Extend operator tooling beyond settlement only so support and + finance can reconstruct points, referrals, wallet-link state, claimability, + and account-facing reward questions deterministically. +- Acceptance criteria: + - operator reports can answer account, referral, and points disputes + - rewards and account state can be reconciled against keeper and chain + evidence where applicable + - support and finance runbooks describe the investigation path +- Dependencies: `PROD-019`, `PROD-048` +- Source docs: `production-readiness-audit-2026-03-29.md`, + `hyperbet-production-deploy.md` +- Suggested owner: `ops` +- Blocker class: `quality-blocking` + +### PROD-051 Extract shared keeper domain logic out of wrapper packages + +- ID: `PROD-051` +- Title: `Extract shared keeper domain logic out of wrapper packages` +- Type: `improvement` +- Priority: `P1` +- Scope: `active` +- Runtime applicability: `cross-runtime` +- Area: `runtime` +- Description: Move reusable keeper domain logic into shared modules so wrapper + packages stop accumulating chain-family business behavior and the shared + runtime contract becomes the default implementation path. +- Acceptance criteria: + - shared keeper modules own common domain behavior where runtime semantics + are actually shared + - wrapper packages are reduced toward execution adapters plus config + - convergence docs accurately describe the resulting ownership model +- Dependencies: `PROD-013` +- Source docs: `system-design-alignment.md`, + `production-readiness-audit-2026-03-29.md` +- Suggested owner: `protocol` +- Blocker class: `quality-blocking` + +### PROD-052 Make Hyperscapes orchestration runtime-explicit and wrapper-agnostic + +- ID: `PROD-052` +- Title: `Make Hyperscapes orchestration runtime-explicit and wrapper-agnostic` +- Type: `improvement` +- Priority: `P1` +- Scope: `active` +- Runtime applicability: `cross-runtime` +- Area: `integration` +- Description: Update local and staged orchestration so Hyperbet runtime + selection is explicit and no older Solana-first or wrapper-specific + assumptions survive in the duel-stack control plane. +- Acceptance criteria: + - orchestration scripts describe runtime selection explicitly + - no active runner depends on an implicit Solana-first sibling-app contract + - local and staged integration docs match the resulting orchestration model +- Dependencies: `PROD-014A`, `PROD-051` +- Source docs: `system-design-alignment.md`, + `hyperscapes-local-pm-integration.md` +- Suggested owner: `integration` +- Blocker class: `quality-blocking` + +### PROD-053 Canonicalize market-maker integration on shared runtime interfaces + +- ID: `PROD-053` +- Title: `Canonicalize market-maker integration on shared runtime interfaces` +- Type: `improvement` +- Priority: `P1` +- Scope: `active` +- Runtime applicability: `cross-runtime` +- Area: `market-making` +- Description: Align the market-maker integration with the same registry, + runtime, and interface contracts that the rest of Hyperbet uses so internal + AMM operations do not depend on ad hoc cross-chain behavior. +- Acceptance criteria: + - market-maker config resolves from canonical runtime and registry truth + - shared runtime interfaces are the documented integration contract + - release and ops docs describe the market-maker as part of the same canonical + launch system +- Dependencies: `PROD-015A`, `PROD-016`, `PROD-051` +- Source docs: `system-design-alignment.md`, + `prediction-market-release-prep.md` +- Suggested owner: `protocol` +- Blocker class: `quality-blocking` + +### PROD-054 Normalize active-scope package READMEs and launch docs to the chain-generality contract + +- ID: `PROD-054` +- Title: `Normalize active-scope package READMEs and launch docs to the chain-generality contract` +- Type: `docs` +- Priority: `P2` +- Scope: `active` +- Runtime applicability: `cross-runtime` +- Area: `docs` +- Description: Remove active-scope wording that can mislead contributors into + treating `BSC` as the permanent EVM boundary or PM as the only product lane, + while preserving historical evidence where appropriate. +- Acceptance criteria: + - active READMEs and guidance docs use the same shared `EVM` plus `SVM` + contract + - historical documents remain preserved without regaining open-work ownership + - contributors can tell which work is wrapper-specific versus shared-runtime + work +- Dependencies: `PROD-013`, `PROD-049` +- Source docs: `tracking-document-map.md`, + `prediction-market-release-prep.md`, + `production-readiness-audit-2026-03-29.md` +- Suggested owner: `release` +- Blocker class: `non-blocking` + +## Epic 6: Reliability, Durability, And Reconciliation + +### PROD-017 Replace ephemeral keeper storage with durable persistence + +- ID: `PROD-017` +- Title: `Replace ephemeral keeper storage with durable persistence` +- Type: `feature` +- Priority: `P0` +- Scope: `active` +- Area: `keeper` +- Description: Move keeper state off ephemeral SQLite semantics in hosted + environments and define backup, restore, and migration behavior. +- Acceptance criteria: + - durable storage backend is selected and wired + - backup and restore runbook exists + - hosted keeper deploy no longer depends on ephemeral local files +- Dependencies: production infra choice +- Source docs: `hyperbet-production-deploy.md` +- Suggested owner: `infra` +- Blocker class: `launch-blocking` + +### PROD-018 Add production observability, SLOs, and alerting + +- ID: `PROD-018` +- Title: `Add production observability, SLOs, and alerting` +- Type: `ops` +- Priority: `P0` +- Scope: `active` +- Area: `observability` +- Description: Add metrics, structured alerts, dashboards, and paging criteria + for keeper health, duel freshness, quote health, claim latency, and RPC + degradation. +- Acceptance criteria: + - SLOs are defined for critical surfaces + - alert thresholds and owners are documented + - dashboards exist for operators +- Dependencies: `PROD-017` +- Source docs: `hyperbet-production-deploy.md`, + `runtime-integration-readiness-matrix.md` +- Suggested owner: `ops` +- Blocker class: `launch-blocking` + +### PROD-018A Add stream freshness, restart recovery, replay, and backfill automation + +- ID: `PROD-018A` +- Title: `Add stream freshness, restart recovery, replay, and backfill automation` +- Type: `feature` +- Priority: `P0` +- Scope: `active` +- Area: `runtime` +- Description: Turn the currently manual recovery and replay assumptions into + explicit production automation for stale stream detection, resets, restart + recovery, and backfill. +- Acceptance criteria: + - stream freshness and reset conditions are measured and alertable + - consumer checkpoints survive restart and are queryable for operators + - keeper restart and Hyperscapes restart paths have deterministic backfill + behavior + - duplicate or replayed source events are applied idempotently + - the proof harness can show source events, consumer checkpoints, and + projected state together + - replay, reset, and backfill behavior is documented and tested +- Dependencies: `PROD-014A`, `PROD-014B`, `PROD-017` +- Source docs: `runtime-integration-readiness-matrix.md`, + `stage-a-browser-acceptance-matrix.md` +- Suggested owner: `integration` +- Blocker class: `launch-blocking` + +### PROD-019 Build result discovery, settlement reconciliation, and payout investigation tooling + +- ID: `PROD-019` +- Title: `Build result discovery, settlement reconciliation, and payout investigation tooling` +- Type: `feature` +- Priority: `P0` +- Scope: `active` +- Runtime applicability: `cross-runtime` +- Area: `ops-tooling` +- Description: Create operator tooling for result correlation, payout disputes, + claim backlog review, market-state reconstruction, user-facing accounting + questions, and the investigation primitives that higher-level account and + rewards tooling will build on. +- Acceptance criteria: + - chain and keeper events can be reconciled into one operator report + - result discovery and settlement correlation are deterministic + - payout investigation flow is documented + - the tool contract is reusable by account, rewards, and support workflows +- Dependencies: `PROD-017`, `PROD-018A` +- Source docs: `production-readiness-audit-2026-03-29.md`, + `runtime-integration-readiness-matrix.md` +- Suggested owner: `ops` +- Blocker class: `launch-blocking` + +### PROD-020 Harden production incident handling and support operations + +- ID: `PROD-020` +- Title: `Harden production incident handling and support operations` +- Type: `ops` +- Priority: `P1` +- Scope: `active` +- Area: `support` +- Description: Complete the operator side of a real betting service: support + queue routing, incident communication templates, and recovery ownership. +- Acceptance criteria: + - support and incident severity matrix exists + - operator handoff process is documented + - customer-facing incident communication templates are ready +- Dependencies: `PROD-018`, `PROD-019` +- Source docs: `production-readiness-audit-2026-03-29.md`, + `docs/runbooks/README.md` +- Suggested owner: `ops` +- Blocker class: `quality-blocking` + +## Epic 7: Security Hardening And External Audit + +### PROD-021 Make keeper write auth fail closed across the active keeper surfaces + +- ID: `PROD-021` +- Title: `Make keeper write auth fail closed across the active keeper surfaces` +- Type: `security` +- Priority: `P0` +- Scope: `active` +- Area: `keeper` +- Description: Align the BSC and Solana keepers with the canonical EVM keeper + so missing write keys reject writes instead of granting access. +- Acceptance criteria: + - `requireWriteAuth()` fails closed when the configured key is unset + - invite redeem, wallet link, external bet recording, and stream publish + reject missing or incorrect auth + - automated tests cover configured, missing-key, and wrong-key cases +- Dependencies: none +- Source docs: `production-readiness-audit-2026-03-29.md`, + `threat-model.md` +- Suggested owner: `security` +- Blocker class: `launch-blocking` + +### PROD-022 Remove origin-based trust from the Solana sender proxy + +- ID: `PROD-022` +- Title: `Remove origin-based trust from the Solana sender proxy` +- Type: `security` +- Priority: `P0` +- Scope: `active` +- Area: `keeper` +- Description: Replace the Solana sender proxy's current allowed-origin + authorization path with an explicit trusted service contract. +- Acceptance criteria: + - no auth decision depends on `Origin` alone + - the proxy requires a privileged key or signed service identity + - spoofed-origin tests are rejected +- Dependencies: `PROD-021` +- Source docs: `production-readiness-audit-2026-03-29.md`, + `stage-a-browser-acceptance-matrix.md` +- Suggested owner: `security` +- Blocker class: `launch-blocking` + +### PROD-023 Enforce read-only allowlists and quotas on public RPC proxies + +- ID: `PROD-023` +- Title: `Enforce read-only allowlists and quotas on public RPC proxies` +- Type: `security` +- Priority: `P0` +- Scope: `active` +- Area: `proxy` +- Description: Bring the keeper RPC proxies in line with their documented + contract as public keyed read-only endpoints. +- Acceptance criteria: + - Solana and EVM proxy handlers enforce approved read-only method sets + - state-changing or unknown methods are rejected before upstream forwarding + - usage is rate-limited and observable per method family +- Dependencies: none +- Source docs: `production-readiness-audit-2026-03-29.md`, + `hyperbet-production-deploy.md` +- Suggested owner: `security` +- Blocker class: `launch-blocking` + +### PROD-024 Close the remaining explicit security test gaps + +- ID: `PROD-024` +- Title: `Close the remaining explicit security test gaps` +- Type: `security` +- Priority: `P0` +- Scope: `active` +- Area: `tests` +- Description: Finish the concrete adversarial tests still called out by the + risk and historical audit docs. +- Acceptance criteria: + - malicious-contract reentrancy test exists and passes + - flash-loan or single-tx abuse scenarios are covered where applicable + - residual-risk register is updated to reflect the new status +- Dependencies: `PROD-021`, `PROD-022`, `PROD-023` +- Source docs: `residual-risk-register.md`, + `convergence-audit-report.md` +- Suggested owner: `security` +- Blocker class: `launch-blocking` + +### PROD-025 Assemble the frozen external audit packet + +- ID: `PROD-025` +- Title: `Assemble the frozen external audit packet` +- Type: `docs` +- Priority: `P0` +- Scope: `active` +- Area: `audit` +- Description: Convert the current audit-prep state into a frozen packet that + an external reviewer can consume without chasing live branch drift. +- Acceptance criteria: + - RC commit and freeze manifest are attached + - staged proof and staged soak artifacts are attached + - coordinated staged smoke and candidate evidence bundle are attached + - governance receipts and final findings ledger are attached +- Dependencies: `PROD-006`, `PROD-007`, `PROD-007A`, `PROD-010`, `PROD-011`, + `PROD-015B`, `PROD-015C`, `PROD-024` +- Source docs: `external-audit-package-checklist.md` +- Suggested owner: `release` +- Blocker class: `launch-blocking` + +### PROD-026 Run the external audit and close the remediation loop + +- ID: `PROD-026` +- Title: `Run the external audit and close the remediation loop` +- Type: `security` +- Priority: `P0` +- Scope: `active` +- Area: `audit` +- Description: Complete the external audit engagement, land required fixes, and + record the final accepted risks and remediated findings. +- Acceptance criteria: + - external findings are logged in one ledger + - fixes are linked to evidence and code changes + - remaining accepted risks have explicit owner signoff +- Dependencies: `PROD-025` +- Source docs: `external-audit-package-checklist.md` +- Suggested owner: `security` +- Blocker class: `launch-blocking` + +### PROD-027 Add coverage reporting and invariant verification to the release packet + +- ID: `PROD-027` +- Title: `Add coverage reporting and invariant verification to the release packet` +- Type: `improvement` +- Priority: `P1` +- Scope: `active` +- Area: `tests` +- Description: Make test quality visible by attaching coverage, invariant, and + adversarial verification summaries to the release and audit packet. +- Acceptance criteria: + - quantitative coverage reports exist for critical packages + - invariant and fuzz suites are linked from the packet + - release docs reference the reports as evidence +- Dependencies: `PROD-024` +- Source docs: `convergence-audit-report.md` +- Suggested owner: `qa` +- Blocker class: `quality-blocking` + +## Epic 8: Gold Asset Architecture + +### PROD-028 Approve the final Gold architecture spec + +- ID: `PROD-028` +- Title: `Approve the final Gold architecture spec` +- Type: `docs` +- Priority: `P1` +- Scope: `active` +- Area: `gold` +- Description: Resolve the canonical Gold model so the product stops making + stronger claims than the implemented asset semantics can support. +- Acceptance criteria: + - canonical supply source is chosen + - issuance, burn, redemption, and chain-support rules are explicit + - supported vs unsupported behavior is stated plainly +- Dependencies: cross-team product decision +- Source docs: `gold-architecture-spec-plan.md`, + `gold-current-state.md` +- Suggested owner: `product` +- Blocker class: `quality-blocking` + +### PROD-029 Align registry, SDK, and product copy with the approved Gold model + +- ID: `PROD-029` +- Title: `Align registry, SDK, and product copy with the approved Gold model` +- Type: `improvement` +- Priority: `P1` +- Scope: `active` +- Area: `gold` +- Description: Once the Gold model is approved, remove misleading aliases and + update runtime semantics, copy, and verification to match it. +- Acceptance criteria: + - field naming matches the approved asset model + - unsupported Gold behaviors are removed or labeled + - verification logic no longer implies a broader rollout than exists +- Dependencies: `PROD-028` +- Source docs: `gold-architecture-spec-plan.md` +- Suggested owner: `sdk` +- Blocker class: `quality-blocking` + +### PROD-030 Implement Gold issuance, redemption, and reconciliation after the spec is approved + +- ID: `PROD-030` +- Title: `Implement Gold issuance, redemption, and reconciliation after the spec is approved` +- Type: `feature` +- Priority: `P2` +- Scope: `active` +- Area: `gold` +- Description: Treat Gold portability and backing as a separate build track + after the architecture is fixed, not as an incidental side effect of launch. +- Acceptance criteria: + - issuance and redemption flows are implemented against the approved model + - reserve and reconciliation invariants are enforced + - Gold-specific runbooks and audit scope exist +- Dependencies: `PROD-028`, `PROD-029` +- Source docs: `gold-architecture-spec-plan.md` +- Suggested owner: `protocol` +- Blocker class: `non-blocking` + +## Epic 9: Compliance, Treasury, And Customer Operations + +### PROD-031 Define jurisdiction, licensing, KYC/AML, and responsible-gaming posture + +- ID: `PROD-031` +- Title: `Define jurisdiction, licensing, KYC/AML, and responsible-gaming posture` +- Type: `ops` +- Priority: `P0` +- Scope: `off-repo` +- Area: `compliance` +- Description: A production betting product needs an explicit legal and + compliance operating model. +- Acceptance criteria: + - supported jurisdictions are defined + - KYC/AML policy is approved + - age gating and responsible-gaming requirements are specified +- Dependencies: legal and compliance ownership +- Source docs: `production-readiness-audit-2026-03-29.md` +- Suggested owner: `legal` +- Blocker class: `launch-blocking` + +### PROD-032 Ship legal disclosures and account-policy UX + +- ID: `PROD-032` +- Title: `Ship legal disclosures and account-policy UX` +- Type: `feature` +- Priority: `P0` +- Scope: `off-repo` +- Area: `legal-ux` +- Description: Add the legal and customer-policy surfaces required for a real + betting product. +- Acceptance criteria: + - terms of service, privacy policy, and risk disclosures exist + - required age or region gating is enforced in product UX + - rewards copy matches the approved legal posture +- Dependencies: `PROD-031` +- Source docs: `production-readiness-audit-2026-03-29.md` +- Suggested owner: `app` +- Blocker class: `launch-blocking` + +### PROD-033 Establish treasury, accounting, and financial-control workflows + +- ID: `PROD-033` +- Title: `Establish treasury, accounting, and financial-control workflows` +- Type: `ops` +- Priority: `P0` +- Scope: `off-repo` +- Area: `finance` +- Description: Define how treasury balances, fees, reserves, and payouts are + tracked and audited outside pure protocol execution. +- Acceptance criteria: + - treasury ownership and approval paths are documented + - accounting and reconciliation cadence is defined + - tax and reporting responsibilities are assigned +- Dependencies: `PROD-019`, `PROD-031` +- Source docs: `production-readiness-audit-2026-03-29.md`, + `gold-architecture-spec-plan.md` +- Suggested owner: `finance-ops` +- Blocker class: `launch-blocking` + +### PROD-034 Create customer support and dispute-resolution operations + +- ID: `PROD-034` +- Title: `Create customer support and dispute-resolution operations` +- Type: `ops` +- Priority: `P1` +- Scope: `off-repo` +- Area: `support` +- Description: Build the human process for payout disputes, delayed claims, + platform incidents, and customer communications. +- Acceptance criteria: + - support escalation path exists + - dispute-resolution workflow exists + - operator tooling can answer the questions support receives +- Dependencies: `PROD-019`, `PROD-020` +- Source docs: `production-readiness-audit-2026-03-29.md` +- Suggested owner: `ops` +- Blocker class: `quality-blocking` + +## Epic 10: Release Governance And Project Management + +### PROD-035 Convert this backlog into GitHub issues and create the production-readiness project + +- ID: `PROD-035` +- Title: `Convert this backlog into GitHub issues and create the production-readiness project` +- Type: `docs` +- Priority: `P1` +- Scope: `active` +- Runtime applicability: `cross-runtime` +- Area: `project-management` +- Description: Turn this document into the active GitHub issue board the team + will execute from after merge, using one full Kanban board with no dated + sprint contract baked into the backlog. +- Acceptance criteria: + - one GitHub Project named `Hyperbet Sprint` exists + - issue titles, fields, and epic mapping match this document + - the project uses `Status`, `Workflow`, `Priority`, `Scope`, + `Runtime Applicability`, `Work Type`, `Parent issue`, + `Sub-issues progress`, `Epic`, `Blocker Class`, `Evidence Required`, and + `Owner` fields + - `Status` exposes the live delivery queue as `Inbox`, `Needs Decision`, + `Ready`, `In Progress`, `In Review`, `In QA`, `Blocked`, and `Done` + - every epic issue has native GitHub sub-issues attached for its child + backlog tickets + - every `PROD-*` issue has the correct native GitHub parent issue + - umbrella issue bodies do not maintain manual tracked-ticket lists as a + substitute for the native hierarchy + - the project exposes `All Work`, `Launch Blockers`, `Current Queue`, + `Parked`, `Off-Repo`, and `By Runtime` views + - each issue has an owner, priority, dependency note, and runtime + applicability field or label (`evm-shared`, `svm`, `cross-runtime`, or + `wrapper-specific`) + - broad active `P0` tickets are split into child execution issues when needed + before import +- Dependencies: agreed working branch or merge target +- Source docs: `github-project-production-backlog.md` +- Suggested owner: `release` +- Blocker class: `non-blocking` + +### PROD-036 Add a go/no-go release review ritual + +- ID: `PROD-036` +- Title: `Add a go/no-go release review ritual` +- Type: `ops` +- Priority: `P1` +- Scope: `active` +- Area: `release` +- Description: Create the review ceremony that decides whether Hyperbet is + ready to promote from testnet-proven to production-ready. +- Acceptance criteria: + - signoff template exists + - required attendees and approvals are defined + - evidence checklist is attached to the ritual +- Dependencies: `PROD-025`, `PROD-031`, `PROD-033` +- Source docs: `external-audit-package-checklist.md`, + `tracking-document-map.md` +- Suggested owner: `release` +- Blocker class: `quality-blocking` + +## Epic 11: Parked And Add-Chain Epics + +### PROD-037 Preserve AVAX as a parked epic without active blocker status + +- ID: `PROD-037` +- Title: `Preserve AVAX as a parked epic without active blocker status` +- Type: `docs` +- Priority: `P1` +- Scope: `parked` +- Area: `avax` +- Description: Keep AVAX code, evidence, and investigation work intact while + ensuring AVAX incompleteness does not block the active `BSC + Solana` board. +- Acceptance criteria: + - active docs consistently mark AVAX as parked or preserved + - AVAX evidence remains linked and accessible + - AVAX does not appear in active blocker summaries unless reactivated +- Dependencies: none +- Source docs: `production-readiness-audit-2026-03-29.md`, + `tracking-document-map.md` +- Suggested owner: `release` +- Blocker class: `parked` + +### PROD-038 Define AVAX reactivation gates and re-entry checklist + +- ID: `PROD-038` +- Title: `Define AVAX reactivation gates and re-entry checklist` +- Type: `docs` +- Priority: `P2` +- Scope: `parked` +- Area: `avax` +- Description: Define the exact conditions under which AVAX can be reactivated + as an active launch lane without rediscovering prior decisions. +- Acceptance criteria: + - re-entry checklist covers registry truth, custody, proof, soak, and runtime + contract verification + - owner signoff is required before AVAX re-enters the blocker list + - active docs reference the same reactivation criteria +- Dependencies: `PROD-037` +- Source docs: `pm-launch-execution-plan.md`, + `tracking-document-map.md` +- Suggested owner: `release` +- Blocker class: `parked` + +### PROD-038A Define Base add-chain promotion checklist + +- ID: `PROD-038A` +- Title: `Define Base add-chain promotion checklist` +- Type: `docs` +- Priority: `P2` +- Scope: `parked` +- Area: `base` +- Description: Keep Base as a non-blocking add-chain lane with one explicit + re-entry checklist so the team can promote it later without reopening scope + confusion about the active launch gate. +- Acceptance criteria: + - Base promotion checklist covers registry truth, custody, staged proof, + runtime contract verification, and product-surface evidence + - Base remains excluded from active blocker summaries until an explicit + product decision promotes it + - owner signoff is required before Base becomes an active launch lane +- Dependencies: `PROD-001A` +- Source docs: `prediction-market-release-prep.md`, + `tracking-document-map.md` +- Suggested owner: `release` +- Blocker class: `parked` + +## Epic 12: Repository Governance, Review Automation, And OSS Housekeeping + +### PROD-039 Establish the repository governance baseline + +- ID: `PROD-039` +- Title: `Establish the repository governance baseline` +- Type: `docs` +- Priority: `P1` +- Scope: `active` +- Area: `repo-governance` +- Description: Add the missing repository control-plane files that make review, + ownership, disclosure, and contribution expectations explicit from day one. +- Acceptance criteria: + - `.github/CODEOWNERS` exists with clear ownership for protocol, keeper, app, + workflows, docs, and release surfaces + - `CONTRIBUTING.md` exists with branch, commit, testing, and PR expectations + - `SECURITY.md` exists with vulnerability reporting and embargo guidance + - `.github/PULL_REQUEST_TEMPLATE.md` and `.github/ISSUE_TEMPLATE/` exist +- Dependencies: owner list and escalation path +- Source docs: `tracking-document-map.md`, + `production-readiness-audit-2026-03-29.md`, + external benchmarks: `milady/.github/PULL_REQUEST_TEMPLATE.md`, + `HyperscapeAI/hyperscape/.github/CODEOWNERS` +- Suggested owner: `release` +- Blocker class: `quality-blocking` + +### PROD-040 Add a PR evidence contract and automated validation + +- ID: `PROD-040` +- Title: `Add a PR evidence contract and automated validation` +- Type: `ci` +- Priority: `P1` +- Scope: `active` +- Area: `review` +- Description: Require PRs to state what changed, why, how it was verified, and + what evidence exists, then validate that contract automatically for + launch-critical surfaces. +- Acceptance criteria: + - PR template requires `What`, `Why`, `How`, `Testing`, `Evidence`, `Risk`, + and `Rollback` + - protocol, keeper, app, and workflow PRs must include concrete evidence such + as test commands, screenshots, tx hashes, or artifact paths + - CI flags incomplete PR bodies or missing evidence for critical change areas +- Dependencies: `PROD-039` +- Source docs: `stage-a-browser-acceptance-matrix.md`, + `stage-a-promotion-execution-ledger.md`, + `external-audit-package-checklist.md`, + external benchmark: `milady/.github/PULL_REQUEST_TEMPLATE.md` +- Suggested owner: `release` +- Blocker class: `quality-blocking` + +### PROD-041 Add a safe AI-assisted PR review workflow for Hyperbet + +- ID: `PROD-041` +- Title: `Add a safe AI-assisted PR review workflow for Hyperbet` +- Type: `feature` +- Priority: `P1` +- Scope: `active` +- Area: `review-automation` +- Description: Add an AI code-review workflow that improves reviewer throughput + without turning the repo into an agent-governed system or exposing the repo + to unsafe workflow patterns. +- Acceptance criteria: + - one primary AI reviewer is selected for Hyperbet PR review automation + - the workflow is read-first, comment-only, and does not auto-close, auto-merge, + or self-approve PRs + - external-contributor safety is explicit: no unsafe `pull_request_target` + secret exposure, no broad write tokens, no unrestricted tool access + - human CODEOWNER approval remains mandatory for protocol, security, release, + and workflow changes +- Dependencies: `PROD-039`, `PROD-046` +- Source docs: `tracking-document-map.md`, + external benchmarks: `HyperscapeAI/hyperscape/.github/workflows/claude-code-review.yml`, + `milady/.github/workflows/agent-review.yml` +- Suggested owner: `infra` +- Blocker class: `quality-blocking` + +### PROD-042 Add workflow linting and agentic-action security scanning + +- ID: `PROD-042` +- Title: `Add workflow linting and agentic-action security scanning` +- Type: `security` +- Priority: `P1` +- Scope: `active` +- Area: `github-actions` +- Description: Add CI checks that specifically protect GitHub Actions and any + future AI-agent workflows from configuration drift and unsafe trigger or + permission choices. +- Acceptance criteria: + - workflow linting runs in CI for every workflow change + - workflow security scanning covers dangerous triggers, excessive + permissions, unpinned actions, and unsafe agent-tool settings + - CI fails when new workflow files violate the policy +- Dependencies: `PROD-041` +- Source docs: `production-readiness-audit-2026-03-29.md`, + external benchmarks: `milady/.github/actionlint.yaml`, + `HyperscapeAI/hyperscape/.github/workflows/security.yml` +- Suggested owner: `security` +- Blocker class: `quality-blocking` + +### PROD-043 Add repository dependency hygiene automation + +- ID: `PROD-043` +- Title: `Add repository dependency hygiene automation` +- Type: `improvement` +- Priority: `P1` +- Scope: `active` +- Area: `dependencies` +- Description: Add automated dependency update flows for npm workspaces, + GitHub Actions, and Docker surfaces so the repo does not drift into stale or + unreviewable infrastructure. +- Acceptance criteria: + - `dependabot.yml` exists and covers active package directories plus + GitHub Actions + - update groups are tuned to avoid review spam + - the review policy for automated dependency PRs is documented +- Dependencies: `PROD-039` +- Source docs: external benchmark: + `HyperscapeAI/hyperscape/.github/dependabot.yml` +- Suggested owner: `infra` +- Blocker class: `quality-blocking` + +### PROD-044 Add repo security automation beyond Solidity-specific scanning + +- ID: `PROD-044` +- Title: `Add repo security automation beyond Solidity-specific scanning` +- Type: `security` +- Priority: `P1` +- Scope: `active` +- Area: `security-automation` +- Description: Complement the current Slither and protocol gates with repo-wide + security automation for JavaScript/TypeScript, workflow surfaces, and secret + exposure. +- Acceptance criteria: + - CodeQL or equivalent static analysis runs on the active JS/TS codebase + - dependency audit runs on a schedule + - secret scanning policy is documented and automated where practical +- Dependencies: `PROD-042`, `PROD-043` +- Source docs: `production-readiness-audit-2026-03-29.md`, + external benchmark: `HyperscapeAI/hyperscape/.github/workflows/security.yml` +- Suggested owner: `security` +- Blocker class: `quality-blocking` + +### PROD-045 Add label and triage automation for PRs and issues + +- ID: `PROD-045` +- Title: `Add label and triage automation for PRs and issues` +- Type: `improvement` +- Priority: `P2` +- Scope: `active` +- Area: `triage` +- Description: Add lightweight labeling and intake automation so the repo can + route bugs, security reports, protocol changes, release work, and docs work + quickly without turning triage into manual bookkeeping. +- Acceptance criteria: + - path-based PR labels are applied automatically + - issue templates feed consistent labels for bug, security, docs, and ops + - triage labels align with the GitHub project fields +- Dependencies: `PROD-039` +- Source docs: `tracking-document-map.md`, + external benchmarks: `milady/.github/labeler.yml`, + `milady/.github/workflows/auto-label.yml` +- Suggested owner: `release` +- Blocker class: `non-blocking` + +### PROD-046 Define branch protection, required checks, and review policy + +- ID: `PROD-046` +- Title: `Define branch protection, required checks, and review policy` +- Type: `ops` +- Priority: `P1` +- Scope: `active` +- Area: `repo-governance` +- Description: Turn the desired repo-review standards into enforced GitHub + settings for `main`, `develop`, and any protected release branches. +- Acceptance criteria: + - required status checks are defined explicitly for CI, prediction-market + gates, and any new review/security workflows + - stale approvals are dismissed on new commits + - CODEOWNER review is required for sensitive paths + - merge policy is documented and enforced consistently +- Dependencies: `PROD-039`, `PROD-040`, `PROD-041` +- Source docs: `tracking-document-map.md`, + `github-project-production-backlog.md` +- Suggested owner: `release` +- Blocker class: `quality-blocking` + +## Suggested Milestones + +- `M1 - Perimeter hardening, launch truth, and governance closeout` +- `M2 - Hyperscapes integration contract and runtime recovery hardening` +- `M3 - Staged proof, soak, and audit packet` +- `M4 - Reliability, reconciliation, and production operations` +- `M5 - Compliance, launch governance, and long-tail hardening` + +## Suggested First 12 Issues To Open + +1. `PROD-021 Make keeper write auth fail closed across the active keeper surfaces` +2. `PROD-022 Remove origin-based trust from the Solana sender proxy` +3. `PROD-023 Enforce read-only allowlists and quotas on public RPC proxies` +4. `PROD-014 Version and contract-test the Hyperscapes to Hyperbet integration boundary` +5. `PROD-014A Canonicalize keeper ingestion onto the versioned Hyperscapes betting feed` +6. `PROD-014B Make feed parsing fail closed on schema drift, replay mismatch, and source resets` +7. `PROD-001 Populate canonical active launch-chain registry truth from final receipts` +8. `PROD-001A Align active launch constants and feature-flag truth with the current scope contract` +9. `PROD-009 Finish multisig, timelock, and upgrade-authority custody for the active launch chains` +10. `PROD-015B Decide and freeze the production AMM settlement model` +11. `PROD-049 Freeze the active app-shell acceptance contract for the full Hyperbet surface` +12. `PROD-047 Productionize shared wallet and account surfaces across the active runtimes` diff --git a/docs/release/launch-ops-evidence-index.md b/docs/release/launch-ops-evidence-index.md index f0d3e30d..de036c13 100644 --- a/docs/release/launch-ops-evidence-index.md +++ b/docs/release/launch-ops-evidence-index.md @@ -1,10 +1,16 @@ # Launch Ops Evidence Index -> **TL;DR:** The repo now contains the full phase-1 non-mainnet proving rails for `PM + perps + internal AMM`, but the evidence bundle is still incomplete. The remaining gaps are real staged environment provisioning, recorded Stage-A receipts, truthful launch-chain mainnet registry values, governance transfer/freeze receipts, and the final audit freeze packet. +> **TL;DR:** The repo now contains the full phase-1 non-mainnet proving rails for `PM + perps + internal AMM` plus the surrounding app-shell and account surfaces, but the evidence bundle is still incomplete. The active production-readiness path is `Solana + BSC`; AVAX evidence is preserved but isolated and non-blocking. The remaining gaps are real staged environment provisioning, recorded Stage-A receipts, truthful active-scope registry values, frozen app-shell/account-surface evidence, governance transfer/freeze receipts, and the final audit freeze packet. This index ties the current launch closeout work to concrete repo artifacts and records what still needs live evidence before release signoff. +Detailed implementation work is tracked in: + +- [GitHub Project Production Backlog](github-project-production-backlog.md) +- [Runtime Integration Readiness Matrix](runtime-integration-readiness-matrix.md) +- [Tracking Document Map](tracking-document-map.md) + ## Phase-1 Non-Mainnet Proving | Surface | Path | Status | @@ -32,7 +38,8 @@ Canonical launch truth is still missing for: - `solana`: `goldAmmMarketProgramId` - `bsc`: AMM and perps canonical fields -- `avax`: PM-core plus AMM and perps canonical fields +- `avax`: PM-core plus AMM and perps canonical fields remain preserved + follow-on work and are not blocking the active scope ## Governance And Operational Control @@ -51,9 +58,14 @@ Remaining live evidence: ## Current Operational Blockers +These are blocker summaries only. Detailed owner tickets live in the canonical +backlog. + - no GitHub `staging` environment exists yet - no `HYPERBET_*_STAGING_*` vars or secrets are provisioned yet -- local BSC and AVAX AMM/perps bring-up still needs shared token addresses +- local BSC AMM/perps bring-up still needs shared token addresses - no real staged proof artifact bundle exists yet - no real staged soak artifact bundle exists yet +- no frozen full-app acceptance bundle exists yet for wallet/account, + claims/positions, and points/referral surfaces - no truthful launch-chain mainnet registry population exists yet diff --git a/docs/release/localnet-headless-validation-tracker.md b/docs/release/localnet-headless-validation-tracker.md index f0ba387f..961e6baa 100644 --- a/docs/release/localnet-headless-validation-tracker.md +++ b/docs/release/localnet-headless-validation-tracker.md @@ -1,5 +1,7 @@ # Localnet Headless Validation Tracker +> **Historical snapshot:** This tracker preserves branch-era local runner and headless validation findings. Current open-work ownership lives in [tracking-document-map.md](tracking-document-map.md) and [github-project-production-backlog.md](github-project-production-backlog.md). Use this file as context and evidence, not as the canonical blocker list. + > **TL;DR:** The local signoff lane is not blocked by product functionality anymore; it is blocked by local orchestration quality. The main issues are runner assumptions, headless stream proof, and final BSC/AVAX local E2E stabilization. The Hyperbet page does **not** need `?debug=1` for real local betting flow, and the integrated runner starts the stream by shelling into the sibling Hyperscapes repo and executing `bun run duel`. ## Current Findings diff --git a/docs/release/pm-launch-execution-plan.md b/docs/release/pm-launch-execution-plan.md index cff41768..dc1a4450 100644 --- a/docs/release/pm-launch-execution-plan.md +++ b/docs/release/pm-launch-execution-plan.md @@ -1,6 +1,8 @@ # PM Launch Execution Plan -> **TL;DR:** Phase-1 launch scope is `Solana + BSC + AVAX`, with user-facing `PM/CLOB duels` and `perps/models`, plus `AMM` as an internal market-maker/liquidity engine. Testnet/staging still prove the system first, but the active implementation train is now a single branch on `audit/develop-pm-hardening`, not a multi-branch PM-only merge tree. +> **Historical snapshot:** This document is preserved as the phase-1 superset strategy and checklist history. Current open-work ownership lives in [tracking-document-map.md](tracking-document-map.md) and [github-project-production-backlog.md](github-project-production-backlog.md). Use this file for preserved context, not as the canonical blocker list. + +> **TL;DR:** Phase-1 product scope still includes `Solana + BSC + AVAX`, with user-facing `PM/CLOB duels` and `perps/models`, plus `AMM` as an internal market-maker/liquidity engine. The active production-readiness gate, however, is now `Solana + BSC`; AVAX checklist items in this plan are preserved follow-on work and are non-blocking unless the lane is explicitly reactivated. --- @@ -14,6 +16,15 @@ main `audit/develop-pm-hardening` is the only active implementation branch for launch-critical closeout work. Do not split remaining launch scope across side branches unless a later release explicitly chooses to do that again. +## Current Scope Note + +Treat this plan as a preserved superset of phase-1 work. + +- current blocking scope: `Solana + BSC` +- preserved but isolated follow-on lane: `AVAX` +- when a checklist item below mentions AVAX, it is preserved work, not a + blocker for the current signoff decision + --- ## Core Principle: Testnet-First, Mainnet-Is-Ceremony @@ -53,7 +64,9 @@ Everything in Stage A is executed by engineering on testnets with test funds. No - [ ] Execute `freeze_config` on Solana devnet for PM, AMM, and perps - [ ] Record all testnet tx hashes in evidence bundle -**Acceptance:** Both EVM testnets (BSC + AVAX) + Solana devnet deployed for full phase-1 scope. Base is out of scope for this Stage A lane. +**Acceptance:** Active-scope testnets (`BSC + Solana`) are deployed and proven +for the current signoff lane. AVAX checklist items remain preserved follow-on +work. Base is out of scope for this Stage A lane. #### WS 0.2A — Testnet Registry Population @@ -65,7 +78,8 @@ Everything in Stage A is executed by engineering on testnets with test funds. No - [ ] Verify `bun test` deployment tests pass with new values - [ ] Verify `bun x tsc --noEmit` passes for all chain apps -**Acceptance:** Registry is complete for all testnets. No blank fields. Deployment tests pass. +**Acceptance:** Registry is complete for the active testnet scope. No blank +active-scope fields. Deployment tests pass. #### WS 0.3A — Deployment Verification Script @@ -114,15 +128,16 @@ Build a script that validates a deployment is correct. Run it on testnet. Run it - [ ] Settlement reflects game result - [ ] Capture screenshots/recordings of each flow as evidence -**Acceptance:** Every user-facing flow works end-to-end on testnets against deployed v3 contracts with real game integration. +**Acceptance:** Every active-scope user-facing flow works end-to-end on +testnets against deployed v3 contracts with real game integration. #### WS 0.5A — Scenario Testing and Simulation Evidence - [ ] Run full CI gate suite against testnet deployments: - [ ] Solana Exploit Gate (all 6 scenarios) - [ ] EVM Exploit Gate - - [ ] Cross-Chain E2E (Solana, BSC, AVAX) - - [ ] Launch-chain runtime smoke remains green for BSC and AVAX + - [ ] Cross-Chain E2E (Solana, BSC) + - [ ] Launch-chain runtime smoke remains green for BSC - [ ] EVM Contract Proof Gate (anvil adversarial simulation) - [ ] Run market-maker adversarial simulations: - [ ] Seed corpus (all chains) @@ -146,7 +161,8 @@ Build a script that validates a deployment is correct. Run it on testnet. Run it - [ ] Verify resting orders can be reclaimed - [ ] Capture all scenario results as structured evidence artifacts -**Acceptance:** Every exploit scenario, adversarial simulation, and operational drill passes on testnets. Evidence artifacts captured and indexed. +**Acceptance:** Every active-scope exploit scenario, adversarial simulation, and +operational drill passes on testnets. Evidence artifacts captured and indexed. #### WS 0.6A — Evidence Bundle Assembly diff --git a/docs/release/prediction-market-launch-freeze-tracker.md b/docs/release/prediction-market-launch-freeze-tracker.md index 25c2d9aa..946f3c0b 100644 --- a/docs/release/prediction-market-launch-freeze-tracker.md +++ b/docs/release/prediction-market-launch-freeze-tracker.md @@ -1,15 +1,24 @@ # Prediction-Market Launch Freeze Tracker -> **TL;DR:** The repo now carries phase-1 full-product plumbing for `PM/CLOB duels + perps/models + internal AMM`, but the release train is still not deploy-only. The remaining blockers are truthful launch-chain registry population, staged environment provisioning, shared non-mainnet token/address inputs, governance/evidence closeout, and the frozen audit packet plus external audit/remediation cycle. +> **TL;DR:** The repo now carries phase-1 full-product plumbing for `PM/CLOB duels + perps/models + internal AMM` plus the surrounding app shell and account surfaces, but the release train is still not deploy-only. The active production-readiness path is `Solana + BSC`; AVAX evidence is preserved but isolated and non-blocking. The remaining blockers are truthful active-scope registry population, staged environment provisioning, shared non-mainnet token/address inputs, full app-shell and account-surface closure, governance/evidence closeout, and the frozen audit packet plus external audit/remediation cycle. Last updated: 2026-03-25 +Detailed implementation work is tracked in: + +- [GitHub Project Production Backlog](github-project-production-backlog.md) +- [Runtime Integration Readiness Matrix](runtime-integration-readiness-matrix.md) +- [Tracking Document Map](tracking-document-map.md) + ## Current Position - Active implementation train: `audit/develop-pm-hardening` -- Launch-blocking chains: `solana`, `bsc`, `avax` +- Active production-readiness chains: `solana`, `bsc` +- Preserved but isolated follow-on lane: `avax` - Non-blocking add-chain lane: `base` -- Launch-scope off-mainnet rehearsal: `solana devnet`, `bsc testnet`, `avax fuji` +- Implementation target: shared `EVM` runtime plus `SVM`, with `bsc` as the + current active EVM proving wrapper +- Active off-mainnet rehearsal: `solana devnet`, `bsc testnet` - User-facing launch surfaces: - `PM/CLOB duels` - `perps/models` @@ -20,7 +29,11 @@ Last updated: 2026-03-25 - PM-core hardening for oracle finality, order semantics, governance freeze, and protocol guardrails -- oracle-only AMM settlement on EVM and Solana +- repo artifact policy is enforced in CI, and the previously flagged tracked + Solana deploy artifacts are no longer present in the tracked tree +- AMM settlement implementation is materially stronger than before, but the + production settlement model still needs an explicit freeze between + oracle-driven and challenge-window paths - Solana perps pause preserved after freeze - Solana full-product deploy, init, freeze, and verify rails now include AMM - canonical EVM receipt writing for PM, AMM, and perps @@ -30,10 +43,16 @@ Last updated: 2026-03-25 ## Remaining Blockers -### 1. Canonical launch-chain mainnet truth is still incomplete +This document keeps the blocker summary and freeze posture. Detailed execution +ownership lives in the canonical backlog. + +### 1. Canonical active-scope truth is still incomplete Current missing launch-truth fields: +- active-scope launch constants still imply `avax` in places that should now + reflect the parked-chain decision +- default feature flags still understate the intended active product surface - `solana` - `goldAmmMarketProgramId` - `bsc` @@ -47,6 +66,7 @@ Current missing launch-truth fields: - AMM canonical fields - perps canonical fields - governance and operator addresses from final receipts + - preserved follow-on work; not blocking the current production-readiness path ### 2. Staged proof and staged soak are blocked on provisioning @@ -61,12 +81,10 @@ blocked. ### 3. Local Stage-A is waiting on shared token and address inputs -Still missing for honest BSC and AVAX AMM/perps rehearsal: +Still missing for honest active-scope BSC AMM/perps rehearsal: - `BSC_TESTNET_MUSD_TOKEN_ADDRESS` -- `AVAX_FUJI_MUSD_TOKEN_ADDRESS` - `BSC_TESTNET_GOLD_TOKEN_ADDRESS` -- `AVAX_FUJI_GOLD_TOKEN_ADDRESS` - optional perps margin token addresses when margin is not the GOLD token ### 4. Governance and audit evidence are still open @@ -81,14 +99,20 @@ Still required: - RC freeze manifest - external audit and remediation outputs +### 5. AMM settlement truth, coordinated full-product smoke, and app/account surface closure are still open + +Still required: + +- explicit production AMM settlement-model freeze +- audit-grade explanation or tightening of the Solana AMM settlement account + posture +- one coordinated staged smoke and evidence bundle for the enabled full-product + surfaces +- one frozen product contract for wallet/account, claims/positions, and + points/referral surfaces on the active runtimes + ## Ordered Next Steps -1. Provision the missing shared testnet token and address inputs. -2. Run local Stage-A bring-up and verification. -3. Create the GitHub `staging` environment and load all required staged vars - and secrets. -4. Run staged proof for `pm`, `perps`, and `amm`. -5. Run staged soak and archive the artifact bundle. -6. Populate launch-chain mainnet registry truth from final receipts only. -7. Complete governance transfer and freeze evidence. -8. Freeze the RC and hand off the audit packet. +The ordered next steps are now owned by the canonical backlog and runtime +matrix. Use this document to verify that freeze posture and blocker summaries +still match those sources. diff --git a/docs/release/production-readiness-audit-2026-03-29.md b/docs/release/production-readiness-audit-2026-03-29.md new file mode 100644 index 00000000..6f46a8b5 --- /dev/null +++ b/docs/release/production-readiness-audit-2026-03-29.md @@ -0,0 +1,453 @@ +# Hyperbet Production Readiness Audit - 2026-03-29 + +> **TL;DR:** Hyperbet is materially stronger and already proves real non-mainnet browser-to-chain betting flows, but it is not yet production-ready for a real-money Hyperscapes betting launch. The biggest remaining gaps are keeper and public-API perimeter hardening, a pinned and consistently enforced `Hyperscapes -> Hyperbet` integration contract, canonical launch-chain registry truth, staged and production environment provisioning, shared app-shell and account-surface closure, governance custody and freeze evidence, durable keeper operations, external audit package completion, and betting-product operational controls that do not yet exist in the repo. + +## Purpose + +This document is the production-readiness audit snapshot for the Hyperbet repo as +of `2026-03-29`. + +It answers two questions: + +1. what is already strong enough to keep +2. what still has to be built, proven, or operated before Hyperbet can be + treated as a production betting product for Hyperscapes + +This audit covers: + +- code and release artifacts in this repo +- repo-adjacent operational work explicitly referenced by the repo +- betting-product workstreams that are not yet represented in code but are + required for a real production launch + +This audit does **not** redefine product scope, but it does make the current +production-readiness gate explicit: + +- active production-readiness scope is `Solana` and `BSC` +- `AVAX` remains a preserved but isolated lane; existing work and evidence stay + in place, but AVAX-specific gaps do not block the current readiness decision +- `Base` remains a non-blocking add-chain lane +- implementation target remains runtime-family based: shared `EVM` plus `SVM`, + with `BSC` serving as the current active EVM wrapper rather than the + long-term exclusive EVM chain target +- user-facing surfaces remain `PM/CLOB duels` and `perps/models` +- `AMM` remains an internal market-making surface, not a retail browser surface + +Detailed execution ownership now lives in: + +- [GitHub Project Production Backlog](github-project-production-backlog.md) +- [Runtime Integration Readiness Matrix](runtime-integration-readiness-matrix.md) +- [Tracking Document Map](tracking-document-map.md) + +## Evidence Base + +Primary source documents for this audit: + +- [Tracking Document Map](tracking-document-map.md) +- [Runtime Integration Readiness Matrix](runtime-integration-readiness-matrix.md) +- [Prediction Market Release Prep](../prediction-market-release-prep.md) +- [Launch Ops Evidence Index](launch-ops-evidence-index.md) +- [Prediction-Market Launch Freeze Tracker](prediction-market-launch-freeze-tracker.md) +- [Stage-A Browser Acceptance Matrix](stage-a-browser-acceptance-matrix.md) +- [Residual Risk Register](residual-risk-register.md) +- [Threat Model](threat-model.md) +- [PM Launch Execution Plan](pm-launch-execution-plan.md) +- [External Audit Package Checklist](external-audit-package-checklist.md) +- [Hyperbet Production Deploy](../hyperbet-production-deploy.md) +- [Cross-Chain Parity Matrix](../protocol/cross-chain-parity-matrix.md) +- [Perps EVM/SVM Parity Matrix](../perps-parity-matrix.md) +- [Hyperbet System Design Alignment](../system-design-alignment.md) +- [Contract Privileged Surface Inventory](contract-privileged-surface-inventory.md) +- [Hyperscapes Local PM Integration](../runbooks/hyperscapes-local-pm-integration.md) +- [Prediction-Market Test Flow](../runbooks/prediction-market-test-flow.md) + +The board-ready follow-on backlog lives in +[GitHub Project Production Backlog](github-project-production-backlog.md). + +## Canonical Tracking Contract + +- this document owns the production-readiness verdict and blocker inventory +- the backlog owns detailed implementation work +- the runtime matrix owns the answer to “is the end-to-end loop actually + running?” +- historical ledgers and branch-era plans preserve evidence, but do not own + current blockers + +## Executive Summary + +### What is already true + +- Stage-A non-mainnet proving is real, not paper-only. +- Direct protocol canaries exist across the phase-1 product surfaces. +- Browser-to-chain acceptance exists against real deployed Stage-A chains. +- The real Hyperscapes duel lane has been proven locally and is documented. +- Repo artifact policy is enforced in CI, and the previously flagged tracked + Solana deploy artifacts are no longer present in the tracked tree. +- Governance freeze and pause controls are materially stronger than the earlier + convergence baseline. +- The repo now contains credible release, ops, threat, and audit-prep + documentation. +- Existing AVAX deployments and investigation work are preserved, but AVAX is + no longer on the current production-critical path. + +### What is still blocking production readiness + +- Keeper and public-API auth hardening is incomplete and contains verified + launch-blocking gaps. +- The `Hyperscapes -> Hyperbet` feed contract is only partially versioned and is + enforced asymmetrically across keeper variants. +- Active launch-chain registry truth is incomplete for `Solana` and `BSC`, and + the active-scope constants and default feature truth still drift from the + current `BSC + Solana` contract; + AVAX registry completion remains non-blocking while the chain is isolated. +- GitHub staged environment and staged proof/soak provisioning are incomplete. +- Governance custody, ownership transfer, upgrade-authority transfer, and final + freeze evidence are incomplete. +- Shared wallet/account surfaces, points/referrals product claims, and full + app-shell acceptance are not yet frozen as one launch-grade contract. +- The AMM settlement model and coordinated full-product staged smoke are not + yet frozen to one audit-grade truth. +- Keeper persistence, observability, alerting, reconciliation, and rollback + posture are not yet production-grade. +- The external audit packet is not complete and a remediation closeout loop has + not been demonstrated from a frozen RC. +- The repo does not yet cover the full legal, compliance, treasury, support, + and incident-management obligations of a real betting product. + +### Overall assessment + +Current maturity: **Moderate with launch-blocking perimeter and integration flaws** + +Release posture: **strong testnet acceptance, not yet production-ready** + +## Current Green State + +The following areas should be treated as already landed and should not be +re-opened without a concrete regression: + +- Stage-A browser acceptance for the current branch scope +- real-duel local Hyperscapes integration lane +- direct canary and verification rails +- launch-scope release docs and freeze tracking +- EVM governance freeze posture +- Solana freeze and pause posture +- explicit distinction between browser surfaces and internal AMM operations + +## AVAX Scope Decision + +AVAX is not being undone, removed, or denied as future scope. The current +decision is narrower: + +- keep existing AVAX code, deployments, and evidence intact +- stop treating AVAX incompleteness as a blocker for the current + production-readiness path +- do not open new AVAX-specific critical-path work unless the lane is + intentionally reactivated + +## Security Findings Snapshot + +The following findings were verified directly against the current repo snapshot +and should be treated as production blockers, not abstract “audit later” work. + +| Severity | Finding | Evidence | Production impact | +|---|---|---|---| +| Critical | BSC, Solana, and AVAX keepers fail open when `ARENA_WRITE_KEY` is unset. | `requireWriteAuth()` returns `true` on missing key in `packages/hyperbet-bsc/keeper/src/service.ts`, `packages/hyperbet-solana/keeper/src/service.ts`, and `packages/hyperbet-avax/keeper/src/service.ts`; the canonical EVM keeper already rejects this state in `packages/hyperbet-evm/keeper/src/service.ts`. | Unauthorized callers can reach write paths such as invite redeem, wallet link, external bet recording fallback, and stream publish if deployment envs omit the key. This is an active blocker for `BSC` and `Solana`; AVAX remains isolated and therefore non-blocking in the current launch gate. | +| High | The Solana sender proxy treats browser `Origin` as authorization. | `handleSolanaSenderProxy()` in `packages/hyperbet-solana/keeper/src/service.ts` allows requests from any allowed origin even without the write key. | A forged client can relay whitelisted Hyperbet Solana transactions through the keyed sender proxy, turning the keeper into an open relay. | +| High | Public RPC proxies do not enforce a read-only method allowlist. | `handleSolanaRpcProxy()` and `handleEvmRpcProxy()` in the keeper services validate JSON-RPC shape and cache TTLs, but do not restrict forwarded methods. | Public clients can consume keyed upstream quota with arbitrary JSON-RPC methods and widen the externally reachable provider surface beyond the documented “read-only proxy” contract. | +| High | Most keepers still trust the legacy raw Hyperscapes stream instead of the newer versioned betting-feed contract. | The local EVM runner wires `BET_SYNC_SOURCE_STATE_URL` and `BET_SYNC_SOURCE_EVENTS_URL` to `/api/internal/bet-sync/*` in `scripts/run-hyperscapes-pm-local.sh`, and Hyperscapes emits `schemaVersion`, `sourceEpoch`, `seq`, `phaseVersion`, and `rendererHealth` from `packages/server/src/routes/streaming-betting-routes.ts`; meanwhile BSC, Solana, and AVAX still ingest `/api/streaming/state` through permissive `toStreamState()` parsers in their keeper services. | Upstream feed drift, replay/reset events, or source-epoch changes can degrade into silent keeper desync instead of a fail-closed integration error. This is launch-blocking for the active `BSC` and `Solana` scope; AVAX remains a preserved follow-on lane. | + +## Maturity Scorecard + +| Category | Rating | Notes | +|---|---|---| +| Arithmetic safety | Moderate | Strong parity and guardrail work exists, but precision and edge-case follow-through is still tracked in hardening plans and exploit coverage. | +| Auditing and observability | Weak | Audit-prep docs exist, but staged evidence, production telemetry, alerting, and incident response automation are incomplete. | +| Authentication and access control | Weak | Governance freeze is materially improved, but live custody transfer is incomplete and the keeper perimeter still has verified fail-open write auth and proxy-trust gaps. | +| Complexity management | Moderate | The architecture direction is clear, but BSC/AVAX wrappers, keeper boundaries, and the Hyperscapes feed contract are still converging toward one canonical runtime contract. AVAX is currently isolated from the active delivery path rather than treated as a gate. | +| Decentralization and custody | Weak | Production governance posture still depends on finishing multisig/timelock rollout and final registry-backed custody truth. | +| Documentation | Satisfactory | Release, threat, parity, and runbook coverage is strong, but production checklists still point to unfinished evidence and freeze outputs. | +| Transaction-ordering risk | Moderate | Risks are understood and documented, but they are largely accepted-risk mitigations rather than full protections. | +| Low-level and unsafe primitives | Satisfactory | No broad low-level red flags dominate the current codebase, but some generated and operational edges still need cleanup. | +| Testing and verification | Moderate | Non-mainnet proving is unusually strong, but staged proof, staged soak, frozen audit packet, and some adversarial gaps remain open. | + +## Detailed Gap Inventory + +### 1. Keeper and API perimeter hardening is incomplete + +The current repo snapshot still exposes internet-facing keeper behavior that is +not safe for production. + +Concrete gaps verified in code: + +- BSC, Solana, and AVAX keepers return `true` from `requireWriteAuth()` when + `ARENA_WRITE_KEY` is unset, unlike the EVM keeper which already fails closed +- write routes protected by that helper include invite redeem, wallet linking, + external bet recording fallback, and stream publish +- the Solana sender proxy currently accepts either a privileged write key or a + trusted `Origin` header, which is not a trustworthy authentication boundary +- public Solana and EVM RPC proxies are described as public keyed read-only + proxies in env docs, but the code does not enforce a read-only method set + +Why this matters: + +- production keepers should never become writable because an env var is missing +- proxy trust based on `Origin` is not a secure server-to-server control +- keyed public RPC surfaces need explicit method and quota boundaries, not just + operator intent + +### 2. Active launch-chain canonical truth is not complete + +The repo still does not contain truthful launch-chain registry values for all +active production surfaces. + +Concrete gaps called out by the release docs: + +- `BETTING_LAUNCH_EVM_CHAIN_ORDER` still implies `avax` instead of the parked + chain posture +- `DEFAULT_FEATURE_FLAGS` still default `perps: false` and `amm: false` even + though active launch docs describe a broader product destination +- `solana` missing canonical `goldAmmMarketProgramId` +- `bsc` missing canonical AMM and perps fields +- `avax` still has incomplete PM, AMM, perps, and governance/operator fields, + but those gaps are currently isolated and non-blocking + +Why this matters: + +- the chain registry is the canonical runtime source of truth +- production deploys and release docs cannot honestly promote without it +- SDK, app builds, and operational tooling cannot stabilize around placeholders + for the active `BSC`/`Solana` launch path + +### 3. Staged and production environment provisioning is incomplete + +The repo already has staged proof and soak rails, but the live environment +contract is not actually provisioned. + +Repo-evidenced gaps: + +- no GitHub `staging` environment +- no `HYPERBET_*_STAGING_*` vars or secrets +- no real staged proof bundle +- no real staged soak bundle + +Why this matters: + +- testnet-first is the release model in this repo +- without staged proof and soak, mainnet remains ceremony-only on paper + +### 4. Governance custody and freeze closeout is incomplete + +The code-level freeze posture is stronger than before, but production custody is +not complete until the live authorities are actually transferred and frozen. + +Repo-evidenced gaps: + +- ownership transfer receipts +- final freeze receipts +- signer provisioning completion +- key-rotation closeout for historical deploy keys +- Solana upgrade-authority transfer evidence + +Why this matters: + +- a frozen governance model is only real once receipts exist +- the threat model assumes multisig and timelock custody, not ad hoc key + handling + +### 5. The Hyperscapes integration boundary is only partially formalized + +The upstream game-to-betting contract has evolved, but Hyperbet still consumes +it asymmetrically. + +Repo-evidenced gaps: + +- Hyperscapes now exposes an authenticated internal betting feed at + `/api/internal/bet-sync/state` and `/api/internal/bet-sync/events` +- that feed carries explicit `schemaVersion`, `sourceEpoch`, `seq`, + `phaseVersion`, and `rendererHealth` fields plus replay/reset semantics +- the local EVM runner already wires the keeper to that richer feed contract +- the current local real-duel evidence resolves against the sibling + `hyperscapes-main-latest-e2e` checkout at commit `4bb8987dc`, but that target + is not yet recorded as a formal Hyperbet release dependency +- the canonical EVM keeper consumes and tracks the richer feed via + `packages/hyperbet-evm/keeper/src/betSync.ts` +- BSC, Solana, and AVAX keepers still poll the legacy + `/api/streaming/state` surface and accept any object with a `cycle` +- those raw-stream parsers silently synthesize missing `seq` and `emittedAt` + instead of rejecting incompatible or degraded payloads +- the current soak and proof posture still leans on state polling as the + primary verification lane rather than treating direct event-feed consumption, + persisted checkpoints, and idempotent replay as the canonical release proof +- the local Hyperscapes integration runbook still describes + `/api/streaming/state` as the primary keeper contract even though the local + runner already prefers the internal bet-sync contract for EVM + +Why this matters: + +- launch releases need one pinned upstream contract, not multiple implied ones +- replay/reset and source-epoch transitions are part of correctness, not only + developer convenience +- permissive parsing hides integration regressions until market state has + already drifted + +### 6. Runtime and product architecture are still converging + +The intended design is one shared product with chain adapters, but some package +layout and keeper responsibilities are still transitional. + +Repo-evidenced gaps: + +- BSC remains the active EVM launch wrapper and AVAX remains a preserved but + isolated wrapper +- canonical EVM runtime convergence is not complete +- several active tickets and docs previously used `BSC` as shorthand for the + active EVM lane, which can cause negative work unless issue conversion keeps + the shared-EVM implementation contract explicit +- SDK surfaces are not yet locked to canonical registry truth +- public chain/runtime contracts between Hyperbet and Hyperscapes are not yet + packaged as a versioned release contract +- the EVM AMM still exposes both challenge-window settlement and oracle-driven + settlement, so production AMM settlement truth is not frozen +- PM has the strongest live browser-backed evidence today; coordinated + full-product staged smoke and evidence parity for the remaining active + surfaces are still open +- wallet/account surfaces and rewards surfaces are still stronger as proven + features than as frozen product contracts with durable support expectations + +Why this matters: + +- production incidents get harder to reason about when each chain drifts +- duplicated wrapper logic increases maintenance and review cost +- auditors and operators should not have to infer AMM settlement truth from + contradictory implementation and documentation + +### 7. Keeper durability and operational reliability are not complete + +The deployment guide is explicit that keeper SQLite is ephemeral unless the +state backend is changed or persistence is attached. + +Repo-evidenced gaps: + +- durable keeper storage strategy +- backup and restore runbooks +- production observability dashboards and alert thresholds +- on-call escalation and incident evidence packaging +- reconciliation tooling for claims, points, referrals, and operator review + +Why this matters: + +- a betting product needs durable audit trails and recovery paths +- real customer support depends on deterministic reconstruction of market and + payout state + +### 8. Security verification is not complete + +The repo has strong exploit and parity work, but the audit packet is still not +frozen, not all explicit test gaps are closed, and the keeper/API perimeter +issues above need remediation before an external audit closeout can honestly be +treated as final. + +Repo-evidenced gaps: + +- missing explicit reentrancy exploit test tracking item +- frozen external audit packet still incomplete +- staged proof and soak evidence missing from the audit package +- final findings ledger and RC freeze manifest still open + +Why this matters: + +- external audit handoff cannot be honest without frozen artifacts +- accepted risk needs explicit owner signoff and expiry, not just a document + +### 9. Gold asset semantics are still a phase-separation gap + +The repo is explicit that phase-1 does not yet have a fully solved cross-chain +Gold architecture. + +Repo-evidenced gaps: + +- canonical Gold source-of-truth decision +- issuance and redemption model +- cross-chain representation model +- reserve and reconciliation invariants +- EVM Gold alias cleanup after the architecture decision + +Why this matters: + +- a production betting application cannot make stronger Gold claims than the + implemented asset model can support + +### 10. Betting-product operational controls are still missing + +The repo documents operator runbooks but does not yet represent the full +commercial launch obligations of a real betting application. + +Operational requirements inferred from the product category: + +- jurisdiction and licensing review +- KYC/AML posture +- age gating and responsible-gaming controls +- terms of service, privacy, and risk disclosures +- treasury, accounting, and tax operations +- customer support, dispute handling, and incident communications + +Why this matters: + +- these are launch-critical for a real-money betting product even when the repo + itself does not yet implement them + +## Launch Readiness Verdict + +### Ready today + +- continued non-mainnet proving +- internal review and audit-prep work +- turning the current branch evidence into an executable project backlog + +### Not ready today + +- production promotion +- internet-exposed keeper deployments with the current perimeter posture +- public claims of mainnet launch readiness +- external audit handoff as a final frozen packet +- real-money betting launch operations + +## Recommended Execution Order + +1. close keeper and public-API auth hardening blockers +2. formalize and harden the Hyperscapes integration boundary across the active + keeper surfaces, promote direct event-feed proof, and keep AVAX isolated +3. close active launch-chain registry truth, active-scope constants, feature + truth, and governance custody for `BSC` and `Solana` +4. freeze the AMM settlement model and close the remaining coordinated + full-product smoke and evidence gaps +5. provision staged environment and capture truthful staged proof/soak evidence +6. freeze the shared app-shell, wallet/account, and rewards product contract + for the active runtimes +7. harden keeper durability, observability, and reconciliation +8. freeze and deliver the external audit packet, then remediate findings +9. finish repo-adjacent production controls: legal, compliance, treasury, and + support +10. only then treat mainnet as a release ceremony + +## Recommended GitHub Project Shape + +Use a single production-readiness project with epics grouped by: + +- chain truth and deploy integrity +- staged and production environment readiness +- governance and key management +- Hyperscapes integration and runtime convergence +- application shell, account surfaces, and rewards +- reliability, observability, and reconciliation +- security hardening and external audit +- Gold asset architecture +- compliance, treasury, support, and launch operations +- repository governance and review automation + +Use one full Kanban board with no dated sprint contract. Sequence work by +status, priority, dependencies, scope, runtime applicability, and blocker +class. + +The issue-ready seed list for that project is in +[GitHub Project Production Backlog](github-project-production-backlog.md). diff --git a/docs/release/release-memo-template.md b/docs/release/release-memo-template.md index f9fae9bd..323d773f 100644 --- a/docs/release/release-memo-template.md +++ b/docs/release/release-memo-template.md @@ -1,6 +1,6 @@ # Release Memo: Phase-1 RC Candidate -> **TL;DR:** This memo tracks the current release-candidate posture for the `Solana + BSC + AVAX` phase-1 launch product. The repo now has full-product non-mainnet rails for `PM/CLOB duels + perps/models + internal AMM`, but launch is still blocked on canonical mainnet registry truth, staged environment provisioning, governance/evidence receipts, and the external audit/remediation cycle. +> **TL;DR:** This memo tracks the current release-candidate posture for the active `Solana + BSC` production-readiness gate, with `AVAX` preserved as an isolated follow-on lane. The repo now has full-product non-mainnet rails for `PM/CLOB duels + perps/models + internal AMM`, but launch is still blocked on canonical active-scope truth, staged environment provisioning, governance/evidence receipts, coordinated full-product smoke, and the external audit/remediation cycle. ## Release Candidate @@ -12,7 +12,8 @@ ## Product Scope -- Launch chains: `Solana`, `BSC`, `AVAX` +- Active production-readiness chains: `Solana`, `BSC` +- Preserved but isolated follow-on lane: `AVAX` - Non-blocking add-chain lane: `Base` - User-facing launch surfaces: - `PM/CLOB duels` @@ -23,7 +24,8 @@ ## Repo Snapshot - PM-core hardening is merged. -- AMM settlement is oracle-only on EVM and Solana. +- AMM settlement implementation is materially stronger than before, but the + production settlement model still needs an explicit freeze. - Solana perps pause survives config freeze. - Solana full-product deploy, init, freeze, and verify paths include `lvr_amm`. - EVM deploy receipts and verification now cover PM, AMM, and perps. @@ -32,11 +34,13 @@ ## Blocking Items -- launch-chain canonical mainnet registry fields are still incomplete +- active-scope launch constants, feature truth, and canonical registry fields + are still incomplete - GitHub staged environment vars and secrets are not provisioned yet -- shared BSC and AVAX testnet token addresses are still missing for local - AMM/perps rehearsal +- shared BSC testnet token addresses are still missing for local AMM/perps + rehearsal - governance transfer and freeze receipts are still pending +- coordinated full-product staged smoke and evidence bundle are still pending - final audit packet, external audit, and remediation are still pending ## Evidence Links diff --git a/docs/release/runtime-integration-readiness-matrix.md b/docs/release/runtime-integration-readiness-matrix.md new file mode 100644 index 00000000..8acd9d34 --- /dev/null +++ b/docs/release/runtime-integration-readiness-matrix.md @@ -0,0 +1,59 @@ +# Runtime Integration Readiness Matrix + +> **TL;DR:** This matrix is the canonical end-to-end runtime status view for Hyperbet's active production-readiness gate. It answers whether `Hyperscapes emits duel -> Hyperbet ingests stream -> Hyperbet writes on-chain -> Hyperbet discovers result -> Hyperbet resolves/claims -> Hyperbet recovers after restart` is actually working for `BSC + Solana`. `AVAX` is preserved as a parked follow-on lane and is intentionally omitted from the active blocker table. The `BSC` rows represent the current active EVM wrapper, not a BSC-only implementation target. + +## Scope And Legend + +- active gate: `BSC + Solana` +- parked lane: `AVAX` +- implementation note: `BSC` rows represent the active EVM proving wrapper; + shared EVM work should land in canonical EVM paths unless explicitly + wrapper-specific +- statuses: + - `Green in Stage-A`: proven locally against deployed Stage-A chains + - `Partial`: working evidence exists, but production-grade hardening or canonicalization is still open + - `Blocked`: not yet production-ready for the active launch gate + +Primary evidence documents: + +- [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md) +- [stage-a-promotion-execution-ledger.md](stage-a-promotion-execution-ledger.md) +- [launch-ops-evidence-index.md](launch-ops-evidence-index.md) + +Open work ownership lives in: + +- [github-project-production-backlog.md](github-project-production-backlog.md) + +## Active Runtime Loops + +| Chain | Runtime loop | Current status | Evidence | Owner doc / ticket | Active blocker | Scope note | +|---|---|---|---|---|---|---| +| `BSC` | Duel discovery / stream ingestion | Partial | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md), [../runbooks/hyperscapes-local-pm-integration.md](../runbooks/hyperscapes-local-pm-integration.md) | `github-project-production-backlog.md` / `PROD-014`, `PROD-014A`, `PROD-014B`, `PROD-018A` | Active BSC keeper still needs a single canonical versioned feed contract with fail-closed parsing, durable checkpoints, and event-feed proof | Active | +| `BSC` | Market materialization | Green in Stage-A | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md), [stage-a-promotion-execution-ledger.md](stage-a-promotion-execution-ledger.md) | `github-project-production-backlog.md` / `PROD-001`, `PROD-006` | Canonical launch registry truth and staged proof artifacts still need to be frozen | Active | +| `BSC` | Trade posting | Green in Stage-A | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md) | `github-project-production-backlog.md` / `PROD-006`, `PROD-007A` | Needs coordinated staged smoke evidence bundle and production telemetry | Active | +| `BSC` | Result discovery / correlation | Partial | [stage-a-promotion-execution-ledger.md](stage-a-promotion-execution-ledger.md) | `github-project-production-backlog.md` / `PROD-014B`, `PROD-019` | Result correlation and operator reconciliation are not yet productionized | Active | +| `BSC` | Resolve / claim / refund | Green in Stage-A | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md) | `github-project-production-backlog.md` / `PROD-010`, `PROD-025` | Governance freeze receipts and audit-packet evidence still need to be attached | Active | +| `BSC` | Perps / models lifecycle | Partial | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md), [../prediction-market-release-prep.md](../prediction-market-release-prep.md) | `github-project-production-backlog.md` / `PROD-015`, `PROD-001`, `PROD-007A` | Final BSC product claim, canonical perps addresses, and coordinated staged browser evidence are still open | Active | +| `BSC` | Wallet / account shell | Partial | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md), [../prediction-market-release-prep.md](../prediction-market-release-prep.md) | `github-project-production-backlog.md` / `PROD-047`, `PROD-049` | Shared wallet/account behavior, claims/positions shell truth, and full-app acceptance are not yet frozen as one canonical launch contract | Active | +| `BSC` | Points / referrals / rewards | Partial | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md), [production-readiness-audit-2026-03-29.md](production-readiness-audit-2026-03-29.md) | `github-project-production-backlog.md` / `PROD-048`, `PROD-050`, `PROD-021` | Rewards durability, referral/account support posture, and operator reconciliation are still open | Active | +| `BSC` | Internal AMM / liquidity dependencies | Partial | [stage-a-promotion-execution-ledger.md](stage-a-promotion-execution-ledger.md), [launch-ops-evidence-index.md](launch-ops-evidence-index.md) | `github-project-production-backlog.md` / `PROD-015A`, `PROD-015B`, `PROD-015C`, `PROD-001`, `PROD-006`, `PROD-007A` | Shared BSC token inputs, AMM settlement-model freeze, canonical AMM fields, and coordinated staged evidence remain open | Active | +| `BSC` | Restart recovery / replay / backfill | Partial | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md) | `github-project-production-backlog.md` / `PROD-018A`, `PROD-014B` | Recovery is proven in Stage-A, but automated replay, reset, and backfill handling is not finished | Active | +| `BSC` | Observability / alerting / reconciliation | Blocked | [../hyperbet-production-deploy.md](../hyperbet-production-deploy.md), [production-readiness-audit-2026-03-29.md](production-readiness-audit-2026-03-29.md) | `github-project-production-backlog.md` / `PROD-017`, `PROD-018`, `PROD-019` | No production-grade persistence, dashboards, alert thresholds, or operator reconciliation tooling yet | Active | +| `Solana` | Duel discovery / stream ingestion | Partial | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md), [../runbooks/hyperscapes-local-pm-integration.md](../runbooks/hyperscapes-local-pm-integration.md) | `github-project-production-backlog.md` / `PROD-014`, `PROD-014A`, `PROD-014B`, `PROD-018A` | Solana keeper still needs the same canonical versioned feed, durable checkpoints, and fail-closed parsing story as BSC | Active | +| `Solana` | Market materialization | Green in Stage-A | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md), [stage-a-promotion-execution-ledger.md](stage-a-promotion-execution-ledger.md) | `github-project-production-backlog.md` / `PROD-001`, `PROD-006` | `goldAmmMarketProgramId` and staged proof packaging still need closeout | Active | +| `Solana` | Trade posting | Green in Stage-A | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md) | `github-project-production-backlog.md` / `PROD-006`, `PROD-007A` | Needs coordinated staged smoke evidence bundle and production telemetry | Active | +| `Solana` | Result discovery / correlation | Partial | [stage-a-promotion-execution-ledger.md](stage-a-promotion-execution-ledger.md) | `github-project-production-backlog.md` / `PROD-014B`, `PROD-019` | Production result correlation and operator reconciliation remain open | Active | +| `Solana` | Resolve / claim / refund | Green in Stage-A | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md) | `github-project-production-backlog.md` / `PROD-010`, `PROD-025` | Governance receipts and frozen audit evidence still need closeout | Active | +| `Solana` | Perps / models lifecycle | Green in Stage-A | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md), [stage-a-promotion-execution-ledger.md](stage-a-promotion-execution-ledger.md) | `github-project-production-backlog.md` / `PROD-005`, `PROD-009`, `PROD-010` | Production custody, staged proof artifacts, and audit evidence are still open | Active | +| `Solana` | Wallet / account shell | Partial | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md), [../prediction-market-release-prep.md](../prediction-market-release-prep.md) | `github-project-production-backlog.md` / `PROD-047`, `PROD-049` | Shared wallet/account behavior, claims/positions shell truth, and full-app acceptance are not yet frozen as one canonical launch contract | Active | +| `Solana` | Points / referrals / rewards | Partial | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md), [production-readiness-audit-2026-03-29.md](production-readiness-audit-2026-03-29.md) | `github-project-production-backlog.md` / `PROD-048`, `PROD-050`, `PROD-021` | Rewards durability, referral/account support posture, and operator reconciliation are still open | Active | +| `Solana` | Internal AMM / liquidity dependencies | Green in Stage-A | [stage-a-promotion-execution-ledger.md](stage-a-promotion-execution-ledger.md), [launch-ops-evidence-index.md](launch-ops-evidence-index.md) | `github-project-production-backlog.md` / `PROD-001`, `PROD-006`, `PROD-015B`, `PROD-015C`, `PROD-007A` | AMM settlement-model freeze, Solana settlement account auditability, registry completion, and coordinated staged evidence remain open | Active | +| `Solana` | Restart recovery / replay / backfill | Partial | [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md) | `github-project-production-backlog.md` / `PROD-018A`, `PROD-014B` | Recovery is proven in Stage-A, but automated replay/reset/backfill handling is not finished | Active | +| `Solana` | Observability / alerting / reconciliation | Blocked | [../hyperbet-production-deploy.md](../hyperbet-production-deploy.md), [production-readiness-audit-2026-03-29.md](production-readiness-audit-2026-03-29.md) | `github-project-production-backlog.md` / `PROD-017`, `PROD-018`, `PROD-019` | No production-grade persistence, dashboards, alert thresholds, or operator reconciliation tooling yet | Active | + +## Parked AVAX Note + +`AVAX` is preserved but intentionally parked. Existing AVAX code, evidence, and investigation stay in the repo, but AVAX rows are excluded from the active blocker table until the lane is explicitly reactivated through: + +- `PROD-037 Preserve AVAX as a parked epic without active blocker status` +- `PROD-038 Define AVAX reactivation gates and re-entry checklist` diff --git a/docs/release/stage-a-browser-acceptance-matrix.md b/docs/release/stage-a-browser-acceptance-matrix.md index cd838bdb..6f412763 100644 --- a/docs/release/stage-a-browser-acceptance-matrix.md +++ b/docs/release/stage-a-browser-acceptance-matrix.md @@ -1,5 +1,7 @@ # Stage-A Browser Acceptance Matrix +> **Historical snapshot:** This document preserves browser-to-chain and real-Hyperscapes acceptance evidence for the Stage-A branch effort. Current open-work ownership lives in [tracking-document-map.md](tracking-document-map.md) and [github-project-production-backlog.md](github-project-production-backlog.md). Use this matrix as evidence, not as the canonical blocker list. + This matrix tracks the browser-to-chain acceptance bar for the current Hyperbet branch against the deployed Stage-A chains: - Solana `devnet` diff --git a/docs/release/stage-a-promotion-execution-ledger.md b/docs/release/stage-a-promotion-execution-ledger.md index 33f57566..408649ec 100644 --- a/docs/release/stage-a-promotion-execution-ledger.md +++ b/docs/release/stage-a-promotion-execution-ledger.md @@ -1,5 +1,7 @@ # Stage-A Promotion Execution Ledger +> **Historical snapshot:** This is an append-only execution ledger for the Stage-A promotion run. Current open-work ownership lives in [tracking-document-map.md](tracking-document-map.md) and [github-project-production-backlog.md](github-project-production-backlog.md). Use this file for evidence and transaction history, not as the canonical blocker list. + > **TL;DR:** This is the live execution log for the current non-mainnet promotion run. The Stage-A wallet set under `keys/stage-a/` is live, all three Stage-A deployments are on-chain on `Solana devnet`, `BSC testnet`, and `AVAX Fuji`, and direct protocol canaries are green on all three chains. Synthetic browser-to-chain acceptance is green on BSC and AVAX and green on the default Solana browser lanes. The explicit Solana matured-winner-claim lane is proven separately in-browser on the recorded `real_hyperscapes` fixture after `finalizableAt`. The real Hyperscapes browser lane is green for the targeted BSC PM, AVAX PM, Solana PM, Solana CLOB, keeper-restart recovery, Hyperscapes-restart recovery, and the bounded observe-only soak. The recorded real Solana proposal-stage fixture is now also finalized and claimed on-chain in-browser. Stage-A browser-to-chain signoff on this branch is complete. AMM is not part of browser-surface signoff. This document records the exact commands, balances, transaction hashes, and blockers for the current Stage-A promotion run. It is intentionally operational and append-only for this execution cycle. diff --git a/docs/release/testnet-operations-ledger.md b/docs/release/testnet-operations-ledger.md index 5add5763..df0bcbf9 100644 --- a/docs/release/testnet-operations-ledger.md +++ b/docs/release/testnet-operations-ledger.md @@ -1,5 +1,7 @@ # Testnet Operations Ledger +> **Historical snapshot:** This ledger preserves the Stage-A wallet, provisioning, and funding record for the branch-era non-mainnet work. Current open-work ownership lives in [tracking-document-map.md](tracking-document-map.md) and [github-project-production-backlog.md](github-project-production-backlog.md). Use this file as evidence and provisioning history, not as the canonical blocker list. + > **TL;DR:** This is the current Stage-A non-mainnet ledger for `Solana devnet`, `BSC testnet`, and `AVAX Fuji`. Repo-level deploy/testnet secrets exist, but GitHub staged proof and staged soak are still blocked because there is no `staging` environment and no `HYPERBET_*_STAGING_*` vars or secrets provisioned yet. Local Stage-A is also still waiting on shared BSC and AVAX token-address inputs for AMM and perps rehearsal. Single source of truth for testnet wallet generation, funding, secret storage, diff --git a/docs/release/tracking-document-map.md b/docs/release/tracking-document-map.md new file mode 100644 index 00000000..f73cdf39 --- /dev/null +++ b/docs/release/tracking-document-map.md @@ -0,0 +1,94 @@ +# Hyperbet Tracking Document Map + +> **TL;DR:** This map defines which documents own live launch truth, which documents own open work, which documents only preserve evidence, and which documents are historical snapshots. Hyperbet's full launch target remains `PM + perps + internal AMM`, the active production-readiness gate is `BSC + Solana`, and `AVAX` is preserved as a parked follow-on epic. The implementation target is runtime-family based: shared `EVM` plus `SVM`, not one named EVM chain. + +## Scope Contract + +Use this scope statement consistently across every active tracking document: + +- full product destination: `PM + perps + internal AMM` +- active production-readiness gate: `BSC + Solana` +- parked follow-on epic: `AVAX` +- `AMM` is internal infrastructure, not a required retail browser surface +- implementation target: shared `EVM` runtime plus `SVM` + +## Chain Generality Contract + +Use this interpretation consistently across active documents and issue creation: + +- `BSC` is the current active EVM proving and launch wrapper, not the exclusive + long-term implementation target +- unless a ticket explicitly says `wrapper-specific`, `deployment-specific`, or + `governance/evidence-only`, EVM work should land in shared EVM modules, + registry-driven config, and reusable app/runtime paths +- `Solana` owns the active `SVM` lane +- future EVM onboardings such as `Base` or reactivated `AVAX` should consume + the shared EVM path rather than require BSC-only rewrites + +## Document Role Contract + +| Role | Purpose | May own open work? | +|---|---|---| +| `Canonical guidance` | Says what is true now, what the active gate is, and what the top blockers are. | Summary only | +| `Canonical backlog` | Owns all remaining implementation, hardening, CI, launch, and ops work. | Yes | +| `Canonical evidence/status` | Proves what already passed and records current runtime status. | No | +| `Runbook` | Explains how to operate, deploy, verify, or recover a system. | No | +| `Historical snapshot` | Preserves prior branch-era execution context and evidence. | No | + +## Canonical Guidance + +| Document | Role | Current job | +|---|---|---| +| [production-readiness-audit-2026-03-29.md](production-readiness-audit-2026-03-29.md) | Canonical guidance | Production-readiness verdict, blocker inventory, execution order | +| [github-project-production-backlog.md](github-project-production-backlog.md) | Canonical backlog | Single issue-seed source for all remaining work | +| [../prediction-market-release-prep.md](../prediction-market-release-prep.md) | Canonical guidance | Reviewer-facing release summary and blocker summary | +| [prediction-market-launch-freeze-tracker.md](prediction-market-launch-freeze-tracker.md) | Canonical guidance | Current freeze posture and blocker summary | +| [launch-ops-evidence-index.md](launch-ops-evidence-index.md) | Canonical guidance | Links launch evidence to repo artifacts and names missing evidence | +| [../hyperbet-production-deploy.md](../hyperbet-production-deploy.md) | Canonical guidance | Production and staging topology, required env contract | +| [external-audit-package-checklist.md](external-audit-package-checklist.md) | Canonical guidance | Audit-handoff packet completeness only | + +## Canonical Evidence And Status + +| Document | Role | Current job | +|---|---|---| +| [runtime-integration-readiness-matrix.md](runtime-integration-readiness-matrix.md) | Canonical evidence/status | End-to-end runtime loop status for `BSC + Solana` | +| [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md) | Canonical evidence/status | Browser-to-chain and real-Hyperscapes acceptance evidence | +| [stage-a-promotion-execution-ledger.md](stage-a-promotion-execution-ledger.md) | Canonical evidence/status | Detailed Stage-A execution log, tx hashes, balances, artifacts | +| [launch-ops-evidence-index.md](launch-ops-evidence-index.md) | Canonical guidance + evidence index | Reviewer entrypoint to evidence references | + +## Runbooks + +| Document | Role | Current job | +|---|---|---| +| [../runbooks/hyperscapes-local-pm-integration.md](../runbooks/hyperscapes-local-pm-integration.md) | Runbook | Local real-duel integration steps and validation | +| [../runbooks/prediction-market-test-flow.md](../runbooks/prediction-market-test-flow.md) | Runbook | PM flow expectations and test guidance | +| [../runbooks/create2-mainnet-deploy.md](../runbooks/create2-mainnet-deploy.md) | Runbook | CREATE2 deployment procedure for active launch scope | +| [../runbooks/README.md](../runbooks/README.md) | Runbook | Index for operational runbooks | + +## Historical Snapshots + +These documents remain important, but they no longer own current blockers or open work: + +| Document | Why it stays | +|---|---| +| [pm-launch-execution-plan.md](pm-launch-execution-plan.md) | Preserved superset strategy and checklist history | +| [testnet-operations-ledger.md](testnet-operations-ledger.md) | Historical Stage-A wallet and provisioning record | +| [localnet-headless-validation-tracker.md](localnet-headless-validation-tracker.md) | Branch-era local runner findings | +| [stage-a-browser-acceptance-matrix.md](stage-a-browser-acceptance-matrix.md) | Execution evidence, not backlog ownership | +| [stage-a-promotion-execution-ledger.md](stage-a-promotion-execution-ledger.md) | Append-only evidence log | +| [../enoomian-prediction-market-sprint.md](../enoomian-prediction-market-sprint.md) | Closed sprint tracker | +| [../enoomian-evm-standardization-decisions.md](../enoomian-evm-standardization-decisions.md) | Historical design log | +| [../enoomian-next-phase-gates.md](../enoomian-next-phase-gates.md) | Historical planning artifact | + +## Usage Rules + +1. If the question is “what is still left?”, use the backlog first. +2. If the question is “is the system actually running end to end?”, use the runtime matrix first. +3. If the question is “what has already been proven?”, use the evidence docs. +4. If the question is “how do I operate this?”, use runbooks. +5. If a historical document disagrees with a canonical document, the canonical document wins unless the historical document is being cited as evidence. +6. The GitHub Project execution surface is one full Kanban board derived from + the canonical backlog; do not create dated sprint contracts or calendar + promises in tracking docs. +7. App shell, account, rewards, SDK, runtime, protocol, and off-repo launch + work all belong on the same execution surface unless explicitly parked. diff --git a/docs/runbooks/create2-mainnet-deploy.md b/docs/runbooks/create2-mainnet-deploy.md index 0d93df76..7741cb4f 100644 --- a/docs/runbooks/create2-mainnet-deploy.md +++ b/docs/runbooks/create2-mainnet-deploy.md @@ -1,11 +1,18 @@ # CREATE2 Deterministic Deployment Runbook -> **TL;DR:** This runbook covers the EVM portion of the phase-1 launch train. The launch-blocking EVM chains are `BSC` and `AVAX`. `Base` remains an optional add-chain rehearsal lane and is not required for phase-1 signoff. PM CREATE2 deployment must be followed by AMM and perps deployment plus full-product verification before any registry values are promoted. +> **TL;DR:** This runbook covers the EVM portion of the phase-1 launch train. The active production-readiness EVM chain is `BSC`. `AVAX` remains a preserved but isolated follow-on lane, and `Base` remains an optional add-chain rehearsal lane. PM CREATE2 deployment must be followed by AMM and perps deployment plus full-product verification before any registry values are promoted. > **Factory:** Arachnid Deterministic Deployment Proxy (`0x4e59b44847b379578588920cA78FbF26c0B4956C`) ## Preflight +Current scope note: + +- blocking EVM path: `BSC` +- preserved but isolated follow-on lane: `AVAX` +- when this runbook mentions AVAX below, treat it as preserved operational + guidance, not a blocker for the current signoff decision + 1. Compute deterministic PM addresses: ```bash @@ -111,3 +118,7 @@ After final mainnet verification succeeds: 2. Archive deployment receipts and verify outputs. 3. Capture staged proof and soak evidence against the promoted environment. 4. Record governance transfer and freeze transaction hashes. + +For the current production-readiness path, only the BSC outputs above are +blocking. AVAX outputs remain preserved follow-on artifacts unless the lane is +explicitly reactivated. diff --git a/docs/runbooks/hyperscapes-local-pm-integration.md b/docs/runbooks/hyperscapes-local-pm-integration.md index 029420f5..de09cf4b 100644 --- a/docs/runbooks/hyperscapes-local-pm-integration.md +++ b/docs/runbooks/hyperscapes-local-pm-integration.md @@ -1,276 +1,115 @@ # Hyperscapes Local PM Integration -> **TL;DR:** This is the fastest local debug lane for the real `Hyperscapes -> Hyperbet` integration. It uses local Hyperscapes plus local keeper and UI, and it can optionally drive local BSC and AVAX write paths with anvil-backed deployments. It is not the final signoff lane; release signoff still comes from staged proof and soak. - -This is the local integration path for prediction markets against the real -Hyperscapes duel stack. It does **not** seed synthetic markets and it does -**not** treat the game server as the Hyperbet API. - -For this local runner, skipping Hyperscapes local MUD chain bootstrap is -intentional. Hyperbet consumes the duel telemetry API from Hyperscapes, not the -sibling repo's local anvil world. The runner also defaults the duel server to -development mode so local smoke does not fail on production-only JWT boot -requirements. - -## Architecture - -1. Hyperscapes is the duel event source. - - local game/server stack serves `GET /api/streaming/state` - - duel lifecycle comes from the running game - -2. Hyperbet keeper is the bridge layer. - - polls the Hyperscapes streaming endpoint - - optionally runs the keeper bot internally - - exposes: - - `/status` - - `/api/streaming/state` - - `/api/streaming/state/events` - - `/api/arena/prediction-markets/active` - -3. Hyperbet UI points at the keeper service, not directly at the game server. - - `VITE_GAME_API_URL=http://127.0.0.1:8080` - - `VITE_GAME_WS_URL=ws://127.0.0.1:5555/ws` - -This split is required because the Hyperscapes server provides duel telemetry, -while the keeper service provides prediction-market state. - -For local write-path smoke, the runner can boot fresh anvil-backed BSC and -AVAX deployments before starting the keeper. That path seeds fresh local EVM -contracts from the repo's local E2E setup scripts and is the preferred default -when you want visible market movement without depending on funded testnet -writer roles. - -## Scope - -The truthful local integrated lifecycle today is: - -- open -- lock -- resolve - -The current game client and generic EVM keeper path do **not** yet model local -duel cancellation as a first-class path. - -## Local Duel Preconditions - -The local Hyperscapes server can be healthy and still remain in `IDLE` forever -if it has no duel agents available. That is the default state on a fresh local -database. - -The minimum viable local duel setup is: - -1. Start Hyperscapes in local integration mode: - -```bash -bash scripts/run-hyperscapes-pm-local.sh -``` - -2. If `/api/streaming/state` stays `IDLE`, create two local agent characters in - the sibling Hyperscapes repo: +This is the local and personal-staging integration model after the streaming +overhaul. + +## Boundary + +Hyperscapes provides: + +- duel lifecycle +- renderer health +- renderer metrics +- delivery metadata + +Hyperbet provides: + +- keeper APIs +- betting surfaces +- chain interaction + +The Hyperbet UI should point at the keeper for canonical session state and use +the playback URL carried in that session. It should not invent its own stream +selection rules beyond explicit debug or operator override behavior. + +## Personal Staging + +For `enoomian` personal staging: + +- Pages hosts `/stream` +- `/stream` is a dedicated capture preset with the duel camera path and no + generic client overhead +- the GPU box renders and encodes +- `cdp` is the primary capture mode; `webcodecs` and `mediarecorder` remain + supported fallback/debug modes +- Railway hosts the Hyperscapes API +- Hyperbet keepers poll Hyperscapes renderer health and session state +- Cloudflare Stream LL-HLS is the canonical bettor viewer rail +- self-hosted HLS remains available only for smoke, debug, and explicit + operator failover +- automatic provider failover is disabled by default: + `STREAM_ENABLE_AUTOMATIC_FAILOVER=false` +- canonical channel and destination truth own ingest metadata; keeper + `delivery` may enrich playback URLs but must not override canonical ingest + transport +- `publicReadiness` is the final server delivery gate for bettors, derived from + source readiness plus persisted Cloudflare authority and playback-probe truth +- player sync telemetry is a separate client-side betting-safety gate; do not + call the page "live synced" from server readiness alone +- Railway env reconciliation is now a separate action from deploy execution: + use `scripts/enoomian-staging/deploy.sh hyperscapes-railway-env`, + `scripts/enoomian-staging/deploy.sh hyperbet-solana-keepers-env`, or + `scripts/enoomian-staging/deploy.sh hyperbet-keepers-env` when env vars need + to be reconciled, then use the normal deploy targets for code-only deploys + +## Local Debug Rules + +When the stream looks stale: + +1. check Hyperscapes capture status +2. check HLS manifest freshness +3. check the keeper canonical session +4. only then inspect the browser player + +If the player alone drifted, rebuild the player. +If render or encode are stale, do not blame the player. + +If self-HLS is healthy but Cloudflare is not, the stream is degraded for +bettors unless an explicit operator failover path has been enabled. + +## Required Checks ```bash -curl http://127.0.0.1:5555/api/characters/db \ - -X POST \ - -H 'content-type: application/json' \ - --data '{"accountId":"local-agent-account-a","name":"Local Agent A","isAgent":true}' - -curl http://127.0.0.1:5555/api/characters/db \ - -X POST \ - -H 'content-type: application/json' \ - --data '{"accountId":"local-agent-account-b","name":"Local Agent B","isAgent":true}' +curl -fsSL "$HYPERSCAPES_URL/api/streaming/capture/status" | jq +curl -fsSL "$HYPERSCAPES_URL/api/streaming/capture/smoke" | jq +curl -fsSL "$HYPERSCAPES_URL/api/streaming/state" | jq +curl -fsSL "$KEEPER_URL/api/streaming/state" | jq ``` -3. Start those agents through the sibling repo's intended embedded-agent route: - -```bash -curl http://127.0.0.1:5555/api/agents//start -X POST -curl http://127.0.0.1:5555/api/agents//start -X POST -``` - -4. Confirm the duel has left `IDLE`: - -```bash -curl http://127.0.0.1:5555/api/streaming/state -curl http://127.0.0.1:8080/api/streaming/state -curl http://127.0.0.1:8080/api/arena/prediction-markets/active -``` - -If you already have model-provider keys configured in Hyperscapes, model-agent -spawning can also satisfy this requirement. The important point is simpler: the -streaming duel scheduler needs at least two available agents, or no duel cycle -will ever start. - -## Authority Model +Look for: -There are two separate wallet classes: +- `canonicalAuthority` +- `publicReadiness` +- `rendererHealth` +- `rendererMetrics` +- `sourceRuntime` +- `delivery` +- `playback.url` +- `currentSceneUrl` +- `activeBundle` -1. Local smoke trader wallets - - used by the UI for order placement and claims - - private keys stay local under `keys/local-smoke/` - - public addresses are tracked in - [local-smoke-wallets.json](../release/evidence/local-smoke-wallets.json) - - GitHub can fund them through - [fund-local-smoke-wallets.yml](../../.github/workflows/fund-local-smoke-wallets.yml) +If DB access is available, inspect the persisted authority record in storage: -2. Keeper writer wallets - - required for deployed testnet market automation - - EVM requires existing `REPORTER`, `MARKET_OPERATOR`, and `FINALIZER` - authority - - new post-deploy EVM writer wallets cannot be granted those roles because - the contracts freeze the governance surface after deployment - - therefore local duel -> deployed market automation requires the existing - writer keys to be available locally, or a separate remote writer service +- `streaming:cloudflare:lifecycle` +- `streaming:cloudflare:last-webhook` +- `streaming:cloudflare:last-lifecycle-poll` +- `streaming:cloudflare:last-playback-probe` +- `streaming:cloudflare:reconciliation` -## Local Run +Decision ordering is: -From the Hyperbet repo root: - -```bash -bash scripts/run-hyperscapes-pm-local.sh -``` - -Repo location discovery: - -- the runner first honors `HYPERSCAPES_ROOT` if you set it explicitly -- otherwise it auto-detects common sibling locations such as: - - `/.worktrees/hyperscapes-stream-bet-sync` - - `/hyperscapes-stream-bet-sync` -- if your Hyperscapes checkout was moved elsewhere, set `HYPERSCAPES_ROOT=/abs/path/to/hyperscapes-stream-bet-sync` - -Defaults: - -- Hyperscapes game/server: `http://127.0.0.1:5555` -- Hyperbet keeper service: `http://127.0.0.1:8080` -- Hyperbet EVM app: `http://127.0.0.1:4179` -- EVM keeper chain scope: `bsc,avax` -- Hyperscapes chain bootstrap: skipped -- Hyperscapes node env: `development` -- interactive UI opening: disabled by default -- local monitor browser mode: headless by default -- screenshot viewport: `1280x720` - -The script: - -1. starts the sibling Hyperscapes duel stack with Hyperbet disabled there -2. when `PM_LOCAL_EVM_MODE=anvil` (the default), starts local anvil-backed BSC - and AVAX deployments by invoking the local E2E seeding scripts under - `packages/hyperbet-bsc/app/tests/e2e/` and - `packages/hyperbet-avax/app/tests/e2e/` -3. starts the local Hyperbet EVM keeper service against - `http://127.0.0.1:5555/api/streaming/state` -4. starts the local Hyperbet EVM app pointed at the keeper service -5. keeps UI opening off by default; manual browser launch is opt-in through - `OPEN_LOCAL_UI=true` -6. can start the PM soak follow monitor in the background, which records JSON - state plus paired UI screenshots into: - - `output/playwright/pm-soak//` - - screenshots are captured headlessly at `1280x720` unless overridden - -The default Hyperbet local page is now the normal betting surface: - -- `http://127.0.0.1:4179/` - -The `?debug=1` query is optional and only enables hidden E2E/operator controls. -It is not required for the real local betting flow or for headless stream -validation. - -Monitor and harness controls are explicit: -- `PM_E2E_MONITOR=true|false` toggles `scripts/pm-soak-monitor.ts` -- `PM_E2E_FULL_SOAK=true|false` toggles `scripts/soak-harness.ts` -- `PW_HEADLESS=1` keeps Playwright headless -- `PW_BROWSER_CHANNEL=chrome` is the preferred macOS local setting -- `PW_WEBGPU_ARGS="--enable-unsafe-webgpu"` keeps the local stream renderer on - the headless WebGPU lane -- `PM_SOAK_SCREENSHOT_WIDTH=1280` -- `PM_SOAK_SCREENSHOT_HEIGHT=720` - -`PM_E2E_MONITOR` defaults to the value of `CAPTURE_LOCAL_UI_FLOW`. - -When you enable `PM_E2E_FULL_SOAK=true`, the runner also starts -`scripts/soak-harness.ts` so the same local session executes an additional -PM-AMM + perps path against local BSC and active stream cycles. - -The PM soak monitor records key incidences automatically: - -- initial stack-up -- duel key change -- duel phase change -- first populated `markets[]` -- market-status changes -- final snapshot when the runner is stopped - -## Optional Local Env - -The runner auto-loads these gitignored local env files when present: - -- `/.env.stage-a.testnet.local` -- `/.env.testnet.local` -- `/packages/hyperbet-evm/keeper/.env` -- `/packages/hyperbet-evm/app/.env.local` - -Relevant writer env names: - -- `EVM_REPORTER_PRIVATE_KEY` -- `EVM_MARKET_OPERATOR_PRIVATE_KEY` -- `EVM_FINALIZER_PRIVATE_KEY` -- `TESTNET_REPORTER_PRIVATE_KEY` -- `TESTNET_MARKET_OPERATOR_PRIVATE_KEY` -- `TESTNET_FINALIZER_PRIVATE_KEY` -- fallback: `EVM_KEEPER_PRIVATE_KEY` - -When `PM_LOCAL_EVM_MODE=anvil`, the runner overrides those writer envs with the -default local-anvil admin key used by the local E2E EVM seeding scripts. The -local anvil ports default to `18545` for BSC and `18546` for AVAX, and the -runner derives the seeded oracle/CLOB addresses from each chain's local -`state.json`. - -If these are missing, the integrated stack still boots, but local duel events -cannot open and resolve deployed BSC/AVAX markets. In that case the runner -defaults `ENABLE_KEEPER_BOT=false` so the read path stays clean. - -Useful overrides: - -```bash -ENABLE_KEEPER_BOT=true bash scripts/run-hyperscapes-pm-local.sh -HYPERSCAPES_SKIP_CHAIN_SETUP=false bash scripts/run-hyperscapes-pm-local.sh -HYPERSCAPES_DUEL_NODE_ENV=production JWT_SECRET=... bash scripts/run-hyperscapes-pm-local.sh -OPEN_LOCAL_UI=false bash scripts/run-hyperscapes-pm-local.sh -CAPTURE_LOCAL_UI_FLOW=false bash scripts/run-hyperscapes-pm-local.sh -HYPERBET_UI_DEBUG=true bash scripts/run-hyperscapes-pm-local.sh -PM_E2E_MONITOR=true \ -PM_E2E_FULL_SOAK=true \ -PM_SOAK_LOCAL_DURATION_MIN=25 \ -PM_E2E_HARNESS_DURATION_MIN=25 \ -bash scripts/run-hyperscapes-pm-local.sh - -# Full E2E without local monitor/UI capture: -OPEN_LOCAL_UI=false \ -PM_E2E_MONITOR=false \ -PM_E2E_FULL_SOAK=true \ -PM_E2E_HARNESS_DURATION_MIN=25 \ -bash scripts/run-hyperscapes-pm-local.sh -``` +1. `source_unready` +2. `provider_not_live` +3. `probe_unready` +4. `authority_stale` -## Acceptance +## Operator Goal -Minimum healthy local integrated state: +Every local and personal-staging integration run should answer: -1. `GET http://127.0.0.1:5555/api/streaming/state` returns live duel state. -2. `GET http://127.0.0.1:8080/status` returns keeper health. -3. `GET http://127.0.0.1:8080/api/arena/prediction-markets/active` returns - prediction-market state. -4. `http://127.0.0.1:4179` loads with duel telemetry from Hyperscapes and - prediction markets from the keeper service. -5. With writer authority present locally, a live duel should drive: - - market open - - market lock - - oracle proposal/finalization - - claimable resolved state +- is the source rendering? +- is encode keeping up? +- is delivery fresh? +- is the player near the live edge? -6. If local writer authority is intentionally absent, the truthful expected - state is: - - live duel state visible in keeper and UI - - empty `markets[]` on `/api/arena/prediction-markets/active` - - `ENABLE_KEEPER_BOT=false` -7. The local evidence bundle contains paired Hyperscapes and Hyperbet UI - screenshots plus the backing keeper/game JSON for each captured incidence. +If those four answers are not separated, the incident data is incomplete. diff --git a/docs/runbooks/staged-live-proof.md b/docs/runbooks/staged-live-proof.md index e89e0865..307d74e5 100644 --- a/docs/runbooks/staged-live-proof.md +++ b/docs/runbooks/staged-live-proof.md @@ -6,11 +6,12 @@ Use this runbook to execute the manual staging proof rail for phase-1 release signoff. This runbook does **not** change production topology. It validates the staged -Solana, BSC, and AVAX rails using the same deployed shape as production: +public lane using the same deployed shape as production: -- staged Solana Pages + staged Solana keeper -- staged BSC Pages + staged BSC keeper -- staged AVAX Pages + staged AVAX keeper +- unified Hyperbet Pages for the public bettor surface +- staged Solana keeper +- staged BSC keeper +- staged AVAX app + staged AVAX keeper for the separate AVAX lane - external staged duel/stream source - keeper-proxied RPC - launch-scope canary writes for `PM`, `perps`, and internal `AMM` @@ -26,16 +27,16 @@ Solana, BSC, and AVAX rails using the same deployed shape as production: Read-only proof surfaces: -- `https:///build-info.json` +- `https:///build-info.json` - `https:///status` - `https:///api/arena/prediction-markets/active` - `https:///api/keeper/bot-health` - `https:///api/streaming/state` - `https:///api/streaming/duel-context` -- `https:///build-info.json` - `https:///status` - `https:///api/arena/prediction-markets/active` - `https:///api/keeper/bot-health` +- legacy Solana/BSC Pages URLs should redirect to the unified app during the compatibility window - `https:///build-info.json` - `https:///status` - `https:///api/arena/prediction-markets/active` @@ -44,10 +45,10 @@ Read-only proof surfaces: Repo-backed staging proof entrypoints: ```bash -bun run staged:proof -- --mode=read-only --target=all +bun run staged:proof -- --mode=read-only --target=unified +bun run staged:proof -- --mode=read-only --target=avax bun run staged:proof -- --mode=canary-write --target=solana bun run staged:proof -- --mode=canary-write --target=bsc -bun run staged:proof -- --mode=canary-write --target=avax ``` Each `canary-write` artifact now emits one nested result per chain: @@ -61,7 +62,7 @@ GitHub manual workflow: - workflow: `Staged Live Proof` - inputs: - `mode=read-only|canary-write` - - `target=all|solana|bsc|avax` + - `target=unified|solana|bsc` Current blocker: @@ -82,27 +83,15 @@ Current blocker: 1. Confirm the staging deployments exist and point at the intended URLs. Required workflow inputs and vars: - - `HYPERBET_SOLANA_PAGES_STAGING_PROJECT_NAME` - - `HYPERBET_SOLANA_PAGES_STAGING_URL` + - `ENOOMIAN_HYPERBET_PAGES_URL` + - `ENOOMIAN_HYPERBET_PAGES_PROJECT_NAME` - `HYPERBET_SOLANA_KEEPER_STAGING_URL` - `HYPERBET_SOLANA_KEEPER_STAGING_WS_URL` - - `HYPERBET_SOLANA_RAILWAY_STAGING_PROJECT_ID` - - `HYPERBET_SOLANA_RAILWAY_STAGING_ENVIRONMENT_ID` - - `HYPERBET_SOLANA_RAILWAY_STAGING_KEEPER_SERVICE_ID` - - `HYPERBET_BSC_PAGES_STAGING_PROJECT_NAME` - - `HYPERBET_BSC_PAGES_STAGING_URL` - `HYPERBET_BSC_KEEPER_STAGING_URL` - `HYPERBET_BSC_KEEPER_STAGING_WS_URL` - - `HYPERBET_BSC_RAILWAY_STAGING_PROJECT_ID` - - `HYPERBET_BSC_RAILWAY_STAGING_ENVIRONMENT_ID` - - `HYPERBET_BSC_RAILWAY_STAGING_KEEPER_SERVICE_ID` - - `HYPERBET_AVAX_PAGES_STAGING_PROJECT_NAME` - - `HYPERBET_AVAX_PAGES_STAGING_URL` - - `HYPERBET_AVAX_KEEPER_STAGING_URL` - - `HYPERBET_AVAX_KEEPER_STAGING_WS_URL` - - `HYPERBET_AVAX_RAILWAY_STAGING_PROJECT_ID` - - `HYPERBET_AVAX_RAILWAY_STAGING_ENVIRONMENT_ID` - - `HYPERBET_AVAX_RAILWAY_STAGING_KEEPER_SERVICE_ID` + - optional during the redirect window only: + - `ENOOMIAN_HYPERBET_SOLANA_PAGES_URL` + - `ENOOMIAN_HYPERBET_BSC_PAGES_URL` 2. Confirm proof vars and secrets are present in the staging environment: - `HYPERBET_SOLANA_STAGING_RPC_URL` - `HYPERBET_SOLANA_STAGING_CLUSTER` (default `devnet` if omitted) @@ -147,9 +136,10 @@ Current blocker: - BSC canary wallet needs native gas plus `mUSD` and the perps margin token - AVAX canary wallet needs native gas plus `mUSD` and the perps margin token - BSC/AVAX admin and market-operator wallets need native gas, and the admin wallet must hold enough `mUSD` to seed one AMM canary market -4. Run `read-only` proof first. -5. If read-only succeeds, run `canary-write` separately for Solana, BSC, and - AVAX. +4. Run `read-only` proof first: + - unified for the public bettor surface + - AVAX separately when that lane is in scope +5. If read-only succeeds, run `canary-write` separately for Solana and BSC. 6. Inspect the generated artifact bundle: - `.ci-artifacts/staged-live-proof/summary.json` - `solana/*` @@ -164,13 +154,12 @@ Current blocker: ## Success Criteria -- Solana read-only proof passes. -- BSC read-only proof passes. +- Unified read-only proof passes. - AVAX read-only proof passes. - Solana canary write proof completes for `pm`, `perps`, and `amm`. - BSC canary write proof completes for `pm`, `perps`, and `amm`. -- AVAX canary write proof completes for `pm`, `perps`, and `amm`. -- `verify:chains` passes for Solana, BSC, and AVAX. +- legacy Solana/BSC Pages URLs redirect cleanly to the unified app during the compatibility window. +- `verify:chains` passes for Solana, BSC, and AVAX where applicable. - AVAX staging app and keeper env audits pass. ## Escalation Criteria diff --git a/docs/runbooks/stale-oracle-or-stale-stream.md b/docs/runbooks/stale-oracle-or-stale-stream.md index 37c47dde..80708f54 100644 --- a/docs/runbooks/stale-oracle-or-stale-stream.md +++ b/docs/runbooks/stale-oracle-or-stale-stream.md @@ -1,51 +1,99 @@ # Stale Oracle Or Stale Stream -## Symptoms +## Goal -- duel phase or HP data stops updating -- quotes remain open around lock/resolve boundaries -- lifecycle status lags real duel outcomes +Separate four failure planes before taking action: + +1. source render +2. capture and encode +3. manifest and delivery freshness +4. player live-edge drift + +Do not treat all stale-stream incidents as one class of outage. ## Detection +Check Hyperscapes first: + +```bash +curl -fsSL "$HYPERSCAPES_URL/api/streaming/capture/status" | jq +curl -fsSL "$HYPERSCAPES_URL/api/streaming/capture/smoke" | jq +curl -fsSL "$HYPERSCAPES_URL/api/streaming/state" | jq +curl -fsSL "$HLS_URL" | head +``` + +Then check the keeper: + ```bash -curl -fsSL "$KEEPER_URL/status" | jq '.parsers, .stream' curl -fsSL "$KEEPER_URL/api/streaming/state" | jq -curl -fsSL "$KEEPER_URL/api/arena/prediction-markets/active" | jq +curl -fsSL "$KEEPER_URL/status" | jq '.stream' curl -fsSL "$KEEPER_URL/api/keeper/bot-health" | jq ``` -## Immediate Containment +## Interpretation -1. Halt quoting on affected chains. -2. Treat stale oracle or stale stream as higher priority than preserving uptime. -3. Do not resolve markets manually unless lifecycle state is confirmed from authoritative chain state. +Treat source truth, canonical delivery truth, and player truth as separate +signals: -## Recovery Steps +- `sourceRuntime` + - worker, browser, and capture health only +- `canonicalAuthority` + - persisted Cloudflare lifecycle, poll, probe, and reconciliation truth +- `channel.publicReadiness` + - canonical public playback truth +- `delivery` / `canonicalDestination` + - consumer-facing playback metadata derived from canonical channel truth +- `fallbackDestination` + - provider-specific warm standby state; not the normal bettor rail +- renderer-health polling may enrich playback URLs, but it must not replace + canonical ingest transport metadata once a canonical destination exists +- player sync telemetry + - bettor-facing live-edge alignment; separate from authority and delivery + availability -1. Confirm whether the failure is upstream stream data or chain/oracle freshness. -2. Restore stream/oracle input first. -3. Restart the keeper if it does not reconcile automatically. -4. Re-check canonical lifecycle state and bot-health freshness timestamps. -5. Re-enable quoting only after stale markers clear and lifecycle state matches chain state. +- `render_tick_stale` + - source page is not advancing +- `visual_change_stale` + - render loop is alive but duel visuals are not changing +- `capture_fps_low` + - capture path is overloaded +- `encoder_fps_low` + - encode path is overloaded or misconfigured +- `manifest_stale` + - delivery path is stale +- `player_drifted` + - viewer was too far behind the live edge +- `player_buffering` + - viewer is near the live edge but is temporarily waiting on buffer -## Success Criteria +Canonical authority decision ordering is: -- stream state updates again -- lifecycle records move out of stale or unknown state -- quote state remains disabled during stale input and resumes only after recovery +1. `source_unready` +2. `provider_not_live` +3. `probe_unready` +4. `authority_stale` -## Escalation +## Recovery Order -Escalate if: +1. restore source render truth +2. restore capture and encode cadence +3. restore manifest and delivery freshness +4. force player rebuild only if the source and delivery are already healthy +5. restart the keeper only if it failed to ingest a now-healthy upstream state -- stale input persists beyond the expected upstream recovery window -- markets remain open through lock or resolve boundaries -- settlement occurs from stale data +Do not page a full-stream outage when the fallback rail alone is red and the +canonical Cloudflare rail is still green. +Do not declare the bettor page "live synced" unless player telemetry is +aligned as well. -## Evidence To Capture +## Success Criteria -- latest stream payload -- `/status` parser and stream fields -- `/api/keeper/bot-health` freshness fields -- exact timestamps of last good update +- Hyperscapes capture status reports fresh `rendererHealth` +- Hyperscapes capture status reports fresh `sourceRuntime` +- Hyperscapes exposes fresh `canonicalAuthority` and `publicReadiness` +- Hyperscapes smoke status reports the expected `/stream` scene URL and bundle +- capture metrics and encode metrics recover +- keepers expose the same canonical ingest and playback truth as Hyperscapes +- viewers return to live-edge playback without repeated rebuild loops +- bettor UI only presents "live synced" when both server readiness and player + sync telemetry agree diff --git a/docs/system-design-alignment.md b/docs/system-design-alignment.md index a22a345b..af29095c 100644 --- a/docs/system-design-alignment.md +++ b/docs/system-design-alignment.md @@ -1,315 +1,80 @@ # Hyperbet System Design Alignment -## Purpose +This is the current authoritative alignment between Hyperbet and Hyperscapes +for the streaming and betting stack. -This document aligns `hyperbet` and `hyperscape` around one platform model. -In the current sprint baseline, this is a **directional architecture document**, -not an override of the launch-hardening source of truth. +## Canonical Ownership -Today, the sprint branch still treats the following as authoritative: +Hyperscapes owns: -- `@hyperbet/chain-registry` for current chain/runtime truth -- the existing CI/deploy/proof rails for release hardening -- the existing BSC/AVAX wrappers and keepers for current launch surfaces +- duel lifecycle truth +- renderer truth +- delivery truth +- the canonical stream session consumed by downstream products -For the detailed keep/adapt/reject record on the imported -`hyperbet-evm-parity-sweep` direction, and the implemented local -standardization outcome, see -`docs/enoomian-evm-standardization-decisions.md`. +Hyperbet owns: -This document captures the intended convergence target while that migration is -still in progress: +- betting UX +- chain-specific keepers +- wallet interaction +- market-facing product surfaces -- `hyperscape` is the source of truth for duel lifecycle and stream state. -- `hyperbet` is the execution, market, and chain-facing product layer. -- Solana and EVM are execution adapters, not separate products. -- Chain-specific sites such as AVAX, BSC, and Base should be themed deployments of one canonical EVM runtime. +Hyperbet must consume Hyperscapes stream truth additively. It must not invent a +parallel renderer-health model. -The goal is to prevent design drift while the codebase transitions from mixed chain packages into a cleaner architecture. +## Streaming Contract -For the current Gold asset interpretation and the planned Gold architecture -track, see: +The canonical stream session now includes: -- `docs/protocol/gold-current-state.md` -- `docs/protocol/gold-architecture-spec-plan.md` +- `rendererHealth` +- `rendererMetrics` + - `captureFps` + - `encodeFps` + - `droppedFrames` + - `latestFrameAt` + - `latestRenderTickAt` + - `latestDuelStateTickAt` + - `latestVisualChangeAt` + - `visualChangeAgeMs` + - `hlsManifest.updatedAt` + - `hlsManifest.mediaSequence` +- `delivery` + - `mode` + - `provider` + - `playbackUrl` + - `hlsUrl` + - `llhlsUrl` -## Canonical Responsibilities +Hyperbet should use: -### Hyperscape +1. explicit Hyperscapes renderer truth first +2. HLS freshness as fallback only +3. local player telemetry to detect live-edge drift separately -- Owns duel lifecycle, stream state, simulation state, and canonical result data. -- Publishes oracle lifecycle events to chain targets. -- Exposes the HTTP and streaming APIs that Hyperbet consumes. +## Delivery Topology -### Hyperbet +- Pages hosts the public Hyperscapes stream page +- Hyperscapes `/stream` is a dedicated low-overhead capture preset +- the GPU host renders and encodes +- Railway serves the keeper-facing and UI-facing stream session +- Cloudflare Stream LL-HLS is the target viewer-delivery path once enabled +- self-hosted HLS remains reachable for smoke and emergency fallback -- Owns prediction and perps market UX. -- Owns chain-facing keepers, market-making, wallet interactions, and deployments. -- Consumes Hyperscape duel truth instead of inventing a separate match state model. +## UI Rule -### Solana runtime +Every player surface should present the same operator-relevant dimensions: -- Lives in `packages/hyperbet-solana`. -- Owns Solana app, Solana keeper, Solana deployment tooling, Solana-specific wallet flow, and Solana program interactions. +- current live-edge latency +- stall count +- rebuild count +- last buffered fragment freshness +- current delivery mode -### EVM runtime +Degraded UI should distinguish: -- Lives in `packages/hyperbet-evm`. -- Owns the canonical EVM app shell and the canonicalized additive EVM keeper - direction on the local sprint-base standardization path. -- `packages/hyperbet-deployments` is subordinate to - `@hyperbet/chain-registry`, not a competing deployment authority. -- AVAX, BSC, and Base should continue converging toward wrapper shells over - this runtime, not separate architectures. +- source stale +- delivery stale +- player drifted +- player buffering -### Shared UI - -- Lives in `packages/hyperbet-ui`. -- Owns reusable components, chain-agnostic frontend logic, theme system, shared market panels, and shared spectator/streaming views. -- Theme should change presentation, not business logic. - -## Package Semantics - -### Hyperbet packages - -- `packages/hyperbet-ui` - - shared React UI and theme system -- `packages/hyperbet-evm` - - canonical EVM app shell - - canonicalized additive EVM keeper/backend surface on the local - sprint-base standardization path -- `packages/hyperbet-solana` - - canonical Solana app + keeper runtime -- `packages/hyperbet-avax` - - temporary EVM deployment wrapper -- `packages/hyperbet-bsc` - - temporary EVM deployment wrapper -- `packages/evm-contracts` - - canonical EVM contracts -- `packages/hyperbet-deployments` - - additive deployment materialization layer introduced for convergence work - - subordinate to the sprint branch's current chain-registry truth -- `packages/hyperbet-sdk` - - shared SDK surface for external consumers -- `packages/market-maker-bot` - - offchain quoting / liquidity automation - -### Hyperscape packages most relevant to Hyperbet - -- `../hyperscape/packages/server` - - duel lifecycle APIs, stream state, and oracle publishing integration -- `../hyperscape/packages/shared` - - shared simulation/runtime primitives -- `../hyperscape/packages/client` - - game client UI -- `../hyperscape/packages/duel-oracle-evm` - - canonical EVM oracle ABI/source -- `../hyperscape/packages/duel-oracle-solana` - - canonical Solana oracle program/IDL - -## Intended Runtime Flow - -1. Hyperscape server schedules and runs duel cycles. -2. Hyperscape emits canonical streaming state and duel lifecycle data. -3. Hyperbet keepers consume that duel lifecycle data. -4. Chain-specific keepers update oracle state and market state on their target chain. -5. Hyperbet frontend consumes: - - streaming state from keeper/Hyperscape APIs - - chain data from contracts or chain-aware keeper APIs -6. Market maker consumes the same canonical duel state and market state to quote liquidity. -7. Users interact with one shared Hyperbet product model through different chain runtimes. - -## Mermaid Diagram - -```mermaid -flowchart LR - subgraph HS[Hyperscape] - HSClient[Client] - HSServer[Server] - HSShared[Shared Runtime] - HSOracle[Duel Oracle Publisher] - end - - subgraph Shared[Shared Hyperbet Surface] - HUI[@hyperbet/ui] - HDeploy[@hyperbet/deployments] - HSDK[@hyperbet/sdk] - end - - subgraph HB[Hyperbet] - HEVM[hyperbet-evm] - HSOL[hyperbet-solana] - HAVAX[hyperbet-avax wrapper] - HBSC[hyperbet-bsc wrapper] - HMM[market-maker-bot] - HContracts[evm-contracts] - end - - HSShared --> HSServer - HSClient --> HSServer - HSServer --> HSOracle - - HSServer -->|stream state / duel lifecycle| HEVM - HSServer -->|stream state / duel lifecycle| HSOL - HSServer -->|state feed| HMM - - HSOracle -->|EVM oracle updates| HContracts - HSOracle -->|Solana oracle updates| HSOL - - HDeploy --> HEVM - HDeploy --> HSOL - HDeploy --> HAVAX - HDeploy --> HBSC - - HUI --> HEVM - HUI --> HSOL - HUI --> HAVAX - HUI --> HBSC - - HContracts --> HEVM - HSDK --> HEVM - HSDK --> HSOL - - HEVM --> HAVAX - HEVM --> HBSC -``` - -## Design Alignment Decisions - -### 1. Keep one product model - -Prediction and perps are product capabilities, not chain-specific products. - -- Duel lifecycle semantics must be identical across chain runtimes. -- Market metadata and result semantics must come from Hyperscape. -- Keepers should differ by execution adapter, not by business meaning. - -### 2. Keep one canonical EVM runtime - -`hyperbet-evm` is now the canonical EVM direction on the local sprint-base -standardization path, but deploy adoption and wrapper retirement still follow -the existing sprint operational model. - -- AVAX, Base, and BSC should be deployment presets plus theme wrappers. -- Chain wrappers should not own their own keeper/business logic long term. - -### 3. Keep one canonical Solana runtime - -`hyperbet-solana` remains the Solana-first runtime. - -- Solana-specific wallet, PDA, Anchor, and program logic should stay there. - -### 4. Converge toward one shared deployment manifest - -`@hyperbet/deployments` is the intended materialized manifest layer for: - -- contract/program addresses -- chain IDs -- operator/admin accounts -- margin token addresses - -It remains subordinate to the sprint branch's existing -`@hyperbet/chain-registry`, which continues to be authoritative for runtime and -release-hardening behavior. - -### 5. Keep one shared UI layer - -`@hyperbet/ui` should be the primary owner of: - -- visual language -- component system -- theme system -- chain-agnostic view logic - -Chain packages should only inject: - -- theme ID -- addresses and RPC config -- chain label/copy -- wallet provider/runtime setup - -## Audit Findings - -### Architecture - -1. The intended architecture is shared-product / chain-adapter, but the repo is still partly package-per-chain and partly product-per-chain. -2. `hyperbet-evm` is now the right direction, but AVAX and BSC wrappers still exist as semi-independent packages instead of thin deployment shells. -3. `market-maker-bot` is still cross-chain in an ad hoc way rather than consuming canonical runtime modules. - -### Keeper boundaries - -1. `hyperbet-evm/keeper` is directionally useful but is not yet accepted as the - canonical keeper baseline; see - `docs/enoomian-evm-standardization-decisions.md`. -2. `hyperbet-solana/keeper` remains the correct home for Solana-first automation. -3. Shared keeper logic has not yet been extracted into reusable modules, so semantic duplication risk remains. - -### UI and UX - -1. `@hyperbet/ui` is now correctly positioned as the shared UI surface. -2. The theme model is aligned with the desired deployment strategy. -3. The frontend still mixes shared market concepts with some chain-specific behavior and copy in package apps. -4. Prediction UX is closer to parity than perps UX. - -### Contract/runtime semantics - -1. EVM prediction flow is structurally integrated. -2. EVM perps are still not fully aligned with the intended economics and runtime behavior. -3. Deployment manifest centralization is correct, but not all consumers are yet manifest-first. - -### Cross-repo relationships - -1. Hyperscape runtime orchestration still points primarily at `hyperbet-solana` in the duel stack. -2. That means the repo orchestration scripts still encode an older “Solana-first sibling app” assumption. -3. The desired long-term architecture is “shared Hyperbet + chain adapters”, but the orchestration layer has not been updated to reflect that. - -## Current State Assessment - -### What is aligned - -- Canonical EVM runtime package now exists. -- Shared UI/theme direction is correct. -- Shared deployment-manifest package now exists as additive scaffolding. -- Solana and EVM runtime split is conceptually correct. - -### What is partially aligned - -- Keeper boundaries -- Chain wrappers -- Market-maker ownership -- Perps contract/runtime semantics -- Hyperscape orchestration scripts - -### What is not yet aligned - -- Thin-wrapper model for AVAX/BSC -- Fully shared keeper semantics -- Fully canonical market-maker integration model -- End-to-end EVM perps parity -- Repo-wide documentation of the target architecture - -## Recommended Next Steps - -### Phase 1: Runtime semantics - -1. Extract shared keeper domain logic into chain-agnostic modules. -2. Leave only execution adapters inside `hyperbet-evm/keeper` and `hyperbet-solana/keeper`. -3. Update Hyperscape duel-stack orchestration so Hyperbet runtime selection is explicit instead of implicitly Solana-first. - -### Phase 2: Package ownership - -1. Reduce `hyperbet-avax` and `hyperbet-bsc` toward thin wrappers over `hyperbet-evm`. -2. Keep all reusable UI in `@hyperbet/ui`. -3. Keep all EVM contract/address logic in `evm-contracts` + `hyperbet-deployments`. - -### Phase 3: Market architecture - -1. Recover intended perps semantics from repo evidence. -2. Align EVM perps contracts and keeper APIs to that spec. -3. Revalidate the market-maker against the canonical contract interfaces. - -### Phase 4: Product audit - -1. Audit UI and UX consistency across Solana and EVM. -2. Audit end-to-end game flow from Hyperscape duel lifecycle to market resolution. -3. Audit package semantics against this document before expanding further. +It should not collapse those conditions into generic stream failure. diff --git a/package.json b/package.json index 13bf1cef..3f6020ba 100644 --- a/package.json +++ b/package.json @@ -56,9 +56,10 @@ "ci:gate:evm": "node --import tsx scripts/ci-gate-scenarios.ts --target=evm", "ci:gate:solana": "node --import tsx scripts/ci-gate-scenarios.ts --target=solana", "ci:gate:solana:build": "node --import tsx scripts/ci-gate-solana-build.ts", - "ci:gate:e2e:solana": "node --import tsx scripts/ci-gate-e2e.ts --chain=solana", - "ci:gate:e2e:bsc": "node --import tsx scripts/ci-gate-e2e.ts --chain=bsc", - "ci:gate:e2e:avax": "node --import tsx scripts/ci-gate-e2e.ts --chain=avax", + "ci:gate:e2e:unified": "node --import tsx scripts/ci-gate-e2e.ts --surface=unified", + "ci:gate:e2e:solana": "node --import tsx scripts/ci-gate-e2e.ts --surface=unified --chain=solana", + "ci:gate:e2e:bsc": "node --import tsx scripts/ci-gate-e2e.ts --surface=unified --chain=bsc", + "ci:gate:e2e:avax": "node --import tsx scripts/ci-gate-e2e.ts --surface=unified --chain=avax", "ci:gate:base": "node --import tsx scripts/ci-gate-base.ts", "ci:gate:registry:full-product": "node --import tsx scripts/ci-gate-registry.ts --surface=full-product", "ci:gate:registry:launch": "node --import tsx scripts/ci-gate-registry.ts --scope=launch --surface=full-product", diff --git a/packages/evm-contracts/contracts/DuelOutcomeOracle.sol b/packages/evm-contracts/contracts/DuelOutcomeOracle.sol index c494ec93..d7bc9813 100644 --- a/packages/evm-contracts/contracts/DuelOutcomeOracle.sol +++ b/packages/evm-contracts/contracts/DuelOutcomeOracle.sol @@ -242,8 +242,8 @@ contract DuelOutcomeOracle is AccessControl { _requireSettleable(duel); if (uint8(status) < uint8(duel.status)) revert InvalidTransition(); - // FIX-4: Lock participant identity and bet timing once betting opens - if (duel.status >= DuelStatus.BETTING_OPEN) { + // FIX-5: Once a duel is prestaged, its manifest becomes immutable. + if (duel.status >= DuelStatus.SCHEDULED) { if (participantAHash != duel.participantAHash) revert ParticipantHashImmutable(); if (participantBHash != duel.participantBHash) revert ParticipantHashImmutable(); if (betOpenTs != duel.betOpenTs) revert TimingImmutable(); diff --git a/packages/evm-contracts/contracts/GoldClob.sol b/packages/evm-contracts/contracts/GoldClob.sol index 8b89ee40..96b85145 100644 --- a/packages/evm-contracts/contracts/GoldClob.sol +++ b/packages/evm-contracts/contracts/GoldClob.sol @@ -305,7 +305,8 @@ contract GoldClob is AccessControl, ReentrancyGuard { DuelOutcomeOracle.DuelState memory duel = duelOracle.getDuel(duelKey); if ( - duel.status != DuelOutcomeOracle.DuelStatus.BETTING_OPEN + duel.status != DuelOutcomeOracle.DuelStatus.SCHEDULED + && duel.status != DuelOutcomeOracle.DuelStatus.BETTING_OPEN && duel.status != DuelOutcomeOracle.DuelStatus.LOCKED ) revert DuelNotMarketable(); @@ -866,6 +867,7 @@ contract GoldClob is AccessControl, ReentrancyGuard { } function _mapDuelStatus(DuelOutcomeOracle.DuelStatus status) internal pure returns (MarketStatus) { + if (status == DuelOutcomeOracle.DuelStatus.SCHEDULED) return MarketStatus.LOCKED; if (status == DuelOutcomeOracle.DuelStatus.BETTING_OPEN) return MarketStatus.OPEN; if (status == DuelOutcomeOracle.DuelStatus.LOCKED) return MarketStatus.LOCKED; if (status == DuelOutcomeOracle.DuelStatus.PROPOSED) return MarketStatus.LOCKED; diff --git a/packages/evm-contracts/scripts/deploy-duel-oracle.ts b/packages/evm-contracts/scripts/deploy-duel-oracle.ts index 3512b962..b2dac27e 100644 --- a/packages/evm-contracts/scripts/deploy-duel-oracle.ts +++ b/packages/evm-contracts/scripts/deploy-duel-oracle.ts @@ -42,6 +42,15 @@ function requireConfiguredAddress(name: string): string { return candidate; } +function resolveDisputeWindowSeconds(): number { + const raw = process.env.DISPUTE_WINDOW_SECONDS?.trim() || "3600"; + const parsed = Number.parseInt(raw, 10); + if (!Number.isInteger(parsed) || parsed < 60) { + throw new Error(`DISPUTE_WINDOW_SECONDS must be an integer >= 60, got: ${raw}`); + } + return parsed; +} + async function main() { const [deployer] = await ethers.getSigners(); const deployedNetwork = await ethers.provider.getNetwork(); @@ -61,6 +70,7 @@ async function main() { const reporter = requireAddress("ORACLE_REPORTER_ADDRESS", deployer.address); const finalizer = requireConfiguredAddress("ORACLE_FINALIZER_ADDRESS"); const challenger = requireConfiguredAddress("ORACLE_CHALLENGER_ADDRESS"); + const disputeWindowSeconds = resolveDisputeWindowSeconds(); console.log("Deploying DuelOutcomeOracle with account:", deployer.address); console.log("Network:", network.name, `(chainId=${chainId})`); @@ -70,6 +80,7 @@ async function main() { console.log("Reporter:", reporter); console.log("Finalizer:", finalizer); console.log("Challenger:", challenger); + console.log("Dispute window seconds:", disputeWindowSeconds); const DuelOutcomeOracle = await ethers.getContractFactory("DuelOutcomeOracle"); @@ -79,7 +90,7 @@ async function main() { finalizer, challenger, emergencyCouncil, - 3600, + disputeWindowSeconds, ); await oracle.waitForDeployment(); @@ -100,6 +111,7 @@ async function main() { reporterAddress: reporter, finalizerAddress: finalizer, challengerAddress: challenger, + disputeWindowSeconds, deploymentTxHash, deployedAt: new Date().toISOString(), }); diff --git a/packages/evm-contracts/scripts/deploy.ts b/packages/evm-contracts/scripts/deploy.ts index 8c1d8306..2e2847f7 100644 --- a/packages/evm-contracts/scripts/deploy.ts +++ b/packages/evm-contracts/scripts/deploy.ts @@ -18,6 +18,15 @@ function isValidAddress(value: string): boolean { return ethers.isAddress(value); } +function resolveDisputeWindowSeconds(): number { + const raw = process.env.DISPUTE_WINDOW_SECONDS?.trim() || "3600"; + const parsed = Number.parseInt(raw, 10); + if (!Number.isInteger(parsed) || parsed < 60) { + throw new Error(`DISPUTE_WINDOW_SECONDS must be an integer >= 60, got: ${raw}`); + } + return parsed; +} + function resolveManifestPaths(): string[] { return [ path.resolve( @@ -150,6 +159,7 @@ async function main() { const challengerAddress = process.env.CHALLENGER_ADDRESS?.trim() || multisigAddress; const goldTokenAddress = process.env.GOLD_TOKEN_ADDRESS?.trim() || ""; + const disputeWindowSeconds = resolveDisputeWindowSeconds(); if (!isValidAddress(adminAddress)) { throw new Error(`Invalid TIMELOCK_ADDRESS/ADMIN_ADDRESS: ${adminAddress}`); @@ -210,7 +220,7 @@ async function main() { finalizerAddress, challengerAddress, emergencyCouncilAddress, - 3600, + disputeWindowSeconds, ); await duelOracle.waitForDeployment(); @@ -236,6 +246,7 @@ async function main() { console.log("- Reporter:", reporterAddress); console.log("- Finalizer:", finalizerAddress); console.log("- Challenger:", challengerAddress); + console.log("- Dispute window seconds:", disputeWindowSeconds); console.log("- Treasury:", treasury); console.log("- Market Maker:", marketMaker); if (goldTokenAddress) { diff --git a/packages/evm-contracts/scripts/predict-create2-addresses.ts b/packages/evm-contracts/scripts/predict-create2-addresses.ts index eb6c590e..0dc51776 100644 --- a/packages/evm-contracts/scripts/predict-create2-addresses.ts +++ b/packages/evm-contracts/scripts/predict-create2-addresses.ts @@ -96,9 +96,9 @@ function resolveGovernanceArgs(): Create2Prediction["constructorArgs"] { if (!ethers.isAddress(pauser)) { throw new Error(`Invalid PAUSER_ADDRESS: ${pauser}`); } - if (disputeWindowSeconds <= 0) { + if (!Number.isInteger(disputeWindowSeconds) || disputeWindowSeconds < 60) { throw new Error( - `DISPUTE_WINDOW_SECONDS must be > 0, got: ${disputeWindowSeconds}`, + `DISPUTE_WINDOW_SECONDS must be an integer >= 60, got: ${disputeWindowSeconds}`, ); } @@ -210,7 +210,9 @@ async function main() { console.log("same constructor arguments and deployer factory."); } -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); +if (require.main === module) { + main().catch((error) => { + console.error(error); + process.exitCode = 1; + }); +} diff --git a/packages/evm-contracts/test/GoldClobCanonical.ts b/packages/evm-contracts/test/GoldClobCanonical.ts index eeb4b217..d6afb0c7 100644 --- a/packages/evm-contracts/test/GoldClobCanonical.ts +++ b/packages/evm-contracts/test/GoldClobCanonical.ts @@ -9,6 +9,7 @@ import { } from "../typed-contracts"; const MARKET_KIND_DUEL_WINNER = 0; +const DUEL_STATUS_SCHEDULED = 1; const DUEL_STATUS_BETTING_OPEN = 2; const DUEL_STATUS_LOCKED = 3; const SIDE_A = 1; @@ -162,6 +163,101 @@ describe("GoldClob", function () { ).to.be.revertedWithCustomError(clob, "MarketExists"); }); + it("allows scheduled prestage but keeps the market locked until the oracle opens it", async function () { + const { clob, oracle, operator, reporter } = await deployFixture(); + const duel = duelKey("duel-scheduled"); + const now = BigInt((await ethers.provider.getBlock("latest"))!.timestamp); + + await oracle + .connect(reporter) + .upsertDuel( + duel, + hashParticipant("agent-a"), + hashParticipant("agent-b"), + now + 30n, + now + 90n, + now + 150n, + "duel-scheduled", + DUEL_STATUS_SCHEDULED, + ); + + await clob + .connect(operator) + .createMarketForDuel(duel, MARKET_KIND_DUEL_WINNER); + + let market = await clob.getMarket(duel, MARKET_KIND_DUEL_WINNER); + expect(market.exists).to.equal(true); + expect(market.status).to.equal(2n); + + await oracle + .connect(reporter) + .upsertDuel( + duel, + hashParticipant("agent-a"), + hashParticipant("agent-b"), + now + 30n, + now + 90n, + now + 150n, + "duel-open", + DUEL_STATUS_BETTING_OPEN, + ); + await clob.connect(operator).syncMarketFromOracle(duel, MARKET_KIND_DUEL_WINNER); + + market = await clob.getMarket(duel, MARKET_KIND_DUEL_WINNER); + expect(market.status).to.equal(1n); + }); + + it("freezes the scheduled duel manifest after prepare", async function () { + const { oracle, reporter } = await deployFixture(); + const duel = duelKey("duel-scheduled-immutable"); + const participantA = hashParticipant("agent-a"); + const participantB = hashParticipant("agent-b"); + const now = BigInt((await ethers.provider.getBlock("latest"))!.timestamp); + + await oracle + .connect(reporter) + .upsertDuel( + duel, + participantA, + participantB, + now + 30n, + now + 90n, + now + 150n, + "duel-scheduled", + DUEL_STATUS_SCHEDULED, + ); + + await expect( + oracle + .connect(reporter) + .upsertDuel( + duel, + participantA, + participantB, + now + 31n, + now + 90n, + now + 150n, + "duel-scheduled-shifted", + DUEL_STATUS_SCHEDULED, + ), + ).to.be.revertedWithCustomError(oracle, "TimingImmutable"); + + await expect( + oracle + .connect(reporter) + .upsertDuel( + duel, + hashParticipant("agent-a-mutated"), + participantB, + now + 30n, + now + 90n, + now + 150n, + "duel-scheduled-mutated", + DUEL_STATUS_SCHEDULED, + ), + ).to.be.revertedWithCustomError(oracle, "ParticipantHashImmutable"); + }); + it("lets the emergency pauser halt new market creation", async function () { const { clob, oracle, operator, reporter, pauser } = await deployFixture(); const duel = duelKey("duel-market-pause"); diff --git a/packages/hyperbet-avax/app/hls-player.html b/packages/hyperbet-avax/app/hls-player.html new file mode 100644 index 00000000..cacfaa27 --- /dev/null +++ b/packages/hyperbet-avax/app/hls-player.html @@ -0,0 +1,27 @@ + + + + + + Hyperscape Live Stream + + + + +
+ + + diff --git a/packages/hyperbet-avax/app/src/hlsPlayerMain.tsx b/packages/hyperbet-avax/app/src/hlsPlayerMain.tsx new file mode 100644 index 00000000..3c73354c --- /dev/null +++ b/packages/hyperbet-avax/app/src/hlsPlayerMain.tsx @@ -0,0 +1,3 @@ +import { mountHlsPlayerApp } from "@hyperbet/ui/player"; + +mountHlsPlayerApp(document.getElementById("root")!); diff --git a/packages/hyperbet-avax/app/vite.config.ts b/packages/hyperbet-avax/app/vite.config.ts index 5210b0f8..503f4f2f 100644 --- a/packages/hyperbet-avax/app/vite.config.ts +++ b/packages/hyperbet-avax/app/vite.config.ts @@ -409,6 +409,10 @@ export default defineConfig(async ({ mode }) => { sourcemap: env.VITE_BUILD_SOURCEMAP === "true", chunkSizeWarningLimit: 3000, rollupOptions: { + input: { + index: path.resolve(__dirname, "index.html"), + "hls-player": path.resolve(__dirname, "hls-player.html"), + }, onwarn(warning, warn) { if ( warning.code === "SOURCEMAP_ERROR" || diff --git a/packages/hyperbet-avax/keeper/src/bot.ts b/packages/hyperbet-avax/keeper/src/bot.ts index facfc478..85a5b83a 100644 --- a/packages/hyperbet-avax/keeper/src/bot.ts +++ b/packages/hyperbet-avax/keeper/src/bot.ts @@ -2024,7 +2024,6 @@ async function upsertDuelLifecycle( ): Promise { const duelKey = duelKeyHexToBytes(data.duelKeyHex); const duelState = findDuelStatePda(fightProgram.programId, duelKey); - const nowSeconds = Math.floor(Date.now() / 1000); const betOpenTs = Math.floor((data.betOpenTime ?? Date.now()) / 1000); const betCloseTs = Math.max( betOpenTs + 1, @@ -2036,9 +2035,6 @@ async function upsertDuelLifecycle( betCloseTs, Math.floor((data.fightStartTime ?? data.betCloseTime ?? Date.now()) / 1000), ); - const requestedStatus = - status === "scheduled" && betOpenTs <= nowSeconds ? "bettingOpen" : status; - await runWithRecovery( () => fightProgram.methods @@ -2050,7 +2046,7 @@ async function upsertDuelLifecycle( new BN(betCloseTs), new BN(duelStartTs), buildDuelMetadata(data), - duelStatusEnum(requestedStatus), + duelStatusEnum(status), ) .accountsPartial({ reporter: botKeypair.publicKey, diff --git a/packages/hyperbet-avax/keeper/src/idl/fight_oracle.json b/packages/hyperbet-avax/keeper/src/idl/fight_oracle.json index d0c45c69..0c8cba6a 100644 --- a/packages/hyperbet-avax/keeper/src/idl/fight_oracle.json +++ b/packages/hyperbet-avax/keeper/src/idl/fight_oracle.json @@ -1,5 +1,5 @@ { - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM", + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB", "metadata": { "name": "fight_oracle", "version": "0.1.0", @@ -343,7 +343,7 @@ }, { "name": "program", - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM" + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB" }, { "name": "program_data" @@ -1051,12 +1051,12 @@ { "code": 6021, "name": "ParticipantHashImmutable", - "msg": "Participant hashes are immutable after betting opens" + "msg": "Participant hashes are immutable after duel prepare" }, { "code": 6022, "name": "TimingImmutable", - "msg": "Bet timing is immutable after betting opens" + "msg": "Bet timing is immutable after duel prepare" }, { "code": 6023, diff --git a/packages/hyperbet-avax/keeper/src/idl/fight_oracle.ts b/packages/hyperbet-avax/keeper/src/idl/fight_oracle.ts index 4f3b083d..7183391c 100644 --- a/packages/hyperbet-avax/keeper/src/idl/fight_oracle.ts +++ b/packages/hyperbet-avax/keeper/src/idl/fight_oracle.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/fight_oracle.json`. */ export type FightOracle = { - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM", + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB", "metadata": { "name": "fightOracle", "version": "0.1.0", @@ -349,7 +349,7 @@ export type FightOracle = { }, { "name": "program", - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM" + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB" }, { "name": "programData" @@ -1057,12 +1057,12 @@ export type FightOracle = { { "code": 6021, "name": "participantHashImmutable", - "msg": "Participant hashes are immutable after betting opens" + "msg": "Participant hashes are immutable after duel prepare" }, { "code": 6022, "name": "timingImmutable", - "msg": "Bet timing is immutable after betting opens" + "msg": "Bet timing is immutable after duel prepare" }, { "code": 6023, diff --git a/packages/hyperbet-avax/keeper/src/idl/gold_clob_market.json b/packages/hyperbet-avax/keeper/src/idl/gold_clob_market.json index d2bb65f3..550fae7e 100644 --- a/packages/hyperbet-avax/keeper/src/idl/gold_clob_market.json +++ b/packages/hyperbet-avax/keeper/src/idl/gold_clob_market.json @@ -1,5 +1,5 @@ { - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy", + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt", "metadata": { "name": "gold_clob_market", "version": "0.1.0", @@ -433,7 +433,7 @@ }, { "name": "program", - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy" + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt" }, { "name": "program_data" diff --git a/packages/hyperbet-avax/keeper/src/idl/gold_clob_market.ts b/packages/hyperbet-avax/keeper/src/idl/gold_clob_market.ts index 16e1d101..aea963e3 100644 --- a/packages/hyperbet-avax/keeper/src/idl/gold_clob_market.ts +++ b/packages/hyperbet-avax/keeper/src/idl/gold_clob_market.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/gold_clob_market.json`. */ export type GoldClobMarket = { - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy", + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt", "metadata": { "name": "goldClobMarket", "version": "0.1.0", @@ -439,7 +439,7 @@ export type GoldClobMarket = { }, { "name": "program", - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy" + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt" }, { "name": "programData" diff --git a/packages/hyperbet-bsc/app/hls-player.html b/packages/hyperbet-bsc/app/hls-player.html new file mode 100644 index 00000000..cacfaa27 --- /dev/null +++ b/packages/hyperbet-bsc/app/hls-player.html @@ -0,0 +1,27 @@ + + + + + + Hyperscape Live Stream + + + + +
+ + + diff --git a/packages/hyperbet-bsc/app/src/App.tsx b/packages/hyperbet-bsc/app/src/App.tsx index 5ecd4d3c..86c2aef5 100644 --- a/packages/hyperbet-bsc/app/src/App.tsx +++ b/packages/hyperbet-bsc/app/src/App.tsx @@ -30,12 +30,37 @@ import { captureInviteCodeFromLocation, getStoredInviteCode, } from "@hyperbet/ui/lib/invite"; -import { usePredictionMarketLifecycle } from "@hyperbet/ui/lib/predictionMarkets"; -import { StreamPlayer } from "@hyperbet/ui/components/StreamPlayer"; +import { ENABLE_STREAM_SOURCE_OVERRIDE } from "@hyperbet/ui/lib/config"; +import { + normalizePredictionMarketDuelKeyHex, + selectPredictionMarketLifecycleRecord, + usePredictionMarketLifecycle, + type PredictionMarketsResponse, +} from "@hyperbet/ui/lib/predictionMarkets"; +import { + describeCanonicalRendererDegradedReason, + resolveCanonicalPlaybackDeliveryMode, + selectBetSurfaceStreamUrl, +} from "@hyperbet/ui/lib/streamSession"; +import { + DEFAULT_SYNC_TOLERANCE_MS, + StreamPlayer, + type StreamPlayerStatus, +} from "@hyperbet/ui/components/StreamPlayer"; import { PointsDisplay } from "@hyperbet/ui/components/PointsDisplay"; import { useChain } from "./lib/ChainContext"; +import { useCanonicalStreamSession } from "@hyperbet/ui/spectator/useCanonicalStreamSession"; import { useStreamingState } from "@hyperbet/ui/spectator/useStreamingState"; import { useDuelContext } from "@hyperbet/ui/spectator/useDuelContext"; +import { + logViewerAlignmentDivergence, + projectCanonicalSessionToSourceTimeline, + projectDuelContextToSourceTimeline, + resolveAlignedCountdownDisplay, + resolveAlignedSessionPhase, + useViewerAlignedBetState, +} from "@hyperbet/ui/lib/viewerAlignment"; +import { useMeasuredContentBox } from "@hyperbet/ui/lib/useMeasuredContentBox"; import { useResizePanel, useIsMobile } from "@hyperbet/ui/lib/useResizePanel"; import { ResizeHandle } from "@hyperbet/ui/components/ResizeHandle"; import { @@ -44,7 +69,6 @@ import { XAxis, YAxis, Tooltip, - ResponsiveContainer, ReferenceLine, } from "recharts"; @@ -96,24 +120,6 @@ function normalizeTimestamp(value: number): number { return Math.floor(value); } -function normalizeRemainingSeconds(value: number | null | undefined): number { - if (!Number.isFinite(value as number)) return 0; - const raw = Math.max(0, Number(value)); - // Streaming API reports ms, while mock mode reports whole seconds. - return raw > 10_000 ? Math.floor(raw / 1000) : Math.floor(raw); -} - -function formatCountdown(seconds: number): string { - if (seconds <= 0) return "00:00"; - const m = Math.floor(seconds / 60) - .toString() - .padStart(2, "0"); - const s = Math.floor(seconds % 60) - .toString() - .padStart(2, "0"); - return `${m}:${s}`; -} - function normalizeEvmAddress(value: string | null | undefined): string | null { const trimmed = value?.trim() ?? ""; return /^0x[a-fA-F0-9]{40}$/.test(trimmed) ? trimmed : null; @@ -720,23 +726,144 @@ export function App() { >("leaderboard"); const appRootRef = useRef(null); const bettingDockInnerRef = useRef(null); + const chartContainerRef = useRef(null); + const chartSize = useMeasuredContentBox(chartContainerRef, !isMobile, 2); const { state: streamingState } = useStreamingState(); + const { + session: canonicalStreamSession, + playback: _canonicalPlayback, + rawSession: rawCanonicalStreamSession, + rendererHealth: canonicalRendererHealth, + deliveryHealth: canonicalDeliveryHealth, + publicReadiness: canonicalPublicReadiness, + authorityHealth: canonicalAuthorityHealth, + presentationDelayMs: canonicalPresentationDelayMs, + } = useCanonicalStreamSession(); const { context: duelContext } = useDuelContext(); - const liveCycle = streamingState?.cycle ?? null; + const freshestCycle = streamingState?.cycle ?? null; const lifecycleChainKey = activeChain === "bsc" || activeChain === "base" || activeChain === "avax" ? activeChain : "solana"; const { + data: lifecyclePayload, duel: lifecycleDuel, market: lifecycleMarket, refresh: refreshLifecycle, } = usePredictionMarketLifecycle( lifecycleChainKey, ); + const allowStreamSourceOverride = + ENABLE_STREAM_SOURCE_OVERRIDE || isE2eDebugMode; const streamSources = STREAM_URLS; - const activeStreamUrl = isE2eMode ? "" : (streamSources[streamSourceIndex] ?? ""); + const lifecycleDuelKey = normalizePredictionMarketDuelKeyHex( + lifecycleDuel?.duelKey ?? null, + ); + const { activeStreamUrl, preloadStreamUrl } = selectBetSurfaceStreamUrl({ + allowFallbackOverride: allowStreamSourceOverride, + authorityHealth: canonicalAuthorityHealth, + fallbackStreamIndex: streamSourceIndex, + fallbackStreamSources: streamSources, + isE2eMode, + lifecycleDuelId: lifecycleDuel?.duelId ?? null, + lifecycleDuelKey, + rendererReady: canonicalRendererHealth?.ready ?? null, + session: canonicalStreamSession, + }); + const mountedStreamUrl = activeStreamUrl || preloadStreamUrl; + const streamDeliveryMode = resolveCanonicalPlaybackDeliveryMode( + canonicalStreamSession, + ); + const [streamPlayerStatus, setStreamPlayerStatus] = + useState(null); + + // Viewer-alignment shadow composition. When + // `VITE_ENABLE_VIEWER_ALIGNED_BET_STATE` is off the inner hook is a + // passthrough (no timers, no buffer pushes) and the panel sees the + // canonical live overrides unchanged. When the flag is on, the + // aligned envelope's `duel` / selected market are used as the + // panel's override payloads so display copy tracks the viewer's + // video frame. Divergence events are emitted as `[viewer-align]` + // shadow-logs. + const viewerAligned = useViewerAlignedBetState< + typeof canonicalStreamSession, + PredictionMarketsResponse | null, + typeof duelContext + >({ + latestSession: rawCanonicalStreamSession ?? canonicalStreamSession, + latestMarket: lifecyclePayload, + latestDuelContext: duelContext, + sessionPresentationDelayMs: canonicalPresentationDelayMs, + streamPlayerStatus, + currentDisplayPhase: + canonicalStreamSession?.cycle.broadcastTimeline?.phase ?? + canonicalStreamSession?.phase ?? + freshestCycle?.phase ?? + null, + extractAlignedPhase: resolveAlignedSessionPhase, + onDivergence: logViewerAlignmentDivergence, + }); + const alignedLifecyclePayload = viewerAligned.enabled + ? viewerAligned.marketOverview ?? null + : null; + const alignedLifecycleDuel = alignedLifecyclePayload?.duel ?? null; + const alignedLifecycleMarket = alignedLifecyclePayload + ? selectPredictionMarketLifecycleRecord( + alignedLifecyclePayload, + lifecycleChainKey, + ) + : null; + const alignedSession = viewerAligned.enabled + ? projectCanonicalSessionToSourceTimeline( + viewerAligned.session ?? canonicalStreamSession, + ) + : null; + const displaySession = alignedSession ?? canonicalStreamSession; + const displayDuelContext = viewerAligned.enabled + ? projectDuelContextToSourceTimeline( + viewerAligned.duelContext ?? duelContext, + ) ?? duelContext + : duelContext; + const liveCycle = + (displaySession?.cycle as typeof freshestCycle) ?? freshestCycle; + const displayLifecycleMarket = alignedLifecycleMarket ?? lifecycleMarket ?? null; + + const streamPlaceholderMessage = useMemo(() => { + if (!canonicalStreamSession) { + return "Connecting to live session..."; + } + if (canonicalAuthorityHealth?.ready === false) { + return "Stream authority unavailable. Waiting for session state."; + } + if (canonicalRendererHealth?.ready === false) { + return describeCanonicalRendererDegradedReason( + canonicalRendererHealth.degradedReason, + copy.waitingForStream, + ); + } + if ( + canonicalPublicReadiness?.ready === false || + canonicalDeliveryHealth?.ready === false + ) { + return describeCanonicalRendererDegradedReason( + canonicalPublicReadiness?.reason ?? + canonicalDeliveryHealth?.degradedReason, + copy.waitingForStream, + ); + } + return copy.waitingForStream; + }, [ + canonicalPublicReadiness?.reason, + canonicalPublicReadiness?.ready, + canonicalDeliveryHealth?.degradedReason, + canonicalDeliveryHealth?.ready, + canonicalAuthorityHealth?.ready, + canonicalRendererHealth?.degradedReason, + canonicalRendererHealth?.ready, + canonicalStreamSession, + copy.waitingForStream, + ]); const handleLocaleChange = useCallback((nextLocale: UiLocale) => { setStoredUiLocale(nextLocale); @@ -744,16 +871,22 @@ export function App() { }, []); const switchToBackupStream = useCallback(() => { + if (!allowStreamSourceOverride) { + return; + } setStreamSourceIndex((current) => current + 1 < streamSources.length ? current + 1 : current, ); - }, [streamSources.length]); + }, [allowStreamSourceOverride, streamSources.length]); const cycleStreamSource = useCallback(() => { + if (!allowStreamSourceOverride) { + return; + } setStreamSourceIndex((current) => streamSources.length > 1 ? (current + 1) % streamSources.length : current, ); - }, [streamSources.length]); + }, [allowStreamSourceOverride, streamSources.length]); useEffect(() => { if (streamSourceIndex < streamSources.length) return; @@ -862,9 +995,9 @@ export function App() { ); const closeTs = normalizeTimestamp( liveCycle.betCloseTime ?? - liveCycle.fightStartTime ?? - liveCycle.duelEndTime ?? - Math.floor(Date.now() / 1000), + liveCycle.fightStartTime ?? + liveCycle.duelEndTime ?? + Math.floor(Date.now() / 1000), ); const resolvedTs = liveCycle.phase === "RESOLUTION" && liveCycle.duelEndTime @@ -913,8 +1046,8 @@ export function App() { const effAgent1Name = currentMatch?.agent1Name ?? liveAgent1Name ?? "Agent A"; const effAgent2Name = currentMatch?.agent2Name ?? liveAgent2Name ?? "Agent B"; - const contextAgent1 = duelContext?.cycle.agent1 ?? null; - const contextAgent2 = duelContext?.cycle.agent2 ?? null; + const contextAgent1 = displayDuelContext?.cycle.agent1 ?? null; + const contextAgent2 = displayDuelContext?.cycle.agent2 ?? null; // Agent context from live SSE + duel-context polling const effA1 = { @@ -985,15 +1118,28 @@ export function App() { const streamPhaseText = liveCycle?.phase ?? null; const marketStatusText = getMarketStatusLabel( - lifecycleMarket?.lifecycleStatus ?? + displayLifecycleMarket?.lifecycleStatus ?? currentMatch?.status ?? streamPhaseText ?? copy.phaseLive, copy, ); - const countdownText = liveCycle - ? formatCountdown(normalizeRemainingSeconds(liveCycle.timeRemaining)) - : ""; + const liveStartDisplay = liveCycle + ? resolveAlignedCountdownDisplay({ + phase: liveCycle.phase ?? null, + viewerClock: viewerAligned.viewerClock, + betCloseTime: liveCycle.betCloseTime ?? null, + fightStartTime: liveCycle.fightStartTime ?? null, + fallbackTimeRemaining: liveCycle.timeRemaining, + }) + : null; + const displayPhaseLabel = + liveStartDisplay?.holdState === "preparing_arena" + ? "Preparing arena" + : liveStartDisplay?.holdState === "starting" + ? "Starting..." + : effPhaseLabel; + const displayCountdownText = ""; // Sidebar bet state const [isSidebarOpen, setIsSidebarOpen] = useState(false); @@ -1398,7 +1544,7 @@ export function App() {
{copy.yesPool}: - GOLD | {copy.noPool}: - GOLD
-
{countdownText}
+
{displayCountdownText}
{status}
- {streamSources.length > 1 && ( + {activeStreamUrl ? ( +
- )} -
+ {allowStreamSourceOverride && streamSources.length > 1 && ( + + )} +
+ ) : null} + {!activeStreamUrl ? ( +
+
+ + {streamPlaceholderMessage} + +
+ ) : null} ) : (
- - {copy.waitingForStream} - + {streamPlaceholderMessage}
)}
{/* Odds Chart */} -
-
- - - -
-
- - {(effYesPercent / 100).toFixed(1)} - -
-
- - - { - const d = new Date(v); - return `${d.getHours()}:${String(d.getMinutes()).padStart(2, "0")}`; - }} - /> - `${v}%`} - /> - - active && payload?.length ? ( -
- {payload[0].value}% -
- ) : null - } - /> - - -
-
+ {!isMobile && ( +
+
+ + + +
+
+ + {(effYesPercent / 100).toFixed(1)} + +
+
+ {chartSize ? ( + + { + const d = new Date(v); + return `${d.getHours()}:${String(d.getMinutes()).padStart(2, "0")}`; + }} + /> + `${v}%`} + /> + + active && payload?.length ? ( +
+ {payload[0].value}% +
+ ) : null + } + /> + + +
+ ) : null} +
-
+ )}
- {effPhaseLabel} + {displayPhaseLabel} + ) : ( + + {({ + openConnectModal, + openAccountModal, + openChainModal, + account, + chain, + mounted, + }) => { + if (!mounted || !account) + return effectiveEvmWalletAddress ? ( + + ) : ( + + ); + if (chain?.unsupported) + return ( + + ); + return ( + + ); + }} + + ); const tradingSidebar = (
- {streamSyncUi.state !== "hidden" ? ( + {streamSyncUiState !== "aligned" ? (
- {streamSyncUi.state === "refreshing" ? ( + {streamSyncUiState === "connecting" ? ( ) : null} - {streamSyncUi.state === "degraded" ? ( + {streamSyncUiState === "recovering" ? ( + ) : null} + + {streamSyncUiState === "degraded" ? ( + ) : null} - {streamSyncUi.state === "drift" ? ( + {streamSyncUiState === "drifted" ? ( } > - + )} {pointsDrawerTab === "history" && ( }> - + )} {pointsDrawerTab === "referral" && ( }> )} @@ -1567,54 +1940,72 @@ export function App() { > 🏆 - - {({ - openConnectModal, - openAccountModal, - openChainModal, - account, - chain, - mounted, - }) => { - if (!mounted || !account) - return effectiveEvmWalletAddress ? ( - - ) : ( - - ); - if (chain?.unsupported) + {isSolanaChain ? ( + + ) : ( + + {({ + openConnectModal, + openAccountModal, + openChainModal, + account, + chain, + mounted, + }) => { + if (!mounted || !account) + return effectiveEvmWalletAddress ? ( + + ) : ( + + ); + if (chain?.unsupported) + return ( + + ); return ( ); - return ( - - ); - }} - + }} + + )}
{/* Row 2: Match strip — name + agent side-select chips */} @@ -1675,54 +2066,7 @@ export function App() { > 🏆 - - {({ - openConnectModal, - openAccountModal, - openChainModal, - account, - chain, - mounted, - }) => { - if (!mounted || !account) - return effectiveEvmWalletAddress ? ( - - ) : ( - - ); - if (chain?.unsupported) - return ( - - ); - return ( - - ); - }} - + {walletButton} )} @@ -1731,21 +2075,7 @@ export function App() { {surfaceMode === "MODELS" ? (
- - } - > - - + {modelsPanelBody}
) : ( @@ -1758,20 +2088,16 @@ export function App() { {/* Game Viewport */}
- {activeStreamUrl ? ( + {mountedStreamUrl ? ( <> { - setStreamSurfaceReady(true); - setStreamSurfaceUnavailable(false); - }} - onStreamUnavailable={() => { - setStreamSurfaceUnavailable(true); - switchToBackupStream(); - }} + onStatusChange={setStreamPlayerStatus} style={{ position: "absolute", inset: 0, @@ -1779,62 +2105,70 @@ export function App() { height: "100%", }} /> -
- - {streamSources.length > 1 && ( + {activeStreamUrl ? ( +
- )} -
+
+ ) : null} + {rendererDegradedOverlayMessage ? ( +
+
+ + {rendererDegradedOverlayMessage} + +
+ ) : null} + {!activeStreamUrl ? ( +
+
+ + {streamPlaceholderMessage} + +
+ ) : null} ) : (
- {copy.waitingForStream} + {streamPlaceholderMessage}
)} diff --git a/packages/hyperbet-evm/app/src/AppRoot.tsx b/packages/hyperbet-evm/app/src/AppRoot.tsx index 181057df..44083a8f 100644 --- a/packages/hyperbet-evm/app/src/AppRoot.tsx +++ b/packages/hyperbet-evm/app/src/AppRoot.tsx @@ -1,14 +1,23 @@ import { ChainProvider } from "./lib/ChainContext"; -import { createEvmAppRoot } from "@hyperbet/ui"; +import { createHybridAppRoot } from "@hyperbet/ui/createHybridAppRoot"; +import { CONFIG, getRpcUrl, getWsUrl } from "@hyperbet/ui/lib/config"; import { wagmiConfig } from "@hyperbet/ui/lib/wagmiConfig"; import { App } from "./App"; import { StreamUIApp } from "./StreamUIApp"; -export default createEvmAppRoot({ +const UnifiedAppRoot = createHybridAppRoot({ ChainProvider, wagmiConfig: wagmiConfig as any, App, StreamUIApp, + getSolanaRpcUrl: getRpcUrl, + getSolanaWsUrl: getWsUrl, + getSolanaCluster: () => CONFIG.cluster, + isStreamUi: import.meta.env.MODE === "stream-ui", themeId: "evm", themeStorageKey: "hyperbet-evm-theme", }); + +export default function AppRoot() { + return ; +} diff --git a/packages/hyperbet-evm/app/src/hlsPlayerMain.tsx b/packages/hyperbet-evm/app/src/hlsPlayerMain.tsx new file mode 100644 index 00000000..3c73354c --- /dev/null +++ b/packages/hyperbet-evm/app/src/hlsPlayerMain.tsx @@ -0,0 +1,3 @@ +import { mountHlsPlayerApp } from "@hyperbet/ui/player"; + +mountHlsPlayerApp(document.getElementById("root")!); diff --git a/packages/hyperbet-evm/app/src/lib/ChainContext.tsx b/packages/hyperbet-evm/app/src/lib/ChainContext.tsx index 39c64fa1..46af09d9 100644 --- a/packages/hyperbet-evm/app/src/lib/ChainContext.tsx +++ b/packages/hyperbet-evm/app/src/lib/ChainContext.tsx @@ -1,12 +1,14 @@ import { createChainProvider } from "@hyperbet/ui/lib/ChainContext"; -import { getEnabledEvmChains } from "@hyperbet/ui/lib/chainConfig"; +import { getAvailableChains } from "@hyperbet/ui/lib/chainConfig"; export { useChain } from "@hyperbet/ui/lib/ChainContext"; -const enabledEvmChains = getEnabledEvmChains().map((chain) => chain.chainId); -const defaultEvmChain = enabledEvmChains[0] ?? "bsc"; +const e2eAvailableChains = + import.meta.env.MODE === "e2e" + ? (["solana", "bsc"] as const) + : getAvailableChains(); export const ChainProvider = createChainProvider({ - e2eDefaultChain: defaultEvmChain, - chains: enabledEvmChains.length ? enabledEvmChains : [defaultEvmChain], + e2eDefaultChain: "solana", + chains: [...e2eAvailableChains], }); diff --git a/packages/hyperbet-evm/app/src/lib/config.ts b/packages/hyperbet-evm/app/src/lib/config.ts index 595aa160..2d2ff036 100644 --- a/packages/hyperbet-evm/app/src/lib/config.ts +++ b/packages/hyperbet-evm/app/src/lib/config.ts @@ -2,6 +2,7 @@ export { ACTIVE_ENV, CONFIG, DEFAULT_REFRESH_INTERVAL_MS, + ENABLE_LIFECYCLE_MISMATCH_CONSOLE, GAME_API_URL, getFixedMatchId, STREAM_URLS, diff --git a/packages/hyperbet-evm/app/src/lib/streamSyncUi.test.ts b/packages/hyperbet-evm/app/src/lib/streamSyncUi.test.ts deleted file mode 100644 index cd8a3c7b..00000000 --- a/packages/hyperbet-evm/app/src/lib/streamSyncUi.test.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { describe, expect, test } from "bun:test"; - -import { - advanceStreamSyncUiState, - INITIAL_STREAM_SYNC_UI_SNAPSHOT, -} from "./streamSyncUi"; - -describe("advanceStreamSyncUiState", () => { - test("hides the shell state when the stream and market are healthy", () => { - expect( - advanceStreamSyncUiState(INITIAL_STREAM_SYNC_UI_SNAPSHOT, { - marketAligned: true, - syncStatus: { - sourceEpoch: 1, - sourceLatestSeq: 5, - lastSeenSeq: 5, - lastAppliedSeq: 5, - applyLagMs: 5, - sourceEventAgeMs: 500, - replayMode: "live", - degradedReason: null, - rendererHealth: { ready: true, degradedReason: null, updatedAt: 10 }, - rendererHealthAgeMs: 500, - lastEventReceivedAt: 10, - lastAppliedAt: 10, - connectedAt: 10, - enabled: true, - }, - rendererReady: true, - streamSurfaceReady: true, - streamSurfaceUnavailable: false, - marketOverviewErrorPresent: false, - }), - ).toEqual({ - state: "hidden", - driftMismatchCount: 0, - }); - }); - - test("shows refreshing for a transient replay or stale-refresh sample", () => { - expect( - advanceStreamSyncUiState(INITIAL_STREAM_SYNC_UI_SNAPSHOT, { - marketAligned: true, - syncStatus: { - sourceEpoch: 1, - sourceLatestSeq: 5, - lastSeenSeq: 5, - lastAppliedSeq: 5, - applyLagMs: 5, - sourceEventAgeMs: 6_000, - replayMode: "replay", - degradedReason: null, - rendererHealth: { ready: true, degradedReason: null, updatedAt: 10 }, - rendererHealthAgeMs: 2_000, - lastEventReceivedAt: 10, - lastAppliedAt: 10, - connectedAt: 10, - enabled: true, - }, - rendererReady: true, - streamSurfaceReady: false, - streamSurfaceUnavailable: false, - marketOverviewErrorPresent: false, - }).state, - ).toBe("refreshing"); - }); - - test("shows degraded when the renderer is unhealthy", () => { - expect( - advanceStreamSyncUiState(INITIAL_STREAM_SYNC_UI_SNAPSHOT, { - marketAligned: true, - syncStatus: null, - rendererReady: false, - streamSurfaceReady: false, - streamSurfaceUnavailable: false, - marketOverviewErrorPresent: false, - }).state, - ).toBe("degraded"); - }); - - test("requires two consecutive mismatches before escalating to drift", () => { - const first = advanceStreamSyncUiState(INITIAL_STREAM_SYNC_UI_SNAPSHOT, { - marketAligned: false, - syncStatus: null, - rendererReady: true, - streamSurfaceReady: true, - streamSurfaceUnavailable: false, - marketOverviewErrorPresent: false, - }); - const second = advanceStreamSyncUiState(first, { - marketAligned: false, - syncStatus: null, - rendererReady: true, - streamSurfaceReady: true, - streamSurfaceUnavailable: false, - marketOverviewErrorPresent: false, - }); - - expect(first).toEqual({ - state: "refreshing", - driftMismatchCount: 1, - }); - expect(second).toEqual({ - state: "drift", - driftMismatchCount: 2, - }); - }); - - test("clears drift after one healthy poll", () => { - const drifted = { - state: "drift" as const, - driftMismatchCount: 2, - }; - - expect( - advanceStreamSyncUiState(drifted, { - marketAligned: true, - syncStatus: { - sourceEpoch: 1, - sourceLatestSeq: 6, - lastSeenSeq: 6, - lastAppliedSeq: 6, - applyLagMs: 1, - sourceEventAgeMs: 100, - replayMode: "live", - degradedReason: null, - rendererHealth: { ready: true, degradedReason: null, updatedAt: 11 }, - rendererHealthAgeMs: 100, - lastEventReceivedAt: 11, - lastAppliedAt: 11, - connectedAt: 11, - enabled: true, - }, - rendererReady: true, - streamSurfaceReady: true, - streamSurfaceUnavailable: false, - marketOverviewErrorPresent: false, - }), - ).toEqual({ - state: "hidden", - driftMismatchCount: 0, - }); - }); - - test("hides renderer-health-stale when the stream surface is visibly ready", () => { - expect( - advanceStreamSyncUiState(INITIAL_STREAM_SYNC_UI_SNAPSHOT, { - marketAligned: true, - syncStatus: { - sourceEpoch: 1, - sourceLatestSeq: 5, - lastSeenSeq: 5, - lastAppliedSeq: 5, - applyLagMs: 0, - sourceEventAgeMs: 455, - replayMode: "live", - degradedReason: "renderer_health_stale", - rendererHealth: { - ready: false, - degradedReason: "renderer_health_stale", - updatedAt: 10, - }, - rendererHealthAgeMs: 455, - lastEventReceivedAt: 10, - lastAppliedAt: 10, - connectedAt: 10, - enabled: true, - }, - rendererReady: false, - streamSurfaceReady: true, - streamSurfaceUnavailable: false, - marketOverviewErrorPresent: false, - }).state, - ).toBe("hidden"); - }); -}); diff --git a/packages/hyperbet-evm/app/src/lib/streamSyncUi.ts b/packages/hyperbet-evm/app/src/lib/streamSyncUi.ts deleted file mode 100644 index d32d4996..00000000 --- a/packages/hyperbet-evm/app/src/lib/streamSyncUi.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { PredictionMarketSyncStatusResponse } from "@hyperbet/ui/lib/predictionMarkets"; - -export type StreamSyncUiState = "hidden" | "refreshing" | "degraded" | "drift"; - -export type StreamSyncUiSnapshot = { - state: StreamSyncUiState; - driftMismatchCount: number; -}; - -export type StreamSyncUiInput = { - marketAligned: boolean; - syncStatus: PredictionMarketSyncStatusResponse | null; - rendererReady: boolean | null; - streamSurfaceReady: boolean; - streamSurfaceUnavailable: boolean; - marketOverviewErrorPresent: boolean; -}; - -type StreamSyncUiSignal = "hidden" | "refreshing" | "degraded" | "drift-candidate"; - -export const INITIAL_STREAM_SYNC_UI_SNAPSHOT: StreamSyncUiSnapshot = { - state: "hidden", - driftMismatchCount: 0, -}; - -function deriveStreamSyncUiSignal(input: StreamSyncUiInput): StreamSyncUiSignal { - const sourceEventAgeMs = input.syncStatus?.sourceEventAgeMs ?? 0; - const rendererHealthAgeMs = input.syncStatus?.rendererHealthAgeMs ?? 0; - const replayMode = input.syncStatus?.replayMode ?? null; - const degradedReason = input.syncStatus?.degradedReason ?? null; - const rendererOnlyStale = - input.streamSurfaceReady && - !input.streamSurfaceUnavailable && - input.rendererReady === false && - degradedReason === "renderer_health_stale" && - replayMode === "live" && - sourceEventAgeMs <= 5_000 && - rendererHealthAgeMs <= 5_000; - - if (!input.marketAligned) { - return "drift-candidate"; - } - - if (rendererOnlyStale) { - return "hidden"; - } - - if ( - input.rendererReady === false || - degradedReason || - sourceEventAgeMs > 15_000 || - rendererHealthAgeMs > 15_000 - ) { - return "degraded"; - } - - if ( - input.streamSurfaceUnavailable || - input.marketOverviewErrorPresent || - replayMode !== "live" || - sourceEventAgeMs > 5_000 || - rendererHealthAgeMs > 5_000 - ) { - return "refreshing"; - } - - return "hidden"; -} - -export function advanceStreamSyncUiState( - previous: StreamSyncUiSnapshot, - input: StreamSyncUiInput, -): StreamSyncUiSnapshot { - const signal = deriveStreamSyncUiSignal(input); - - if (signal !== "drift-candidate") { - return { - state: signal, - driftMismatchCount: 0, - }; - } - - const driftMismatchCount = previous.driftMismatchCount + 1; - return { - state: driftMismatchCount >= 2 ? "drift" : "refreshing", - driftMismatchCount, - }; -} diff --git a/packages/hyperbet-evm/app/src/styles.css b/packages/hyperbet-evm/app/src/styles.css index 66fc7fdb..985ed2ea 100644 --- a/packages/hyperbet-evm/app/src/styles.css +++ b/packages/hyperbet-evm/app/src/styles.css @@ -3121,6 +3121,48 @@ select:focus-visible { text-shadow: 0 0 16px var(--hm-gold-glow-subtle); } +.hm-stream-degraded-overlay { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + z-index: 7; + pointer-events: none; +} + +.hm-stream-degraded-backdrop { + position: absolute; + inset: 0; + background: + radial-gradient( + ellipse 52% 42% at 50% 52%, + rgba(232, 65, 66, 0.12) 0%, + rgba(232, 65, 66, 0) 68% + ), + linear-gradient(180deg, rgba(5, 6, 7, 0.42) 0%, rgba(5, 6, 7, 0.68) 100%); + backdrop-filter: blur(4px); +} + +.hm-stream-degraded-message { + position: relative; + max-width: min(70%, 440px); + padding: 16px 20px; + border: 1px solid var(--hm-gold-border-light); + background: rgba(10, 10, 12, 0.86); + color: var(--hm-accent-gold); + font-family: var(--hm-font-display); + font-size: 12px; + font-weight: 700; + letter-spacing: 2px; + line-height: 1.55; + text-align: center; + text-transform: uppercase; + box-shadow: + 0 18px 40px rgba(0, 0, 0, 0.38), + 0 0 22px var(--hm-gold-glow-subtle); +} + .hm-stream-controls { position: absolute; bottom: 12px; diff --git a/packages/hyperbet-evm/app/tests/e2e/seed-api-local.ts b/packages/hyperbet-evm/app/tests/e2e/seed-api-local.ts index 0075a544..9d28caec 100644 --- a/packages/hyperbet-evm/app/tests/e2e/seed-api-local.ts +++ b/packages/hyperbet-evm/app/tests/e2e/seed-api-local.ts @@ -9,6 +9,8 @@ type E2eState = { currentMatchId?: number; currentDuelKeyHex?: string; currentBetWindowSeconds?: number; + evmMatchId?: number; + evmDuelKeyHex?: string; }; async function readState(): Promise { @@ -23,11 +25,24 @@ function requireString(value: string | undefined, label: string): string { return trimmed; } +function requireNumber(value: number | undefined, label: string): number { + if (!Number.isFinite(value)) { + throw new Error(`Missing ${label} in e2e state`); + } + return Number(value); +} + async function requestJson(url: string, init?: RequestInit): Promise { + const writeKey = + process.env.E2E_ARENA_WRITE_KEY?.trim() || + process.env.ARENA_EXTERNAL_BET_WRITE_KEY?.trim() || + process.env.VITE_ARENA_WRITE_KEY?.trim() || + ""; const response = await fetch(url, { ...init, headers: { "content-type": "application/json", + ...(writeKey ? { "x-arena-write-key": writeKey } : {}), ...(init?.headers || {}), }, }); @@ -53,11 +68,8 @@ async function main(): Promise { "perpsCharacterId", ); const perpsModelName = state.perpsModelName?.trim() || "E2E Model Alpha"; - const duelKeyHex = requireString( - state.currentDuelKeyHex, - "currentDuelKeyHex", - ); - const duelId = String(state.currentMatchId || Date.now()); + const duelKeyHex = requireString(state.currentDuelKeyHex, "currentDuelKeyHex"); + const duelId = String(requireNumber(state.currentMatchId, "currentMatchId")); const currentBetWindowSeconds = Math.max( 30, Number(state.currentBetWindowSeconds || 45), @@ -161,7 +173,7 @@ async function main(): Promise { method: "POST", body: JSON.stringify({ cycle: { - cycleId: "e2e-cycle-active", + cycleId: duelId, phase: "FIGHTING", duelId, duelKeyHex, diff --git a/packages/hyperbet-evm/app/tests/e2e/setup-api-local.ts b/packages/hyperbet-evm/app/tests/e2e/setup-api-local.ts index 7bc179dc..edd69116 100644 --- a/packages/hyperbet-evm/app/tests/e2e/setup-api-local.ts +++ b/packages/hyperbet-evm/app/tests/e2e/setup-api-local.ts @@ -3,6 +3,8 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import { + saveChainScopedPerpsMarket, + saveChainScopedPerpsOracleSnapshot, savePerpsMarket, savePerpsOracleSnapshot, saveWalletDisplay, @@ -41,7 +43,14 @@ async function main(): Promise { const marketId = Number(state.perpsMarketId) || modelMarketIdFromCharacterId(characterId); const modelName = state.perpsModelName?.trim() || "E2E Model Alpha"; + const localChainKey = "bsc" as const; const now = Date.now(); + const nextState: E2eState = { + ...state, + perpsCharacterId: characterId, + perpsMarketId: marketId, + perpsModelName: modelName, + }; const seededWallets = [ primaryWallet, @@ -88,6 +97,24 @@ async function main(): Promise { deprecatedAt: null, updatedAt: now, }); + saveChainScopedPerpsMarket({ + chainKey: localChainKey, + agentId: characterId, + marketId, + rank: 1, + name: modelName, + provider: "Hyperscape", + model: "alpha-local", + wins: 12, + losses: 4, + winRate: 75, + combatLevel: 88, + currentStreak: 4, + status: "ACTIVE", + lastSeenAt: now, + deprecatedAt: null, + updatedAt: now, + }); const oracleSnapshots = [ { spotIndex: 118, mu: 27.2, sigma: 4.6, recordedAt: now - 60 * 60 * 1000 }, @@ -107,8 +134,20 @@ async function main(): Promise { sigma: snapshot.sigma, recordedAt: snapshot.recordedAt, }); + saveChainScopedPerpsOracleSnapshot({ + chainKey: localChainKey, + agentId: characterId, + marketId, + spotIndex: snapshot.spotIndex, + conservativeSkill: snapshot.mu - snapshot.sigma * 3, + mu: snapshot.mu, + sigma: snapshot.sigma, + recordedAt: snapshot.recordedAt, + }); } + await fs.writeFile(statePath, `${JSON.stringify(nextState, null, 2)}\n`, "utf8"); + console.log( JSON.stringify( { diff --git a/packages/hyperbet-evm/app/tests/e2e/setup-evm-local.ts b/packages/hyperbet-evm/app/tests/e2e/setup-evm-local.ts index f316f6ed..22dfc6e0 100644 --- a/packages/hyperbet-evm/app/tests/e2e/setup-evm-local.ts +++ b/packages/hyperbet-evm/app/tests/e2e/setup-evm-local.ts @@ -19,6 +19,7 @@ import goldClobArtifact from "../../../../evm-contracts/out/GoldClob.sol/GoldClo type E2eState = Record & { currentDuelKeyHex?: string; + currentBetWindowSeconds?: number; evmRpcUrl?: string; evmChainId?: number; evmHeadlessAddress?: string; @@ -46,6 +47,7 @@ const SELL_SIDE = 2; const DUEL_STATUS_BETTING_OPEN = 2; const ORDER_FLAG_GTC = 0x01; const DUEL_ORACLE_DISPUTE_WINDOW_SECONDS = 3_600; +const E2E_BET_WINDOW_SECONDS = 300; type EvmArtifact = { abi: unknown[]; @@ -375,10 +377,18 @@ async function main(): Promise { await publicClient.waitForTransactionReceipt({ hash: seedYesOrderTx }); const env = await readEnv(envPath); - env.VITE_AVAX_RPC_URL = rpcUrl; - env.VITE_AVAX_CHAIN_ID = String(chainId); - env.VITE_AVAX_GOLD_CLOB_ADDRESS = goldClobAddress; - env.VITE_AVAX_GOLD_TOKEN_ADDRESS = goldTokenAddress; + env.VITE_BSC_RPC_URL = rpcUrl; + env.VITE_BSC_CHAIN_ID = String(chainId); + env.VITE_BSC_GOLD_CLOB_ADDRESS = goldClobAddress; + env.VITE_BSC_GOLD_TOKEN_ADDRESS = goldTokenAddress; + delete env.VITE_BASE_RPC_URL; + delete env.VITE_BASE_CHAIN_ID; + delete env.VITE_BASE_GOLD_CLOB_ADDRESS; + delete env.VITE_BASE_GOLD_TOKEN_ADDRESS; + delete env.VITE_AVAX_RPC_URL; + delete env.VITE_AVAX_CHAIN_ID; + delete env.VITE_AVAX_GOLD_CLOB_ADDRESS; + delete env.VITE_AVAX_GOLD_TOKEN_ADDRESS; env.VITE_EVM_PRIVATE_KEY = adminPrivateKey; env.VITE_HEADLESS_EVM_PRIVATE_KEY = adminPrivateKey; env.VITE_HEADLESS_EVM_ADDRESS = adminAccount.address; @@ -386,12 +396,15 @@ async function main(): Promise { env.VITE_E2E_EVM_ADDRESS = adminAccount.address; await fs.writeFile(envPath, serializeDotEnv(env), "utf8"); - const state: E2eState = { - ...existingState, - evmRpcUrl: rpcUrl, - evmChainId: chainId, - evmHeadlessAddress: adminAccount.address, - evmGoldTokenAddress: goldTokenAddress, +const state: E2eState = { + ...existingState, + currentMatchId: 1, + currentDuelKeyHex: duelKey, + currentBetWindowSeconds: E2E_BET_WINDOW_SECONDS, + evmRpcUrl: rpcUrl, + evmChainId: chainId, + evmHeadlessAddress: adminAccount.address, + evmGoldTokenAddress: goldTokenAddress, evmGoldClobAddress: goldClobAddress, evmMatchId: 1, evmDuelKeyHex: duelKey, diff --git a/packages/hyperbet-evm/app/tests/e2e/unified-app.e2e.ts b/packages/hyperbet-evm/app/tests/e2e/unified-app.e2e.ts new file mode 100644 index 00000000..6b2b8297 --- /dev/null +++ b/packages/hyperbet-evm/app/tests/e2e/unified-app.e2e.ts @@ -0,0 +1,466 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +import { expect, test, type APIRequestContext, type Page } from "@playwright/test"; + +type E2eState = { + evmHeadlessAddress?: string; + perpsCharacterId?: string; + perpsModelName?: string; +}; + +type PredictionMarketsResponse = { + duel: { + duelKey: string | null; + duelId: string | null; + phase: string | null; + winner: string | null; + betCloseTime: number | null; + }; + markets: Array<{ + chainKey: string; + marketRef: string | null; + lifecycleStatus: string; + contractAddress: string | null; + programId: string | null; + }>; + updatedAt: number | null; +}; + +type PerpsMarketsResponse = { + markets: Array<{ + characterId: string; + marketId: number; + name: string; + }>; +}; + +type PointsResponse = { + wallet: string; + pointsScope?: "WALLET" | "LINKED"; + identityWalletCount?: number; + invitedWalletCount?: number; + totalPoints: number; + referredBy: { wallet: string; code: string } | null; +}; + +type PointsHistoryResponse = { + total: number; +}; + +type InviteResponse = { + inviteCode: string; + platformView?: string; + invitedWalletCount?: number; +}; + +type KeeperStatusResponse = { + ok?: boolean; + predictionMarkets?: { + activeDuelKey?: string | null; + marketCount?: number | null; + chains?: Array<{ chainKey: string; lifecycleStatus: string }> | null; + }; +}; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const statePath = path.resolve(__dirname, "./state.json"); +const BASE_URL = (process.env.E2E_BASE_URL || "http://127.0.0.1:4181") + .trim() + .replace(/\/$/, ""); +const GAME_API_URL = (process.env.E2E_GAME_API_URL || "http://127.0.0.1:5555") + .trim() + .replace(/\/$/, ""); + +function loadState(): E2eState { + return JSON.parse(fs.readFileSync(statePath, "utf8")) as E2eState; +} + +function requireString(value: string | undefined, label: string): string { + const trimmed = value?.trim() || ""; + if (!trimmed) { + throw new Error(`Missing ${label} in e2e state`); + } + return trimmed; +} + +async function fetchJson( + request: APIRequestContext, + baseUrl: string, + pathname: string, +): Promise { + const response = await request.get(`${baseUrl}${pathname}`); + expect(response.ok(), `GET ${pathname} should succeed`).toBeTruthy(); + return (await response.json()) as T; +} + +async function gotoApp(page: Page): Promise { + await page.goto("/?debug=1", { waitUntil: "domcontentloaded" }); + await expect + .poll(async () => { + const bodyText = (await page.locator("body").textContent().catch(() => "")) ?? ""; + return bodyText.trim().length > 0 ? bodyText.trim() : ""; + }) + .not.toBe(""); + await expect(page.locator("#chain-selector").first()).toBeVisible({ + timeout: 30_000, + }); +} + +async function ensureWalletConnected(page: Page): Promise { + const hasConnectedSolanaWallet = async (): Promise => { + const walletButtonCandidates = [ + page.getByRole("button", { name: /^SOL\s+.+/i }).first(), + page.getByRole("button", { name: /^◎\s*.+/i }).first(), + ]; + + for (const candidate of walletButtonCandidates) { + if (!(await candidate.isVisible().catch(() => false))) continue; + const label = (await candidate.textContent().catch(() => ""))?.trim() ?? ""; + if (!label || /connect solana|connect wallet|select wallet/i.test(label)) { + continue; + } + if (/^SOL\s+/i.test(label) || /^◎\s*/i.test(label)) { + return true; + } + } + + return false; + }; + + const selectHeadlessWallet = async (): Promise => { + const walletOption = page.getByRole("button", { name: /E2E Trader/i }).first(); + if (!(await walletOption.isVisible().catch(() => false))) return false; + await walletOption.click({ force: true }); + await expect( + page.getByRole("dialog", { + name: /Connect a wallet on Solana to continue/i, + }), + ) + .toBeHidden({ timeout: 30_000 }) + .catch(() => undefined); + return true; + }; + + for (let attempt = 0; attempt < 4; attempt += 1) { + if (await hasConnectedSolanaWallet()) return; + + if (await selectHeadlessWallet()) { + await page.waitForTimeout(2_000); + continue; + } + + const connectButton = page + .getByRole("button", { + name: /connect wallet|select wallet|connect|add sol wallet|connect sol/i, + }) + .first(); + if (await connectButton.isVisible().catch(() => false)) { + await connectButton.click(); + } + await selectHeadlessWallet(); + await page.waitForTimeout(2_000); + } + + await expect.poll(hasConnectedSolanaWallet, { timeout: 60_000 }).toBe(true); +} + +async function clickVisibleTestId(page: Page, testId: string): Promise { + const locator = page.locator(`[data-testid="${testId}"]:visible`).first(); + await expect(locator).toBeVisible({ timeout: 30_000 }); + try { + await locator.click({ timeout: 10_000 }); + } catch { + await locator.evaluate((node) => { + (node as HTMLButtonElement).click(); + }); + } +} + +async function selectChain( + page: Page, + chain: "solana" | "bsc" | "base" | "avax", +): Promise { + const selector = page.locator("#chain-selector").first(); + await expect(selector).toBeVisible({ timeout: 30_000 }); + await selector.selectOption(chain); + await expect(selector).toHaveValue(chain); +} + +async function openReferralPanel(page: Page): Promise { + await clickVisibleTestId(page, "points-drawer-open"); + await expect(page.getByTestId("points-drawer")).toBeVisible({ + timeout: 30_000, + }); + await clickVisibleTestId(page, "points-drawer-tab-referral"); + await expect(page.getByTestId("points-drawer-panel-referral")).toBeVisible({ + timeout: 30_000, + }); +} + +async function readTestIdText(page: Page, testId: string): Promise { + return ( + (await page.getByTestId(testId).first().textContent().catch(() => "")) || "" + ).trim(); +} + +async function waitForTxUpdate( + page: Page, + testId: string, + previous: string, +): Promise { + await expect + .poll( + async () => { + const current = await readTestIdText(page, testId); + return current !== previous ? current : ""; + }, + { timeout: 120_000, intervals: [500, 1_000, 2_000, 5_000] }, + ) + .toMatch(/^0x[0-9a-f]+$/i); + return readTestIdText(page, testId); +} + +async function waitForChangedText( + page: Page, + testId: string, + previous: string, +): Promise { + await expect + .poll( + async () => { + const current = await readTestIdText(page, testId); + return current !== previous && current.trim() && !current.trim().endsWith("-") + ? current + : ""; + }, + { timeout: 120_000, intervals: [500, 1_000, 2_000, 5_000] }, + ) + .not.toBe(""); + return readTestIdText(page, testId); +} + +test.describe("unified bets page coverage", () => { + test("keeper backend exposes unified markets and points data", async ({ + request, + }) => { + const state = loadState(); + const wallet = requireString(state.evmHeadlessAddress, "evmHeadlessAddress"); + const perpsCharacterId = requireString( + state.perpsCharacterId, + "perpsCharacterId", + ); + + const status = await fetchJson( + request, + GAME_API_URL, + "/status", + ); + expect(status.ok).toBe(true); + + await expect + .poll(async () => { + const snapshot = await fetchJson( + request, + GAME_API_URL, + "/api/arena/prediction-markets/active", + ); + return ( + snapshot.markets.find((market) => market.chainKey === "solana") + ?.lifecycleStatus ?? null + ); + }, { timeout: 30_000, intervals: [500, 1_000, 2_000] }) + .toBe("OPEN"); + const markets = await fetchJson( + request, + GAME_API_URL, + "/api/arena/prediction-markets/active", + ); + expect(markets.duel.duelKey || markets.duel.duelId).toBeTruthy(); + expect(markets.markets.some((market) => market.chainKey === "solana")).toBe( + true, + ); + expect(markets.markets.some((market) => market.chainKey === "bsc")).toBe( + true, + ); + const points = await fetchJson( + request, + GAME_API_URL, + `/api/arena/points/${encodeURIComponent(wallet)}?scope=linked`, + ); + expect(points.wallet.toLowerCase()).toContain(wallet.toLowerCase()); + expect(points.totalPoints).toBeGreaterThan(0); + + const invite = await fetchJson( + request, + GAME_API_URL, + `/api/arena/invite/${encodeURIComponent(wallet)}?platform=evm`, + ); + expect(invite.inviteCode).toMatch(/^HS/i); + const walletHistory = await fetchJson( + request, + GAME_API_URL, + `/api/arena/points/history/${encodeURIComponent(wallet)}?scope=wallet`, + ); + const linkedHistory = await fetchJson( + request, + GAME_API_URL, + `/api/arena/points/history/${encodeURIComponent(wallet)}?scope=linked`, + ); + expect(linkedHistory.total).toBeGreaterThan(walletHistory.total); + + const perpsMarkets = await fetchJson( + request, + GAME_API_URL, + "/api/perps/markets", + ); + expect( + perpsMarkets.markets.some( + (market) => market.characterId === perpsCharacterId, + ), + ).toBe(true); + }); + + test("chain selector swaps between Solana and EVM duels on one page", async ({ + page, + }) => { + await gotoApp(page); + + const selector = page.locator("#chain-selector").first(); + const optionValues = await selector.locator("option").evaluateAll((options) => + options.map((option) => (option as HTMLOptionElement).value), + ); + expect(optionValues).toContain("solana"); + expect(optionValues).toContain("bsc"); + expect(optionValues).not.toContain("avax"); + expect(optionValues).not.toContain("base"); + + await selectChain(page, "solana"); + await expect(page.getByTestId("solana-clob-panel")).toBeVisible({ + timeout: 60_000, + }); + await selectChain(page, "bsc"); + await expect(page.getByTestId("evm-panel").first()).toBeVisible({ + timeout: 60_000, + }); + await selectChain(page, "solana"); + await expect(page.getByTestId("solana-clob-panel")).toBeVisible({ + timeout: 60_000, + }); + }); + + test("points drawer and referral panel follow the active chain", async ({ + page, + }) => { + await gotoApp(page); + + await selectChain(page, "bsc"); + await openReferralPanel(page); + await expect(page.getByTestId("referral-panel")).toBeVisible(); + await expect(page.getByTestId("referral-panel-points-scope")).toContainText( + /linked/i, + ); + await expect(page.getByTestId("referral-panel-referred-by")).toBeVisible(); + await clickVisibleTestId(page, "points-drawer-close"); + + await selectChain(page, "solana"); + await openReferralPanel(page); + await expect(page.getByTestId("referral-panel")).toContainText( + /connect a wallet/i, + ); + await expect(page.getByTestId("referral-panel-points-scope")).toHaveCount(0); + }); + + test("solana models tab renders with the shared Solana runtime", async ({ + page, + }) => { + const state = loadState(); + const perpsCharacterId = requireString( + state.perpsCharacterId, + "perpsCharacterId", + ); + const perpsModelName = state.perpsModelName?.trim() || "E2E Model Alpha"; + + await gotoApp(page); + await selectChain(page, "solana"); + await clickVisibleTestId(page, "surface-mode-models"); + + await expect(page.getByTestId("models-market-view")).toBeVisible({ + timeout: 60_000, + }); + await expect( + page.getByTestId(`models-market-card-${perpsCharacterId}`), + ).toBeVisible({ + timeout: 60_000, + }); + await expect(page.getByTestId("models-market-view")).toContainText( + perpsModelName, + ); + }); + + test("bsc submit path executes on the unified page", async ({ + page, + }) => { + await gotoApp(page); + await selectChain(page, "bsc"); + + const evmPanel = page.getByTestId("evm-panel").first(); + await expect(evmPanel).toBeVisible({ timeout: 60_000 }); + await expect(evmPanel.getByTestId("prediction-submit")).toBeEnabled({ + timeout: 60_000, + }); + + await evmPanel.getByTestId("prediction-amount-input").fill("1"); + await evmPanel.getByTestId("evm-price-input").fill("600"); + await evmPanel.getByTestId("prediction-select-yes").click(); + + const previousOrderTx = await readTestIdText(page, "evm-last-order-tx"); + await evmPanel.getByTestId("prediction-submit").click(); + const nextOrderTx = await waitForTxUpdate( + page, + "evm-last-order-tx", + previousOrderTx, + ); + + expect(nextOrderTx).toMatch(/^0x[0-9a-f]+$/i); + await expect(page.getByTestId("evm-status")).not.toContainText(/order failed/i); + await expect(evmPanel.getByTestId("prediction-submit")).toBeVisible(); + }); + + test("solana CLOB submit path executes on the unified page", async ({ + page, + }) => { + await gotoApp(page); + await selectChain(page, "solana"); + await ensureWalletConnected(page); + await page.getByTestId("refresh-market").click(); + + await expect(page.getByTestId("current-match-id")).not.toContainText("—", { + timeout: 60_000, + }); + await expect(page.getByTestId("solana-clob-panel")).toBeVisible({ + timeout: 60_000, + }); + await expect(page.getByTestId("prediction-submit")).toBeEnabled({ + timeout: 60_000, + }); + + await page.getByTestId("prediction-select-yes").click({ force: true }); + await page.getByTestId("prediction-amount-input").fill("1"); + await page.getByTestId("solana-clob-price-input").fill("600"); + + const previousOrderTx = await readTestIdText(page, "solana-clob-place-order-tx"); + await page.getByTestId("prediction-submit").click({ force: true }); + const nextOrderTx = await waitForChangedText( + page, + "solana-clob-place-order-tx", + previousOrderTx, + ); + + expect(nextOrderTx).toMatch(/LAST_TX:\s*[1-9A-HJ-NP-Za-km-z]{20,}/); + await expect(page.getByTestId("solana-clob-status")).not.toContainText( + /order failed/i, + ); + }); +}); diff --git a/packages/hyperbet-evm/app/vite.config.ts b/packages/hyperbet-evm/app/vite.config.ts index 060da364..cd634691 100644 --- a/packages/hyperbet-evm/app/vite.config.ts +++ b/packages/hyperbet-evm/app/vite.config.ts @@ -259,11 +259,37 @@ export default defineConfig(async ({ mode }) => { }; plugins.push(buildInfoPlugin); + const solanaRpcTarget = env.VITE_SOLANA_RPC_URL?.trim(); + const solanaWsTarget = env.VITE_SOLANA_WS_URL?.trim(); + const useLocalSolanaProxy = + Boolean(solanaRpcTarget) && + /^https?:\/\/(127\.0\.0\.1|localhost|0\.0\.0\.0|\[::1\])(?::\d+)?/i.test( + solanaRpcTarget, + ); + const solanaProxyConfig = useLocalSolanaProxy + ? { + "/__solana/rpc": { + target: solanaRpcTarget, + changeOrigin: true, + secure: false, + rewrite: () => "/", + }, + "/__solana/ws": { + target: solanaWsTarget || solanaRpcTarget, + changeOrigin: true, + secure: false, + ws: true, + rewrite: () => "/", + }, + } + : undefined; + const config: UserConfig = { plugins, server: { host: true, port: 4179, + proxy: solanaProxyConfig, watch: { ignored: [ "**/test-results/**", @@ -281,6 +307,7 @@ export default defineConfig(async ({ mode }) => { }, preview: { host: true, + proxy: solanaProxyConfig, }, resolve: { alias: [ @@ -334,6 +361,10 @@ export default defineConfig(async ({ mode }) => { sourcemap: env.VITE_BUILD_SOURCEMAP === "true", chunkSizeWarningLimit: 3000, rollupOptions: { + input: { + index: path.resolve(__dirname, "index.html"), + "hls-player": path.resolve(__dirname, "hls-player.html"), + }, onwarn(warning, warn) { if ( warning.code === "SOURCEMAP_ERROR" || diff --git a/packages/hyperbet-evm/keeper/Dockerfile b/packages/hyperbet-evm/keeper/Dockerfile index da899d63..a0e50d7a 100644 --- a/packages/hyperbet-evm/keeper/Dockerfile +++ b/packages/hyperbet-evm/keeper/Dockerfile @@ -2,15 +2,16 @@ FROM oven/bun:1.3.8 WORKDIR /app -COPY keeper/package.json ./keeper/package.json -COPY keeper/bun.lock ./keeper/bun.lock -RUN cd keeper && bun install --frozen-lockfile --production +COPY keeper/workspace.package.json ./package.json +COPY keeper/workspace.bun.lock ./bun.lock +COPY keeper/workspace-packages/packages ./packages +COPY keeper/package.json ./packages/hyperbet-evm/keeper/package.json +COPY keeper/tsconfig.json ./packages/hyperbet-evm/keeper/tsconfig.json +COPY keeper/src ./packages/hyperbet-evm/keeper/src -COPY deployments ./deployments -COPY keeper/tsconfig.json ./keeper/tsconfig.json -COPY keeper/src ./keeper/src +RUN bun install --frozen-lockfile --production -WORKDIR /app/keeper +WORKDIR /app/packages/hyperbet-evm/keeper ENV NODE_ENV=production ENV PORT=8080 diff --git a/packages/hyperbet-evm/keeper/src/betSync.test.ts b/packages/hyperbet-evm/keeper/src/betSync.test.ts index 66d694ef..06ba99eb 100644 --- a/packages/hyperbet-evm/keeper/src/betSync.test.ts +++ b/packages/hyperbet-evm/keeper/src/betSync.test.ts @@ -6,11 +6,13 @@ import { parseBetSyncBootstrapState, parseBetSyncEvent, parsePredictionMarketsOverview, + publicStreamStateChanged, resolveBetSyncReplayMode, rollPredictionMarketsOverview, selectBetSyncReplayUntilSeq, selectBetSyncResumeSeq, toStreamStateFromBetSyncEvent, + type StreamState, } from "./betSync"; describe("bet-sync helpers", () => { @@ -21,6 +23,13 @@ describe("bet-sync helpers", () => { sourceEpoch: 4, seq: 12, emittedAt: 1_700_000_000_000, + cycle: { + cycleId: "raw-cycle-12", + phase: "RESOLUTION", + winnerId: "agent-a", + winnerName: "Agent A", + winReason: "knockout", + }, duelId: "duel-12", duelKey: `0x${duelKey}`, phase: "FIGHTING", @@ -38,26 +47,304 @@ describe("bet-sync helpers", () => { arenaPositions: { agent1: [0, 0, 0], agent2: [1, 0, 0] }, leaderboard: [{ id: "a" }], rendererHealth: { ready: true, degradedReason: null, updatedAt: 123 }, + marketParity: { + bundleId: "bundle-12", + duelKey: `0x${duelKey}`, + duelId: "duel-12", + revision: 4, + requiredChains: ["solana", "bsc"], + confirmedChains: ["solana"], + state: "awaiting_confirmations", + phase: "ANNOUNCEMENT", + safeToBet: false, + openedAtMs: null, + lockedAtMs: null, + resolvedAtMs: null, + freezeReason: null, + updatedAtMs: 1_700_000_000_001, + receipts: [ + { + chainKey: "solana", + preparedAtMs: 1_700_000_000_001, + openedAtMs: null, + lockedAtMs: null, + resolvedAtMs: null, + cancelledAtMs: null, + confirmedAtMs: 1_700_000_000_001, + lifecycleStatus: "PENDING", + txRef: "sol-tx", + note: null, + }, + ], + }, }); expect(parsed).not.toBeNull(); expect(parsed?.duelKey).toBe(duelKey); + expect(parsed?.marketParity).toMatchObject({ + bundleId: "bundle-12", + duelKey, + state: "awaiting_confirmations", + safeToBet: false, + }); expect(toStreamStateFromBetSyncEvent(parsed!)).toMatchObject({ seq: 12, emittedAt: 1_700_000_000_000, + marketParity: { + bundleId: "bundle-12", + duelKey, + state: "awaiting_confirmations", + safeToBet: false, + }, cycle: { + cycleId: "raw-cycle-12", duelId: "duel-12", duelKey: duelKey, duelKeyHex: `0x${duelKey}`, phase: "FIGHTING", phaseVersion: 3, + winnerId: null, + winnerName: null, + winReason: null, + rawCycle: { + cycleId: "raw-cycle-12", + phase: "RESOLUTION", + winnerId: "agent-a", + winnerName: "Agent A", + winReason: "knockout", + }, seed: "777", replayHash: "cd".repeat(32), }, }); }); + test("preserves canonical delivery and authority contract fields", () => { + const parsed = parseBetSyncEvent({ + sourceEpoch: 7, + seq: 12, + emittedAt: 1_712_345_678_000, + channel: { + id: "main", + mode: "always_on", + }, + publicReadiness: { + ready: false, + reason: "manifest_stale", + }, + canonicalDestination: { + id: "canonical-cloudflare", + playbackReady: false, + }, + fallbackDestination: { + id: "fallback-self-hls", + playbackReady: true, + }, + delivery: { + mode: "self_hls", + provider: "self_hls", + playbackUrl: "https://example.com/live/stream.m3u8", + hlsUrl: "https://example.com/live/stream.m3u8", + llhlsUrl: null, + ingestUrl: null, + }, + canonicalAuthority: { + providerLive: true, + playbackProbeReady: false, + decision: "blocked", + reason: "probe_unready", + revision: 9, + updatedAt: 1_712_345_678_100, + liveInputId: "live-input-123", + videoUid: "video-456", + lifecycleStatus: "connected", + playbackUrl: "https://video.example/live.m3u8?protocol=llhls", + playbackProbeStatusCode: 503, + playbackManifestStatus: "stale", + }, + sourceRuntime: { + ready: false, + statusSource: "external_worker", + captureMode: "cdp", + degradedReason: "worker_missing", + }, + deliveryHealth: { + ready: false, + degradedReason: "manifest_stale", + }, + }); + + expect(parsed).not.toBeNull(); + expect(parsed?.delivery).toEqual({ + mode: "self_hls", + provider: "self_hls", + playbackUrl: "https://example.com/live/stream.m3u8", + hlsUrl: "https://example.com/live/stream.m3u8", + llhlsUrl: null, + ingestUrl: null, + }); + expect(parsed?.publicReadiness).toEqual({ + ready: false, + reason: "manifest_stale", + }); + expect(parsed?.canonicalAuthority).toEqual({ + providerLive: true, + playbackProbeReady: false, + decision: "blocked", + reason: "probe_unready", + revision: 9, + updatedAt: 1_712_345_678_100, + liveInputId: "live-input-123", + videoUid: "video-456", + lifecycleStatus: "connected", + playbackUrl: "https://video.example/live.m3u8?protocol=llhls", + playbackProbeStatusCode: 503, + playbackManifestStatus: "stale", + }); + + expect(toStreamStateFromBetSyncEvent(parsed!)).toMatchObject({ + delivery: { + mode: "self_hls", + provider: "self_hls", + playbackUrl: "https://example.com/live/stream.m3u8", + }, + publicReadiness: { + ready: false, + reason: "manifest_stale", + }, + canonicalAuthority: { + decision: "blocked", + reason: "probe_unready", + revision: 9, + }, + sourceRuntime: { + ready: false, + statusSource: "external_worker", + captureMode: "cdp", + }, + deliveryHealth: { + ready: false, + degradedReason: "manifest_stale", + }, + }); + }); + + test("preserves broadcastTimeline without disturbing legacy cycle fields", () => { + const parsed = parseBetSyncEvent({ + schemaVersion: 3, + sourceEpoch: 8, + seq: 13, + emittedAt: 1_712_345_679_000, + duelId: "duel-2", + duelKey: "22".repeat(32), + phase: "FIGHTING", + betOpenTime: 1_000, + betCloseTime: 2_000, + fightStartTime: 3_000, + duelEndTime: 9_000, + broadcastTimeline: { + phase: "COUNTDOWN", + betOpenTime: 5_000, + betCloseTime: 6_000, + fightStartTime: 7_000, + duelEndTime: 13_000, + presentationDelayMs: 4_000, + updatedAt: 1_712_345_679_000, + }, + }); + + expect(parsed?.broadcastTimeline).toEqual({ + phase: "COUNTDOWN", + betOpenTime: 5_000, + betCloseTime: 6_000, + fightStartTime: 7_000, + duelEndTime: 13_000, + presentationDelayMs: 4_000, + updatedAt: 1_712_345_679_000, + }); + expect(parsed?.betCloseTime).toBe(2_000); + + const nextState = toStreamStateFromBetSyncEvent(parsed!); + expect(nextState.cycle.broadcastTimeline).toEqual({ + phase: "COUNTDOWN", + betOpenTime: 5_000, + betCloseTime: 6_000, + fightStartTime: 7_000, + duelEndTime: 13_000, + presentationDelayMs: 4_000, + updatedAt: 1_712_345_679_000, + }); + expect(nextState.cycle.betCloseTime).toBe(2_000); + expect(nextState.phase).toBe("FIGHTING"); + expect(nextState.phaseVersion).toBeNull(); + expect(nextState.broadcastTimeline).toEqual({ + phase: "COUNTDOWN", + betOpenTime: 5_000, + betCloseTime: 6_000, + fightStartTime: 7_000, + duelEndTime: 13_000, + presentationDelayMs: 4_000, + updatedAt: 1_712_345_679_000, + }); + }); + + test("preserves sourceTimeline alongside the projected timeline", () => { + const parsed = parseBetSyncEvent({ + schemaVersion: 3, + sourceEpoch: 8, + seq: 14, + emittedAt: 1_712_345_680_000, + duelId: "duel-3", + duelKey: "33".repeat(32), + phase: "COUNTDOWN", + broadcastTimeline: { + phase: "COUNTDOWN", + betOpenTime: 5_000, + betCloseTime: 6_000, + fightStartTime: 7_000, + duelEndTime: 13_000, + presentationDelayMs: 4_000, + updatedAt: 1_712_345_680_000, + }, + sourceTimeline: { + phase: "FIGHTING", + betOpenTime: 1_000, + betCloseTime: 2_000, + fightStartTime: 3_000, + duelEndTime: 9_000, + updatedAt: 1_712_345_676_000, + }, + }); + + expect(parsed?.sourceTimeline).toEqual({ + phase: "FIGHTING", + betOpenTime: 1_000, + betCloseTime: 2_000, + fightStartTime: 3_000, + duelEndTime: 9_000, + updatedAt: 1_712_345_676_000, + }); + + const nextState = toStreamStateFromBetSyncEvent(parsed!); + expect(nextState.cycle.sourceTimeline).toEqual({ + phase: "FIGHTING", + betOpenTime: 1_000, + betCloseTime: 2_000, + fightStartTime: 3_000, + duelEndTime: 9_000, + updatedAt: 1_712_345_676_000, + }); + expect(nextState.sourceTimeline).toEqual({ + phase: "FIGHTING", + betOpenTime: 1_000, + betCloseTime: 2_000, + fightStartTime: 3_000, + duelEndTime: 9_000, + updatedAt: 1_712_345_676_000, + }); + }); + test("parses bootstrap state with latest event", () => { const parsed = parseBetSyncBootstrapState({ sourceEpoch: 9, @@ -86,6 +373,71 @@ describe("bet-sync helpers", () => { }); }); + test("retains latest event delivery fields during bootstrap", () => { + const parsed = parseBetSyncBootstrapState({ + sourceEpoch: 9, + latestSeq: 101, + latestEvent: { + sourceEpoch: 9, + seq: 101, + emittedAt: 1_712_345_678_999, + delivery: { + mode: "self_hls", + provider: "self_hls", + playbackUrl: "https://example.com/live/stream.m3u8", + hlsUrl: "https://example.com/live/stream.m3u8", + llhlsUrl: null, + ingestUrl: null, + }, + publicReadiness: { + ready: true, + reason: null, + }, + canonicalAuthority: { + providerLive: true, + playbackProbeReady: true, + decision: "ready", + reason: null, + revision: 5, + updatedAt: 1_712_345_679_111, + liveInputId: "live-input-123", + videoUid: "video-456", + lifecycleStatus: "connected", + playbackUrl: "https://video.example/live.m3u8", + playbackProbeStatusCode: 200, + playbackManifestStatus: "ok", + }, + }, + }); + + expect(parsed?.latestEvent?.delivery).toEqual({ + mode: "self_hls", + provider: "self_hls", + playbackUrl: "https://example.com/live/stream.m3u8", + hlsUrl: "https://example.com/live/stream.m3u8", + llhlsUrl: null, + ingestUrl: null, + }); + expect(parsed?.latestEvent?.publicReadiness).toEqual({ + ready: true, + reason: null, + }); + expect(parsed?.latestEvent?.canonicalAuthority).toEqual({ + providerLive: true, + playbackProbeReady: true, + decision: "ready", + reason: null, + revision: 5, + updatedAt: 1_712_345_679_111, + liveInputId: "live-input-123", + videoUid: "video-456", + lifecycleStatus: "connected", + playbackUrl: "https://video.example/live.m3u8", + playbackProbeStatusCode: 200, + playbackManifestStatus: "ok", + }); + }); + test("rolls previous live duel into recent settlement on handoff", () => { const overview = parsePredictionMarketsOverview({ updatedAt: 10, @@ -119,12 +471,26 @@ describe("bet-sync helpers", () => { }, markets: [], updatedAt: 11, + sourceEmittedAt: 11, + serverEmittedAt: 11, }, 11, ); expect(next.live?.duel.duelId).toBe("duel-next"); expect(next.recentSettlement?.duel.duelId).toBe("duel-live"); + // recentSettlement.sourceEmittedAt must be preserved from the + // previous live surface — rolling forward does not re-stamp the + // source anchor. The selector in commit 3 depends on this invariant + // to place recentSettlement at the right point on the viewer's + // playback timeline. + expect(next.recentSettlement?.sourceEmittedAt).toBe(10); + // live.sourceEmittedAt comes from the new live surface. + expect(next.live?.sourceEmittedAt).toBe(11); + // Envelope max over the two surfaces. + expect(next.sourceEmittedAt).toBe(11); + // serverEmittedAt tracks the roll wall-clock. + expect(next.serverEmittedAt).toBe(11); }); test("preserves stronger lifecycle state when the same duel refresh weakens", () => { @@ -158,6 +524,8 @@ describe("bet-sync helpers", () => { }, ], updatedAt: 100, + sourceEmittedAt: 100, + serverEmittedAt: 100, }, { duel: { @@ -187,6 +555,8 @@ describe("bet-sync helpers", () => { }, ], updatedAt: 200, + sourceEmittedAt: 200, + serverEmittedAt: 200, }, ); @@ -279,4 +649,243 @@ describe("bet-sync helpers", () => { }), ).toBe(false); }); + + test("detects same-duel readiness flips in polled stream state", () => { + const base = { + type: "STREAMING_STATE_UPDATE" as const, + cycle: { + cycleId: "cycle-1", + phase: "FIGHTING", + duelId: "duel-1", + duelKeyHex: "0x11", + winnerId: null, + }, + leaderboard: [], + cameraTarget: "arena", + seq: 10, + emittedAt: 1_700_000_000_000, + publicReadiness: { + ready: false, + reason: "source_unready", + }, + canonicalAuthority: { + providerLive: true, + playbackProbeReady: false, + decision: "blocked", + reason: "source_unready", + revision: 9, + playbackManifestStatus: "stale", + }, + sourceRuntime: { + ready: false, + statusSource: "external_worker", + captureMode: "cdp", + degradedReason: "worker_missing", + }, + rendererHealth: { + ready: false, + degradedReason: "render_tick_stale", + }, + deliveryHealth: { + ready: false, + degradedReason: "manifest_stale", + }, + delivery: { + mode: "external_hls", + provider: "cloudflare_stream", + playbackUrl: "https://video.example/live.m3u8?protocol=llhls", + }, + channel: { + id: "main", + mode: "always_on", + canonicalDestinationId: "canonical-cloudflare", + publicPlaybackUrl: "https://video.example/live.m3u8?protocol=llhls", + }, + canonicalDestination: { + id: "canonical-cloudflare", + playbackReady: false, + manifestStatus: "stale", + }, + }; + + const next = { + ...base, + seq: 11, + emittedAt: 1_700_000_000_500, + publicReadiness: { + ready: true, + reason: null, + }, + canonicalAuthority: { + ...base.canonicalAuthority, + playbackProbeReady: true, + decision: "ready", + reason: null, + revision: 10, + playbackManifestStatus: "ok", + }, + sourceRuntime: { + ...base.sourceRuntime, + ready: true, + degradedReason: null, + }, + rendererHealth: { + ready: true, + degradedReason: null, + }, + deliveryHealth: { + ready: true, + degradedReason: null, + }, + canonicalDestination: { + ...base.canonicalDestination, + playbackReady: true, + manifestStatus: "ok", + }, + }; + + expect( + publicStreamStateChanged( + base as unknown as StreamState, + next as unknown as StreamState, + ), + ).toBe(true); + }); + + test("ignores seq and emittedAt churn when the public stream surface is unchanged", () => { + const base = { + type: "STREAMING_STATE_UPDATE" as const, + cycle: { + cycleId: "cycle-1", + phase: "FIGHTING", + duelId: "duel-1", + duelKeyHex: "0x11", + }, + leaderboard: [], + cameraTarget: "arena", + seq: 10, + emittedAt: 1_700_000_000_000, + publicReadiness: { + ready: true, + reason: null, + }, + canonicalAuthority: { + providerLive: true, + playbackProbeReady: true, + decision: "ready", + reason: null, + revision: 9, + playbackManifestStatus: "ok", + }, + sourceRuntime: { + ready: true, + statusSource: "external_worker", + captureMode: "cdp", + degradedReason: null, + }, + rendererHealth: { + ready: true, + degradedReason: null, + }, + }; + + const next = { + ...base, + seq: 11, + emittedAt: 1_700_000_000_500, + }; + + expect( + publicStreamStateChanged( + base as unknown as StreamState, + next as unknown as StreamState, + ), + ).toBe(false); + }); + + test("ignores volatile cycle and timeline telemetry churn", () => { + const base = { + type: "STREAMING_STATE_UPDATE" as const, + cycle: { + cycleId: "cycle-1", + phase: "FIGHTING", + phaseVersion: 4, + duelId: "duel-1", + duelKeyHex: "0x11", + winnerId: null, + rawCycle: { + telemetryNonce: 1, + }, + rendererHealth: { + ready: true, + degradedReason: null, + updatedAt: 100, + }, + }, + leaderboard: [], + cameraTarget: "arena", + seq: 10, + emittedAt: 1_700_000_000_000, + broadcastTimeline: { + phase: "FIGHTING", + updatedAt: 100, + }, + sourceTimeline: { + phase: "FIGHTING", + updatedAt: 100, + }, + publicReadiness: { + ready: true, + reason: null, + }, + canonicalAuthority: { + providerLive: true, + playbackProbeReady: true, + decision: "ready", + reason: null, + revision: 9, + playbackManifestStatus: "ok", + }, + sourceRuntime: { + ready: true, + statusSource: "external_worker", + captureMode: "cdp", + degradedReason: null, + }, + rendererHealth: { + ready: true, + degradedReason: null, + }, + }; + + const next = { + ...base, + cycle: { + ...base.cycle, + rawCycle: { + telemetryNonce: 2, + }, + rendererHealth: { + ready: true, + degradedReason: null, + updatedAt: 200, + }, + }, + broadcastTimeline: { + ...base.broadcastTimeline, + updatedAt: 200, + }, + sourceTimeline: { + ...base.sourceTimeline, + updatedAt: 200, + }, + }; + + expect( + publicStreamStateChanged( + base as unknown as StreamState, + next as unknown as StreamState, + ), + ).toBe(false); + }); }); diff --git a/packages/hyperbet-evm/keeper/src/betSync.ts b/packages/hyperbet-evm/keeper/src/betSync.ts index 1041e522..ce39a206 100644 --- a/packages/hyperbet-evm/keeper/src/betSync.ts +++ b/packages/hyperbet-evm/keeper/src/betSync.ts @@ -3,12 +3,15 @@ import type { PredictionMarketLifecycleStatus, PredictionMarketWinner, } from "../../../hyperbet-chain-registry/src/index"; +import type { KeeperMarketParitySnapshot } from "../../../hyperbet-mm-core/src/index"; import { normalizePredictionMarketTimestamp, normalizePredictionMarketWinner, } from "../../../hyperbet-chain-registry/src/index"; type JsonRecord = Record; +type JsonPrimitive = string | number | boolean | null; +type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }; export type BetSyncRendererHealth = { ready: boolean; @@ -16,15 +19,80 @@ export type BetSyncRendererHealth = { updatedAt: number | null; }; +export type BetSyncHlsManifest = { + updatedAt: number | null; + mediaSequence: number | null; +}; + +export type BetSyncRendererMetrics = { + captureFps: number | null; + encodeFps: number | null; + droppedFrames: number | null; + renderTick: number | null; + duelStateTick: number | null; + latestFrameAt: number | null; + latestRenderTickAt: number | null; + latestDuelStateTickAt: number | null; + latestVisualChangeAt: number | null; + visualChangeAgeMs: number | null; + hlsManifest: BetSyncHlsManifest | null; +}; + +export type BetSyncDelivery = { + mode: "self_hls" | "external_hls"; + provider: string | null; + playbackUrl: string | null; + hlsUrl: string | null; + llhlsUrl: string | null; + ingestUrl: string | null; +}; + +export type BetSyncBroadcastTimeline = { + phase: string | null; + betOpenTime: number | null; + betCloseTime: number | null; + fightStartTime: number | null; + duelEndTime: number | null; + presentationDelayMs: number; + updatedAt: number | null; +}; + +export type BetSyncSourceTimeline = { + phase: string | null; + betOpenTime: number | null; + betCloseTime: number | null; + fightStartTime: number | null; + duelEndTime: number | null; + updatedAt: number | null; +}; + +export type BetSyncCanonicalAuthority = { + providerLive: boolean; + playbackProbeReady: boolean; + decision: string | null; + reason: string | null; + revision: number | null; + updatedAt: number | null; + liveInputId: string | null; + videoUid: string | null; + lifecycleStatus: string | null; + playbackUrl: string | null; + playbackProbeStatusCode: number | null; + playbackManifestStatus: string | null; +}; + export type BetSyncEvent = { schemaVersion: number; sourceEpoch: number; seq: number; emittedAt: number; + cycle: JsonRecord | null; duelId: string | null; duelKey: string | null; phase: string | null; phaseVersion: number | null; + broadcastTimeline: BetSyncBroadcastTimeline | null; + sourceTimeline: BetSyncSourceTimeline | null; betOpenTime: number | null; betCloseTime: number | null; fightStartTime: number | null; @@ -40,6 +108,16 @@ export type BetSyncEvent = { leaderboard: JsonRecord[]; cameraTarget: string | null; rendererHealth: BetSyncRendererHealth | null; + rendererMetrics: BetSyncRendererMetrics | null; + delivery: BetSyncDelivery | null; + sourceRuntime: JsonRecord | null; + channel: JsonRecord | null; + publicReadiness: JsonRecord | null; + canonicalDestination: JsonRecord | null; + fallbackDestination: JsonRecord | null; + canonicalAuthority: BetSyncCanonicalAuthority | null; + deliveryHealth: JsonRecord | null; + marketParity?: KeeperMarketParitySnapshot | null; }; export type BetSyncBootstrapState = { @@ -56,6 +134,21 @@ export type StreamState = { cameraTarget: string | null; seq: number; emittedAt: number; + phase?: string | null; + phaseVersion?: number | null; + broadcastTimeline?: BetSyncBroadcastTimeline | null; + sourceTimeline?: BetSyncSourceTimeline | null; + rendererHealth?: BetSyncRendererHealth | null; + rendererMetrics?: BetSyncRendererMetrics | null; + delivery?: BetSyncDelivery | null; + sourceRuntime?: JsonRecord | null; + channel?: JsonRecord | null; + publicReadiness?: JsonRecord | null; + canonicalDestination?: JsonRecord | null; + fallbackDestination?: JsonRecord | null; + canonicalAuthority?: BetSyncCanonicalAuthority | null; + deliveryHealth?: JsonRecord | null; + marketParity?: KeeperMarketParitySnapshot | null; }; export type PredictionMarketsDuelSnapshot = { @@ -71,11 +164,45 @@ export type PredictionMarketsDuelSnapshot = { export type PredictionMarketsSurface = { duel: PredictionMarketsDuelSnapshot; markets: PredictionMarketLifecycleRecord[]; + /** + * Legacy wall-clock timestamp: when the keeper built / last updated this + * surface. Retained for backwards compatibility with consumers that pre- + * date the source/server split. Prefer `serverEmittedAt` going forward. + */ updatedAt: number | null; + /** + * Source-time emission anchor — the upstream `streamState.emittedAt` + * of the stream frame used to derive this surface. Preserved across + * `rollPredictionMarketsOverview` forward-rolls so a `recentSettlement` + * surface retains its original source anchor even as wall-clock + * `serverEmittedAt` advances. Selector keys (commit 3) use this field + * to align surface history against the viewer's playback clock. + */ + sourceEmittedAt: number | null; + /** + * Server-clock timestamp at which this surface was (re)built by the + * keeper. Staleness / max-age budgets key off this field; selectors + * key off `sourceEmittedAt`. + */ + serverEmittedAt: number; + marketParity?: KeeperMarketParitySnapshot | null; }; export type PredictionMarketsOverviewResponse = { updatedAt: number | null; + /** + * Envelope-level convenience: the newest source emission across + * `live` and `recentSettlement`. Consumers doing per-surface + * alignment should read the per-surface fields directly; this is + * just a top-level summary. + */ + sourceEmittedAt: number | null; + /** + * Server-clock timestamp at which the keeper emitted this envelope. + * Distinct from `updatedAt` (kept for legacy compatibility); always + * set to `Date.now()` at the moment the response is constructed. + */ + serverEmittedAt: number; live: PredictionMarketsSurface | null; recentSettlement: PredictionMarketsSurface | null; }; @@ -96,6 +223,289 @@ function asString(value: unknown): string | null { return typeof value === "string" && value.trim().length > 0 ? value : null; } +function asBoolean(value: unknown): boolean | null { + return typeof value === "boolean" ? value : null; +} + +function normalizeForChangeDetection(value: unknown): JsonValue | undefined { + if (value == null) return null; + if (typeof value === "string" || typeof value === "boolean") { + return value; + } + if (typeof value === "number") { + return Number.isFinite(value) ? value : null; + } + if (Array.isArray(value)) { + return value + .map((entry) => normalizeForChangeDetection(entry)) + .filter((entry): entry is JsonValue => entry !== undefined); + } + if (typeof value === "object") { + const entries = Object.entries(value as Record) + .sort(([left], [right]) => left.localeCompare(right)) + .flatMap(([key, entryValue]) => { + const normalized = normalizeForChangeDetection(entryValue); + return normalized === undefined ? [] : [[key, normalized] as const]; + }); + return Object.fromEntries(entries); + } + return undefined; +} + +function summarizeStreamHealthForChangeDetection(value: unknown): JsonValue { + const candidate = asRecord(value); + if (!candidate) return null; + return normalizeForChangeDetection({ + ready: asBoolean(candidate.ready), + degradedReason: asString(candidate.degradedReason), + }) ?? null; +} + +function summarizeSourceRuntimeForChangeDetection(value: unknown): JsonValue { + const candidate = asRecord(value); + if (!candidate) return null; + return normalizeForChangeDetection({ + ready: asBoolean(candidate.ready), + statusSource: asString(candidate.statusSource), + captureMode: asString(candidate.captureMode), + degradedReason: asString(candidate.degradedReason), + currentSceneUrl: asString(candidate.currentSceneUrl), + activeBundle: asString(candidate.activeBundle), + }) ?? null; +} + +function summarizeDeliveryForChangeDetection(value: unknown): JsonValue { + const candidate = asRecord(value); + if (!candidate) return null; + return normalizeForChangeDetection({ + mode: asString(candidate.mode), + provider: asString(candidate.provider), + playbackUrl: asString(candidate.playbackUrl), + hlsUrl: asString(candidate.hlsUrl), + llhlsUrl: asString(candidate.llhlsUrl), + ingestUrl: asString(candidate.ingestUrl), + }) ?? null; +} + +function summarizePublicReadinessForChangeDetection(value: unknown): JsonValue { + const candidate = asRecord(value); + if (!candidate) return null; + return normalizeForChangeDetection({ + ready: asBoolean(candidate.ready), + reason: asString(candidate.reason), + }) ?? null; +} + +function summarizeDestinationForChangeDetection(value: unknown): JsonValue { + const candidate = asRecord(value); + if (!candidate) return null; + return normalizeForChangeDetection({ + id: asString(candidate.id), + name: asString(candidate.name), + role: asString(candidate.role), + provider: asString(candidate.provider), + transport: asString(candidate.transport), + playbackUrl: asString(candidate.playbackUrl), + ingestUrl: asString(candidate.ingestUrl), + connected: asBoolean(candidate.connected), + transportHealthy: asBoolean(candidate.transportHealthy), + playbackReady: asBoolean(candidate.playbackReady), + manifestStatus: asString(candidate.manifestStatus), + lastError: asString(candidate.lastError), + }) ?? null; +} + +function summarizeChannelForChangeDetection(value: unknown): JsonValue { + const candidate = asRecord(value); + if (!candidate) return null; + return normalizeForChangeDetection({ + id: asString(candidate.id), + mode: asString(candidate.mode), + presentationDelayMs: asFiniteNumber(candidate.presentationDelayMs), + activeDuelId: asString(candidate.activeDuelId), + activeDuelKey: normalizeDuelKey(candidate.activeDuelKey), + canonicalDestinationId: asString(candidate.canonicalDestinationId), + fallbackDestinationId: asString(candidate.fallbackDestinationId), + publicPlaybackUrl: asString(candidate.publicPlaybackUrl), + publicReadiness: summarizePublicReadinessForChangeDetection( + candidate.publicReadiness, + ), + }) ?? null; +} + +function summarizeCanonicalAuthorityForChangeDetection( + value: unknown, +): JsonValue { + const candidate = asRecord(value); + if (!candidate) return null; + return normalizeForChangeDetection({ + providerLive: asBoolean(candidate.providerLive), + playbackProbeReady: asBoolean(candidate.playbackProbeReady), + decision: asString(candidate.decision), + reason: asString(candidate.reason), + revision: asFiniteNumber(candidate.revision), + liveInputId: asString(candidate.liveInputId), + videoUid: asString(candidate.videoUid), + lifecycleStatus: asString(candidate.lifecycleStatus), + playbackUrl: asString(candidate.playbackUrl), + playbackProbeStatusCode: asFiniteNumber(candidate.playbackProbeStatusCode), + playbackManifestStatus: asString(candidate.playbackManifestStatus), + }) ?? null; +} + +function summarizeTimelineForChangeDetection(value: unknown): JsonValue { + const candidate = asRecord(value); + if (!candidate) return null; + return normalizeForChangeDetection({ + phase: asString(candidate.phase), + betOpenTime: asFiniteNumber(candidate.betOpenTime), + betCloseTime: asFiniteNumber(candidate.betCloseTime), + fightStartTime: asFiniteNumber(candidate.fightStartTime), + duelEndTime: asFiniteNumber(candidate.duelEndTime), + presentationDelayMs: asFiniteNumber(candidate.presentationDelayMs), + }) ?? null; +} + +function summarizeCycleForChangeDetection(value: unknown): JsonValue { + const candidate = asRecord(value); + if (!candidate) return null; + return normalizeForChangeDetection({ + cycleId: asString(candidate.cycleId), + duelId: asString(candidate.duelId), + duelKey: + normalizeDuelKey(candidate.duelKey) ?? normalizeDuelKey(candidate.duelKeyHex), + phase: asString(candidate.phase), + phaseVersion: asFiniteNumber(candidate.phaseVersion), + betOpenTime: asFiniteNumber(candidate.betOpenTime), + betCloseTime: asFiniteNumber(candidate.betCloseTime), + fightStartTime: asFiniteNumber(candidate.fightStartTime), + duelEndTime: asFiniteNumber(candidate.duelEndTime), + winnerId: asString(candidate.winnerId), + winnerName: asString(candidate.winnerName), + winReason: asString(candidate.winReason), + seed: asString(candidate.seed), + replayHash: asString(candidate.replayHash), + agent1: normalizeForChangeDetection(asRecord(candidate.agent1)), + agent2: normalizeForChangeDetection(asRecord(candidate.agent2)), + arenaPositions: normalizeForChangeDetection(asRecord(candidate.arenaPositions)), + }) ?? null; +} + +function buildPublicStreamStateChangeSnapshot(state: StreamState): JsonValue { + return ( + normalizeForChangeDetection({ + cycle: summarizeCycleForChangeDetection(state.cycle), + leaderboard: state.leaderboard ?? [], + cameraTarget: state.cameraTarget ?? null, + phase: state.phase ?? null, + phaseVersion: state.phaseVersion ?? null, + broadcastTimeline: summarizeTimelineForChangeDetection( + state.broadcastTimeline, + ), + sourceTimeline: summarizeTimelineForChangeDetection(state.sourceTimeline), + rendererHealth: summarizeStreamHealthForChangeDetection( + state.rendererHealth, + ), + delivery: summarizeDeliveryForChangeDetection(state.delivery), + sourceRuntime: summarizeSourceRuntimeForChangeDetection( + state.sourceRuntime, + ), + channel: summarizeChannelForChangeDetection(state.channel), + publicReadiness: summarizePublicReadinessForChangeDetection( + state.publicReadiness, + ), + canonicalDestination: summarizeDestinationForChangeDetection( + state.canonicalDestination, + ), + fallbackDestination: summarizeDestinationForChangeDetection( + state.fallbackDestination, + ), + canonicalAuthority: summarizeCanonicalAuthorityForChangeDetection( + state.canonicalAuthority, + ), + deliveryHealth: summarizeStreamHealthForChangeDetection( + state.deliveryHealth, + ), + }) ?? null + ); +} + +function normalizeMarketParity( + value: unknown, +): KeeperMarketParitySnapshot | null { + const candidate = asRecord(value); + if (!candidate) return null; + const bundleId = asString(candidate.bundleId); + if (!bundleId) return null; + return { + bundleId, + duelKey: normalizeDuelKey(candidate.duelKey), + duelId: asString(candidate.duelId), + revision: Math.max(1, asFiniteNumber(candidate.revision) ?? 1), + requiredChains: Array.isArray(candidate.requiredChains) + ? candidate.requiredChains.filter( + (chain): chain is KeeperMarketParitySnapshot["requiredChains"][number] => + typeof chain === "string", + ) + : [], + confirmedChains: Array.isArray(candidate.confirmedChains) + ? candidate.confirmedChains.filter( + (chain): chain is KeeperMarketParitySnapshot["confirmedChains"][number] => + typeof chain === "string", + ) + : [], + state: (asString(candidate.state) as KeeperMarketParitySnapshot["state"]) ?? "preparing", + phase: asString(candidate.phase), + safeToBet: candidate.safeToBet === true, + openedAtMs: normalizePredictionMarketTimestamp(candidate.openedAtMs), + lockedAtMs: normalizePredictionMarketTimestamp(candidate.lockedAtMs), + resolvedAtMs: normalizePredictionMarketTimestamp(candidate.resolvedAtMs), + freezeReason: asString(candidate.freezeReason), + updatedAtMs: normalizePredictionMarketTimestamp(candidate.updatedAtMs) ?? 0, + receipts: Array.isArray(candidate.receipts) + ? candidate.receipts + .map((receipt) => { + const normalized = asRecord(receipt); + const chainKey = normalized && typeof normalized.chainKey === "string" + ? normalized.chainKey + : null; + if (!normalized || !chainKey) { + return null; + } + return { + chainKey, + preparedAtMs: normalizePredictionMarketTimestamp( + normalized.preparedAtMs, + ), + openedAtMs: normalizePredictionMarketTimestamp(normalized.openedAtMs), + lockedAtMs: normalizePredictionMarketTimestamp(normalized.lockedAtMs), + resolvedAtMs: normalizePredictionMarketTimestamp( + normalized.resolvedAtMs, + ), + cancelledAtMs: normalizePredictionMarketTimestamp( + normalized.cancelledAtMs, + ), + confirmedAtMs: normalizePredictionMarketTimestamp( + normalized.confirmedAtMs, + ), + lifecycleStatus: + (asString(normalized.lifecycleStatus) as PredictionMarketLifecycleStatus | null) ?? + null, + txRef: asString(normalized.txRef), + note: asString(normalized.note), + }; + }) + .filter( + ( + receipt, + ): receipt is KeeperMarketParitySnapshot["receipts"][number] => { + return receipt != null; + }, + ) + : [], + }; +} + function normalizeDuelKey(value: unknown): string | null { const raw = asString(value); if (!raw) return null; @@ -202,6 +612,113 @@ function normalizeRendererHealth(value: unknown): BetSyncRendererHealth | null { }; } +function normalizeHlsManifest(value: unknown): BetSyncHlsManifest | null { + const candidate = asRecord(value); + if (!candidate) return null; + return { + updatedAt: normalizePredictionMarketTimestamp(candidate.updatedAt), + mediaSequence: asFiniteNumber(candidate.mediaSequence), + }; +} + +function normalizeRendererMetrics( + value: unknown, +): BetSyncRendererMetrics | null { + const candidate = asRecord(value); + if (!candidate) return null; + return { + captureFps: asFiniteNumber(candidate.captureFps), + encodeFps: asFiniteNumber(candidate.encodeFps), + droppedFrames: asFiniteNumber(candidate.droppedFrames), + renderTick: asFiniteNumber(candidate.renderTick), + duelStateTick: asFiniteNumber(candidate.duelStateTick), + latestFrameAt: normalizePredictionMarketTimestamp(candidate.latestFrameAt), + latestRenderTickAt: normalizePredictionMarketTimestamp( + candidate.latestRenderTickAt, + ), + latestDuelStateTickAt: normalizePredictionMarketTimestamp( + candidate.latestDuelStateTickAt, + ), + latestVisualChangeAt: normalizePredictionMarketTimestamp( + candidate.latestVisualChangeAt, + ), + visualChangeAgeMs: asFiniteNumber(candidate.visualChangeAgeMs), + hlsManifest: normalizeHlsManifest(candidate.hlsManifest), + }; +} + +function normalizeDelivery(value: unknown): BetSyncDelivery | null { + const candidate = asRecord(value); + if (!candidate) return null; + const mode = asString(candidate.mode); + if (mode !== "self_hls" && mode !== "external_hls") { + return null; + } + return { + mode, + provider: asString(candidate.provider), + playbackUrl: asString(candidate.playbackUrl), + hlsUrl: asString(candidate.hlsUrl), + llhlsUrl: asString(candidate.llhlsUrl), + ingestUrl: asString(candidate.ingestUrl), + }; +} + +function normalizeBroadcastTimeline( + value: unknown, +): BetSyncBroadcastTimeline | null { + const candidate = asRecord(value); + if (!candidate) return null; + return { + phase: asString(candidate.phase), + betOpenTime: normalizePredictionMarketTimestamp(candidate.betOpenTime), + betCloseTime: normalizePredictionMarketTimestamp(candidate.betCloseTime), + fightStartTime: normalizePredictionMarketTimestamp(candidate.fightStartTime), + duelEndTime: normalizePredictionMarketTimestamp(candidate.duelEndTime), + presentationDelayMs: Math.max( + 0, + asFiniteNumber(candidate.presentationDelayMs) ?? 0, + ), + updatedAt: normalizePredictionMarketTimestamp(candidate.updatedAt), + }; +} + +function normalizeSourceTimeline( + value: unknown, +): BetSyncSourceTimeline | null { + const candidate = asRecord(value); + if (!candidate) return null; + return { + phase: asString(candidate.phase), + betOpenTime: normalizePredictionMarketTimestamp(candidate.betOpenTime), + betCloseTime: normalizePredictionMarketTimestamp(candidate.betCloseTime), + fightStartTime: normalizePredictionMarketTimestamp(candidate.fightStartTime), + duelEndTime: normalizePredictionMarketTimestamp(candidate.duelEndTime), + updatedAt: normalizePredictionMarketTimestamp(candidate.updatedAt), + }; +} + +function normalizeCanonicalAuthority( + value: unknown, +): BetSyncCanonicalAuthority | null { + const candidate = asRecord(value); + if (!candidate) return null; + return { + providerLive: candidate.providerLive === true, + playbackProbeReady: candidate.playbackProbeReady === true, + decision: asString(candidate.decision), + reason: asString(candidate.reason), + revision: asFiniteNumber(candidate.revision), + updatedAt: normalizePredictionMarketTimestamp(candidate.updatedAt), + liveInputId: asString(candidate.liveInputId), + videoUid: asString(candidate.videoUid), + lifecycleStatus: asString(candidate.lifecycleStatus), + playbackUrl: asString(candidate.playbackUrl), + playbackProbeStatusCode: asFiniteNumber(candidate.playbackProbeStatusCode), + playbackManifestStatus: asString(candidate.playbackManifestStatus), + }; +} + export function parseBetSyncEvent(payload: unknown): BetSyncEvent | null { const candidate = asRecord(payload); if (!candidate) return null; @@ -214,15 +731,25 @@ export function parseBetSyncEvent(payload: unknown): BetSyncEvent | null { return null; } + const broadcastTimeline = normalizeBroadcastTimeline( + candidate.broadcastTimeline, + ); + const sourceTimeline = normalizeSourceTimeline( + candidate.sourceTimeline, + ); + return { schemaVersion: asFiniteNumber(candidate.schemaVersion) ?? 1, sourceEpoch, seq, emittedAt, + cycle: asRecord(candidate.cycle), duelId: asString(candidate.duelId), duelKey: normalizeDuelKey(candidate.duelKey), phase: asString(candidate.phase), phaseVersion: asFiniteNumber(candidate.phaseVersion), + broadcastTimeline, + sourceTimeline, betOpenTime: normalizePredictionMarketTimestamp(candidate.betOpenTime), betCloseTime: normalizePredictionMarketTimestamp(candidate.betCloseTime), fightStartTime: normalizePredictionMarketTimestamp(candidate.fightStartTime), @@ -242,6 +769,16 @@ export function parseBetSyncEvent(payload: unknown): BetSyncEvent | null { : [], cameraTarget: asString(candidate.cameraTarget), rendererHealth: normalizeRendererHealth(candidate.rendererHealth), + rendererMetrics: normalizeRendererMetrics(candidate.rendererMetrics), + delivery: normalizeDelivery(candidate.delivery), + sourceRuntime: asRecord(candidate.sourceRuntime), + channel: asRecord(candidate.channel), + publicReadiness: asRecord(candidate.publicReadiness), + canonicalDestination: asRecord(candidate.canonicalDestination), + fallbackDestination: asRecord(candidate.fallbackDestination), + canonicalAuthority: normalizeCanonicalAuthority(candidate.canonicalAuthority), + deliveryHealth: asRecord(candidate.deliveryHealth), + marketParity: normalizeMarketParity(candidate.marketParity), }; } @@ -274,16 +811,89 @@ export function parseBetSyncBootstrapState( }; } +export function parseStreamStatePayload( + payload: unknown, + fallback: { seq: number; emittedAt: number }, +): StreamState | null { + const candidate = asRecord(payload); + const cycle = asRecord(candidate?.cycle); + if (!candidate || !cycle) { + return null; + } + + return { + type: "STREAMING_STATE_UPDATE", + cycle, + leaderboard: Array.isArray(candidate.leaderboard) + ? candidate.leaderboard + .map((entry) => asRecord(entry)) + .filter((entry): entry is JsonRecord => entry !== null) + : [], + cameraTarget: + typeof candidate.cameraTarget === "string" || candidate.cameraTarget === null + ? candidate.cameraTarget + : null, + seq: + typeof candidate.seq === "number" && Number.isFinite(candidate.seq) + ? candidate.seq + : fallback.seq, + emittedAt: + typeof candidate.emittedAt === "number" && + Number.isFinite(candidate.emittedAt) + ? candidate.emittedAt + : fallback.emittedAt, + phase: + typeof candidate.phase === "string" || candidate.phase === null + ? candidate.phase + : null, + phaseVersion: + typeof candidate.phaseVersion === "number" && + Number.isFinite(candidate.phaseVersion) + ? candidate.phaseVersion + : null, + broadcastTimeline: normalizeBroadcastTimeline(candidate.broadcastTimeline), + sourceTimeline: normalizeSourceTimeline(candidate.sourceTimeline), + rendererHealth: normalizeRendererHealth(candidate.rendererHealth), + rendererMetrics: normalizeRendererMetrics(candidate.rendererMetrics), + delivery: normalizeDelivery(candidate.delivery), + sourceRuntime: asRecord(candidate.sourceRuntime), + channel: asRecord(candidate.channel), + publicReadiness: asRecord(candidate.publicReadiness), + canonicalDestination: asRecord(candidate.canonicalDestination), + fallbackDestination: asRecord(candidate.fallbackDestination), + canonicalAuthority: normalizeCanonicalAuthority(candidate.canonicalAuthority), + deliveryHealth: asRecord(candidate.deliveryHealth), + marketParity: normalizeMarketParity(candidate.marketParity), + }; +} + +export function publicStreamStateChanged( + previous: StreamState, + next: StreamState, +): boolean { + return ( + JSON.stringify(buildPublicStreamStateChangeSnapshot(previous)) !== + JSON.stringify(buildPublicStreamStateChangeSnapshot(next)) + ); +} + export function toStreamStateFromBetSyncEvent(event: BetSyncEvent): StreamState { + const rawCycle = event.cycle; return { type: "STREAMING_STATE_UPDATE", cycle: { - cycleId: event.duelId ?? `bet-sync-${event.sourceEpoch}-${event.seq}`, + cycleId: + asString(rawCycle?.cycleId) ?? + event.duelId ?? + `bet-sync-${event.sourceEpoch}-${event.seq}`, duelId: event.duelId, duelKey: event.duelKey, duelKeyHex: event.duelKey ? `0x${event.duelKey}` : null, phase: event.phase ?? "IDLE", phaseVersion: event.phaseVersion, + rawCycle, + broadcastTimeline: event.broadcastTimeline, + sourceTimeline: event.sourceTimeline, betOpenTime: event.betOpenTime, betCloseTime: event.betCloseTime, fightStartTime: event.fightStartTime, @@ -302,6 +912,21 @@ export function toStreamStateFromBetSyncEvent(event: BetSyncEvent): StreamState cameraTarget: event.cameraTarget, seq: event.seq, emittedAt: event.emittedAt, + phase: event.phase, + phaseVersion: event.phaseVersion, + broadcastTimeline: event.broadcastTimeline, + sourceTimeline: event.sourceTimeline, + rendererHealth: event.rendererHealth, + rendererMetrics: event.rendererMetrics, + delivery: event.delivery, + sourceRuntime: event.sourceRuntime, + channel: event.channel, + publicReadiness: event.publicReadiness, + canonicalDestination: event.canonicalDestination, + fallbackDestination: event.fallbackDestination, + canonicalAuthority: event.canonicalAuthority, + deliveryHealth: event.deliveryHealth, + marketParity: event.marketParity ?? null, }; } @@ -314,6 +939,13 @@ export function parsePredictionMarketsSurface( return null; } + const parsedUpdatedAt = normalizePredictionMarketTimestamp(candidate.updatedAt); + const parsedSourceEmittedAt = normalizePredictionMarketTimestamp( + candidate.sourceEmittedAt, + ); + const parsedServerEmittedAt = normalizePredictionMarketTimestamp( + candidate.serverEmittedAt, + ); return { duel: { duelKey: normalizeDuelKey(duel.duelKey), @@ -328,7 +960,15 @@ export function parsePredictionMarketsSurface( (market): market is PredictionMarketLifecycleRecord => Boolean(market) && typeof market === "object", ) as PredictionMarketLifecycleRecord[], - updatedAt: normalizePredictionMarketTimestamp(candidate.updatedAt), + updatedAt: parsedUpdatedAt, + // Backfill rules for snapshots parsed from legacy payloads (pre- + // commit-2 keeper builds): when the new fields are absent, derive + // them from `updatedAt` so the surface still has the fields the + // selector expects. This keeps downstream buffers safe against + // mixed-version environments during rollout. + sourceEmittedAt: parsedSourceEmittedAt ?? parsedUpdatedAt ?? null, + serverEmittedAt: parsedServerEmittedAt ?? parsedUpdatedAt ?? 0, + marketParity: normalizeMarketParity(candidate.marketParity), }; } @@ -337,10 +977,34 @@ export function parsePredictionMarketsOverview( ): PredictionMarketsOverviewResponse | null { const candidate = asRecord(payload); if (!candidate) return null; + const parsedUpdatedAt = normalizePredictionMarketTimestamp(candidate.updatedAt); + const parsedSourceEmittedAt = normalizePredictionMarketTimestamp( + candidate.sourceEmittedAt, + ); + const parsedServerEmittedAt = normalizePredictionMarketTimestamp( + candidate.serverEmittedAt, + ); + const live = parsePredictionMarketsSurface(candidate.live); + const recentSettlement = parsePredictionMarketsSurface( + candidate.recentSettlement, + ); + // Envelope backfill: if the top-level source/server fields are + // absent on legacy payloads, derive from per-surface values so + // consumers see a coherent envelope. + const envelopeSourceCandidates = [ + parsedSourceEmittedAt, + live?.sourceEmittedAt, + recentSettlement?.sourceEmittedAt, + ].filter((value): value is number => typeof value === "number"); return { - updatedAt: normalizePredictionMarketTimestamp(candidate.updatedAt), - live: parsePredictionMarketsSurface(candidate.live), - recentSettlement: parsePredictionMarketsSurface(candidate.recentSettlement), + updatedAt: parsedUpdatedAt, + sourceEmittedAt: + envelopeSourceCandidates.length > 0 + ? Math.max(...envelopeSourceCandidates) + : null, + serverEmittedAt: parsedServerEmittedAt ?? parsedUpdatedAt ?? 0, + live, + recentSettlement, }; } @@ -388,6 +1052,15 @@ export function mergePredictionMarketsSurface( duel: mergeDuelSnapshot(previous.duel, next.duel), markets: Array.from(byChain.values()), updatedAt: next.updatedAt, + // Merge semantics for source/server emission: both come from the + // NEXT surface (the newer build). `sourceEmittedAt` follows the + // newer source anchor; `serverEmittedAt` follows the newer build + // clock. Falling back to `previous` only when `next` is missing the + // field (transitional safety during rollout before every caller is + // updated to pass these fields). + sourceEmittedAt: next.sourceEmittedAt ?? previous.sourceEmittedAt ?? null, + serverEmittedAt: next.serverEmittedAt ?? previous.serverEmittedAt, + marketParity: next.marketParity ?? previous.marketParity ?? null, }; } @@ -404,11 +1077,32 @@ export function rollPredictionMarketsOverview( hasMeaningfulSurface(nextLive) && !sameDuelIdentity(previous?.live, nextLive) ) { + // Roll the previous live surface forward into recentSettlement. We + // deliberately keep the surface object intact — including its + // original `sourceEmittedAt` — because that anchor ties the surface + // back to the stream frame that produced it. Downstream selectors + // (commit 3) rely on this to place recentSettlement at the right + // point on the viewer's playback timeline. recentSettlement = previous?.live ?? null; } + // Envelope-level `sourceEmittedAt` is max over present surfaces. + // Null only when BOTH surfaces are missing or both lack a source + // anchor — which only happens transitionally during rollout before + // upstream `streamState.emittedAt` is populated. + const envelopeSourceCandidates = [ + nextLive?.sourceEmittedAt, + recentSettlement?.sourceEmittedAt, + ].filter((value): value is number => typeof value === "number"); + const sourceEmittedAt = + envelopeSourceCandidates.length > 0 + ? Math.max(...envelopeSourceCandidates) + : null; + return { updatedAt, + sourceEmittedAt, + serverEmittedAt: updatedAt, live: nextLive, recentSettlement, }; diff --git a/packages/hyperbet-evm/keeper/src/bot.ts b/packages/hyperbet-evm/keeper/src/bot.ts index 822ee18d..49a8cd75 100644 --- a/packages/hyperbet-evm/keeper/src/bot.ts +++ b/packages/hyperbet-evm/keeper/src/bot.ts @@ -9,11 +9,13 @@ import { Transaction, } from "@solana/web3.js"; import { + type BettingChainKey, BETTING_EVM_CHAIN_ORDER, normalizeSolanaCluster, parseBettingEvmChainList, resolveBettingEvmRuntimeEnv, type BettingEvmChain, + type PredictionMarketLifecycleStatus, type PredictionMarketWinner, } from "@hyperbet/chain-registry"; import { @@ -21,7 +23,10 @@ import { DEFAULT_MARKET_MAKER_CONFIG, evaluateQuoteDecision, type KeeperBotHealthSnapshot, + type KeeperMarketParitySnapshot, type KeeperMarketHealthRecord, + type KeeperParityBundleState, + type KeeperParityChainReceipt, type KeeperRecoveryState, type MarketSnapshot, type QuotePlan, @@ -48,19 +53,42 @@ import { readKeypair, requireEnv, } from "./common"; +import { resolveResultCatchupBearerToken } from "./resultCatchupAuth"; const EVM_DUEL_WINNER_MARKET_KIND = 0; const EVM_KEEPER_DEFER_FINALIZE = process.env.EVM_KEEPER_DEFER_FINALIZE === "true"; const EVM_KEEPER_ENABLE_LIFECYCLE_WRITES = process.env.EVM_KEEPER_ENABLE_LIFECYCLE_WRITES !== "false"; const EVM_DUEL_STATUS_NULL = 0; +const EVM_DUEL_STATUS_SCHEDULED = 1; const EVM_DUEL_STATUS_BETTING_OPEN = 2; const EVM_DUEL_STATUS_LOCKED = 3; const EVM_DUEL_STATUS_PROPOSED = 4; const EVM_DUEL_STATUS_CHALLENGED = 5; const EVM_DUEL_STATUS_RESOLVED = 6; const EVM_DUEL_STATUS_CANCELLED = 7; +const EVM_MARKET_STATUS_NULL = 0; +const EVM_MARKET_STATUS_OPEN = 1; +const EVM_MARKET_STATUS_LOCKED = 2; +const EVM_MARKET_STATUS_RESOLVED = 3; +const EVM_MARKET_STATUS_CANCELLED = 4; const DEFAULT_DUEL_START_DELAY_MS = 60_000; +const DEFAULT_SOLANA_ORACLE_DISPUTE_WINDOW_SECS = 3600; +const configuredSolanaOracleDisputeWindowSecs = Number( + process.env.SOLANA_ORACLE_DISPUTE_WINDOW_SECS ?? + DEFAULT_SOLANA_ORACLE_DISPUTE_WINDOW_SECS, +); +const SOLANA_ORACLE_DISPUTE_WINDOW_SECS = Number.isFinite( + configuredSolanaOracleDisputeWindowSecs, +) + ? Math.max(60, Math.floor(configuredSolanaOracleDisputeWindowSecs)) + : DEFAULT_SOLANA_ORACLE_DISPUTE_WINDOW_SECS; +const EVM_DISPUTE_WINDOW_ACTIVE_SELECTOR = "0xe52e798f"; +const DEFAULT_REQUIRED_PARITY_CHAINS: readonly BettingChainKey[] = [ + "solana", + "bsc", +]; +const DEFAULT_REMOTE_EVM_PARITY_CONFIRMATIONS = 5; function asNum(value: unknown, fallback = 0): number { if (typeof value === "number") return value; @@ -72,6 +100,20 @@ function asNum(value: unknown, fallback = 0): number { return fallback; } +function parsePositiveInteger(value: string | undefined): number | null { + if (!value) return null; + const parsed = Number.parseInt(value.trim(), 10); + return Number.isInteger(parsed) && parsed > 0 ? parsed : null; +} + +const configuredEvmParityConfirmations = parsePositiveInteger( + process.env.EVM_PARITY_CONFIRMATIONS, +); +const evmTxReceiptTimeoutMs = Math.max( + 30_000, + Number(process.env.EVM_TX_RECEIPT_TIMEOUT_MS || 120_000), +); + function asBigInt(value: unknown, fallback = 0n): bigint { if (typeof value === "bigint") return value; if (typeof value === "number") return BigInt(Math.trunc(value)); @@ -85,6 +127,72 @@ function asBigInt(value: unknown, fallback = 0n): bigint { return fallback; } +function isSolanaDisputeWindowActiveError(error: unknown): boolean { + const message = (error as Error)?.message ?? ""; + return /DisputeWindowActive/i.test(message); +} + +async function withTimeout( + promise: Promise, + timeoutMs: number, + label: string, +): Promise { + let timeout: ReturnType | null = null; + try { + return await Promise.race([ + promise, + new Promise((_, reject) => { + timeout = setTimeout(() => { + reject(new Error(`${label} timed out after ${timeoutMs}ms`)); + }, timeoutMs); + }), + ]); + } finally { + if (timeout) { + clearTimeout(timeout); + } + } +} + +function asChainKey(value: string): BettingChainKey | null { + switch (value.trim().toLowerCase()) { + case "solana": + case "bsc": + case "base": + case "avax": + return value.trim().toLowerCase() as BettingChainKey; + default: + return null; + } +} + +function parseRequiredParityChains(value: string | undefined): BettingChainKey[] { + const requested = (value ?? "") + .split(",") + .map((entry) => asChainKey(entry)) + .filter((entry): entry is BettingChainKey => entry != null); + const seed = + requested.length > 0 + ? requested + : [...DEFAULT_REQUIRED_PARITY_CHAINS]; + return Array.from(new Set(seed)); +} + +function lifecycleStatusFromEvmWriteStatus( + status: 1 | 2 | 3, +): PredictionMarketLifecycleStatus { + switch (status) { + case 1: + return "PENDING"; + case 2: + return "OPEN"; + case 3: + return "LOCKED"; + default: + return "UNKNOWN"; + } +} + function clampPerpsOracleSpotIndex( nextSpotIndex: number, previousSpotIndex: number | null, @@ -118,6 +226,24 @@ function hashParticipant(agent: { id?: string; name?: string } | null): number[] return Array.from(createHash("sha256").update(id).digest()); } +function toByteArray32(value: unknown): number[] | null { + if (Array.isArray(value) && value.length === 32) { + const normalized = value.map((entry) => Number(entry)); + return normalized.every( + (entry) => Number.isInteger(entry) && entry >= 0 && entry <= 255, + ) + ? normalized + : null; + } + if (value instanceof Uint8Array && value.length === 32) { + return Array.from(value); + } + if (Buffer.isBuffer(value) && value.length === 32) { + return Array.from(value); + } + return null; +} + type WinnerSide = "A" | "B"; type DuelStatusState = | { scheduled: {} } @@ -200,6 +326,13 @@ const DUEL_OUTCOME_ORACLE_ABI = [ ], outputs: [], }, + { + type: "function", + name: "cancelDuel", + stateMutability: "nonpayable", + inputs: [{ type: "bytes32" }, { type: "string" }], + outputs: [], + }, { type: "function", name: "proposeResult", @@ -355,6 +488,8 @@ function isRpcConnectivityError(error: unknown): boolean { message.includes("unable to connect") || message.includes("fetch failed") || message.includes("failed to fetch") || + message.includes("aborterror") || + message.includes("aborted") || message.includes("econnrefused") || message.includes("connection refused") || message.includes("connection reset") || @@ -364,6 +499,14 @@ function isRpcConnectivityError(error: unknown): boolean { ); } +function isEvmDisputeWindowActiveError(error: unknown): boolean { + const message = (error as Error)?.message ?? ""; + return ( + message.includes("DisputeWindowActive") || + message.includes(EVM_DISPUTE_WINDOW_ACTIVE_SELECTOR) + ); +} + async function waitForTxBySignature( connection: Connection, signature: string, @@ -420,6 +563,11 @@ const args = await yargs(hideBin(process.argv)) default: Number(process.env.AUTO_SEED_DELAY_SECONDS || 10), describe: "Auto-seed delay for new markets", }) + .option("result-post-delay-seconds", { + type: "number", + default: Number(process.env.RESULT_POST_DELAY_SECONDS || 0), + describe: "Delay after an authoritative stream result before posting oracle resolution", + }) .option("seed-sol", { type: "number", default: Number( @@ -504,13 +652,17 @@ import { privateKeyToAccount } from "viem/accounts"; import { loadAgentRatings, loadPerpsMarkets, + saveChainScopedPerpsMarket, + saveChainScopedPerpsOracleSnapshot, saveAgentRating, saveAgentRatings, savePerpsMarket, savePerpsOracleSnapshot, + type EvmPerpsChainKey, type DbPerpsMarketRecord, type DbPerpsMarketStatus, } from "./db"; +import { resolvePublicEvmPerpsChains } from "./publicPerps"; const BPF_LOADER_UPGRADEABLE_PROGRAM_ID = new PublicKey( "BPFLoaderUpgradeab1e11111111111111111111111", @@ -549,8 +701,48 @@ const botKeypair = readKeypair( process.env.MARKET_MAKER_KEYPAIR || requireEnv("ORACLE_AUTHORITY_KEYPAIR"), ); +function readOptionalKeypair(name: string): Keypair | null { + const value = process.env[name]?.trim(); + return value ? readKeypair(value) : null; +} + +const hasExplicitOracleConfigAuthority = Boolean( + process.env.ORACLE_CONFIG_AUTHORITY_KEYPAIR?.trim(), +); +const hasExplicitClobConfigAuthority = Boolean( + process.env.CLOB_CONFIG_AUTHORITY_KEYPAIR?.trim() || + process.env.CLOB_AUTHORITY_KEYPAIR?.trim(), +); +const oracleConfigAuthorityKeypair = + readOptionalKeypair("ORACLE_CONFIG_AUTHORITY_KEYPAIR") || + readOptionalKeypair("ORACLE_AUTHORITY_KEYPAIR") || + botKeypair; +const oracleReporterKeypair = + readOptionalKeypair("ORACLE_REPORTER_KEYPAIR") || + readOptionalKeypair("ORACLE_AUTHORITY_KEYPAIR") || + botKeypair; +const oracleFinalizerKeypair = + readOptionalKeypair("ORACLE_FINALIZER_KEYPAIR") || + readOptionalKeypair("ORACLE_AUTHORITY_KEYPAIR") || + oracleReporterKeypair; +const oracleChallengerKeypair = + readOptionalKeypair("ORACLE_CHALLENGER_KEYPAIR") || + readOptionalKeypair("ORACLE_AUTHORITY_KEYPAIR") || + oracleReporterKeypair; +const clobConfigAuthorityKeypair = + readOptionalKeypair("CLOB_CONFIG_AUTHORITY_KEYPAIR") || + readOptionalKeypair("CLOB_AUTHORITY_KEYPAIR") || + readOptionalKeypair("ORACLE_CONFIG_AUTHORITY_KEYPAIR") || + readOptionalKeypair("ORACLE_AUTHORITY_KEYPAIR") || + botKeypair; +const clobMarketOperatorKeypair = + readOptionalKeypair("CLOB_MARKET_OPERATOR_KEYPAIR") || + readOptionalKeypair("MARKET_OPERATOR_KEYPAIR") || + readOptionalKeypair("ORACLE_REPORTER_KEYPAIR") || + readOptionalKeypair("ORACLE_AUTHORITY_KEYPAIR") || + botKeypair; const { connection, provider, fightOracle, goldClobMarket, goldPerpsMarket } = - createPrograms(botKeypair); + createPrograms(botKeypair, { usePollingSendAndConfirm: true }); const fightProgram = fightOracle as Program; const marketProgram = goldClobMarket as Program; const perpsProgram = goldPerpsMarket as Program; @@ -686,6 +878,11 @@ const PERPS_MARKET_MAKER_RECYCLE_MIN_SOL = Math.max( 0, Number(process.env.PERPS_MARKET_MAKER_RECYCLE_MIN_SOL || 0.25), ); +const PERPS_MAINTENANCE_TIMEOUT_MS = Math.max( + 5_000, + Number(process.env.PERPS_MAINTENANCE_TIMEOUT_MS || 30_000), +); +let perpsMaintenancePromise: Promise | null = null; if (PERPS_ORACLE_ENABLED && !PERPS_LIQUIDATOR_ENABLED) { console.warn( @@ -1029,11 +1226,13 @@ async function maybeArchiveSettledPerpsMarkets(): Promise { continue; } - savePerpsMarket({ + const nextRecord = { ...record, status: PERPS_MARKET_STATUS_ARCHIVED, updatedAt: now, - }); + }; + savePerpsMarket(nextRecord); + savePublicPerpsMarket(nextRecord); console.log( `[Keeper] Archived perps market ${record.marketId} for ${record.agentId}`, ); @@ -1087,12 +1286,14 @@ async function deprecateMissingPerpsMarkets( continue; } - savePerpsMarket({ + const nextRecord = { ...record, status: PERPS_MARKET_STATUS_CLOSE_ONLY, deprecatedAt: now, updatedAt: now, - }); + }; + savePerpsMarket(nextRecord); + savePublicPerpsMarket(nextRecord); console.log( `[Keeper] Deprecated perps market ${record.marketId} for ${record.agentId}`, ); @@ -1160,7 +1361,7 @@ async function updatePerpsOracle( .rpc(), connection, ); - savePerpsOracleSnapshot({ + const snapshot = { agentId, marketId, spotIndex, @@ -1168,7 +1369,9 @@ async function updatePerpsOracle( mu: rating.mu, sigma: rating.sigma, recordedAt: Date.now(), - }); + }; + savePerpsOracleSnapshot(snapshot); + savePublicPerpsOracleSnapshot(snapshot); await ensurePerpsMarketBootstrapInsurance(marketId); await maybeRecyclePerpsMarketMakerFees(marketId); console.log( @@ -1346,9 +1549,14 @@ async function syncPerpsOracles( continue; } - savePerpsMarket( - toPerpsMarketRecord(entry, PERPS_MARKET_STATUS_ACTIVE, now, previous), + const nextRecord = toPerpsMarketRecord( + entry, + PERPS_MARKET_STATUS_ACTIVE, + now, + previous, ); + savePerpsMarket(nextRecord); + savePublicPerpsMarket(nextRecord); agentIdByMarketId.set( modelMarketIdFromCharacterId(entry.characterId), entry.characterId, @@ -1482,6 +1690,10 @@ const autoSeedDelayMs = Math.max( 0, Math.floor(Number(args["auto-seed-delay-seconds"]) * 1000), ); +const resultPostDelayMs = Math.max( + 0, + Math.floor(Number(args["result-post-delay-seconds"]) * 1000), +); const configuredBidPrice = Math.max( 1, Math.min(999, Math.floor(Number(process.env.MARKET_MAKER_BID_PRICE || 400))), @@ -1554,6 +1766,34 @@ type EvmOracleDuelState = { metadataUri: string; }; +type EvmResolutionOutcome = { + chainKey: BettingChainKey; + lifecycleStatus: PredictionMarketLifecycleStatus; + finalized: boolean; + note: string | null; +}; + +type SolanaResolutionOutcome = { + lifecycleStatus: PredictionMarketLifecycleStatus; + finalized: boolean; + note: string | null; +}; + +type EvmGoldClobMarketState = { + exists: boolean; + duelKey: Hex; + status: number; + winner: number; + tradeTreasuryFeeBpsSnapshot: number; + tradeMarketMakerFeeBpsSnapshot: number; + winningsMarketMakerFeeBpsSnapshot: number; + nextOrderId: bigint; + bestBid: number; + bestAsk: number; + totalAShares: bigint; + totalBShares: bigint; +}; + function parseAddressEnv(value: string | undefined): Address | null { const trimmed = value?.trim() ?? ""; if (!/^0x[0-9a-fA-F]{40}$/.test(trimmed)) { @@ -1748,9 +1988,248 @@ const configuredEvmKeeperChains = parseBettingEvmChainList( process.env.EVM_KEEPER_CHAINS, BETTING_EVM_CHAIN_ORDER, ); +const configuredPublicPerpsChains = resolvePublicEvmPerpsChains({ + configuredChains: configuredEvmKeeperChains, + publicChains: process.env.EVM_PUBLIC_PERPS_CHAINS, +}); const evmKeeperChains = configuredEvmKeeperChains .map((chainKey) => buildEvmRuntime(chainKey)) .filter((chain): chain is EvmKeeperRuntime => chain !== null); +const requiredParityChains = parseRequiredParityChains( + process.env.MARKET_PARITY_REQUIRED_CHAINS, +); + +function requiredEvmParityChains(): BettingEvmChain[] { + return requiredParityChains.filter( + (chainKey): chainKey is BettingEvmChain => + chainKey === "bsc" || chainKey === "base" || chainKey === "avax", + ); +} + +function evmParityConfirmationCount(chain: EvmKeeperRuntime): number { + if (configuredEvmParityConfirmations != null) { + return configuredEvmParityConfirmations; + } + return chain.supportsTimeTravel ? 1 : DEFAULT_REMOTE_EVM_PARITY_CONFIRMATIONS; +} + +async function waitForEvmTransactionReceipt( + chain: EvmKeeperRuntime, + hash: Hex, +): Promise { + await chain.publicClient.waitForTransactionReceipt({ + hash, + confirmations: evmParityConfirmationCount(chain), + timeout: evmTxReceiptTimeoutMs, + }); + lastSuccessfulRpcAtMs = Date.now(); +} + +async function finalizeEvmDuelResultIfReady( + chain: EvmKeeperRuntime, + duelKey: Hex, + metadata: string, +): Promise<"resolved" | "proposed"> { + if (!chain.finalizer) { + throw new Error( + `missing finalizer signer for ${chain.chainKey}; set TESTNET_FINALIZER_PRIVATE_KEY or EVM_FINALIZER_PRIVATE_KEY`, + ); + } + try { + const finalizeHash = await chain.finalizer.walletClient.writeContract({ + chain: undefined, + address: chain.duelOracleAddress, + abi: DUEL_OUTCOME_ORACLE_ABI, + functionName: "finalizeResult", + args: [duelKey, metadata], + account: chain.finalizer.account, + }); + await waitForEvmTransactionReceipt(chain, finalizeHash); + } catch (error) { + if (isEvmDisputeWindowActiveError(error)) { + return "proposed"; + } + throw error; + } + + const finalizeSyncHash = await chain.operator.walletClient.writeContract({ + chain: undefined, + address: chain.goldClobAddress, + abi: EVM_GOLD_CLOB_ADMIN_ABI, + functionName: "syncMarketFromOracle", + args: [duelKey, EVM_DUEL_WINNER_MARKET_KIND], + account: chain.operator.account, + }); + await waitForEvmTransactionReceipt(chain, finalizeSyncHash); + return "resolved"; +} + +async function readEvmDuelState( + chain: EvmKeeperRuntime, + duelKey: Hex, +): Promise { + const duel = (await chain.publicClient.readContract({ + address: chain.duelOracleAddress, + abi: DUEL_OUTCOME_ORACLE_ABI, + functionName: "getDuel", + args: [duelKey], + })) as EvmOracleDuelState; + lastSuccessfulRpcAtMs = Date.now(); + return duel; +} + +async function readEvmMarketState( + chain: EvmKeeperRuntime, + duelKey: Hex, +): Promise { + const market = (await chain.publicClient.readContract({ + address: chain.goldClobAddress, + abi: EVM_GOLD_CLOB_ADMIN_ABI, + functionName: "getMarket", + args: [duelKey, EVM_DUEL_WINNER_MARKET_KIND], + })) as EvmGoldClobMarketState; + lastSuccessfulRpcAtMs = Date.now(); + return market; +} + +function expectedMarketStatusForDuelStatus(status: number): number { + switch (status) { + case EVM_DUEL_STATUS_SCHEDULED: + return EVM_MARKET_STATUS_LOCKED; + case EVM_DUEL_STATUS_BETTING_OPEN: + return EVM_MARKET_STATUS_OPEN; + case EVM_DUEL_STATUS_LOCKED: + case EVM_DUEL_STATUS_PROPOSED: + case EVM_DUEL_STATUS_CHALLENGED: + return EVM_MARKET_STATUS_LOCKED; + case EVM_DUEL_STATUS_RESOLVED: + return EVM_MARKET_STATUS_RESOLVED; + case EVM_DUEL_STATUS_CANCELLED: + return EVM_MARKET_STATUS_CANCELLED; + default: + return EVM_MARKET_STATUS_NULL; + } +} + +async function verifyEvmLifecycleReadback(params: { + chain: EvmKeeperRuntime; + duelKey: Hex; + duelId: string; + minimumDuelStatus: number; + expectedMarketStatus: number; + participantAHash: Hex; + participantBHash: Hex; + betOpenTs: bigint; + betCloseTs: bigint; + duelStartTs: bigint; + expectedWinner?: number | null; + attempts?: number; +}): Promise { + const { + chain, + duelKey, + duelId, + minimumDuelStatus, + expectedMarketStatus, + participantAHash, + participantBHash, + betOpenTs, + betCloseTs, + duelStartTs, + expectedWinner = null, + attempts = 5, + } = params; + let lastError: Error | null = null; + + for (let attempt = 1; attempt <= attempts; attempt += 1) { + try { + const [duel, market] = await Promise.all([ + readEvmDuelState(chain, duelKey), + readEvmMarketState(chain, duelKey), + ]); + if (normalizeHex32(duel.duelKey) !== duelKey) { + throw new Error("duel key readback mismatch"); + } + if (asNum(duel.status, EVM_DUEL_STATUS_NULL) < minimumDuelStatus) { + throw new Error( + `duel status ${asNum(duel.status)} below expected ${minimumDuelStatus}`, + ); + } + if (normalizeHex32(duel.participantAHash) !== participantAHash) { + throw new Error("participant A hash mismatch"); + } + if (normalizeHex32(duel.participantBHash) !== participantBHash) { + throw new Error("participant B hash mismatch"); + } + if (asBigInt(duel.betOpenTs) !== betOpenTs) { + throw new Error("bet open timestamp mismatch"); + } + if (asBigInt(duel.betCloseTs) !== betCloseTs) { + throw new Error("bet close timestamp mismatch"); + } + if (asBigInt(duel.duelStartTs) !== duelStartTs) { + throw new Error("duel start timestamp mismatch"); + } + if (expectedWinner != null && asNum(duel.winner) !== expectedWinner) { + throw new Error( + `winner ${asNum(duel.winner)} did not match expected ${expectedWinner}`, + ); + } + if (!market.exists) { + throw new Error("market missing after lifecycle write"); + } + if (normalizeHex32(market.duelKey) !== duelKey) { + throw new Error("market duel key mismatch"); + } + if (asNum(market.status, EVM_MARKET_STATUS_NULL) !== expectedMarketStatus) { + throw new Error( + `market status ${asNum(market.status)} did not match expected ${expectedMarketStatus}`, + ); + } + return; + } catch (error) { + lastError = + error instanceof Error ? error : new Error(String(error ?? "unknown")); + if (attempt < attempts) { + await sleep(500); + } + } + } + + throw new Error( + `${chain.chainKey} lifecycle readback verification failed for ${duelId}: ${lastError?.message ?? "unknown error"}`, + ); +} + +function publicPerpsChains(): EvmPerpsChainKey[] { + return configuredPublicPerpsChains; +} + +function savePublicPerpsOracleSnapshot(snapshot: { + agentId: string; + marketId: number; + spotIndex: number; + conservativeSkill: number; + mu: number; + sigma: number; + recordedAt: number; +}): void { + for (const chainKey of publicPerpsChains()) { + saveChainScopedPerpsOracleSnapshot({ + ...snapshot, + chainKey, + }); + } +} + +function savePublicPerpsMarket(record: DbPerpsMarketRecord): void { + for (const chainKey of publicPerpsChains()) { + saveChainScopedPerpsMarket({ + ...record, + chainKey, + }); + } +} const requiredPrograms = [ { @@ -1938,6 +2417,10 @@ async function ensureWalletAccountReady( } const ensureOracleReady = async (): Promise => { + const expectedAuthority = oracleConfigAuthorityKeypair.publicKey; + const expectedReporter = oracleReporterKeypair.publicKey; + const expectedFinalizer = oracleFinalizerKeypair.publicKey; + const expectedChallenger = oracleChallengerKeypair.publicKey; let config = await fightProgram.account.oracleConfig.fetchNullable(oracleConfigPda); if (!config) { @@ -1945,18 +2428,19 @@ const ensureOracleReady = async (): Promise => { () => fightProgram.methods .initializeOracle( - botKeypair.publicKey, - botKeypair.publicKey, - botKeypair.publicKey, - new BN(3600), + expectedReporter, + expectedFinalizer, + expectedChallenger, + new BN(SOLANA_ORACLE_DISPUTE_WINDOW_SECS), ) .accountsPartial({ - authority: botKeypair.publicKey, + authority: expectedAuthority, oracleConfig: oracleConfigPda, program: fightProgram.programId, programData: deriveProgramDataAddress(fightProgram.programId), systemProgram: SystemProgram.programId, }) + .signers([oracleConfigAuthorityKeypair]) .rpc(), connection, ); @@ -1968,31 +2452,38 @@ const ensureOracleReady = async (): Promise => { `Oracle config ${oracleConfigPda.toBase58()} was not created`, ); } - if (!(config.authority as PublicKey).equals(botKeypair.publicKey)) { + const actualAuthority = config.authority as PublicKey; + if (hasExplicitOracleConfigAuthority && !actualAuthority.equals(expectedAuthority)) { throw new Error( - `Bot wallet ${botKeypair.publicKey.toBase58()} is not oracle authority`, + `Configured oracle authority ${expectedAuthority.toBase58()} does not match on-chain oracle authority ${actualAuthority.toBase58()}`, ); } const configNeedsUpdate = - !(config.reporter as PublicKey).equals(botKeypair.publicKey) || - !(config.finalizer as PublicKey).equals(botKeypair.publicKey) || - !(config.challenger as PublicKey).equals(botKeypair.publicKey); + !(config.reporter as PublicKey).equals(expectedReporter) || + !(config.finalizer as PublicKey).equals(expectedFinalizer) || + !(config.challenger as PublicKey).equals(expectedChallenger) || + asNum(config.disputeWindowSecs) !== SOLANA_ORACLE_DISPUTE_WINDOW_SECS; if (configNeedsUpdate) { - const disputeWindowSecs = asNum(config.disputeWindowSecs); + if (!actualAuthority.equals(expectedAuthority)) { + throw new Error( + `Oracle role update requires config authority ${actualAuthority.toBase58()}, but keeper has ${expectedAuthority.toBase58()}`, + ); + } await runWithRecovery( () => fightProgram.methods .updateOracleConfig( - botKeypair.publicKey, - botKeypair.publicKey, - botKeypair.publicKey, - botKeypair.publicKey, - new BN(disputeWindowSecs), + expectedAuthority, + expectedReporter, + expectedFinalizer, + expectedChallenger, + new BN(SOLANA_ORACLE_DISPUTE_WINDOW_SECS), ) .accountsPartial({ - authority: botKeypair.publicKey, + authority: expectedAuthority, oracleConfig: oracleConfigPda, }) + .signers([oracleConfigAuthorityKeypair]) .rpc(), connection, ); @@ -2011,6 +2502,8 @@ const ensureMarketConfigReady = async (): Promise => { const existingConfig = await marketProgram.account.marketConfig.fetchNullable(marketConfigPda); const expectedConfig = { + authority: clobConfigAuthorityKeypair.publicKey, + marketOperator: clobMarketOperatorKeypair.publicKey, treasury: configuredTradeTreasuryWallet, marketMaker: configuredTradeMarketMakerWallet, tradeTreasuryFeeBps, @@ -2023,7 +2516,7 @@ const ensureMarketConfigReady = async (): Promise => { () => marketProgram.methods .initializeConfig( - botKeypair.publicKey, + expectedConfig.marketOperator, expectedConfig.treasury, expectedConfig.marketMaker, expectedConfig.tradeTreasuryFeeBps, @@ -2031,12 +2524,13 @@ const ensureMarketConfigReady = async (): Promise => { expectedConfig.winningsMarketMakerFeeBps, ) .accountsPartial({ - authority: botKeypair.publicKey, + authority: expectedConfig.authority, config: marketConfigPda, program: marketProgram.programId, programData: deriveProgramDataAddress(marketProgram.programId), systemProgram: SystemProgram.programId, }) + .signers([clobConfigAuthorityKeypair]) .rpc(), connection, ); @@ -2046,7 +2540,17 @@ const ensureMarketConfigReady = async (): Promise => { return; } + const actualAuthority = existingConfig.authority as PublicKey; + if (hasExplicitClobConfigAuthority && !actualAuthority.equals(expectedConfig.authority)) { + throw new Error( + `Configured CLOB authority ${expectedConfig.authority.toBase58()} does not match on-chain CLOB authority ${actualAuthority.toBase58()}`, + ); + } + const configNeedsUpdate = + !(existingConfig.marketOperator as PublicKey).equals( + expectedConfig.marketOperator, + ) || !(existingConfig.treasury as PublicKey).equals(expectedConfig.treasury) || !(existingConfig.marketMaker as PublicKey).equals( expectedConfig.marketMaker, @@ -2059,12 +2563,17 @@ const ensureMarketConfigReady = async (): Promise => { expectedConfig.winningsMarketMakerFeeBps; if (configNeedsUpdate) { + if (!actualAuthority.equals(expectedConfig.authority)) { + throw new Error( + `CLOB config update requires authority ${actualAuthority.toBase58()}, but keeper has ${expectedConfig.authority.toBase58()}`, + ); + } await runWithRecovery( () => marketProgram.methods .updateConfig( - botKeypair.publicKey, - botKeypair.publicKey, + expectedConfig.authority, + expectedConfig.marketOperator, expectedConfig.treasury, expectedConfig.marketMaker, expectedConfig.tradeTreasuryFeeBps, @@ -2072,9 +2581,10 @@ const ensureMarketConfigReady = async (): Promise => { expectedConfig.winningsMarketMakerFeeBps, ) .accountsPartial({ - authority: botKeypair.publicKey, + authority: expectedConfig.authority, config: marketConfigPda, }) + .signers([clobConfigAuthorityKeypair]) .rpc(), connection, ); @@ -2096,6 +2606,15 @@ async function getDuelState( return duelState; } +async function getOracleConfigState(): Promise< + Awaited> +> { + const oracleConfig = + await fightProgram.account.oracleConfig.fetchNullable(oracleConfigPda); + markRpcSuccess(); + return oracleConfig; +} + async function getClobMarketState( marketStatePda: PublicKey, ): Promise>> { @@ -2209,13 +2728,21 @@ async function ensureClobVaultReady(vault: PublicKey): Promise { } function buildDuelMetadata(data: DuelLifecycleEvent): string { - return JSON.stringify({ - cycleId: data.cycleId, - duelId: data.duelId, - duelKeyHex: data.duelKeyHex, - agent1: data.agent1?.name ?? "Agent A", - agent2: data.agent2?.name ?? "Agent B", - }); + return buildDuelMetadataForDuelKey(data.duelKeyHex, data.duelId); +} + +function buildDuelMetadataForDuelKey( + duelKeyHex: string, + duelId: string | null, +): string { + const duelKeyRef = duelKeyHex.trim().replace(/^0x/i, "").toLowerCase(); + const metadataUri = `hyperscapes://duel/${duelKeyRef}`; + if (Buffer.byteLength(metadataUri, "utf8") > 200) { + throw new Error( + `duel metadata URI exceeds Solana oracle limit for duel ${duelId ?? duelKeyRef}`, + ); + } + return metadataUri; } function deriveCanonicalDuelTimes(data: DuelLifecycleEvent): { @@ -2258,30 +2785,42 @@ async function upsertDuelLifecycle( ): Promise { const duelKey = duelKeyHexToBytes(data.duelKeyHex); const duelState = findDuelStatePda(fightProgram.programId, duelKey); - const nowSeconds = Math.floor(Date.now() / 1000); - const { betOpenTs, betCloseTs, duelStartTs } = deriveCanonicalDuelTimes(data); - const requestedStatus = - status === "scheduled" && betOpenTs <= nowSeconds ? "bettingOpen" : status; + const existingDuelState = await getDuelState(duelState); + const derivedTimes = deriveCanonicalDuelTimes(data); + const betOpenTs = existingDuelState + ? asNum(existingDuelState.betOpenTs, derivedTimes.betOpenTs) + : derivedTimes.betOpenTs; + const betCloseTs = existingDuelState + ? asNum(existingDuelState.betCloseTs, derivedTimes.betCloseTs) + : derivedTimes.betCloseTs; + const duelStartTs = existingDuelState + ? asNum(existingDuelState.duelStartTs, derivedTimes.duelStartTs) + : derivedTimes.duelStartTs; + const participantAHash = + toByteArray32(existingDuelState?.participantAHash) ?? hashParticipant(data.agent1); + const participantBHash = + toByteArray32(existingDuelState?.participantBHash) ?? hashParticipant(data.agent2); await runWithRecovery( () => fightProgram.methods .upsertDuel( Array.from(duelKey), - hashParticipant(data.agent1), - hashParticipant(data.agent2), + participantAHash, + participantBHash, new BN(betOpenTs), new BN(betCloseTs), new BN(duelStartTs), buildDuelMetadata(data), - duelStatusEnum(requestedStatus), + duelStatusEnum(status), ) .accountsPartial({ - reporter: botKeypair.publicKey, + reporter: oracleReporterKeypair.publicKey, oracleConfig: oracleConfigPda, duelState, systemProgram: SystemProgram.programId, }) + .signers([oracleReporterKeypair]) .rpc(), connection, ); @@ -2663,10 +3202,99 @@ async function ensureManagedClobOrder( ); } -async function createOrSyncRound( +function buildTrackedMatch( + data: DuelLifecycleEvent, + duelState: PublicKey, + marketState: PublicKey, + vault: PublicKey, +): ActiveClobMatch { + return buildTrackedMatchFromKeys({ + duelId: data.duelId, + duelKeyHex: data.duelKeyHex, + duelState, + marketState, + vault, + createdAt: Date.now(), + }); +} + +function buildTrackedMatchFromKeys(params: { + duelId: string; + duelKeyHex: string; + duelState: PublicKey; + marketState: PublicKey; + vault: PublicKey; + createdAt?: number | null; +}): ActiveClobMatch { + return { + duelId: params.duelId, + duelKeyHex: params.duelKeyHex, + duelState: params.duelState, + marketState: params.marketState, + vault: params.vault, + createdAt: params.createdAt ?? Date.now(), + lastStreamAtMs: Date.now(), + lastOracleAtMs: null, + lastRpcAtMs: null, + lastSyncedAtMs: null, + lastResolvedAtMs: null, + lastClaimAtMs: null, + lastQuoteSnapshot: null, + lastQuotePlan: null, + winner: "NONE", + yesBidOrder: null, + noAskOrder: null, + }; +} + +function deriveTrackedMatchForParitySnapshot( + snapshot: KeeperMarketParitySnapshot, +): ActiveClobMatch | null { + const duelKey = normalizedBundleDuelKey(snapshot.duelKey); + if (!duelKey) return null; + + const existing = snapshot.duelId + ? activeClobMatches.get(snapshot.duelId) + : null; + if ( + existing && + normalizedBundleDuelKey(existing.duelKeyHex) === duelKey + ) { + return existing; + } + + const duelKeyBytes = duelKeyHexToBytes(duelKey); + const duelState = findDuelStatePda(fightProgram.programId, duelKeyBytes); + const marketState = findMarketPda( + marketProgram.programId, + duelState, + SOLANA_DUEL_WINNER_MARKET_KIND, + ); + const vault = findClobVaultPda(marketProgram.programId, marketState); + return buildTrackedMatchFromKeys({ + duelId: snapshot.duelId ?? `parity:${duelKey}`, + duelKeyHex: duelKey, + duelState, + marketState, + vault, + createdAt: snapshot.openedAtMs ?? snapshot.updatedAtMs, + }); +} + +function resolvedWinnerFromDuelState( + duelState: Record | null, +): PredictionMarketWinner { + if (!duelState) return "NONE"; + if (enumIs(duelState.winner, "a")) return "A"; + if (enumIs(duelState.winner, "b")) return "B"; + return "NONE"; +} + +async function ensureSolanaRound( data: DuelLifecycleEvent, + status: "scheduled" | "bettingOpen", ): Promise { - const duelState = await upsertDuelLifecycle(data, "bettingOpen"); + const duelState = await upsertDuelLifecycle(data, status); const marketState = findMarketPda( marketProgram.programId, duelState, @@ -2681,13 +3309,14 @@ async function createOrSyncRound( marketProgram.methods .initializeMarket(Array.from(duelKey), SOLANA_DUEL_WINNER_MARKET_KIND) .accountsPartial({ - operator: botKeypair.publicKey, + operator: clobMarketOperatorKeypair.publicKey, config: marketConfigPda, duelState, marketState, vault, systemProgram: SystemProgram.programId, }) + .signers([clobMarketOperatorKeypair]) .rpc(), connection, ); @@ -2700,43 +3329,69 @@ async function createOrSyncRound( } } - const trackedMatch: ActiveClobMatch = { - duelId: data.duelId, - duelKeyHex: data.duelKeyHex, - duelState, - marketState, - vault, - createdAt: Date.now(), - lastStreamAtMs: Date.now(), - lastOracleAtMs: null, - lastRpcAtMs: null, - lastSyncedAtMs: null, - lastResolvedAtMs: null, - lastClaimAtMs: null, - lastQuoteSnapshot: null, - lastQuotePlan: null, - winner: "NONE", - yesBidOrder: null, - noAskOrder: null, - }; + const trackedMatch = buildTrackedMatch(data, duelState, marketState, vault); await syncTrackedMarketFromOracle(trackedMatch); console.log( - `[bot] Duel market ready duel=${data.duelId} duelState=${duelState.toBase58()} market=${marketState.toBase58()}`, + `[bot] Duel market ${status} duel=${data.duelId} duelState=${duelState.toBase58()} market=${marketState.toBase58()}`, ); return trackedMatch; } +async function prepareRound(data: DuelLifecycleEvent): Promise { + await ensureSolanaRound(data, "scheduled"); +} + +async function createOrSyncRound( + data: DuelLifecycleEvent, +): Promise { + return ensureSolanaRound(data, "bettingOpen"); +} + async function lockRound(data: DuelLifecycleEvent): Promise { - const trackedMatch = activeClobMatches.get(data.duelId); + let trackedMatch = activeClobMatches.get(data.duelId); + if (!trackedMatch && marketParitySnapshot) { + trackedMatch = + deriveTrackedMatchForParitySnapshot(marketParitySnapshot) ?? undefined; + } if (!trackedMatch) { - return; + throw new Error( + `lockRound: cannot resolve Solana tracked match for duel ${data.duelId}`, + ); } await upsertDuelLifecycle(data, "locked"); await syncTrackedMarketFromOracle(trackedMatch); } +async function cancelRound( + trackedMatch: ActiveClobMatch, + metadataUri = "atomic-parity-abort", +): Promise { + await cancelManagedClobQuotes(trackedMatch, "atomic-parity-abort"); + + await runWithRecovery( + () => + fightProgram.methods + .cancelDuel(Array.from(duelKeyHexToBytes(trackedMatch.duelKeyHex)), metadataUri) + .accountsPartial({ + authority: botKeypair.publicKey, + oracleConfig: oracleConfigPda, + duelState: trackedMatch.duelState, + }) + .rpc(), + connection, + ); + const cancelledAt = markRpcSuccess(trackedMatch); + trackedMatch.lastOracleAtMs = cancelledAt; + trackedMatch.lastResolvedAtMs = cancelledAt; + + unresolvedOracleWarningMatches.delete(trackedMatch.duelId); + await syncTrackedMarketFromOracle(trackedMatch); + await captureSettledClobHealth(trackedMatch, "CANCELLED"); + activeClobMatches.delete(trackedMatch.duelId); +} + async function maybeWarnUnresolvedDuel(trackedMatch: ActiveClobMatch): Promise { const duelState = await getDuelState(trackedMatch.duelState); if (!duelState || !enumIs(duelState.status, "locked")) { @@ -2797,6 +3452,322 @@ if (previousBotHealthSnapshot?.markets.some((market) => market.openOrderCount > .map((market) => market.marketRef ?? market.duelKey ?? market.duelId ?? "unknown") .join(", ")}`; } +let marketParityRevision = previousBotHealthSnapshot?.marketParity?.revision ?? 0; +let marketParitySnapshot: KeeperMarketParitySnapshot | null = + previousBotHealthSnapshot?.marketParity ?? null; + +function normalizedBundleDuelKey(duelKeyHex: string | null | undefined): string | null { + const raw = (duelKeyHex ?? "").trim().replace(/^0x/i, "").toLowerCase(); + return /^[0-9a-f]{64}$/.test(raw) ? raw : null; +} + +function buildMarketParityBundleId( + data: DuelLifecycleEvent, + revision: number, +): string { + return [ + normalizedBundleDuelKey(data.duelKeyHex) ?? data.duelId ?? "unknown-duel", + revision.toString(), + ].join(":"); +} + +function buildMarketParityReceipts( + requiredChains: readonly BettingChainKey[], +): KeeperParityChainReceipt[] { + return requiredChains.map((chainKey) => ({ + chainKey, + preparedAtMs: null, + openedAtMs: null, + lockedAtMs: null, + resolvedAtMs: null, + cancelledAtMs: null, + confirmedAtMs: null, + lifecycleStatus: null, + txRef: null, + note: null, + })); +} + +function isTerminalMarketParityState(state: KeeperParityBundleState): boolean { + return state === "resolved" || state === "cancelled" || state === "aborted"; +} + +function isSameMarketParityBundle( + current: KeeperMarketParitySnapshot, + data: DuelLifecycleEvent, +): boolean { + return ( + current.duelKey === normalizedBundleDuelKey(data.duelKeyHex) && + current.duelId === data.duelId + ); +} + +function shouldFreezeMismatchedMarketParityEvent( + current: KeeperMarketParitySnapshot, +): boolean { + if (current.state === "frozen") { + return current.openedAtMs != null; + } + if (current.openedAtMs || current.safeToBet || current.state === "open") { + return true; + } + return false; +} + +function blockMismatchedMarketParityEvent( + data: DuelLifecycleEvent, + eventName: string, +): boolean { + if ( + !marketParitySnapshot || + isTerminalMarketParityState(marketParitySnapshot.state) || + isSameMarketParityBundle(marketParitySnapshot, data) + ) { + return false; + } + + const incomingDuelKey = normalizedBundleDuelKey(data.duelKeyHex); + const reason = `blocked_${eventName}_for_${incomingDuelKey ?? data.duelId ?? "unknown"}_while_${marketParitySnapshot.bundleId}_is_${marketParitySnapshot.state}`; + const shouldFreeze = shouldFreezeMismatchedMarketParityEvent( + marketParitySnapshot, + ); + const logBlockedEvent = shouldFreeze ? console.error : console.warn; + logBlockedEvent("[bot] market_parity_blocked_event", { + eventName, + reason, + action: shouldFreeze ? "freeze" : "abort", + activeBundleId: marketParitySnapshot.bundleId, + activeDuelKey: marketParitySnapshot.duelKey, + activeDuelId: marketParitySnapshot.duelId, + activeState: marketParitySnapshot.state, + incomingDuelKey, + incomingDuelId: data.duelId, + }); + if (!shouldFreeze) { + finalizeMarketParityState("aborted", marketParitySnapshot.phase, { + reason, + }); + writeBotHealthSnapshot(); + return eventName !== "duel_started"; + } + const freezeReason = + marketParitySnapshot.state === "frozen" + ? marketParitySnapshot.freezeReason ?? reason + : reason; + finalizeMarketParityState("frozen", marketParitySnapshot.phase, { + reason: freezeReason, + }); + writeBotHealthSnapshot(); + return true; +} + +function requireExistingMarketParityBundle( + data: DuelLifecycleEvent, + eventName: string, +): KeeperMarketParitySnapshot | null { + if (blockMismatchedMarketParityEvent(data, eventName)) { + return null; + } + + if ( + !marketParitySnapshot || + isTerminalMarketParityState(marketParitySnapshot.state) || + !isSameMarketParityBundle(marketParitySnapshot, data) + ) { + console.warn("[bot] market_parity_dropped_event", { + eventName, + reason: `dropped_${eventName}_without_existing_public_bundle`, + incomingDuelKey: normalizedBundleDuelKey(data.duelKeyHex), + incomingDuelId: data.duelId, + activeBundleId: marketParitySnapshot?.bundleId ?? null, + activeState: marketParitySnapshot?.state ?? null, + }); + writeBotHealthSnapshot(); + return null; + } + + return marketParitySnapshot; +} + +function updateMarketParitySnapshot( + updater: ( + current: KeeperMarketParitySnapshot | null, + ) => KeeperMarketParitySnapshot | null, +): void { + marketParitySnapshot = updater(marketParitySnapshot); + if (marketParitySnapshot) { + marketParitySnapshot = { + ...marketParitySnapshot, + updatedAtMs: Date.now(), + }; + } +} + +function beginMarketParityBundle( + data: DuelLifecycleEvent, + phase: string | null, +): KeeperMarketParitySnapshot { + const duelKey = normalizedBundleDuelKey(data.duelKeyHex); + if ( + marketParitySnapshot && + marketParitySnapshot.duelKey === duelKey && + marketParitySnapshot.duelId === data.duelId && + !isTerminalMarketParityState(marketParitySnapshot.state) + ) { + return marketParitySnapshot; + } + + marketParityRevision += 1; + marketParitySnapshot = { + bundleId: buildMarketParityBundleId(data, marketParityRevision), + duelKey, + duelId: data.duelId, + revision: marketParityRevision, + requiredChains: [...requiredParityChains], + confirmedChains: [], + state: "preparing", + phase, + safeToBet: false, + openedAtMs: null, + lockedAtMs: null, + resolvedAtMs: null, + freezeReason: null, + updatedAtMs: Date.now(), + receipts: buildMarketParityReceipts(requiredParityChains), + }; + return marketParitySnapshot; +} + +function resetMarketParityConfirmations( + nextState: KeeperParityBundleState, + phase: string | null, + safeToBet: boolean, +): void { + updateMarketParitySnapshot((current) => { + if (!current) return current; + return { + ...current, + state: nextState, + phase, + safeToBet, + confirmedChains: [], + freezeReason: null, + }; + }); +} + +function markMarketParityChainConfirmed(params: { + chainKey: BettingChainKey; + transition: "prepare" | "open" | "lock" | "propose" | "resolve" | "cancel"; + lifecycleStatus: PredictionMarketLifecycleStatus | null; + countsForQuorum?: boolean; + txRef?: string | null; + note?: string | null; +}): void { + const now = Date.now(); + updateMarketParitySnapshot((current) => { + if (!current) return current; + const receipts = current.receipts.map((receipt) => { + if (receipt.chainKey !== params.chainKey) { + return receipt; + } + return { + ...receipt, + preparedAtMs: + params.transition === "prepare" ? now : receipt.preparedAtMs, + openedAtMs: params.transition === "open" ? now : receipt.openedAtMs, + lockedAtMs: params.transition === "lock" ? now : receipt.lockedAtMs, + resolvedAtMs: + params.transition === "resolve" ? now : receipt.resolvedAtMs, + cancelledAtMs: + params.transition === "cancel" ? now : receipt.cancelledAtMs, + confirmedAtMs: now, + lifecycleStatus: params.lifecycleStatus, + txRef: params.txRef ?? receipt.txRef, + note: params.note ?? receipt.note, + }; + }); + const confirmedChains = + params.countsForQuorum === false + ? current.confirmedChains + : current.confirmedChains.includes(params.chainKey) + ? current.confirmedChains + : [...current.confirmedChains, params.chainKey]; + return { + ...current, + confirmedChains, + receipts, + }; + }); +} + +function allRequiredParityChainsConfirmed(): boolean { + return Boolean( + marketParitySnapshot && + marketParitySnapshot.requiredChains.length > 0 && + marketParitySnapshot.requiredChains.every((chainKey) => + marketParitySnapshot?.confirmedChains.includes(chainKey), + ), + ); +} + +function hasPendingEvmParityResolution(): boolean { + if (!marketParitySnapshot || marketParitySnapshot.phase !== "RESOLUTION") { + return false; + } + const evmParityChains = new Set(requiredEvmParityChains()); + return marketParitySnapshot.receipts.some( + (receipt) => + evmParityChains.has(receipt.chainKey as BettingEvmChain) && + receipt.lifecycleStatus === "PROPOSED", + ); +} + +function hasPendingParityResolution(): boolean { + return Boolean( + marketParitySnapshot && + marketParitySnapshot.phase === "RESOLUTION" && + marketParitySnapshot.receipts.some( + (receipt) => receipt.lifecycleStatus === "PROPOSED", + ), + ); +} + +function finalizeMarketParityState( + nextState: Extract< + KeeperParityBundleState, + "open" | "locked" | "resolved" | "cancelled" | "frozen" | "aborted" + >, + phase: string | null, + options: { + safeToBet?: boolean; + reason?: string | null; + } = {}, +): void { + const now = Date.now(); + updateMarketParitySnapshot((current) => { + if (!current) return current; + return { + ...current, + state: nextState, + phase, + safeToBet: options.safeToBet === true, + freezeReason: options.reason ?? null, + openedAtMs: + nextState === "open" + ? current.openedAtMs ?? now + : current.openedAtMs, + lockedAtMs: + nextState === "locked" + ? current.lockedAtMs ?? now + : current.lockedAtMs, + resolvedAtMs: + nextState === "resolved" || nextState === "cancelled" + ? current.resolvedAtMs ?? now + : current.resolvedAtMs, + }; + }); +} function trimSettledClobHealth(now = Date.now()): void { for (const [duelId, record] of settledClobHealth.entries()) { @@ -2976,6 +3947,7 @@ function writeBotHealthSnapshot(): void { lastSuccessfulRpcAtMs, recovery: buildBotRecoveryStates(), markets: [...activeRecords, ...recentSettledRecords], + marketParity: marketParitySnapshot, }; fs_node.mkdirSync(path.dirname(BOT_HEALTH_FILE), { recursive: true }); fs_node.writeFileSync(BOT_HEALTH_FILE, JSON.stringify(snapshot, null, 2)); @@ -2987,8 +3959,14 @@ function writeBotHealthSnapshot(): void { async function upsertEvmDuelLifecycle( data: DuelLifecycleEvent, status: 1 | 2 | 3, + targetChains: readonly BettingEvmChain[] = evmKeeperChains.map( + (chain) => chain.chainKey, + ), ): Promise { - if (evmKeeperChains.length === 0) { + const selectedChains = evmKeeperChains.filter((chain) => + targetChains.includes(chain.chainKey), + ); + if (selectedChains.length === 0) { return; } @@ -3000,13 +3978,8 @@ async function upsertEvmDuelLifecycle( const metadata = buildDuelMetadata(data); const results = await Promise.allSettled( - evmKeeperChains.map(async (chain) => { - const existing = (await chain.publicClient.readContract({ - address: chain.duelOracleAddress, - abi: DUEL_OUTCOME_ORACLE_ABI, - functionName: "getDuel", - args: [duelKey], - })) as EvmOracleDuelState; + selectedChains.map(async (chain) => { + const existing = await readEvmDuelState(chain, duelKey); const existingStatus = asNum(existing?.status, EVM_DUEL_STATUS_NULL); const participantAHash = existingStatus > EVM_DUEL_STATUS_NULL @@ -3017,15 +3990,15 @@ async function upsertEvmDuelLifecycle( ? existing.participantBHash : participantHashHex(data.agent2); const canonicalBetOpenTs = - existingStatus >= EVM_DUEL_STATUS_BETTING_OPEN + existingStatus >= EVM_DUEL_STATUS_SCHEDULED ? existing.betOpenTs : betOpenTs; const canonicalBetCloseTs = - existingStatus >= EVM_DUEL_STATUS_BETTING_OPEN + existingStatus >= EVM_DUEL_STATUS_SCHEDULED ? existing.betCloseTs : betCloseTs; const canonicalDuelStartTs = - existingStatus >= EVM_DUEL_STATUS_BETTING_OPEN + existingStatus >= EVM_DUEL_STATUS_SCHEDULED ? existing.duelStartTs : duelStartTs; if (status >= 3) { @@ -3052,17 +4025,9 @@ async function upsertEvmDuelLifecycle( ], account: chain.reporter.account, }); - await chain.publicClient.waitForTransactionReceipt({ - hash: upsertHash, - confirmations: 1, - }); + await waitForEvmTransactionReceipt(chain, upsertHash); - const market = (await chain.publicClient.readContract({ - address: chain.goldClobAddress, - abi: EVM_GOLD_CLOB_ADMIN_ABI, - functionName: "getMarket", - args: [duelKey, EVM_DUEL_WINNER_MARKET_KIND], - })) as { exists: boolean }; + const market = await readEvmMarketState(chain, duelKey); if (!market.exists) { const createHash = await chain.operator.walletClient.writeContract({ @@ -3073,10 +4038,7 @@ async function upsertEvmDuelLifecycle( args: [duelKey, EVM_DUEL_WINNER_MARKET_KIND], account: chain.operator.account, }); - await chain.publicClient.waitForTransactionReceipt({ - hash: createHash, - confirmations: 1, - }); + await waitForEvmTransactionReceipt(chain, createHash); } const syncHash = await chain.operator.walletClient.writeContract({ @@ -3087,16 +4049,25 @@ async function upsertEvmDuelLifecycle( args: [duelKey, EVM_DUEL_WINNER_MARKET_KIND], account: chain.operator.account, }); - await chain.publicClient.waitForTransactionReceipt({ - hash: syncHash, - confirmations: 1, - }); + await waitForEvmTransactionReceipt(chain, syncHash); lastSuccessfulRpcAtMs = Date.now(); + await verifyEvmLifecycleReadback({ + chain, + duelKey, + duelId: data.duelId, + minimumDuelStatus: status, + expectedMarketStatus: expectedMarketStatusForDuelStatus(status), + participantAHash, + participantBHash, + betOpenTs: canonicalBetOpenTs, + betCloseTs: canonicalBetCloseTs, + duelStartTs: canonicalDuelStartTs, + }); }), ); const failures = results - .map((result, index) => ({ chain: evmKeeperChains[index], result })) + .map((result, index) => ({ chain: selectedChains[index], result })) .filter( ( entry, @@ -3123,9 +4094,34 @@ async function upsertEvmDuelLifecycle( } } -async function reportEvmResult(data: DuelLifecycleEvent): Promise { - if (evmKeeperChains.length === 0 || !data.seed || !data.replayHash) { - return; +async function reportEvmResult( + data: DuelLifecycleEvent, + targetChains: readonly BettingEvmChain[] = evmKeeperChains.map( + (chain) => chain.chainKey, + ), +): Promise> { + const selectedChains = evmKeeperChains.filter((chain) => + targetChains.includes(chain.chainKey), + ); + // Legitimate no-op: no EVM chains configured for this build. Return early + // without throwing so Solana-only deployments don't see spurious errors. + if (selectedChains.length === 0) { + return []; + } + + // Data-quality preconditions. These used to silently return [] and collapse + // into the caller's generic "failed to resolve required parity chains" + // freezeReason. Throw specific errors so freezeReason captures the real + // root cause. + if (!data.seed) { + throw new Error( + `reportEvmResult: duel ${data.duelId} missing seed; refusing to post an unverifiable EVM result`, + ); + } + if (!data.replayHash) { + throw new Error( + `reportEvmResult: duel ${data.duelId} missing replayHash; refusing to post an unverifiable EVM result`, + ); } const duelKey = normalizeHex32(data.duelKeyHex); @@ -3133,7 +4129,9 @@ async function reportEvmResult(data: DuelLifecycleEvent): Promise { const winner = data.winnerId === data.agent1?.id ? 1 : data.winnerId === data.agent2?.id ? 2 : 0; if (winner === 0) { - return; + throw new Error( + `reportEvmResult: duel ${data.duelId} supplied unknown winner id ${data.winnerId ?? ""}; agent1=${data.agent1?.id ?? ""} agent2=${data.agent2?.id ?? ""}`, + ); } const canonicalTimes = deriveCanonicalDuelTimes(data); const defaultBetOpenTs = BigInt(canonicalTimes.betOpenTs); @@ -3143,13 +4141,8 @@ async function reportEvmResult(data: DuelLifecycleEvent): Promise { const metadata = buildDuelMetadata(data); const results = await Promise.allSettled( - evmKeeperChains.map(async (chain) => { - const existing = (await chain.publicClient.readContract({ - address: chain.duelOracleAddress, - abi: DUEL_OUTCOME_ORACLE_ABI, - functionName: "getDuel", - args: [duelKey], - })) as EvmOracleDuelState; + selectedChains.map(async (chain): Promise => { + const existing = await readEvmDuelState(chain, duelKey); const existingStatus = asNum(existing?.status, EVM_DUEL_STATUS_NULL); const participantAHash = existingStatus > EVM_DUEL_STATUS_NULL @@ -3160,23 +4153,30 @@ async function reportEvmResult(data: DuelLifecycleEvent): Promise { ? existing.participantBHash : participantHashHex(data.agent2); const canonicalBetOpenTs = - existingStatus >= EVM_DUEL_STATUS_BETTING_OPEN + existingStatus >= EVM_DUEL_STATUS_SCHEDULED ? existing.betOpenTs : defaultBetOpenTs; const canonicalBetCloseTs = - existingStatus >= EVM_DUEL_STATUS_BETTING_OPEN + existingStatus >= EVM_DUEL_STATUS_SCHEDULED ? existing.betCloseTs : defaultBetCloseTs; const canonicalDuelStartTs = - existingStatus >= EVM_DUEL_STATUS_BETTING_OPEN + existingStatus >= EVM_DUEL_STATUS_SCHEDULED ? existing.duelStartTs : defaultDuelStartTs; - if ( - existingStatus >= EVM_DUEL_STATUS_RESOLVED || - existingStatus === EVM_DUEL_STATUS_CANCELLED - ) { - return; + if (existingStatus === EVM_DUEL_STATUS_CANCELLED) { + throw new Error( + `${chain.chainKey} duel ${data.duelId} is cancelled and cannot satisfy parity resolution`, + ); + } + if (existingStatus >= EVM_DUEL_STATUS_RESOLVED) { + return { + chainKey: chain.chainKey, + lifecycleStatus: "RESOLVED", + finalized: true, + note: "already-resolved", + }; } // Fail-closed: if the result proposal has been challenged, sync the @@ -3192,14 +4192,25 @@ async function reportEvmResult(data: DuelLifecycleEvent): Promise { args: [duelKey, EVM_DUEL_WINNER_MARKET_KIND], account: chain.operator.account, }); - await chain.publicClient.waitForTransactionReceipt({ - hash: syncHash, - confirmations: 1, + await waitForEvmTransactionReceipt(chain, syncHash); + await verifyEvmLifecycleReadback({ + chain, + duelKey, + duelId: data.duelId, + minimumDuelStatus: EVM_DUEL_STATUS_CHALLENGED, + expectedMarketStatus: EVM_MARKET_STATUS_LOCKED, + participantAHash, + participantBHash, + betOpenTs: canonicalBetOpenTs, + betCloseTs: canonicalBetCloseTs, + duelStartTs: canonicalDuelStartTs, }); console.warn( `[bot] Duel ${data.duelId} result proposal is challenged on ${chain.chainKey}; leaving market fail-closed until manual resolution.`, ); - return; + throw new Error( + `${chain.chainKey} duel ${data.duelId} is challenged and cannot satisfy parity resolution`, + ); } await alignLocalChainTime( @@ -3226,10 +4237,7 @@ async function reportEvmResult(data: DuelLifecycleEvent): Promise { ], account: chain.reporter.account, }); - await chain.publicClient.waitForTransactionReceipt({ - hash: lockHash, - confirmations: 1, - }); + await waitForEvmTransactionReceipt(chain, lockHash); const lockSyncHash = await chain.operator.walletClient.writeContract({ chain: undefined, @@ -3239,37 +4247,74 @@ async function reportEvmResult(data: DuelLifecycleEvent): Promise { args: [duelKey, EVM_DUEL_WINNER_MARKET_KIND], account: chain.operator.account, }); - await chain.publicClient.waitForTransactionReceipt({ - hash: lockSyncHash, - confirmations: 1, - }); + await waitForEvmTransactionReceipt(chain, lockSyncHash); } if (existingStatus === EVM_DUEL_STATUS_PROPOSED) { if (EVM_KEEPER_DEFER_FINALIZE) { - console.log( - `[bot] EVM finalization deferred on ${chain.chainKey}; existing result proposal left pending.`, - ); - return; + await verifyEvmLifecycleReadback({ + chain, + duelKey, + duelId: data.duelId, + minimumDuelStatus: EVM_DUEL_STATUS_PROPOSED, + expectedMarketStatus: EVM_MARKET_STATUS_LOCKED, + participantAHash, + participantBHash, + betOpenTs: canonicalBetOpenTs, + betCloseTs: canonicalBetCloseTs, + duelStartTs: canonicalDuelStartTs, + }); + return { + chainKey: chain.chainKey, + lifecycleStatus: "PROPOSED", + finalized: false, + note: "finalization-deferred", + }; } - if (!chain.finalizer) { - throw new Error( - `missing finalizer signer for ${chain.chainKey}; set TESTNET_FINALIZER_PRIVATE_KEY or EVM_FINALIZER_PRIVATE_KEY`, - ); + const finalization = await finalizeEvmDuelResultIfReady( + chain, + duelKey, + metadata, + ); + if (finalization === "proposed") { + await verifyEvmLifecycleReadback({ + chain, + duelKey, + duelId: data.duelId, + minimumDuelStatus: EVM_DUEL_STATUS_PROPOSED, + expectedMarketStatus: EVM_MARKET_STATUS_LOCKED, + participantAHash, + participantBHash, + betOpenTs: canonicalBetOpenTs, + betCloseTs: canonicalBetCloseTs, + duelStartTs: canonicalDuelStartTs, + }); + return { + chainKey: chain.chainKey, + lifecycleStatus: "PROPOSED", + finalized: false, + note: "dispute-window-active", + }; } - const finalizeHash = await chain.finalizer.walletClient.writeContract({ - chain: undefined, - address: chain.duelOracleAddress, - abi: DUEL_OUTCOME_ORACLE_ABI, - functionName: "finalizeResult", - args: [duelKey, metadata], - account: chain.finalizer.account, - }); - await chain.publicClient.waitForTransactionReceipt({ - hash: finalizeHash, - confirmations: 1, + await verifyEvmLifecycleReadback({ + chain, + duelKey, + duelId: data.duelId, + minimumDuelStatus: EVM_DUEL_STATUS_RESOLVED, + expectedMarketStatus: EVM_MARKET_STATUS_RESOLVED, + participantAHash, + participantBHash, + betOpenTs: canonicalBetOpenTs, + betCloseTs: canonicalBetCloseTs, + duelStartTs: canonicalDuelStartTs, + expectedWinner: winner, }); - return; + return { + chainKey: chain.chainKey, + lifecycleStatus: "RESOLVED", + finalized: true, + note: null, + }; } const proposeHash = await chain.reporter.walletClient.writeContract({ @@ -3293,10 +4338,7 @@ async function reportEvmResult(data: DuelLifecycleEvent): Promise { ], account: chain.reporter.account, }); - await chain.publicClient.waitForTransactionReceipt({ - hash: proposeHash, - confirmations: 1, - }); + await waitForEvmTransactionReceipt(chain, proposeHash); const syncHash = await chain.operator.walletClient.writeContract({ chain: undefined, @@ -3306,41 +4348,79 @@ async function reportEvmResult(data: DuelLifecycleEvent): Promise { args: [duelKey, EVM_DUEL_WINNER_MARKET_KIND], account: chain.operator.account, }); - await chain.publicClient.waitForTransactionReceipt({ - hash: syncHash, - confirmations: 1, - }); + await waitForEvmTransactionReceipt(chain, syncHash); lastSuccessfulRpcAtMs = Date.now(); if (EVM_KEEPER_DEFER_FINALIZE) { - console.log( - `[bot] EVM finalization deferred on ${chain.chainKey}; result proposal synced and left pending for staged dispute-window finalization.`, - ); - return; + await verifyEvmLifecycleReadback({ + chain, + duelKey, + duelId: data.duelId, + minimumDuelStatus: EVM_DUEL_STATUS_PROPOSED, + expectedMarketStatus: EVM_MARKET_STATUS_LOCKED, + participantAHash, + participantBHash, + betOpenTs: canonicalBetOpenTs, + betCloseTs: canonicalBetCloseTs, + duelStartTs: canonicalDuelStartTs, + }); + return { + chainKey: chain.chainKey, + lifecycleStatus: "PROPOSED", + finalized: false, + note: "finalization-deferred", + }; } - if (!chain.finalizer) { - throw new Error( - `missing finalizer signer for ${chain.chainKey}; set TESTNET_FINALIZER_PRIVATE_KEY or EVM_FINALIZER_PRIVATE_KEY`, - ); + const finalization = await finalizeEvmDuelResultIfReady( + chain, + duelKey, + metadata, + ); + if (finalization === "proposed") { + await verifyEvmLifecycleReadback({ + chain, + duelKey, + duelId: data.duelId, + minimumDuelStatus: EVM_DUEL_STATUS_PROPOSED, + expectedMarketStatus: EVM_MARKET_STATUS_LOCKED, + participantAHash, + participantBHash, + betOpenTs: canonicalBetOpenTs, + betCloseTs: canonicalBetCloseTs, + duelStartTs: canonicalDuelStartTs, + }); + return { + chainKey: chain.chainKey, + lifecycleStatus: "PROPOSED", + finalized: false, + note: "dispute-window-active", + }; } - const finalizeHash = await chain.finalizer.walletClient.writeContract({ - chain: undefined, - address: chain.duelOracleAddress, - abi: DUEL_OUTCOME_ORACLE_ABI, - functionName: "finalizeResult", - args: [duelKey, metadata], - account: chain.finalizer.account, - }); - await chain.publicClient.waitForTransactionReceipt({ - hash: finalizeHash, - confirmations: 1, + await verifyEvmLifecycleReadback({ + chain, + duelKey, + duelId: data.duelId, + minimumDuelStatus: EVM_DUEL_STATUS_RESOLVED, + expectedMarketStatus: EVM_MARKET_STATUS_RESOLVED, + participantAHash, + participantBHash, + betOpenTs: canonicalBetOpenTs, + betCloseTs: canonicalBetCloseTs, + duelStartTs: canonicalDuelStartTs, + expectedWinner: winner, }); + return { + chainKey: chain.chainKey, + lifecycleStatus: "RESOLVED", + finalized: true, + note: null, + }; }), ); const failures = results - .map((result, index) => ({ chain: evmKeeperChains[index], result })) + .map((result, index) => ({ chain: selectedChains[index], result })) .filter( ( entry, @@ -3365,48 +4445,925 @@ async function reportEvmResult(data: DuelLifecycleEvent): Promise { .join(", ")}`, ); } + return results.map((result) => { + if (result.status === "fulfilled") { + return result.value; + } + throw result.reason; + }); } -async function reportRoundResult(data: DuelLifecycleEvent): Promise { - const trackedMatch = activeClobMatches.get(data.duelId); - if (!trackedMatch) { - return; - } +function buildSyntheticLockEvent( + snapshot: KeeperMarketParitySnapshot, + fallbackDuelKeyHex: string | null = null, +): DuelLifecycleEvent | null { + const duelKey = snapshot.duelKey ?? fallbackDuelKeyHex; + if (!duelKey) return null; + return { + cycleId: snapshot.bundleId, + duelId: snapshot.duelId ?? `parity:${duelKey}`, + duelKeyHex: duelKey, + betOpenTime: null, + betCloseTime: null, + fightStartTime: null, + duelEndTime: null, + phase: snapshot.phase ?? "COUNTDOWN", + winnerId: null, + seed: null, + replayHash: null, + agent1: null, + agent2: null, + }; +} - if (!data.seed || !data.replayHash) { - console.warn( - `[Keeper] duel:completed for ${data.duelId} is missing seed or replayHash; refusing to post an unverifiable oracle result.`, +function isPendingParityLock(snapshot: KeeperMarketParitySnapshot): boolean { + if (isTerminalMarketParityState(snapshot.state)) return false; + if (snapshot.state === "locked") return false; + if (!snapshot.openedAtMs) return false; + // Check per-chain receipts rather than the top-level lockedAtMs, because + // partial success (one chain locked, others still open) can set the top-level + // timestamp while leaving some chains unlocked. We keep reconciling until + // every required chain's receipt reports LOCKED or a terminal state. + return snapshot.requiredChains.some((chainKey) => { + const receipt = snapshot.receipts.find((entry) => entry.chainKey === chainKey); + if (!receipt) return true; + return ( + receipt.lifecycleStatus !== "LOCKED" && + receipt.lifecycleStatus !== "PROPOSED" && + receipt.lifecycleStatus !== "RESOLVED" && + receipt.lifecycleStatus !== "CANCELLED" ); + }); +} + +async function maybeFinalizePendingSolanaParityLock(): Promise { + const current = marketParitySnapshot; + if (!current || !isPendingParityLock(current)) { + return; + } + if (!requiredParityChains.includes("solana")) { return; } - const winnerId = data.winnerId; - const winnerSide = - winnerId && winnerId === data.agent1?.id - ? "A" - : winnerId && winnerId === data.agent2?.id - ? "B" - : null; - if (!winnerSide) { - console.warn( - `[Keeper] duel:completed for ${data.duelId} supplied an unknown winner id; refusing to post oracle result.`, - ); + const receipt = current.receipts.find((entry) => entry.chainKey === "solana"); + if ( + receipt?.lifecycleStatus === "LOCKED" || + receipt?.lifecycleStatus === "RESOLVED" + ) { return; } - let replayHashHex: string; - try { - replayHashHex = normalizeHex32Raw(data.replayHash); - } catch { - console.warn( - `[Keeper] duel:completed for ${data.duelId} supplied an invalid replayHash; refusing to post oracle result.`, - ); + const trackedMatch = deriveTrackedMatchForParitySnapshot(current); + if (!trackedMatch) { + console.warn("[bot] market_parity_solana_lock_skip", { + reason: "missing-duel-key", + duelId: current.duelId, + duelKey: current.duelKey, + }); return; } - const resolvedSeed = data.seed; + const duelState = await getDuelState(trackedMatch.duelState); + if (!duelState) { + console.warn("[bot] market_parity_solana_lock_skip", { + reason: "duel-state-missing", + duelId: current.duelId, + duelKey: current.duelKey, + }); + return; + } + + if (enumIs(duelState.status, "challenged") || Boolean(duelState.pendingChallenged)) { + finalizeMarketParityState("frozen", current.phase, { + reason: `solana duel ${current.duelId ?? current.duelKey} is challenged`, + }); + return; + } + + if ( + enumIs(duelState.status, "locked") || + enumIs(duelState.status, "proposed") || + enumIs(duelState.status, "resolved") + ) { + markMarketParityChainConfirmed({ + chainKey: "solana", + transition: "lock", + lifecycleStatus: "LOCKED", + note: "locked-on-readback", + }); + if (allRequiredParityChainsConfirmed()) { + finalizeMarketParityState("locked", current.phase ?? "COUNTDOWN"); + } + return; + } + + if (!enumIs(duelState.status, "bettingOpen")) { + console.warn("[bot] market_parity_solana_lock_skip", { + reason: "unexpected-status", + duelId: current.duelId, + duelKey: current.duelKey, + status: duelState.status, + }); + return; + } + + const betCloseTs = asNum(duelState.betCloseTs, 0); + if (betCloseTs > 0 && Date.now() < betCloseTs * 1_000) { + return; + } + + const syntheticEvent = buildSyntheticLockEvent(current, trackedMatch.duelKeyHex); + if (!syntheticEvent) return; + + try { + await lockRound(syntheticEvent); + markMarketParityChainConfirmed({ + chainKey: "solana", + transition: "lock", + lifecycleStatus: "LOCKED", + note: "reconciler-lock", + }); + if (allRequiredParityChainsConfirmed()) { + finalizeMarketParityState("locked", current.phase ?? "COUNTDOWN"); + } + } catch (error) { + console.warn("[bot] market_parity_solana_lock_retry", { + duelId: current.duelId, + duelKey: current.duelKey, + error: error instanceof Error ? error.message : String(error), + }); + } +} + +async function maybeFinalizePendingEvmParityLock(): Promise { + const current = marketParitySnapshot; + if (!current || !isPendingParityLock(current) || !current.duelKey) { + return; + } + + const evmParityChains = requiredEvmParityChains(); + if (evmParityChains.length === 0) { + return; + } + + if (!EVM_KEEPER_ENABLE_LIFECYCLE_WRITES) { + return; + } + + const duelKey = normalizeHex32(current.duelKey); + const syntheticEvent = buildSyntheticLockEvent(current); + if (!syntheticEvent) return; + + let confirmedHere = false; + for (const chain of evmKeeperChains.filter((entry) => + evmParityChains.includes(entry.chainKey), + )) { + try { + const receipt = marketParitySnapshot?.receipts.find( + (entry) => entry.chainKey === chain.chainKey, + ); + if ( + receipt?.lifecycleStatus === "LOCKED" || + receipt?.lifecycleStatus === "RESOLVED" + ) { + continue; + } + + const duel = await readEvmDuelState(chain, duelKey); + const status = asNum(duel.status, EVM_DUEL_STATUS_NULL); + + if (status === EVM_DUEL_STATUS_CHALLENGED) { + finalizeMarketParityState("frozen", current.phase, { + reason: `${chain.chainKey} duel ${current.duelId ?? current.duelKey} is challenged`, + }); + return; + } + + if (status >= EVM_DUEL_STATUS_LOCKED) { + markMarketParityChainConfirmed({ + chainKey: chain.chainKey, + transition: "lock", + lifecycleStatus: lifecycleStatusFromEvmWriteStatus(3), + note: "locked-on-readback", + }); + confirmedHere = true; + continue; + } + + if (status !== EVM_DUEL_STATUS_BETTING_OPEN) { + console.warn("[bot] market_parity_evm_lock_skip", { + chainKey: chain.chainKey, + duelId: current.duelId, + duelKey: current.duelKey, + status, + }); + continue; + } + + const betCloseTs = Number(duel.betCloseTs ?? 0n); + if (betCloseTs > 0 && Date.now() < betCloseTs * 1_000) { + continue; + } + + await upsertEvmDuelLifecycle(syntheticEvent, 3, [chain.chainKey]); + markMarketParityChainConfirmed({ + chainKey: chain.chainKey, + transition: "lock", + lifecycleStatus: lifecycleStatusFromEvmWriteStatus(3), + note: "reconciler-lock", + }); + confirmedHere = true; + } catch (error) { + console.warn("[bot] market_parity_evm_lock_retry", { + chainKey: chain.chainKey, + duelId: current.duelId, + duelKey: current.duelKey, + error: error instanceof Error ? error.message : String(error), + }); + } + } + + if (confirmedHere && allRequiredParityChainsConfirmed()) { + finalizeMarketParityState("locked", current.phase ?? "COUNTDOWN"); + } +} + +// ── Missed-duel-end catch-up ───────────────────────────────────────────────── +// If a keeper restart (or WebSocket drop) loses a live `duel_ended` event, +// the bundle gets stuck in state=locked with phase="COUNTDOWN" — the resolve +// reconcilers below gate on phase==="RESOLUTION" and never fire, and Solana +// resolution requires seed+replayHash that only arrive on that event. The +// Hyperscapes API persists those fields to streaming_duel_history at +// resolution time and exposes them via GET /api/streaming/results/:duelId +// (bearer-auth). This helper polls that endpoint for stuck bundles and +// synthesises a DuelLifecycleEvent the existing onDuelEnd flow can consume. + +type HyperscapesDuelResult = { + duelId: string; + cycleId: string; + duelKeyHex: string; + duelEndTime: number | null; + seed: string; + replayHash: string; + winnerId: string; + winnerName: string; + loserId: string; + loserName: string; + winReason: string; + damageWinner: number; + damageLoser: number; + finishedAt: number; +}; + +const RESULT_CATCHUP_BEARER_TOKEN = + resolveResultCatchupBearerToken(process.env).token ?? ""; + +const RESULT_CATCHUP_TIMEOUT_MS = Math.max( + 1_000, + Number(process.env.HYPERSCAPES_RESULT_LOOKUP_TIMEOUT_MS || 5_000), +); + +async function fetchHyperscapesDuelResult( + duelId: string, +): Promise { + if (!RESULT_CATCHUP_BEARER_TOKEN) return null; + + const base = args["game-url"]; + if (!base) return null; + + const controller = new AbortController(); + const timeoutId = setTimeout( + () => controller.abort(), + RESULT_CATCHUP_TIMEOUT_MS, + ); + try { + const res = await fetch( + `${base.replace(/\/$/, "")}/api/streaming/results/${encodeURIComponent(duelId)}`, + { + cache: "no-store", + headers: { + authorization: `Bearer ${RESULT_CATCHUP_BEARER_TOKEN}`, + connection: "close", + }, + signal: controller.signal, + }, + ); + if (res.status === 404 || res.status === 409) { + // 404: result not yet persisted, retry later. + // 409: legacy row without oracle proof, can't be used for synthetic replay. + try { + await res.body?.cancel(); + } catch {} + return null; + } + if (!res.ok) { + try { + await res.body?.cancel(); + } catch {} + return null; + } + return (await res.json()) as HyperscapesDuelResult; + } catch { + return null; + } finally { + clearTimeout(timeoutId); + } +} + +async function maybeCatchUpMissedDuelEnd(): Promise { + const current = marketParitySnapshot; + if (!current) return; + if (isTerminalMarketParityState(current.state)) return; + // Bundle must be past the open window (locked or a post-lock frozen state) + // and not already confirmed resolved. + if (!current.lockedAtMs) return; + if (current.resolvedAtMs) return; + if (current.phase === "RESOLUTION") return; // existing reconcilers handle this + if (!current.duelId) return; + + const result = await fetchHyperscapesDuelResult(current.duelId); + if (!result) return; + + // Reject mismatched catch-up results to avoid resolving with wrong data. + const expectedDuelKey = normalizedBundleDuelKey(current.duelKey); + const resultDuelKey = normalizedBundleDuelKey(result.duelKeyHex); + if (expectedDuelKey && resultDuelKey && expectedDuelKey !== resultDuelKey) { + console.warn("[bot] result_catchup_duelkey_mismatch", { + duelId: current.duelId, + expectedDuelKey, + resultDuelKey, + }); + return; + } + + const winnerIsAgent1 = result.winnerId === "agent1" || result.winnerId.endsWith(":agent1"); + const syntheticEvent: DuelLifecycleEvent = { + cycleId: result.cycleId, + duelId: result.duelId, + duelKeyHex: result.duelKeyHex, + betOpenTime: null, + betCloseTime: null, + fightStartTime: null, + duelEndTime: result.duelEndTime, + phase: "RESOLUTION", + winnerId: result.winnerId, + seed: result.seed, + replayHash: result.replayHash, + agent1: { + id: winnerIsAgent1 ? result.winnerId : result.loserId, + name: winnerIsAgent1 ? result.winnerName : result.loserName, + }, + agent2: { + id: winnerIsAgent1 ? result.loserId : result.winnerId, + name: winnerIsAgent1 ? result.loserName : result.winnerName, + }, + }; + + console.log("[bot] result_catchup_firing_synthetic_duel_end", { + duelId: current.duelId, + winnerId: result.winnerId, + finishedAt: result.finishedAt, + }); + + // Prime the snapshot for resolution and run the resolve flow directly. + // We mirror onDuelEnd's resolve portion, skipping the ratings/monologues + // bookkeeping — those are already persisted by Hyperscapes and don't + // belong in a catch-up. + beginMarketParityBundle(syntheticEvent, "RESOLUTION"); + resetMarketParityConfirmations("awaiting_confirmations", "RESOLUTION", false); + writeBotHealthSnapshot(); + + try { + if (requiredParityChains.includes("solana")) { + if (!solanaKeeperWriteEnabled) { + throw new Error( + "result_catchup: solana parity required but keeper writes disabled", + ); + } + if (!(await ensureKeeperChainReady())) { + throw new Error( + "result_catchup: solana parity required but keeper chain not ready", + ); + } + if (!(await ensureBotSignerFunding())) { + throw new Error( + "result_catchup: solana parity required but bot signer funding is below threshold", + ); + } + const solanaOutcome = await reportRoundResult(syntheticEvent); + markMarketParityChainConfirmed({ + chainKey: "solana", + transition: + solanaOutcome.lifecycleStatus === "RESOLVED" ? "resolve" : "propose", + lifecycleStatus: solanaOutcome.lifecycleStatus, + countsForQuorum: solanaOutcome.finalized, + note: `catchup:${solanaOutcome.note ?? "ok"}`, + }); + writeBotHealthSnapshot(); + } + + const evmParityChains = requiredEvmParityChains(); + if (evmParityChains.length > 0) { + if (!EVM_KEEPER_ENABLE_LIFECYCLE_WRITES) { + throw new Error( + "result_catchup: EVM parity required but lifecycle writes disabled", + ); + } + const evmOutcomes = await reportEvmResult( + syntheticEvent, + evmParityChains, + ); + for (const outcome of evmOutcomes) { + markMarketParityChainConfirmed({ + chainKey: outcome.chainKey, + transition: + outcome.lifecycleStatus === "RESOLVED" ? "resolve" : "propose", + lifecycleStatus: outcome.lifecycleStatus, + countsForQuorum: outcome.lifecycleStatus === "RESOLVED", + note: `catchup:${outcome.note ?? "ok"}`, + }); + } + writeBotHealthSnapshot(); + } + + if (allRequiredParityChainsConfirmed()) { + finalizeMarketParityState("resolved", "RESOLUTION"); + } + // Else: the normal resolve reconcilers will pick up any remaining + // dispute-window chains on subsequent maintenance ticks. + } catch (err) { + finalizeMarketParityState("frozen", "RESOLUTION", { + reason: `result_catchup_failed: ${err instanceof Error ? err.message : String(err)}`, + }); + console.error("[bot] result_catchup_failed", err); + } + writeBotHealthSnapshot(); +} + +// Catch up ALL locked-past-betClose duels whose authoritative result is now +// available in Hyperscapes. The parity-snapshot healing above only addresses +// the CURRENT bundle; this handles the backlog of older duels that were +// locked but stranded (a keeper restart dropped duel_ended for them and the +// snapshot has since been replaced by newer cycles). These duels show up in +// `unresolvedOracleWarningMatches` — the same set the health snapshot's +// "awaiting-result" recovery code references. For each such duel we pull the +// persisted oracle proof and invoke the on-chain proposers directly, without +// touching the live `marketParitySnapshot` (which is for a different duel). +async function maybeCatchUpStrandedLockedDuels(): Promise { + if (unresolvedOracleWarningMatches.size === 0) return; + + // Snapshot the set so mutations during iteration don't skip entries. + const candidates = Array.from(unresolvedOracleWarningMatches); + + for (const duelId of candidates) { + // Skip the current parity snapshot's duel — it's handled by + // maybeCatchUpMissedDuelEnd, which takes the parity-snapshot path. + if (marketParitySnapshot?.duelId === duelId) continue; + + const trackedMatch = activeClobMatches.get(duelId); + if (!trackedMatch) { + // No local tracked match — catch-up would have no on-chain reference. + // Drop the warning entry so we stop flagging it. + unresolvedOracleWarningMatches.delete(duelId); + continue; + } + + const result = await fetchHyperscapesDuelResult(duelId); + if (!result) { + // Row not in DB yet (or endpoint unreachable). Retry next tick. + continue; + } + + const expectedDuelKey = normalizedBundleDuelKey(trackedMatch.duelKeyHex); + const resultDuelKey = normalizedBundleDuelKey(result.duelKeyHex); + if (expectedDuelKey && resultDuelKey && expectedDuelKey !== resultDuelKey) { + console.warn("[bot] result_catchup_stranded_duelkey_mismatch", { + duelId, + expectedDuelKey, + resultDuelKey, + }); + continue; + } + + const winnerIsAgent1 = + result.winnerId === "agent1" || result.winnerId.endsWith(":agent1"); + const syntheticEvent: DuelLifecycleEvent = { + cycleId: result.cycleId, + duelId: result.duelId, + duelKeyHex: result.duelKeyHex, + betOpenTime: null, + betCloseTime: null, + fightStartTime: null, + duelEndTime: result.duelEndTime, + phase: "RESOLUTION", + winnerId: result.winnerId, + seed: result.seed, + replayHash: result.replayHash, + agent1: { + id: winnerIsAgent1 ? result.winnerId : result.loserId, + name: winnerIsAgent1 ? result.winnerName : result.loserName, + }, + agent2: { + id: winnerIsAgent1 ? result.loserId : result.winnerId, + name: winnerIsAgent1 ? result.loserName : result.winnerName, + }, + }; + + console.log("[bot] result_catchup_stranded_firing", { + duelId, + winnerId: result.winnerId, + finishedAt: result.finishedAt, + backlogSize: unresolvedOracleWarningMatches.size, + }); + + // Advance on-chain state directly. This does NOT touch the current + // marketParitySnapshot (which is for a different duel) — we're just + // closing out an orphaned market on-chain so the keeper can stop + // flagging it as awaiting authoritative result. + try { + if (requiredParityChains.includes("solana") && solanaKeeperWriteEnabled) { + if (!(await ensureKeeperChainReady())) continue; + if (!(await ensureBotSignerFunding())) continue; + await reportRoundResult(syntheticEvent); + } + + const evmParityChains = requiredEvmParityChains(); + if (evmParityChains.length > 0 && EVM_KEEPER_ENABLE_LIFECYCLE_WRITES) { + await reportEvmResult(syntheticEvent, evmParityChains); + } + + unresolvedOracleWarningMatches.delete(duelId); + console.log("[bot] result_catchup_stranded_ok", { + duelId, + remainingBacklog: unresolvedOracleWarningMatches.size, + }); + } catch (err) { + console.warn("[bot] result_catchup_stranded_failed", { + duelId, + error: err instanceof Error ? err.message : String(err), + }); + // Leave the duelId in the warning set — retry next tick. + } + } +} + +async function maybeFinalizePendingEvmParityResolution(): Promise { + const current = marketParitySnapshot; + if (!current || isTerminalMarketParityState(current.state) || !current.duelKey) { + return; + } + // Phase-independent on-chain readback: even if `onDuelEnd` was missed (so + // phase never advanced to "RESOLUTION"), we still want to reconcile when + // on-chain state shows the duel has moved past LOCKED. Gate on: + // - phase === "RESOLUTION" (normal flow), OR + // - bundle already reached LOCKED but hasn't been resolved and at least + // one EVM chain receipt is still short of RESOLVED. + const hasPendingEvmReceipt = + current.lockedAtMs != null && + current.resolvedAtMs == null && + current.receipts.some( + (receipt) => + receipt.lifecycleStatus !== "RESOLVED" && + receipt.lifecycleStatus !== "CANCELLED", + ); + if (current.phase !== "RESOLUTION" && !hasPendingEvmReceipt) { + return; + } + + const duelKey = normalizeHex32(current.duelKey); + const metadata = buildDuelMetadataForDuelKey(current.duelKey, current.duelId); + const evmParityChains = requiredEvmParityChains(); + if (evmParityChains.length === 0) { + return; + } + + for (const chain of evmKeeperChains.filter((entry) => + evmParityChains.includes(entry.chainKey), + )) { + try { + const receipt = marketParitySnapshot?.receipts.find( + (entry) => entry.chainKey === chain.chainKey, + ); + if (receipt?.lifecycleStatus === "RESOLVED") { + continue; + } + + const duel = await readEvmDuelState(chain, duelKey); + const status = asNum(duel.status, EVM_DUEL_STATUS_NULL); + if (status === EVM_DUEL_STATUS_RESOLVED) { + markMarketParityChainConfirmed({ + chainKey: chain.chainKey, + transition: "resolve", + lifecycleStatus: "RESOLVED", + note: "resolved-on-readback", + }); + continue; + } + if (status === EVM_DUEL_STATUS_CHALLENGED) { + finalizeMarketParityState("frozen", "RESOLUTION", { + reason: `${chain.chainKey} duel ${current.duelId ?? current.duelKey} is challenged`, + }); + return; + } + if (status !== EVM_DUEL_STATUS_PROPOSED) { + console.warn("[bot] market_parity_evm_finalize_skip", { + chainKey: chain.chainKey, + duelId: current.duelId, + duelKey: current.duelKey, + status, + }); + continue; + } + + const finalization = await finalizeEvmDuelResultIfReady( + chain, + duelKey, + metadata, + ); + markMarketParityChainConfirmed({ + chainKey: chain.chainKey, + transition: finalization === "resolved" ? "resolve" : "propose", + lifecycleStatus: finalization === "resolved" ? "RESOLVED" : "PROPOSED", + countsForQuorum: finalization === "resolved", + note: + finalization === "resolved" + ? "finalized-after-dispute-window" + : "dispute-window-active", + }); + } catch (error) { + console.warn("[bot] market_parity_evm_finalize_retry", { + chainKey: chain.chainKey, + duelId: current.duelId, + duelKey: current.duelKey, + error: error instanceof Error ? error.message : String(error), + }); + } + } + + if (allRequiredParityChainsConfirmed()) { + finalizeMarketParityState("resolved", "RESOLUTION"); + } +} + +async function maybeFinalizePendingSolanaParityResolution(): Promise { + const current = marketParitySnapshot; + if ( + !current || + isTerminalMarketParityState(current.state) || + !requiredParityChains.includes("solana") + ) { + return; + } + + const receipt = current.receipts.find((entry) => entry.chainKey === "solana"); + if (receipt?.lifecycleStatus === "RESOLVED") { + return; + } + + // Phase-independent readback: run when phase === "RESOLUTION" (normal flow) + // OR when the bundle is already locked on-chain but the Solana receipt + // hasn't reached RESOLVED yet. The latter covers the case where + // `onDuelEnd` was missed and `reportRoundResult` was never invoked; an + // on-chain readback can still promote the receipt to RESOLVED if the + // proposal has already been finalized out-of-band, or catch a CHALLENGED + // transition. + // We already returned above when the Solana receipt is RESOLVED, so from + // here the receipt is null/undefined/pending or something earlier in the + // lifecycle. Accept phase==="RESOLUTION" (normal flow) OR a bundle that + // reached LOCKED on-chain but never got its Solana receipt advanced. + const hasPendingSolanaReceipt = + current.lockedAtMs != null && + current.resolvedAtMs == null && + receipt?.lifecycleStatus !== "CANCELLED"; + if (current.phase !== "RESOLUTION" && !hasPendingSolanaReceipt) { + return; + } + + const trackedMatch = deriveTrackedMatchForParitySnapshot(current); + if (!trackedMatch) { + console.warn("[bot] market_parity_solana_finalize_skip", { + reason: "missing-duel-key", + duelId: current.duelId, + duelKey: current.duelKey, + }); + return; + } + + let duelState = await getDuelState(trackedMatch.duelState); + if (!duelState) { + console.warn("[bot] market_parity_solana_finalize_skip", { + reason: "duel-state-missing", + duelId: current.duelId, + duelKey: current.duelKey, + duelState: trackedMatch.duelState.toBase58(), + }); + return; + } + + if (enumIs(duelState.status, "challenged") || Boolean(duelState.pendingChallenged)) { + finalizeMarketParityState("frozen", "RESOLUTION", { + reason: `solana duel ${current.duelId ?? current.duelKey} is challenged`, + }); + return; + } + + if (enumIs(duelState.status, "resolved")) { + trackedMatch.winner = resolvedWinnerFromDuelState(duelState); + await syncTrackedMarketFromOracle(trackedMatch); + await captureSettledClobHealth(trackedMatch, "RESOLVED"); + if (trackedMatch.duelId) { + activeClobMatches.delete(trackedMatch.duelId); + unresolvedOracleWarningMatches.delete(trackedMatch.duelId); + } + markMarketParityChainConfirmed({ + chainKey: "solana", + transition: "resolve", + lifecycleStatus: "RESOLVED", + note: "resolved-on-readback", + }); + if (allRequiredParityChainsConfirmed()) { + finalizeMarketParityState("resolved", "RESOLUTION"); + } + return; + } + + if (!enumIs(duelState.status, "proposed")) { + console.warn("[bot] market_parity_solana_finalize_skip", { + reason: "not-proposed", + duelId: current.duelId, + duelKey: current.duelKey, + status: duelState.status, + }); + return; + } + + const metadata = buildDuelMetadataForDuelKey( + trackedMatch.duelKeyHex, + trackedMatch.duelId, + ); + let finalized = false; + try { + finalized = await maybeFinalizeTrackedProposal( + trackedMatch, + duelState, + metadata, + ); + } catch (error) { + if (!isSolanaDisputeWindowActiveError(error)) { + throw error; + } + } + + if (!finalized) { + if (trackedMatch.duelId) { + unresolvedOracleWarningMatches.delete(trackedMatch.duelId); + } + markMarketParityChainConfirmed({ + chainKey: "solana", + transition: "propose", + lifecycleStatus: "PROPOSED", + countsForQuorum: false, + note: "dispute-window-active", + }); + return; + } + + duelState = await getDuelState(trackedMatch.duelState); + if (!duelState || !enumIs(duelState.status, "resolved")) { + console.warn("[bot] market_parity_solana_finalize_skip", { + reason: "finalize-readback-not-resolved", + duelId: current.duelId, + duelKey: current.duelKey, + status: duelState?.status ?? null, + }); + return; + } + + trackedMatch.winner = resolvedWinnerFromDuelState(duelState); + await syncTrackedMarketFromOracle(trackedMatch); + await captureSettledClobHealth(trackedMatch, "RESOLVED"); + if (trackedMatch.duelId) { + activeClobMatches.delete(trackedMatch.duelId); + unresolvedOracleWarningMatches.delete(trackedMatch.duelId); + } + markMarketParityChainConfirmed({ + chainKey: "solana", + transition: "resolve", + lifecycleStatus: "RESOLVED", + note: "finalized-after-dispute-window", + }); + if (allRequiredParityChainsConfirmed()) { + finalizeMarketParityState("resolved", "RESOLUTION"); + } +} + +async function maybeFinalizeTrackedProposal( + trackedMatch: ActiveClobMatch, + duelState: Awaited>, + metadataUri: string, +): Promise { + if ( + !duelState || + !enumIs(duelState.status, "proposed") || + Boolean(duelState.pendingChallenged) + ) { + return false; + } + + const oracleConfig = await getOracleConfigState(); + if (!oracleConfig) { + throw new Error(`Missing oracle config ${oracleConfigPda.toBase58()}`); + } + + const finalizableAt = + asNum(duelState.pendingProposedAt) + asNum(oracleConfig.disputeWindowSecs); + if (Math.floor(Date.now() / 1000) < finalizableAt) { + return false; + } + + try { + await runWithRecovery( + () => + fightProgram.methods + .finalizeResult( + Array.from(duelKeyHexToBytes(trackedMatch.duelKeyHex)), + metadataUri, + ) + .accountsPartial({ + finalizer: oracleFinalizerKeypair.publicKey, + oracleConfig: oracleConfigPda, + duelState: trackedMatch.duelState, + }) + .signers([oracleFinalizerKeypair]) + .rpc(), + connection, + ); + } catch (error) { + if (isSolanaDisputeWindowActiveError(error)) { + return false; + } + throw error; + } + markRpcSuccess(trackedMatch); + return true; +} - const duelState = await getDuelState(trackedMatch.duelState); +async function reportRoundResult( + data: DuelLifecycleEvent, +): Promise { + // Rehydrate the tracked match from the persisted parity snapshot if the + // in-memory map lost it across a restart. Mirrors the lockRound fix so the + // resolve path survives the same class of restart window. + let trackedMatch = activeClobMatches.get(data.duelId); + if (!trackedMatch && marketParitySnapshot) { + trackedMatch = + deriveTrackedMatchForParitySnapshot(marketParitySnapshot) ?? undefined; + } + if (!trackedMatch) { + throw new Error( + `reportRoundResult: cannot resolve Solana tracked match for duel ${data.duelId}`, + ); + } + + // Data-quality preconditions. These used to return null and collapse into + // the caller's generic "failed to report Solana result" freezeReason. Throw + // specific errors so freezeReason captures the real root cause. + if (!data.seed) { + throw new Error( + `reportRoundResult: duel ${data.duelId} missing seed; refusing to post an unverifiable oracle result`, + ); + } + if (!data.replayHash) { + throw new Error( + `reportRoundResult: duel ${data.duelId} missing replayHash; refusing to post an unverifiable oracle result`, + ); + } + + const winnerId = data.winnerId; + const winnerSide = + winnerId && winnerId === data.agent1?.id + ? "A" + : winnerId && winnerId === data.agent2?.id + ? "B" + : null; + if (!winnerSide) { + throw new Error( + `reportRoundResult: duel ${data.duelId} supplied unknown winner id ${winnerId ?? ""}; agent1=${data.agent1?.id ?? ""} agent2=${data.agent2?.id ?? ""}`, + ); + } + + let replayHashHex: string; + try { + replayHashHex = normalizeHex32Raw(data.replayHash); + } catch (err) { + throw new Error( + `reportRoundResult: duel ${data.duelId} supplied invalid replayHash ${data.replayHash}: ${err instanceof Error ? err.message : String(err)}`, + ); + } + + const resolvedSeed = data.seed; + + let duelState = await getDuelState(trackedMatch.duelState); if (duelState && enumIs(duelState.status, "resolved")) { trackedMatch.winner = winnerSide; await syncTrackedMarketFromOracle(trackedMatch); @@ -3415,7 +5372,11 @@ async function reportRoundResult(data: DuelLifecycleEvent): Promise { activeClobMatches.delete(data.duelId); unresolvedOracleWarningMatches.delete(data.duelId); writeBotHealthSnapshot(); - return; + return { + lifecycleStatus: "RESOLVED", + finalized: true, + note: "already-resolved", + }; } const duelKey = duelKeyHexToBytes(data.duelKeyHex); @@ -3424,47 +5385,86 @@ async function reportRoundResult(data: DuelLifecycleEvent): Promise { duelState ? asNum(duelState.betCloseTs) : 0, ); - console.log( - `[Keeper] Waiting 15s before posting result for duel ${data.duelId} to sync with stream...`, - ); - await sleep(15_000); + if (resultPostDelayMs > 0) { + console.log( + `[Keeper] Waiting ${Math.ceil( + resultPostDelayMs / 1000, + )}s before posting result for duel ${data.duelId}...`, + ); + await sleep(resultPostDelayMs); + } - await runWithRecovery( - () => - fightProgram.methods - .proposeResult( - Array.from(duelKey), - winnerSide === "A" ? ({ a: {} } as DuelWinner) : ({ b: {} } as DuelWinner), - new BN(resolvedSeed), - Array.from(Buffer.from(replayHashHex, "hex")), - buildResultHash( - data.duelKeyHex, - winnerSide, - resolvedSeed, - replayHashHex, - ), - new BN(duelEndTs), - buildDuelMetadata(data), - ) - .accountsPartial({ - reporter: botKeypair.publicKey, - oracleConfig: oracleConfigPda, - duelState: trackedMatch.duelState, - }) - .rpc() - .then(() => - fightProgram.methods - .finalizeResult(Array.from(duelKey), buildDuelMetadata(data)) - .accountsPartial({ - finalizer: botKeypair.publicKey, - oracleConfig: oracleConfigPda, - duelState: trackedMatch.duelState, - }) - .rpc(), - ), - connection, - ); - const resolutionRecordedAt = markRpcSuccess(trackedMatch); + if (duelState && enumIs(duelState.status, "challenged")) { + await syncTrackedMarketFromOracle(trackedMatch); + unresolvedOracleWarningMatches.delete(data.duelId); + console.warn( + `[Keeper] Duel ${data.duelId} result proposal is challenged; leaving the market fail-closed until manual resolution.`, + ); + return { + lifecycleStatus: "CHALLENGED", + finalized: false, + note: "proposal-challenged", + }; + } + + if (duelState && enumIs(duelState.status, "locked")) { + await runWithRecovery( + () => + fightProgram.methods + .proposeResult( + Array.from(duelKey), + winnerSide === "A" ? ({ a: {} } as DuelWinner) : ({ b: {} } as DuelWinner), + new BN(resolvedSeed), + Array.from(Buffer.from(replayHashHex, "hex")), + buildResultHash( + data.duelKeyHex, + winnerSide, + resolvedSeed, + replayHashHex, + ), + new BN(duelEndTs), + buildDuelMetadata(data), + ) + .accountsPartial({ + reporter: oracleReporterKeypair.publicKey, + oracleConfig: oracleConfigPda, + duelState: trackedMatch.duelState, + }) + .signers([oracleReporterKeypair]) + .rpc(), + connection, + ); + trackedMatch.lastOracleAtMs = markRpcSuccess(trackedMatch); + duelState = await getDuelState(trackedMatch.duelState); + } + + if ( + duelState && + enumIs(duelState.status, "proposed") && + !(await maybeFinalizeTrackedProposal( + trackedMatch, + duelState, + buildDuelMetadata(data), + )) + ) { + unresolvedOracleWarningMatches.delete(data.duelId); + return { + lifecycleStatus: "PROPOSED", + finalized: false, + note: "dispute-window-active", + }; + } + + duelState = await getDuelState(trackedMatch.duelState); + if (!duelState || !enumIs(duelState.status, "resolved")) { + return { + lifecycleStatus: "UNKNOWN", + finalized: false, + note: "resolution-not-finalized", + }; + } + + const resolutionRecordedAt = Date.now(); trackedMatch.lastOracleAtMs = resolutionRecordedAt; trackedMatch.lastResolvedAtMs = resolutionRecordedAt; trackedMatch.winner = winnerSide; @@ -3488,6 +5488,11 @@ async function reportRoundResult(data: DuelLifecycleEvent): Promise { ), ); writeBotHealthSnapshot(); + return { + lifecycleStatus: "RESOLVED", + finalized: true, + note: null, + }; } // Event-driven Logic @@ -3501,34 +5506,110 @@ gameClient.onDuelStart(async (data) => { } console.log("Duel Started:", data); - if (solanaKeeperWriteEnabled) { - if (!(await ensureKeeperChainReady())) { - console.warn( - "[bot] Skipping duel-start market creation because keeper chain is not ready.", - ); - } else if (!(await ensureBotSignerFunding())) { - console.warn( - "[bot] Skipping duel-start market creation because bot signer funding is below threshold.", - ); - } else { - try { - await ensureMarketConfigReady(); - const trackedMatch = await createOrSyncRound(data); - activeClobMatches.set(data.duelId, trackedMatch); - await maybeSeedMarket(trackedMatch); - console.log(`Created canonical CLOB market for duel ${data.duelId}`); - } catch (err) { - console.error("Failed to create market for duel:", err); + const parityPhase = data.phase || "ANNOUNCEMENT"; + if (blockMismatchedMarketParityEvent(data, "duel_started")) { + return; + } + beginMarketParityBundle(data, parityPhase); + writeBotHealthSnapshot(); + + try { + if (requiredParityChains.includes("solana")) { + if (!solanaKeeperWriteEnabled) { + throw new Error("solana parity chain is required but Solana keeper writes are disabled"); + } + if (!(await ensureKeeperChainReady())) { + throw new Error("solana parity chain is required but keeper chain is not ready"); + } + if (!(await ensureBotSignerFunding())) { + throw new Error("solana parity chain is required but bot signer funding is below threshold"); + } + await ensureMarketConfigReady(); + await prepareRound(data); + markMarketParityChainConfirmed({ + chainKey: "solana", + transition: "prepare", + lifecycleStatus: "PENDING", + }); + writeBotHealthSnapshot(); + } + + const evmParityChains = requiredEvmParityChains(); + if (evmParityChains.length > 0) { + if (!EVM_KEEPER_ENABLE_LIFECYCLE_WRITES) { + throw new Error("EVM parity chains are required but EVM lifecycle writes are disabled"); + } + await upsertEvmDuelLifecycle(data, 1, evmParityChains); + for (const chainKey of evmParityChains) { + markMarketParityChainConfirmed({ + chainKey, + transition: "prepare", + lifecycleStatus: lifecycleStatusFromEvmWriteStatus(1), + }); } + writeBotHealthSnapshot(); } - } - if (EVM_KEEPER_ENABLE_LIFECYCLE_WRITES) { - try { - await upsertEvmDuelLifecycle(data, 2); - } catch (err) { - console.error("Failed to upsert EVM market lifecycle for duel:", err); + if (!allRequiredParityChainsConfirmed()) { + throw new Error("failed to prepare required parity chains"); } + + resetMarketParityConfirmations("awaiting_confirmations", parityPhase, false); + writeBotHealthSnapshot(); + + if (requiredParityChains.includes("solana")) { + const trackedMatch = await createOrSyncRound(data); + activeClobMatches.set(data.duelId, trackedMatch); + await maybeSeedMarket(trackedMatch); + markMarketParityChainConfirmed({ + chainKey: "solana", + transition: "open", + lifecycleStatus: "OPEN", + }); + console.log(`Created canonical CLOB market for duel ${data.duelId}`); + writeBotHealthSnapshot(); + } + + const evmParityChainsForOpen = requiredEvmParityChains(); + if (evmParityChainsForOpen.length > 0) { + await upsertEvmDuelLifecycle(data, 2, evmParityChainsForOpen); + for (const chainKey of evmParityChainsForOpen) { + markMarketParityChainConfirmed({ + chainKey, + transition: "open", + lifecycleStatus: lifecycleStatusFromEvmWriteStatus(2), + }); + } + writeBotHealthSnapshot(); + } + + if (!allRequiredParityChainsConfirmed()) { + throw new Error("failed to open required parity chains"); + } + + finalizeMarketParityState("open", parityPhase, { safeToBet: true }); + } catch (err) { + let reason = err instanceof Error ? err.message : String(err); + const trackedMatch = activeClobMatches.get(data.duelId) ?? null; + if (trackedMatch) { + try { + await cancelRound(trackedMatch); + markMarketParityChainConfirmed({ + chainKey: "solana", + transition: "cancel", + lifecycleStatus: "CANCELLED", + note: "aborted-before-public-open", + }); + } catch (cancelErr) { + const cancelReason = + cancelErr instanceof Error ? cancelErr.message : String(cancelErr); + reason = `${reason}; solana_unwind_failed: ${cancelReason}`; + } + } + finalizeMarketParityState("aborted", parityPhase, { + reason, + }); + console.error("Failed to create atomic parity bundle for duel:", err); } writeBotHealthSnapshot(); }); @@ -3541,24 +5622,76 @@ gameClient.onBettingLocked(async (data) => { } await waitUntilTimestamp(data.betCloseTime ?? null, "bet close"); + const activeBundle = requireExistingMarketParityBundle(data, "betting_locked"); + if (!activeBundle) { + return; + } + if (activeBundle.state !== "open" || !activeBundle.openedAtMs) { + const reason = `betting_locked_for_${data.duelId}_before_public_open`; + finalizeMarketParityState( + activeBundle.openedAtMs ? "frozen" : "aborted", + "COUNTDOWN", + { reason }, + ); + console.warn("[bot] market_parity_dropped_event", { + eventName: "betting_locked", + reason, + activeBundleId: activeBundle.bundleId, + activeState: activeBundle.state, + activeOpenedAtMs: activeBundle.openedAtMs, + }); + writeBotHealthSnapshot(); + return; + } - if (solanaKeeperWriteEnabled) { - if ((await ensureKeeperChainReady()) && (await ensureBotSignerFunding())) { - try { - await lockRound(data); - console.log(`Locked duel market for ${data.duelId}`); - } catch (error) { - console.error("Failed to lock market for duel:", error); + try { + beginMarketParityBundle(data, "COUNTDOWN"); + resetMarketParityConfirmations("awaiting_confirmations", "COUNTDOWN", false); + writeBotHealthSnapshot(); + + if (requiredParityChains.includes("solana")) { + if (!solanaKeeperWriteEnabled) { + throw new Error("solana parity chain is required for lock but Solana keeper writes are disabled"); + } + if (!(await ensureKeeperChainReady()) || !(await ensureBotSignerFunding())) { + throw new Error("solana parity chain is required for lock but Solana keeper is not ready"); } + await lockRound(data); + markMarketParityChainConfirmed({ + chainKey: "solana", + transition: "lock", + lifecycleStatus: "LOCKED", + }); + console.log(`Locked duel market for ${data.duelId}`); + writeBotHealthSnapshot(); } - } - if (EVM_KEEPER_ENABLE_LIFECYCLE_WRITES) { - try { - await upsertEvmDuelLifecycle(data, 3); - } catch (error) { - console.error("Failed to lock EVM market for duel:", error); + const evmParityChains = requiredEvmParityChains(); + if (evmParityChains.length > 0) { + if (!EVM_KEEPER_ENABLE_LIFECYCLE_WRITES) { + throw new Error("EVM parity chains are required for lock but EVM lifecycle writes are disabled"); + } + await upsertEvmDuelLifecycle(data, 3, evmParityChains); + for (const chainKey of evmParityChains) { + markMarketParityChainConfirmed({ + chainKey, + transition: "lock", + lifecycleStatus: lifecycleStatusFromEvmWriteStatus(3), + }); + } + writeBotHealthSnapshot(); + } + + if (!allRequiredParityChainsConfirmed()) { + throw new Error("failed to lock required parity chains"); } + + finalizeMarketParityState("locked", "COUNTDOWN"); + } catch (error) { + finalizeMarketParityState("frozen", "COUNTDOWN", { + reason: error instanceof Error ? error.message : String(error), + }); + console.error("Failed to lock atomic parity bundle for duel:", error); } writeBotHealthSnapshot(); }); @@ -3571,7 +5704,30 @@ gameClient.onDuelEnd(async (data) => { } console.log("Duel Ended:", data); + const activeBundle = requireExistingMarketParityBundle(data, "duel_ended"); + if (!activeBundle) { + return; + } + if (activeBundle.state !== "locked" || !activeBundle.lockedAtMs) { + const reason = `duel_ended_for_${data.duelId}_before_parity_lock`; + finalizeMarketParityState( + activeBundle.openedAtMs ? "frozen" : "aborted", + "RESOLUTION", + { reason }, + ); + console.warn("[bot] market_parity_dropped_event", { + eventName: "duel_ended", + reason, + activeBundleId: activeBundle.bundleId, + activeState: activeBundle.state, + activeOpenedAtMs: activeBundle.openedAtMs, + activeLockedAtMs: activeBundle.lockedAtMs, + }); + writeBotHealthSnapshot(); + return; + } try { + beginMarketParityBundle(data, "RESOLUTION"); const winnerId = data.winnerId; const isAgent1 = winnerId === data.agent1?.id; @@ -3621,25 +5777,67 @@ gameClient.onDuelEnd(async (data) => { ); } - if (solanaKeeperWriteEnabled) { + resetMarketParityConfirmations("awaiting_confirmations", "RESOLUTION", false); + writeBotHealthSnapshot(); + + if (requiredParityChains.includes("solana")) { + if (!solanaKeeperWriteEnabled) { + throw new Error("solana parity chain is required for resolution but Solana keeper writes are disabled"); + } if (!(await ensureKeeperChainReady())) { - console.warn( - "[bot] Skipping duel-end resolution because keeper chain is not ready.", - ); - } else if (!(await ensureBotSignerFunding())) { - console.warn( - "[bot] Skipping duel-end resolution because bot signer funding is below threshold.", - ); - } else { - await reportRoundResult(data); + throw new Error("solana parity chain is required for resolution but keeper chain is not ready"); } + if (!(await ensureBotSignerFunding())) { + throw new Error("solana parity chain is required for resolution but bot signer funding is below threshold"); + } + const solanaOutcome = await reportRoundResult(data); + markMarketParityChainConfirmed({ + chainKey: "solana", + transition: + solanaOutcome.lifecycleStatus === "RESOLVED" ? "resolve" : "propose", + lifecycleStatus: solanaOutcome.lifecycleStatus, + countsForQuorum: solanaOutcome.finalized, + note: solanaOutcome.note, + }); + writeBotHealthSnapshot(); } - if (EVM_KEEPER_ENABLE_LIFECYCLE_WRITES) { - await reportEvmResult(data); + const evmParityChains = requiredEvmParityChains(); + if (evmParityChains.length > 0) { + if (!EVM_KEEPER_ENABLE_LIFECYCLE_WRITES) { + throw new Error("EVM parity chains are required for resolution but EVM lifecycle writes are disabled"); + } + const evmOutcomes = await reportEvmResult(data, evmParityChains); + for (const outcome of evmOutcomes) { + markMarketParityChainConfirmed({ + chainKey: outcome.chainKey, + transition: + outcome.lifecycleStatus === "RESOLVED" ? "resolve" : "propose", + lifecycleStatus: outcome.lifecycleStatus, + countsForQuorum: outcome.lifecycleStatus === "RESOLVED", + note: outcome.note, + }); + } console.log(`Resolved market for duel ${data.duelId}`); + writeBotHealthSnapshot(); + } + + if (!allRequiredParityChainsConfirmed()) { + if (hasPendingParityResolution()) { + console.warn( + `[bot] Duel ${data.duelId} resolution is waiting for final confirmation`, + ); + writeBotHealthSnapshot(); + return; + } + throw new Error("failed to resolve required parity chains"); } + + finalizeMarketParityState("resolved", "RESOLUTION"); } catch (err) { + finalizeMarketParityState("frozen", "RESOLUTION", { + reason: err instanceof Error ? err.message : String(err), + }); console.error("Failed to resolve market:", err); } writeBotHealthSnapshot(); @@ -3667,22 +5865,23 @@ async function runMaintenance(): Promise { return; } await ensureOracleReady(); - await ensurePerpsConfigReady(); - // ... (simplified loop for seeing liquidity and resolving old markets) - await syncPerpsOraclesFromLeaderboard(); - if (PERPS_MARKET_MAKER_RECYCLE_ENABLED) { - const perpsMarkets = loadPerpsMarkets().filter( - (record) => record.status !== PERPS_MARKET_STATUS_ARCHIVED, - ); - for (const record of perpsMarkets) { - await maybeRecyclePerpsMarketMakerFees(record.marketId); - } - } - await maybeArchiveSettledPerpsMarkets(); + await maybeFinalizePendingSolanaParityLock(); + await maybeFinalizePendingEvmParityLock(); + // Catch up bundles whose live duel_ended event was missed (keeper restart, + // WebSocket drop, etc.). Runs BEFORE the resolve reconcilers so a + // successfully caught-up bundle flips to RESOLUTION phase and the normal + // resolve reconcilers can finish it in the same tick. + await maybeCatchUpMissedDuelEnd(); + // Drain the backlog of OLDER duels that are locked-but-unresolved on-chain + // (tracked in unresolvedOracleWarningMatches). The current-bundle catch-up + // above can only ever heal one duel per tick; this handles everything else. + await maybeCatchUpStrandedLockedDuels(); + await maybeFinalizePendingSolanaParityResolution(); + await maybeFinalizePendingEvmParityResolution(); // Poll only the actively tracked CLOB markets we created. for (const [duelId, trackedMatch] of activeClobMatches.entries()) { - const duelState = await getDuelState(trackedMatch.duelState); + let duelState = await getDuelState(trackedMatch.duelState); if (!duelState) { continue; } @@ -3700,6 +5899,46 @@ async function runMaintenance(): Promise { continue; } + if (enumIs(duelState.status, "challenged")) { + finalizeMarketParityState("frozen", "RESOLUTION", { + reason: `solana duel ${duelId} is challenged`, + }); + continue; + } + + if (enumIs(duelState.status, "proposed")) { + const finalized = await maybeFinalizeTrackedProposal( + trackedMatch, + duelState, + buildDuelMetadataForDuelKey(trackedMatch.duelKeyHex, duelId), + ); + if (!finalized) { + continue; + } + duelState = await getDuelState(trackedMatch.duelState); + if ( + marketParitySnapshot?.duelKey === + normalizedBundleDuelKey(trackedMatch.duelKeyHex) && + marketParitySnapshot.phase === "RESOLUTION" && + duelState && + enumIs(duelState.status, "resolved") + ) { + markMarketParityChainConfirmed({ + chainKey: "solana", + transition: "resolve", + lifecycleStatus: "RESOLVED", + note: "finalized-after-dispute-window", + }); + if (allRequiredParityChainsConfirmed()) { + finalizeMarketParityState("resolved", "RESOLUTION"); + } + } + } + + if (!duelState) { + continue; + } + if (enumIs(duelState.status, "resolved") || enumIs(duelState.status, "cancelled")) { trackedMatch.lastResolvedAtMs = trackedMatch.lastResolvedAtMs ?? Date.now(); await captureSettledClobHealth( @@ -3713,11 +5952,57 @@ async function runMaintenance(): Promise { // NOTE: We do NOT create new rounds here anymore. + // Perps maintenance is intentionally after CLOB/parity lifecycle work so a + // slow oracle-directory sync cannot stall public market finality. + await maybeRunPerpsMaintenance(); +} + +async function runPerpsMaintenance(): Promise { + await ensurePerpsConfigReady(); + await syncPerpsOraclesFromLeaderboard(); + if (PERPS_MARKET_MAKER_RECYCLE_ENABLED) { + const perpsMarkets = loadPerpsMarkets().filter( + (record) => record.status !== PERPS_MARKET_STATUS_ARCHIVED, + ); + for (const record of perpsMarkets) { + await maybeRecyclePerpsMarketMakerFees(record.marketId); + } + } + await maybeArchiveSettledPerpsMarkets(); + if (PERPS_LIQUIDATOR_ENABLED) { await runLiquidatorLoop(); } } +function startPerpsMaintenance(): Promise { + const task = (async () => { + try { + await runPerpsMaintenance(); + } catch (error) { + console.warn("[bot] perps_maintenance_failed", { + error: error instanceof Error ? error.message : String(error), + }); + } + })(); + perpsMaintenancePromise = task.finally(() => { + perpsMaintenancePromise = null; + }); + return perpsMaintenancePromise; +} + +async function maybeRunPerpsMaintenance(): Promise { + const task = perpsMaintenancePromise ?? startPerpsMaintenance(); + try { + await withTimeout(task, PERPS_MAINTENANCE_TIMEOUT_MS, "perps maintenance"); + } catch (error) { + console.warn("[bot] perps_maintenance_deferred", { + timeoutMs: PERPS_MAINTENANCE_TIMEOUT_MS, + error: error instanceof Error ? error.message : String(error), + }); + } +} + async function runLiquidatorLoop(): Promise { if (!keeperProgramApiReady || !PERPS_LIQUIDATOR_ENABLED) return; try { diff --git a/packages/hyperbet-evm/keeper/src/common.ts b/packages/hyperbet-evm/keeper/src/common.ts index 640a6f3a..197721a5 100644 --- a/packages/hyperbet-evm/keeper/src/common.ts +++ b/packages/hyperbet-evm/keeper/src/common.ts @@ -6,6 +6,8 @@ import BN from "bn.js"; import { AnchorProvider, Idl, Program, Wallet } from "@coral-xyz/anchor"; import { Connection, + type ConfirmOptions, + type FetchFn, Keypair, PublicKey, Transaction, @@ -45,11 +47,63 @@ dotenv.config({ path: path.join(envRoot, `.env.${envClusterSuffix}`) }); dotenv.config({ path: path.join(envRoot, ".env") }); type SignableTx = Transaction | VersionedTransaction; +type CommitmentLevel = "processed" | "confirmed" | "finalized"; + +type CreateProgramsOptions = { + usePollingSendAndConfirm?: boolean; + commitment?: CommitmentLevel; + preflightCommitment?: CommitmentLevel; + confirmTimeoutMs?: number; +}; type AnchorLikeWallet = Wallet & { payer: Keypair; }; +const DEFAULT_SOLANA_RPC_REQUEST_TIMEOUT_MS = 15_000; + +function resolveSolanaRpcRequestTimeoutMs(): number { + const configured = Number(process.env.SOLANA_RPC_REQUEST_TIMEOUT_MS); + return Number.isFinite(configured) && configured > 0 + ? Math.floor(configured) + : DEFAULT_SOLANA_RPC_REQUEST_TIMEOUT_MS; +} + +function createSolanaRpcFetch(timeoutMs: number): FetchFn { + const solanaFetch = (async (input, init) => { + const controller = new AbortController(); + const upstreamSignal = init?.signal ?? null; + const abortFromUpstream = () => controller.abort(upstreamSignal?.reason); + + if (upstreamSignal?.aborted) { + controller.abort(upstreamSignal.reason); + } else { + upstreamSignal?.addEventListener("abort", abortFromUpstream, { + once: true, + }); + } + + const timeout = setTimeout(() => { + controller.abort( + new Error(`Solana RPC request timed out after ${timeoutMs}ms`), + ); + }, timeoutMs); + + try { + return await fetch(input, { + ...(init ?? {}), + signal: controller.signal, + }); + } finally { + clearTimeout(timeout); + upstreamSignal?.removeEventListener("abort", abortFromUpstream); + } + }) as FetchFn; + + solanaFetch.preconnect = fetch.preconnect.bind(fetch); + return solanaFetch; +} + function signTx(tx: SignableTx, signer: Keypair): SignableTx { if (tx instanceof VersionedTransaction) { tx.sign([signer]); @@ -73,6 +127,247 @@ function toAnchorWallet(signer: Keypair): AnchorLikeWallet { }; } +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function cloneTransaction(transaction: Transaction): Transaction { + const clone = new Transaction(); + clone.instructions = [...transaction.instructions]; + clone.feePayer = transaction.feePayer; + clone.nonceInfo = transaction.nonceInfo; + clone.minNonceContextSlot = transaction.minNonceContextSlot; + return clone; +} + +async function getLatestBlockhashWithRetries( + connection: Connection, + commitment: CommitmentLevel, + maxAttempts = 4, +): Promise<{ blockhash: string; lastValidBlockHeight: number }> { + let lastError: unknown = null; + for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { + try { + return await connection.getLatestBlockhash(commitment); + } catch (error) { + lastError = error; + if (attempt < maxAttempts) { + await sleep(Math.min(2_000, 250 * attempt)); + } + } + } + + throw new Error( + `failed to fetch latest blockhash after ${maxAttempts} attempts: ${String(lastError)}`, + ); +} + +export async function confirmSignatureByPolling( + connection: Connection, + signature: string, + lastValidBlockHeight?: number, + timeoutMs = Number.parseInt( + process.env.HYPERBET_SOLANA_CONFIRM_TIMEOUT_MS ?? "180000", + 10, + ), + commitment: CommitmentLevel = "confirmed", +): Promise { + const deadline = Date.now() + timeoutMs; + let lastRpcError: unknown = null; + let pollCount = 0; + + while (Date.now() < deadline) { + pollCount += 1; + try { + const statuses = await connection.getSignatureStatuses([signature], { + searchTransactionHistory: true, + }); + const status = statuses.value[0]; + + if (status?.err) { + throw new Error( + `transaction ${signature} failed: ${JSON.stringify(status.err)}`, + ); + } + + if ( + status && + (status.confirmationStatus === "confirmed" || + status.confirmationStatus === "finalized") + ) { + return; + } + + if (lastValidBlockHeight && pollCount % 8 === 0) { + const currentBlockHeight = await connection.getBlockHeight(commitment); + if (currentBlockHeight > lastValidBlockHeight) { + throw new Error( + `transaction ${signature} expired at block height ${lastValidBlockHeight}`, + ); + } + } + } catch (error) { + if ( + error instanceof Error && + (error.message.includes(`transaction ${signature} failed`) || + error.message.includes("expired at block height")) + ) { + throw error; + } + lastRpcError = error; + } + + await sleep(250); + } + + const reason = + lastRpcError instanceof Error ? ` (${lastRpcError.message})` : ""; + throw new Error( + `timed out waiting for confirmation for ${signature}${reason}`, + ); +} + +async function sendAndConfirmWithPolling( + provider: AnchorProvider, + transaction: Transaction, + signers: Keypair[] = [], + options?: ConfirmOptions, +): Promise { + const opts = { + ...provider.opts, + ...options, + }; + const commitment = + (opts.preflightCommitment ?? + opts.commitment ?? + "confirmed") as CommitmentLevel; + let lastError: unknown = null; + + for (let attempt = 1; attempt <= 4; attempt += 1) { + try { + const tx = cloneTransaction(transaction); + tx.feePayer = tx.feePayer ?? provider.wallet.publicKey; + + const { blockhash, lastValidBlockHeight } = + await getLatestBlockhashWithRetries(provider.connection, commitment); + tx.recentBlockhash = blockhash; + + if (signers.length > 0) { + tx.partialSign(...signers); + } + + const signedTx = await provider.wallet.signTransaction(tx); + const signature = await provider.connection.sendRawTransaction( + signedTx.serialize(), + { + maxRetries: 8, + preflightCommitment: commitment, + skipPreflight: opts.skipPreflight ?? false, + }, + ); + + await confirmSignatureByPolling( + provider.connection, + signature, + lastValidBlockHeight, + undefined, + commitment, + ); + return signature; + } catch (error) { + lastError = error; + if (attempt < 4) { + await sleep(250 * attempt); + } + } + } + + throw lastError instanceof Error ? lastError : new Error(String(lastError)); +} + +async function sendVersionedTransactionWithPolling( + provider: AnchorProvider, + transaction: VersionedTransaction, + signers: Keypair[] = [], + options?: ConfirmOptions, +): Promise { + const opts = { + ...provider.opts, + ...options, + }; + const commitment = + (opts.preflightCommitment ?? + opts.commitment ?? + "confirmed") as CommitmentLevel; + let lastError: unknown = null; + + for (let attempt = 1; attempt <= 4; attempt += 1) { + try { + if (signers.length > 0) { + transaction.sign(signers); + } + const signedTx = await provider.wallet.signTransaction(transaction); + const signature = await provider.connection.sendRawTransaction( + signedTx.serialize(), + { + maxRetries: 8, + preflightCommitment: commitment, + skipPreflight: opts.skipPreflight ?? false, + }, + ); + + await confirmSignatureByPolling( + provider.connection, + signature, + undefined, + undefined, + commitment, + ); + return signature; + } catch (error) { + lastError = error; + if (attempt < 4) { + await sleep(250 * attempt); + } + } + } + + throw lastError instanceof Error ? lastError : new Error(String(lastError)); +} + +export function createPollingProvider( + connection: Connection, + wallet: Wallet, + options?: Partial, +): AnchorProvider { + const provider = new AnchorProvider(connection, wallet, { + commitment: "confirmed", + preflightCommitment: "confirmed", + ...(options ?? {}), + }); + const defaultSendAndConfirm = provider.sendAndConfirm.bind(provider); + provider.sendAndConfirm = async (tx, signers, overrideOptions) => { + if (tx instanceof VersionedTransaction) { + return sendVersionedTransactionWithPolling( + provider, + tx, + (signers ?? []) as Keypair[], + overrideOptions, + ); + } + if (tx instanceof Transaction) { + return sendAndConfirmWithPolling( + provider, + tx, + (signers ?? []) as Keypair[], + overrideOptions, + ); + } + return defaultSendAndConfirm(tx, signers, overrideOptions); + }; + return provider; +} + export function getRpcUrl(): string { if (process.env.SOLANA_RPC_URL) return process.env.SOLANA_RPC_URL; @@ -193,7 +488,7 @@ const GOLD_PERPS_MARKET_IDL = ensureIdlAddress( GOLD_PERPS_MARKET_PROGRAM_ID, ); -export function createPrograms(signer: Keypair): { +export function createPrograms(signer: Keypair, options?: CreateProgramsOptions): { connection: Connection; provider: AnchorProvider; fightOracle: Program; @@ -202,14 +497,24 @@ export function createPrograms(signer: Keypair): { /** @deprecated Binary market removed. Returns null. */ goldBinaryMarket: null; } { + const commitment = options?.commitment ?? "confirmed"; + const preflightCommitment = + options?.preflightCommitment ?? options?.commitment ?? "confirmed"; const connection = new Connection(getRpcUrl(), { - commitment: "confirmed", + commitment, + confirmTransactionInitialTimeout: options?.confirmTimeoutMs, + fetch: createSolanaRpcFetch(resolveSolanaRpcRequestTimeoutMs()), }); const wallet = toAnchorWallet(signer); - const provider = new AnchorProvider(connection, wallet, { - commitment: "confirmed", - preflightCommitment: "confirmed", - }); + const provider = options?.usePollingSendAndConfirm + ? createPollingProvider(connection, wallet, { + commitment, + preflightCommitment, + }) + : new AnchorProvider(connection, wallet, { + commitment, + preflightCommitment, + }); const fightOracle: Program = new Program( FIGHT_ORACLE_IDL, diff --git a/packages/hyperbet-evm/keeper/src/db.test.ts b/packages/hyperbet-evm/keeper/src/db.test.ts index a0895b00..f3fb0f59 100644 --- a/packages/hyperbet-evm/keeper/src/db.test.ts +++ b/packages/hyperbet-evm/keeper/src/db.test.ts @@ -144,6 +144,7 @@ describe("keeper db persistence", () => { afterEach(() => { delete process.env.KEEPER_DB_PATH; + delete process.env.KEEPER_DB_BUSY_TIMEOUT_MS; for (const module of loadedModules) { module.closeDb(); } @@ -245,6 +246,135 @@ describe("keeper db persistence", () => { ]); }); + test("stores chain-scoped public perps rows separately by chain", async () => { + const db = (await import( + `./db.ts?case=${Date.now()}-chain-scoped-perps` + )) as typeof import("./db.ts"); + loadedModules.push(db); + + db.saveChainScopedPerpsOracleSnapshot({ + chainKey: "bsc", + agentId: "gpt-4.1", + marketId: 42, + spotIndex: 118.25, + conservativeSkill: 1011, + mu: 1200, + sigma: 63, + recordedAt: 1_700_000_000_000, + }); + db.saveChainScopedPerpsOracleSnapshot({ + chainKey: "base", + agentId: "gpt-4.1", + marketId: 42, + spotIndex: 119.25, + conservativeSkill: 1012, + mu: 1201, + sigma: 62, + recordedAt: 1_700_000_000_100, + }); + db.saveChainScopedPerpsMarket({ + chainKey: "bsc", + agentId: "gpt-4.1", + marketId: 42, + rank: 1, + name: "GPT 4.1", + provider: "OpenAI", + model: "gpt-4.1", + wins: 12, + losses: 3, + winRate: 80, + combatLevel: 99, + currentStreak: 4, + status: "ACTIVE", + lastSeenAt: 1_700_000_000_000, + deprecatedAt: null, + updatedAt: 1_700_000_000_500, + }); + db.saveChainScopedPerpsMarket({ + chainKey: "base", + agentId: "gpt-4.1", + marketId: 42, + rank: 1, + name: "GPT 4.1", + provider: "OpenAI", + model: "gpt-4.1", + wins: 14, + losses: 2, + winRate: 87.5, + combatLevel: 101, + currentStreak: 6, + status: "ACTIVE", + lastSeenAt: 1_700_000_000_250, + deprecatedAt: null, + updatedAt: 1_700_000_000_750, + }); + + expect(db.loadChainScopedPerpsOracleSnapshots("bsc", "gpt-4.1", 10)).toEqual([ + { + chainKey: "bsc", + agentId: "gpt-4.1", + marketId: 42, + spotIndex: 118.25, + conservativeSkill: 1011, + mu: 1200, + sigma: 63, + recordedAt: 1_700_000_000_000, + }, + ]); + expect(db.loadChainScopedPerpsOracleSnapshots("base", "gpt-4.1", 10)).toEqual([ + { + chainKey: "base", + agentId: "gpt-4.1", + marketId: 42, + spotIndex: 119.25, + conservativeSkill: 1012, + mu: 1201, + sigma: 62, + recordedAt: 1_700_000_000_100, + }, + ]); + expect(db.loadChainScopedPerpsMarkets("bsc")).toEqual([ + { + chainKey: "bsc", + agentId: "gpt-4.1", + marketId: 42, + rank: 1, + name: "GPT 4.1", + provider: "OpenAI", + model: "gpt-4.1", + wins: 12, + losses: 3, + winRate: 80, + combatLevel: 99, + currentStreak: 4, + status: "ACTIVE", + lastSeenAt: 1_700_000_000_000, + deprecatedAt: null, + updatedAt: 1_700_000_000_500, + }, + ]); + expect(db.loadChainScopedPerpsMarkets("base")).toEqual([ + { + chainKey: "base", + agentId: "gpt-4.1", + marketId: 42, + rank: 1, + name: "GPT 4.1", + provider: "OpenAI", + model: "gpt-4.1", + wins: 14, + losses: 2, + winRate: 87.5, + combatLevel: 101, + currentStreak: 6, + status: "ACTIVE", + lastSeenAt: 1_700_000_000_250, + deprecatedAt: null, + updatedAt: 1_700_000_000_750, + }, + ]); + }); + test("quarantines duplicate recorded bets before enforcing uniqueness", async () => { seedDuplicateBets(process.env.KEEPER_DB_PATH!); @@ -467,4 +597,54 @@ describe("keeper db persistence", () => { db.loadPredictionMarketsOverviewState()?.liveJson, ).toBe(JSON.stringify({ duel: { duelId: "duel-1" } })); }); + + test("treats a busy bet-sync projection write as a skipped derived commit", async () => { + process.env.KEEPER_DB_BUSY_TIMEOUT_MS = "25"; + const dbPath = process.env.KEEPER_DB_PATH; + if (!dbPath) { + throw new Error("KEEPER_DB_PATH missing for busy projection test"); + } + const db = (await import( + `./db.ts?case=${Date.now()}-projection-busy` + )) as typeof import("./db.ts"); + loadedModules.push(db); + + const blocker = new Database(dbPath, { create: true }); + blocker.run("PRAGMA busy_timeout = 25"); + blocker.run("BEGIN IMMEDIATE"); + try { + expect( + db.commitBetSyncProjectionState({ + streamState: { + stateJson: JSON.stringify({ + type: "STREAMING_STATE_UPDATE", + cycle: { duelId: "duel-busy", phase: "RESOLUTION" }, + leaderboard: [], + cameraTarget: null, + seq: 19, + emittedAt: 1_700_000_202_000, + }), + updatedAt: 1_700_000_202_000, + }, + checkpoint: { + sourceEpoch: 9, + lastSeenSeq: 19, + lastAppliedSeq: 19, + replayMode: "live", + degradedReason: null, + updatedAt: 1_700_000_202_010, + }, + overview: { + liveJson: JSON.stringify({ duel: { duelId: "duel-busy" } }), + recentSettlementJson: null, + updatedAt: 1_700_000_202_020, + }, + }), + ).toBe(false); + expect(db.loadBetSyncCheckpoint()).toBeNull(); + } finally { + blocker.run("ROLLBACK"); + blocker.close(false); + } + }); }); diff --git a/packages/hyperbet-evm/keeper/src/db.ts b/packages/hyperbet-evm/keeper/src/db.ts index b7e33afc..cc07b344 100644 --- a/packages/hyperbet-evm/keeper/src/db.ts +++ b/packages/hyperbet-evm/keeper/src/db.ts @@ -75,6 +75,7 @@ export type DbPerpsOracleSnapshot = { }; export type DbPerpsMarketStatus = "ACTIVE" | "CLOSE_ONLY" | "ARCHIVED"; +export type EvmPerpsChainKey = "bsc" | "base" | "avax"; export type DbPerpsMarketRecord = { agentId: string; @@ -94,6 +95,14 @@ export type DbPerpsMarketRecord = { updatedAt: number; }; +export type DbChainScopedPerpsOracleSnapshot = DbPerpsOracleSnapshot & { + chainKey: EvmPerpsChainKey; +}; + +export type DbChainScopedPerpsMarketRecord = DbPerpsMarketRecord & { + chainKey: EvmPerpsChainKey; +}; + export type DbBetSyncCheckpoint = { sourceEpoch: number; lastSeenSeq: number; @@ -135,6 +144,20 @@ db.run("PRAGMA journal_mode = WAL"); db.run("PRAGMA synchronous = NORMAL"); db.run("PRAGMA foreign_keys = ON"); db.run("PRAGMA auto_vacuum = INCREMENTAL"); +db.run( + `PRAGMA busy_timeout = ${Math.max( + 0, + Math.floor(Number(process.env.KEEPER_DB_BUSY_TIMEOUT_MS || 5_000)), + )}`, +); + +function isSqliteBusyError(error: unknown): boolean { + const candidate = error as { code?: unknown; message?: unknown }; + const code = typeof candidate?.code === "string" ? candidate.code : ""; + const message = + typeof candidate?.message === "string" ? candidate.message : String(error); + return code === "SQLITE_BUSY" || /database is (?:locked|busy)/i.test(message); +} // ── Schema ──────────────────────────────────────────────────────────────────── @@ -422,6 +445,44 @@ db.run(`CREATE TABLE IF NOT EXISTS perps_markets ( db.run(`CREATE INDEX IF NOT EXISTS idx_perps_markets_status_seen ON perps_markets (status, last_seen_at DESC)`); +db.run(`CREATE TABLE IF NOT EXISTS perps_oracle_snapshots_v2 ( + chain_key TEXT NOT NULL, + agent_id TEXT NOT NULL, + market_id INTEGER NOT NULL, + spot_index REAL NOT NULL, + conservative_skill REAL NOT NULL, + mu REAL NOT NULL, + sigma REAL NOT NULL, + recorded_at INTEGER NOT NULL, + CHECK (chain_key IN ('bsc', 'base', 'avax')) +)`); +db.run(`CREATE INDEX IF NOT EXISTS idx_perps_oracle_snapshots_v2_chain_agent_time + ON perps_oracle_snapshots_v2 (chain_key, agent_id, recorded_at DESC)`); +db.run(`CREATE INDEX IF NOT EXISTS idx_perps_oracle_snapshots_v2_chain_market_time + ON perps_oracle_snapshots_v2 (chain_key, market_id, recorded_at DESC)`); +db.run(`CREATE TABLE IF NOT EXISTS perps_markets_v2 ( + chain_key TEXT NOT NULL, + agent_id TEXT NOT NULL, + market_id INTEGER NOT NULL, + rank INTEGER, + name TEXT NOT NULL DEFAULT '', + provider TEXT NOT NULL DEFAULT '', + model TEXT NOT NULL DEFAULT '', + wins INTEGER NOT NULL DEFAULT 0, + losses INTEGER NOT NULL DEFAULT 0, + win_rate REAL NOT NULL DEFAULT 0, + combat_level INTEGER NOT NULL DEFAULT 0, + current_streak INTEGER NOT NULL DEFAULT 0, + status TEXT NOT NULL DEFAULT 'ACTIVE', + last_seen_at INTEGER NOT NULL, + deprecated_at INTEGER, + updated_at INTEGER NOT NULL, + UNIQUE(chain_key, agent_id), + UNIQUE(chain_key, market_id), + CHECK (chain_key IN ('bsc', 'base', 'avax')) +)`); +db.run(`CREATE INDEX IF NOT EXISTS idx_perps_markets_v2_chain_status_seen + ON perps_markets_v2 (chain_key, status, last_seen_at DESC)`); db.run(`CREATE TABLE IF NOT EXISTS bet_sync_checkpoint ( id INTEGER PRIMARY KEY CHECK (id = 1), @@ -545,6 +606,9 @@ const upsertAgentRating = db.prepare(`INSERT INTO agent_ratings const insertPerpsOracleSnapshot = db.prepare(`INSERT INTO perps_oracle_snapshots (agent_id, market_id, spot_index, conservative_skill, mu, sigma, recorded_at) VALUES ($agentId, $marketId, $spotIndex, $conservativeSkill, $mu, $sigma, $recordedAt)`); +const insertPerpsOracleSnapshotV2 = db.prepare(`INSERT INTO perps_oracle_snapshots_v2 + (chain_key, agent_id, market_id, spot_index, conservative_skill, mu, sigma, recorded_at) + VALUES ($chainKey, $agentId, $marketId, $spotIndex, $conservativeSkill, $mu, $sigma, $recordedAt)`); const upsertPerpsMarket = db.prepare(`INSERT INTO perps_markets (agent_id, market_id, rank, name, provider, model, wins, losses, win_rate, @@ -566,6 +630,26 @@ const upsertPerpsMarket = db.prepare(`INSERT INTO perps_markets last_seen_at = excluded.last_seen_at, deprecated_at = excluded.deprecated_at, updated_at = excluded.updated_at`); +const upsertPerpsMarketV2 = db.prepare(`INSERT INTO perps_markets_v2 + (chain_key, agent_id, market_id, rank, name, provider, model, wins, losses, win_rate, + combat_level, current_streak, status, last_seen_at, deprecated_at, updated_at) + VALUES ($chainKey, $agentId, $marketId, $rank, $name, $provider, $model, $wins, $losses, $winRate, + $combatLevel, $currentStreak, $status, $lastSeenAt, $deprecatedAt, $updatedAt) + ON CONFLICT(chain_key, agent_id) DO UPDATE SET + market_id = excluded.market_id, + rank = excluded.rank, + name = excluded.name, + provider = excluded.provider, + model = excluded.model, + wins = excluded.wins, + losses = excluded.losses, + win_rate = excluded.win_rate, + combat_level = excluded.combat_level, + current_streak = excluded.current_streak, + status = excluded.status, + last_seen_at = excluded.last_seen_at, + deprecated_at = excluded.deprecated_at, + updated_at = excluded.updated_at`); const upsertBetSyncCheckpoint = db.prepare(`INSERT INTO bet_sync_checkpoint (id, source_epoch, last_seen_seq, last_applied_seq, replay_mode, degraded_reason, updated_at) @@ -1067,6 +1151,99 @@ export function loadPerpsMarkets( ); } +export function loadChainScopedPerpsOracleSnapshots( + chainKey: EvmPerpsChainKey, + agentId?: string, + limit = 100, +): DbChainScopedPerpsOracleSnapshot[] { + const rows = agentId + ? (db + .prepare( + `SELECT chain_key, agent_id, market_id, spot_index, conservative_skill, mu, sigma, recorded_at + FROM perps_oracle_snapshots_v2 + WHERE chain_key = ? AND agent_id = ? + ORDER BY recorded_at DESC + LIMIT ?`, + ) + .all(chainKey, agentId, limit) as Array>) + : (db + .prepare( + `SELECT chain_key, agent_id, market_id, spot_index, conservative_skill, mu, sigma, recorded_at + FROM perps_oracle_snapshots_v2 + WHERE chain_key = ? + ORDER BY recorded_at DESC + LIMIT ?`, + ) + .all(chainKey, limit) as Array>); + + return rows.map( + (row): DbChainScopedPerpsOracleSnapshot => ({ + chainKey: String(row.chain_key) as EvmPerpsChainKey, + agentId: String(row.agent_id), + marketId: Number(row.market_id), + spotIndex: Number(row.spot_index), + conservativeSkill: Number(row.conservative_skill), + mu: Number(row.mu), + sigma: Number(row.sigma), + recordedAt: Number(row.recorded_at), + }), + ); +} + +export function loadChainScopedPerpsMarkets( + chainKey: EvmPerpsChainKey, + status?: DbPerpsMarketStatus, +): DbChainScopedPerpsMarketRecord[] { + const rows = status + ? (db + .prepare( + `SELECT chain_key, agent_id, market_id, rank, name, provider, model, wins, losses, win_rate, + combat_level, current_streak, status, last_seen_at, deprecated_at, updated_at + FROM perps_markets_v2 + WHERE chain_key = ? AND status = ? + ORDER BY COALESCE(rank, 2147483647) ASC, name ASC`, + ) + .all(chainKey, status) as Array>) + : (db + .prepare( + `SELECT chain_key, agent_id, market_id, rank, name, provider, model, wins, losses, win_rate, + combat_level, current_streak, status, last_seen_at, deprecated_at, updated_at + FROM perps_markets_v2 + WHERE chain_key = ? + ORDER BY + CASE status + WHEN 'ACTIVE' THEN 0 + WHEN 'CLOSE_ONLY' THEN 1 + ELSE 2 + END, + COALESCE(rank, 2147483647) ASC, + name ASC`, + ) + .all(chainKey) as Array>); + + return rows.map( + (row): DbChainScopedPerpsMarketRecord => ({ + chainKey: String(row.chain_key) as EvmPerpsChainKey, + agentId: String(row.agent_id), + marketId: Number(row.market_id), + rank: row.rank == null ? null : Number(row.rank), + name: String(row.name ?? ""), + provider: String(row.provider ?? ""), + model: String(row.model ?? ""), + wins: Number(row.wins ?? 0), + losses: Number(row.losses ?? 0), + winRate: Number(row.win_rate ?? 0), + combatLevel: Number(row.combat_level ?? 0), + currentStreak: Number(row.current_streak ?? 0), + status: String(row.status) as DbPerpsMarketStatus, + lastSeenAt: Number(row.last_seen_at ?? 0), + deprecatedAt: + row.deprecated_at == null ? null : Number(row.deprecated_at), + updatedAt: Number(row.updated_at ?? 0), + }), + ); +} + export function saveAgentRating( agentId: string, rating: AgentRating, @@ -1134,6 +1311,44 @@ export function savePerpsMarket(record: DbPerpsMarketRecord): void { }); } +export function saveChainScopedPerpsOracleSnapshot( + snapshot: DbChainScopedPerpsOracleSnapshot, +): void { + insertPerpsOracleSnapshotV2.run({ + $chainKey: snapshot.chainKey, + $agentId: snapshot.agentId, + $marketId: snapshot.marketId, + $spotIndex: snapshot.spotIndex, + $conservativeSkill: snapshot.conservativeSkill, + $mu: snapshot.mu, + $sigma: snapshot.sigma, + $recordedAt: snapshot.recordedAt, + }); +} + +export function saveChainScopedPerpsMarket( + record: DbChainScopedPerpsMarketRecord, +): void { + upsertPerpsMarketV2.run({ + $chainKey: record.chainKey, + $agentId: record.agentId, + $marketId: record.marketId, + $rank: record.rank, + $name: record.name, + $provider: record.provider, + $model: record.model, + $wins: record.wins, + $losses: record.losses, + $winRate: record.winRate, + $combatLevel: record.combatLevel, + $currentStreak: record.currentStreak, + $status: record.status, + $lastSeenAt: record.lastSeenAt, + $deprecatedAt: record.deprecatedAt, + $updatedAt: record.updatedAt, + }); +} + export function loadBetSyncCheckpoint(): DbBetSyncCheckpoint | null { const row = db .prepare( @@ -1154,15 +1369,28 @@ export function loadBetSyncCheckpoint(): DbBetSyncCheckpoint | null { }; } -export function saveBetSyncCheckpoint(checkpoint: DbBetSyncCheckpoint): void { - upsertBetSyncCheckpoint.run({ - $sourceEpoch: checkpoint.sourceEpoch, - $lastSeenSeq: checkpoint.lastSeenSeq, - $lastAppliedSeq: checkpoint.lastAppliedSeq, - $replayMode: checkpoint.replayMode, - $degradedReason: checkpoint.degradedReason, - $updatedAt: checkpoint.updatedAt, - }); +export function saveBetSyncCheckpoint(checkpoint: DbBetSyncCheckpoint): boolean { + try { + upsertBetSyncCheckpoint.run({ + $sourceEpoch: checkpoint.sourceEpoch, + $lastSeenSeq: checkpoint.lastSeenSeq, + $lastAppliedSeq: checkpoint.lastAppliedSeq, + $replayMode: checkpoint.replayMode, + $degradedReason: checkpoint.degradedReason, + $updatedAt: checkpoint.updatedAt, + }); + return true; + } catch (error) { + if (!isSqliteBusyError(error)) { + throw error; + } + console.warn("[db] skipped bet-sync checkpoint write because SQLite is busy", { + sourceEpoch: checkpoint.sourceEpoch, + lastSeenSeq: checkpoint.lastSeenSeq, + lastAppliedSeq: checkpoint.lastAppliedSeq, + }); + return false; + } } export function appendBetSyncApplyLogEntry( @@ -1281,12 +1509,24 @@ export function commitBetSyncProjectionState(input: { }, ); - return commit( - input.streamState, - input.checkpoint, - input.overview, - input.applyLogEntry, - ); + try { + return commit( + input.streamState, + input.checkpoint, + input.overview, + input.applyLogEntry, + ); + } catch (error) { + if (!isSqliteBusyError(error)) { + throw error; + } + console.warn("[db] skipped bet-sync projection commit because SQLite is busy", { + sourceEpoch: input.checkpoint.sourceEpoch, + lastSeenSeq: input.checkpoint.lastSeenSeq, + lastAppliedSeq: input.checkpoint.lastAppliedSeq, + }); + return false; + } } export function closeDb(): void { diff --git a/packages/hyperbet-evm/keeper/src/game-client.test.ts b/packages/hyperbet-evm/keeper/src/game-client.test.ts index a4379adb..7e6441da 100644 --- a/packages/hyperbet-evm/keeper/src/game-client.test.ts +++ b/packages/hyperbet-evm/keeper/src/game-client.test.ts @@ -55,7 +55,7 @@ describe("GameClient lifecycle reconciliation", () => { globalThis.fetch = originalFetch; }); - test("replays locked and resolved callbacks when the first poll lands mid-resolution", async () => { + test("does not start a keeper lifecycle when the first poll lands mid-resolution", async () => { mockFetchSequence([ makeCycle({ phase: "RESOLUTION", @@ -79,11 +79,35 @@ describe("GameClient lifecycle reconciliation", () => { await (client as any).poll(); - expect(events).toEqual(["start", "lock", "end"]); + expect(events).toEqual([]); + }); + + test("starts when a same-cycle idle placeholder becomes a prelock duel", async () => { + mockFetchSequence([ + makeCycle({ phase: "IDLE", duelId: null, duelKeyHex: null }), + makeCycle({ phase: "ANNOUNCEMENT" }), + makeCycle({ phase: "COUNTDOWN" }), + ]); + + const events: string[] = []; + const client = new GameClient("https://example.test"); + client.onDuelStart(async () => { + events.push("start"); + }); + client.onBettingLocked(async () => { + events.push("lock"); + }); + + await (client as any).poll(); + await (client as any).poll(); + await (client as any).poll(); + + expect(events).toEqual(["start", "lock"]); }); test("re-emits resolution when authoritative result fields arrive after the phase flip", async () => { mockFetchSequence([ + makeCycle({ phase: "BETTING" }), makeCycle({ phase: "FIGHTING" }), makeCycle({ phase: "RESOLUTION" }), makeCycle({ @@ -109,6 +133,7 @@ describe("GameClient lifecycle reconciliation", () => { await (client as any).poll(); await (client as any).poll(); await (client as any).poll(); + await (client as any).poll(); expect(events).toEqual(["start", "lock", "end:-", "end:42"]); }); diff --git a/packages/hyperbet-evm/keeper/src/game-client.ts b/packages/hyperbet-evm/keeper/src/game-client.ts index f3ddecf8..20a8328b 100644 --- a/packages/hyperbet-evm/keeper/src/game-client.ts +++ b/packages/hyperbet-evm/keeper/src/game-client.ts @@ -84,6 +84,7 @@ export class GameClient { private lastCycleId: string | null = null; private lastPhase: string | null = null; + private lastStartedCycleId: string | null = null; private lastLockedCycleId: string | null = null; private lastResolutionEventKey: string | null = null; @@ -139,6 +140,14 @@ export class GameClient { ); } + private isPrelockPhase(phase: string | null): boolean { + return phase === "ANNOUNCEMENT" || phase === "BETTING"; + } + + private hasStartedCycle(cycleId: string): boolean { + return this.lastStartedCycleId === cycleId; + } + private resolutionEventKey(event: DuelLifecycleEvent): string { return [ event.cycleId, @@ -154,6 +163,17 @@ export class GameClient { } } + private async emitDuelStartIfEligible( + event: DuelLifecycleEvent, + phase: string | null, + ) { + if (this.hasStartedCycle(event.cycleId) || !this.isPrelockPhase(phase)) { + return; + } + this.lastStartedCycleId = event.cycleId; + await this.emitDuelStart(event); + } + private async emitBettingLocked(event: DuelLifecycleEvent) { if (!this.onBettingLockedCb || this.lastLockedCycleId === event.cycleId) { return; @@ -219,27 +239,30 @@ export class GameClient { this.lastResolutionEventKey = null; if (lifecycleEvent) { - await this.emitDuelStart(lifecycleEvent); - if (this.isLockedPhase(currentPhase)) { - await this.emitBettingLocked(lifecycleEvent); - } - if (currentPhase === "RESOLUTION") { - await this.emitDuelEnd(lifecycleEvent); - } + await this.emitDuelStartIfEligible(lifecycleEvent, currentPhase); } return; } + if (lifecycleEvent) { + await this.emitDuelStartIfEligible(lifecycleEvent, currentPhase); + } + const transitionedToLocked = lifecycleEvent && + this.hasStartedCycle(currentCycle.cycleId) && this.isLockedPhase(currentPhase) && !this.isLockedPhase(this.lastPhase); if (transitionedToLocked) { await this.emitBettingLocked(lifecycleEvent); } - if (lifecycleEvent && currentPhase === "RESOLUTION") { + if ( + lifecycleEvent && + this.hasStartedCycle(currentCycle.cycleId) && + currentPhase === "RESOLUTION" + ) { await this.emitDuelEnd(lifecycleEvent); } diff --git a/packages/hyperbet-evm/keeper/src/idl/fight_oracle.json b/packages/hyperbet-evm/keeper/src/idl/fight_oracle.json index d0c45c69..0c8cba6a 100644 --- a/packages/hyperbet-evm/keeper/src/idl/fight_oracle.json +++ b/packages/hyperbet-evm/keeper/src/idl/fight_oracle.json @@ -1,5 +1,5 @@ { - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM", + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB", "metadata": { "name": "fight_oracle", "version": "0.1.0", @@ -343,7 +343,7 @@ }, { "name": "program", - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM" + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB" }, { "name": "program_data" @@ -1051,12 +1051,12 @@ { "code": 6021, "name": "ParticipantHashImmutable", - "msg": "Participant hashes are immutable after betting opens" + "msg": "Participant hashes are immutable after duel prepare" }, { "code": 6022, "name": "TimingImmutable", - "msg": "Bet timing is immutable after betting opens" + "msg": "Bet timing is immutable after duel prepare" }, { "code": 6023, diff --git a/packages/hyperbet-evm/keeper/src/idl/fight_oracle.ts b/packages/hyperbet-evm/keeper/src/idl/fight_oracle.ts index 4f3b083d..7183391c 100644 --- a/packages/hyperbet-evm/keeper/src/idl/fight_oracle.ts +++ b/packages/hyperbet-evm/keeper/src/idl/fight_oracle.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/fight_oracle.json`. */ export type FightOracle = { - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM", + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB", "metadata": { "name": "fightOracle", "version": "0.1.0", @@ -349,7 +349,7 @@ export type FightOracle = { }, { "name": "program", - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM" + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB" }, { "name": "programData" @@ -1057,12 +1057,12 @@ export type FightOracle = { { "code": 6021, "name": "participantHashImmutable", - "msg": "Participant hashes are immutable after betting opens" + "msg": "Participant hashes are immutable after duel prepare" }, { "code": 6022, "name": "timingImmutable", - "msg": "Bet timing is immutable after betting opens" + "msg": "Bet timing is immutable after duel prepare" }, { "code": 6023, diff --git a/packages/hyperbet-evm/keeper/src/idl/gold_clob_market.json b/packages/hyperbet-evm/keeper/src/idl/gold_clob_market.json index d2bb65f3..550fae7e 100644 --- a/packages/hyperbet-evm/keeper/src/idl/gold_clob_market.json +++ b/packages/hyperbet-evm/keeper/src/idl/gold_clob_market.json @@ -1,5 +1,5 @@ { - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy", + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt", "metadata": { "name": "gold_clob_market", "version": "0.1.0", @@ -433,7 +433,7 @@ }, { "name": "program", - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy" + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt" }, { "name": "program_data" diff --git a/packages/hyperbet-evm/keeper/src/idl/gold_clob_market.ts b/packages/hyperbet-evm/keeper/src/idl/gold_clob_market.ts index 16e1d101..aea963e3 100644 --- a/packages/hyperbet-evm/keeper/src/idl/gold_clob_market.ts +++ b/packages/hyperbet-evm/keeper/src/idl/gold_clob_market.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/gold_clob_market.json`. */ export type GoldClobMarket = { - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy", + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt", "metadata": { "name": "goldClobMarket", "version": "0.1.0", @@ -439,7 +439,7 @@ export type GoldClobMarket = { }, { "name": "program", - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy" + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt" }, { "name": "programData" diff --git a/packages/hyperbet-evm/keeper/src/marketParity.test.ts b/packages/hyperbet-evm/keeper/src/marketParity.test.ts new file mode 100644 index 00000000..23c76506 --- /dev/null +++ b/packages/hyperbet-evm/keeper/src/marketParity.test.ts @@ -0,0 +1,487 @@ +import { describe, expect, test } from "bun:test"; +import type { KeeperMarketParitySnapshot } from "@hyperbet/mm-core"; + +import { + applyMarketParityReceiptsToMarkets, + buildProjectedMarketParitySnapshot, + buildRecoveredMarketParitySnapshot, + isPublicMarketParitySnapshot, + isPublicMarketParitySnapshotForSourceDuel, + redactPendingMarketParity, +} from "./marketParity"; + +describe("market parity helpers", () => { + test("reconstructs a stable open bundle only when every required chain agrees", () => { + const recovered = buildRecoveredMarketParitySnapshot({ + duelKey: "ab".repeat(32), + duelId: "duel-open", + phase: "ANNOUNCEMENT", + requiredChains: ["solana", "bsc"], + updatedAtMs: 1_700_000_000_000, + streamSafe: true, + markets: [ + { + chainKey: "solana", + duelKey: "ab".repeat(32), + duelId: "duel-open", + marketId: "sol-market", + marketRef: "sol-market", + lifecycleStatus: "OPEN", + winner: "NONE", + betCloseTime: 1_700_000_060_000, + contractAddress: null, + programId: "sol-program", + txRef: "sol-signature", + syncedAt: 1_700_000_000_000, + metadata: undefined, + }, + { + chainKey: "bsc", + duelKey: "ab".repeat(32), + duelId: "duel-open", + marketId: "0xmarket", + marketRef: "0xmarket", + lifecycleStatus: "OPEN", + winner: "NONE", + betCloseTime: 1_700_000_060_000, + contractAddress: "0x0000000000000000000000000000000000000001", + programId: null, + txRef: null, + syncedAt: 1_700_000_000_000, + metadata: undefined, + }, + ], + }); + + expect(recovered).toMatchObject({ + bundleId: `recovered:${"ab".repeat(32)}`, + duelKey: "ab".repeat(32), + duelId: "duel-open", + state: "open", + safeToBet: true, + confirmedChains: ["solana", "bsc"], + }); + }); + + test("refuses to reconstruct parity when required chains disagree", () => { + const recovered = buildRecoveredMarketParitySnapshot({ + duelKey: "cd".repeat(32), + duelId: "duel-mismatch", + phase: "COUNTDOWN", + requiredChains: ["solana", "bsc"], + updatedAtMs: 1_700_000_000_000, + streamSafe: true, + markets: [ + { + chainKey: "solana", + duelKey: "cd".repeat(32), + duelId: "duel-mismatch", + marketId: "sol-market", + marketRef: "sol-market", + lifecycleStatus: "OPEN", + winner: "NONE", + betCloseTime: 1_700_000_060_000, + contractAddress: null, + programId: "sol-program", + txRef: null, + syncedAt: 1_700_000_000_000, + metadata: undefined, + }, + { + chainKey: "bsc", + duelKey: "cd".repeat(32), + duelId: "duel-mismatch", + marketId: "0xmarket", + marketRef: "0xmarket", + lifecycleStatus: "LOCKED", + winner: "NONE", + betCloseTime: 1_700_000_060_000, + contractAddress: "0x0000000000000000000000000000000000000001", + programId: null, + txRef: null, + syncedAt: 1_700_000_000_000, + metadata: undefined, + }, + ], + }); + + expect(recovered).toBeNull(); + }); + + test("redacts duel identity from pending bundles", () => { + const redacted = redactPendingMarketParity({ + bundleId: `abcd:4`, + duelKey: "ab".repeat(32), + duelId: "duel-secret", + revision: 4, + requiredChains: ["solana", "bsc"], + confirmedChains: ["solana"], + state: "awaiting_confirmations", + phase: "ANNOUNCEMENT", + safeToBet: false, + openedAtMs: null, + lockedAtMs: null, + resolvedAtMs: null, + freezeReason: "ignore-me", + updatedAtMs: 1_700_000_000_000, + receipts: [ + { + chainKey: "solana", + preparedAtMs: 1_700_000_000_000, + openedAtMs: null, + lockedAtMs: null, + resolvedAtMs: null, + cancelledAtMs: null, + confirmedAtMs: 1_700_000_000_000, + lifecycleStatus: "PENDING", + txRef: "sol-tx", + note: "prepared", + }, + ], + }); + + expect(redacted).toMatchObject({ + bundleId: "pending:4", + duelKey: null, + duelId: null, + state: "preparing", + freezeReason: null, + }); + expect(redacted?.receipts[0]).toMatchObject({ + txRef: null, + note: null, + }); + }); + + test("keeps post-open confirmation gaps public while downgrading masked pre-open placeholders", () => { + const preOpenAwaiting: KeeperMarketParitySnapshot = { + bundleId: "bundle-pre-open-awaiting", + duelKey: "ef".repeat(32), + duelId: "duel-secret", + revision: 8, + requiredChains: ["solana", "bsc"], + confirmedChains: ["solana"], + state: "awaiting_confirmations", + phase: "ANNOUNCEMENT", + safeToBet: false, + openedAtMs: null, + lockedAtMs: null, + resolvedAtMs: null, + freezeReason: null, + updatedAtMs: 1_700_000_000_000, + receipts: [ + { + chainKey: "solana", + preparedAtMs: 1_700_000_000_000, + openedAtMs: 1_700_000_000_000, + lockedAtMs: null, + resolvedAtMs: null, + cancelledAtMs: null, + confirmedAtMs: 1_700_000_000_000, + lifecycleStatus: "OPEN", + txRef: "sol-tx", + note: "prepared", + }, + ], + }; + + expect(redactPendingMarketParity(preOpenAwaiting)).toMatchObject({ + bundleId: "pending:8", + duelKey: null, + duelId: null, + state: "preparing", + safeToBet: false, + }); + }); + + test("redacts pre-open frozen bundles instead of treating them as public", () => { + const frozen: KeeperMarketParitySnapshot = { + bundleId: "bundle-pre-open", + duelKey: "ab".repeat(32), + duelId: "duel-secret", + revision: 5, + requiredChains: ["solana", "bsc"], + confirmedChains: ["solana"], + state: "frozen", + phase: "ANNOUNCEMENT", + safeToBet: false, + openedAtMs: null, + lockedAtMs: null, + resolvedAtMs: null, + freezeReason: "chain mismatch", + updatedAtMs: 1_700_000_000_000, + receipts: [ + { + chainKey: "solana", + preparedAtMs: 1_700_000_000_000, + openedAtMs: null, + lockedAtMs: null, + resolvedAtMs: null, + cancelledAtMs: null, + confirmedAtMs: 1_700_000_000_000, + lifecycleStatus: "PENDING", + txRef: "sol-tx", + note: "prepared", + }, + ], + }; + + expect(isPublicMarketParitySnapshot(frozen)).toBe(false); + expect(redactPendingMarketParity(frozen)).toMatchObject({ + bundleId: "pending:5", + duelKey: null, + duelId: null, + state: "aborted", + safeToBet: false, + freezeReason: null, + receipts: [ + { + txRef: null, + note: null, + }, + ], + }); + }); + + test("keeps post-open confirmation gaps public", () => { + const awaitingResolve: KeeperMarketParitySnapshot = { + bundleId: "bundle-awaiting-resolve", + duelKey: "ca".repeat(32), + duelId: "duel-public", + revision: 6, + requiredChains: ["solana", "bsc"], + confirmedChains: ["solana", "bsc"], + state: "awaiting_confirmations", + phase: "RESOLUTION", + safeToBet: false, + openedAtMs: 1_700_000_000_000, + lockedAtMs: 1_700_000_030_000, + resolvedAtMs: null, + freezeReason: null, + updatedAtMs: 1_700_000_060_000, + receipts: [ + { + chainKey: "solana", + preparedAtMs: 1_699_999_990_000, + openedAtMs: 1_700_000_000_000, + lockedAtMs: 1_700_000_030_000, + resolvedAtMs: null, + cancelledAtMs: null, + confirmedAtMs: 1_700_000_060_000, + lifecycleStatus: "PROPOSED", + txRef: "sol-tx", + note: "waiting-dispute-window", + }, + { + chainKey: "bsc", + preparedAtMs: 1_699_999_990_000, + openedAtMs: 1_700_000_000_000, + lockedAtMs: 1_700_000_030_000, + resolvedAtMs: null, + cancelledAtMs: null, + confirmedAtMs: 1_700_000_060_000, + lifecycleStatus: "PROPOSED", + txRef: null, + note: "waiting-dispute-window", + }, + ], + }; + + expect(isPublicMarketParitySnapshot(awaitingResolve)).toBe(true); + expect(redactPendingMarketParity(awaitingResolve)).toBe(awaitingResolve); + expect( + isPublicMarketParitySnapshotForSourceDuel( + awaitingResolve, + awaitingResolve.duelKey, + awaitingResolve.duelId, + ), + ).toBe(true); + expect( + isPublicMarketParitySnapshotForSourceDuel( + awaitingResolve, + "db".repeat(32), + "next-duel", + ), + ).toBe(false); + }); + + test("aligns public market rows from confirmed parity receipts", () => { + const duelKey = "da".repeat(32); + const aligned = applyMarketParityReceiptsToMarkets( + [ + { + chainKey: "bsc", + duelKey, + duelId: "duel-public", + marketId: "0xmarket", + marketRef: "0xmarket", + lifecycleStatus: "PENDING", + winner: "NONE", + betCloseTime: 1_700_000_030_000, + contractAddress: "0x0000000000000000000000000000000000000001", + programId: null, + txRef: null, + syncedAt: 1_700_000_010_000, + metadata: undefined, + }, + ], + { + bundleId: `${duelKey}:7`, + duelKey, + duelId: "duel-public", + revision: 7, + requiredChains: ["solana", "bsc"], + confirmedChains: ["solana", "bsc"], + state: "resolved", + phase: "RESOLUTION", + safeToBet: false, + openedAtMs: 1_700_000_000_000, + lockedAtMs: 1_700_000_030_000, + resolvedAtMs: 1_700_000_060_000, + freezeReason: null, + updatedAtMs: 1_700_000_060_000, + receipts: [ + { + chainKey: "bsc", + preparedAtMs: 1_699_999_990_000, + openedAtMs: 1_700_000_000_000, + lockedAtMs: 1_700_000_030_000, + resolvedAtMs: 1_700_000_060_000, + cancelledAtMs: null, + confirmedAtMs: 1_700_000_060_000, + lifecycleStatus: "RESOLVED", + txRef: null, + note: "finalized-after-dispute-window", + }, + ], + }, + "B", + ); + + expect(aligned[0]).toMatchObject({ + chainKey: "bsc", + duelKey, + lifecycleStatus: "RESOLVED", + winner: "B", + syncedAt: 1_700_000_060_000, + metadata: { + parityReceiptLifecycleStatus: "RESOLVED", + parityReceiptConfirmedAtMs: 1_700_000_060_000, + }, + }); + }); + + test("keeps post-open frozen bundles public", () => { + const frozen: KeeperMarketParitySnapshot = { + bundleId: "bundle-opened", + duelKey: "cd".repeat(32), + duelId: "duel-public", + revision: 6, + requiredChains: ["solana", "bsc"], + confirmedChains: ["solana", "bsc"], + state: "frozen", + phase: "COUNTDOWN", + safeToBet: false, + openedAtMs: 1_700_000_000_000, + lockedAtMs: null, + resolvedAtMs: null, + freezeReason: "post-open drift", + updatedAtMs: 1_700_000_010_000, + receipts: [], + }; + + expect(isPublicMarketParitySnapshot(frozen)).toBe(true); + expect(redactPendingMarketParity(frozen)).toBe(frozen); + }); + + test("projects a preparing bundle when a live duel exists but no chain receipts do yet", () => { + const projected = buildProjectedMarketParitySnapshot({ + duelKey: "ef".repeat(32), + duelId: "duel-preparing", + phase: "ANNOUNCEMENT", + requiredChains: ["solana", "bsc"], + updatedAtMs: 1_700_000_000_000, + streamSafe: false, + markets: [], + }); + + expect(projected).toMatchObject({ + bundleId: `recovered-pending:${"ef".repeat(32)}`, + duelKey: "ef".repeat(32), + duelId: "duel-preparing", + state: "preparing", + safeToBet: false, + confirmedChains: [], + }); + expect(projected?.receipts).toHaveLength(2); + expect(projected?.receipts[0]).toMatchObject({ + lifecycleStatus: null, + confirmedAtMs: null, + }); + }); + + test("projects awaiting confirmations when required chains are mid-transition", () => { + const projected = buildProjectedMarketParitySnapshot({ + duelKey: "12".repeat(32), + duelId: "duel-awaiting", + phase: "ANNOUNCEMENT", + requiredChains: ["solana", "bsc"], + updatedAtMs: 1_700_000_000_000, + streamSafe: true, + markets: [ + { + chainKey: "solana", + duelKey: "12".repeat(32), + duelId: "duel-awaiting", + marketId: "sol-market", + marketRef: "sol-market", + lifecycleStatus: "OPEN", + winner: "NONE", + betCloseTime: 1_700_000_060_000, + contractAddress: null, + programId: "sol-program", + txRef: "sol-signature", + syncedAt: 1_700_000_000_000, + metadata: undefined, + }, + { + chainKey: "bsc", + duelKey: "12".repeat(32), + duelId: "duel-awaiting", + marketId: "0xmarket", + marketRef: "0xmarket", + lifecycleStatus: "PENDING", + winner: "NONE", + betCloseTime: 1_700_000_060_000, + contractAddress: "0x0000000000000000000000000000000000000001", + programId: null, + txRef: null, + syncedAt: 1_700_000_000_000, + metadata: undefined, + }, + ], + }); + + expect(projected).toMatchObject({ + duelKey: "12".repeat(32), + duelId: "duel-awaiting", + state: "awaiting_confirmations", + safeToBet: false, + confirmedChains: ["solana"], + }); + expect(projected?.receipts).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + chainKey: "solana", + lifecycleStatus: "OPEN", + openedAtMs: 1_700_000_000_000, + }), + expect.objectContaining({ + chainKey: "bsc", + lifecycleStatus: "PENDING", + preparedAtMs: 1_700_000_000_000, + }), + ]), + ); + }); +}); diff --git a/packages/hyperbet-evm/keeper/src/marketParity.ts b/packages/hyperbet-evm/keeper/src/marketParity.ts new file mode 100644 index 00000000..e038afa8 --- /dev/null +++ b/packages/hyperbet-evm/keeper/src/marketParity.ts @@ -0,0 +1,371 @@ +import type { + BettingChainKey, + PredictionMarketLifecycleRecord, +} from "@hyperbet/chain-registry"; +import type { + KeeperMarketParitySnapshot, + KeeperParityChainReceipt, +} from "@hyperbet/mm-core"; + +const DEFAULT_REQUIRED_PARITY_CHAINS: readonly BettingChainKey[] = [ + "solana", + "bsc", +]; + +function asChainKey(value: string): BettingChainKey | null { + switch (value.trim().toLowerCase()) { + case "solana": + case "bsc": + case "base": + case "avax": + return value.trim().toLowerCase() as BettingChainKey; + default: + return null; + } +} + +export function parseRequiredParityChains( + value: string | undefined, +): BettingChainKey[] { + const parsed = (value ?? "") + .split(",") + .map((entry) => asChainKey(entry)) + .filter((entry): entry is BettingChainKey => entry != null); + return parsed.length > 0 ? Array.from(new Set(parsed)) : [...DEFAULT_REQUIRED_PARITY_CHAINS]; +} + +function parityStateFromLifecycleStatuses( + statuses: readonly PredictionMarketLifecycleRecord["lifecycleStatus"][], +): KeeperMarketParitySnapshot["state"] | null { + if (statuses.length === 0) return null; + if (statuses.every((status) => status === "OPEN")) return "open"; + if (statuses.every((status) => status === "LOCKED")) return "locked"; + if (statuses.every((status) => status === "RESOLVED")) return "resolved"; + if (statuses.every((status) => status === "CANCELLED")) return "cancelled"; + return null; +} + +function lifecycleRank( + status: PredictionMarketLifecycleRecord["lifecycleStatus"] | null | undefined, +): number { + switch (status) { + case "PENDING": + return 1; + case "OPEN": + return 2; + case "LOCKED": + return 3; + case "RESOLVED": + case "CANCELLED": + return 4; + default: + return 0; + } +} + +function buildEmptyReceipt(chainKey: BettingChainKey): KeeperParityChainReceipt { + return { + chainKey, + preparedAtMs: null, + openedAtMs: null, + lockedAtMs: null, + resolvedAtMs: null, + cancelledAtMs: null, + confirmedAtMs: null, + lifecycleStatus: null, + txRef: null, + note: null, + }; +} + +function findMatchingParityRecord( + requiredChain: BettingChainKey, + markets: readonly PredictionMarketLifecycleRecord[], + duelKey: string | null, + duelId: string | null, +): PredictionMarketLifecycleRecord | null { + return ( + markets.find((record) => { + if (record.chainKey !== requiredChain) return false; + if (duelKey && record.duelKey) return record.duelKey === duelKey; + if (duelId && record.duelId) return record.duelId === duelId; + return false; + }) ?? null + ); +} + +function redactedReceipt(receipt: KeeperParityChainReceipt): KeeperParityChainReceipt { + return { + ...receipt, + txRef: null, + note: null, + }; +} + +export function isPublicMarketParitySnapshot( + snapshot: KeeperMarketParitySnapshot | null | undefined, +): boolean { + if (!snapshot) return false; + if ( + snapshot.openedAtMs != null && + snapshot.state === "awaiting_confirmations" + ) { + return true; + } + return ( + snapshot.state === "open" || + snapshot.state === "locked" || + snapshot.state === "resolved" || + snapshot.state === "cancelled" || + (snapshot.state === "frozen" && snapshot.openedAtMs != null) + ); +} + +export function marketParityMatchesDuel( + snapshot: KeeperMarketParitySnapshot | null | undefined, + duelKey: string | null | undefined, + duelId: string | null | undefined, +): boolean { + if (!snapshot) return false; + if (snapshot.duelKey && duelKey) return snapshot.duelKey === duelKey; + if (snapshot.duelId && duelId) return snapshot.duelId === duelId; + return false; +} + +export function isPublicMarketParitySnapshotForSourceDuel( + snapshot: KeeperMarketParitySnapshot | null | undefined, + duelKey: string | null | undefined, + duelId: string | null | undefined, +): boolean { + if (!isPublicMarketParitySnapshot(snapshot)) return false; + if (!duelKey && !duelId) return true; + return marketParityMatchesDuel(snapshot, duelKey, duelId); +} + +export function applyMarketParityReceiptsToMarkets( + markets: readonly PredictionMarketLifecycleRecord[], + snapshot: KeeperMarketParitySnapshot | null | undefined, + winner: PredictionMarketLifecycleRecord["winner"] | null = null, +): PredictionMarketLifecycleRecord[] { + if (!snapshot || !isPublicMarketParitySnapshot(snapshot)) { + return [...markets]; + } + return markets.map((market) => { + if (!marketParityMatchesDuel(snapshot, market.duelKey, market.duelId)) { + return market; + } + const receipt = snapshot.receipts.find( + (candidate) => candidate.chainKey === market.chainKey, + ); + if (!receipt?.lifecycleStatus) return market; + return { + ...market, + duelKey: snapshot.duelKey ?? market.duelKey, + duelId: snapshot.duelId ?? market.duelId, + lifecycleStatus: receipt.lifecycleStatus, + winner: + receipt.lifecycleStatus === "RESOLVED" && winner && winner !== "NONE" + ? winner + : market.winner, + txRef: receipt.txRef ?? market.txRef, + syncedAt: receipt.confirmedAtMs ?? market.syncedAt, + metadata: { + ...(market.metadata ?? {}), + parityReceiptLifecycleStatus: receipt.lifecycleStatus, + parityReceiptConfirmedAtMs: receipt.confirmedAtMs, + }, + }; + }); +} + +export function redactPendingMarketParity( + snapshot: KeeperMarketParitySnapshot | null, +): KeeperMarketParitySnapshot | null { + if (!snapshot) return null; + if (isPublicMarketParitySnapshot(snapshot)) { + return snapshot; + } + const publicPlaceholderState = + snapshot.state === "awaiting_confirmations" && snapshot.openedAtMs == null + ? "preparing" + : snapshot.state === "frozen" + ? "aborted" + : snapshot.state; + return { + ...snapshot, + bundleId: `pending:${snapshot.revision}`, + duelKey: null, + duelId: null, + state: publicPlaceholderState, + safeToBet: false, + freezeReason: null, + receipts: snapshot.receipts.map(redactedReceipt), + }; +} + +function buildRecoveredReceipt( + record: PredictionMarketLifecycleRecord, + updatedAtMs: number, +): KeeperParityChainReceipt { + return { + chainKey: record.chainKey, + preparedAtMs: updatedAtMs, + openedAtMs: record.lifecycleStatus === "OPEN" ? updatedAtMs : null, + lockedAtMs: record.lifecycleStatus === "LOCKED" ? updatedAtMs : null, + resolvedAtMs: record.lifecycleStatus === "RESOLVED" ? updatedAtMs : null, + cancelledAtMs: record.lifecycleStatus === "CANCELLED" ? updatedAtMs : null, + confirmedAtMs: updatedAtMs, + lifecycleStatus: record.lifecycleStatus, + txRef: null, + note: "recovered-from-market-health", + }; +} + +function defaultPhaseForState( + state: KeeperMarketParitySnapshot["state"], +): string | null { + switch (state) { + case "open": + return "ANNOUNCEMENT"; + case "locked": + return "COUNTDOWN"; + case "resolved": + case "cancelled": + return "RESOLUTION"; + default: + return null; + } +} + +export function buildRecoveredMarketParitySnapshot(input: { + duelKey: string | null; + duelId: string | null; + phase: string | null; + requiredChains: readonly BettingChainKey[]; + markets: readonly PredictionMarketLifecycleRecord[]; + updatedAtMs: number; + streamSafe: boolean; +}): KeeperMarketParitySnapshot | null { + const { + duelKey, + duelId, + phase, + requiredChains, + markets, + updatedAtMs, + streamSafe, + } = input; + if (!duelKey && !duelId) return null; + + const receipts = requiredChains.map((requiredChain) => + markets.find((record) => { + if (record.chainKey !== requiredChain) return false; + if (duelKey && record.duelKey) return record.duelKey === duelKey; + if (duelId && record.duelId) return record.duelId === duelId; + return false; + }) ?? null, + ); + if (receipts.some((record) => record == null)) { + return null; + } + + const confirmedRecords = receipts.filter( + (record): record is PredictionMarketLifecycleRecord => record != null, + ); + const state = parityStateFromLifecycleStatuses( + confirmedRecords.map((record) => record.lifecycleStatus), + ); + if (!state) { + return null; + } + + return { + bundleId: `recovered:${duelKey ?? duelId ?? "unknown"}`, + duelKey, + duelId, + revision: 0, + requiredChains: [...requiredChains], + confirmedChains: [...requiredChains], + state, + phase: phase ?? defaultPhaseForState(state), + safeToBet: state === "open" && streamSafe, + openedAtMs: state === "open" ? updatedAtMs : null, + lockedAtMs: state === "locked" ? updatedAtMs : null, + resolvedAtMs: + state === "resolved" || state === "cancelled" ? updatedAtMs : null, + freezeReason: null, + updatedAtMs, + receipts: confirmedRecords.map((record) => + buildRecoveredReceipt(record, updatedAtMs), + ), + }; +} + +export function buildProjectedMarketParitySnapshot(input: { + duelKey: string | null; + duelId: string | null; + phase: string | null; + requiredChains: readonly BettingChainKey[]; + markets: readonly PredictionMarketLifecycleRecord[]; + updatedAtMs: number; + streamSafe: boolean; +}): KeeperMarketParitySnapshot | null { + const recovered = buildRecoveredMarketParitySnapshot(input); + if (recovered) { + return recovered; + } + + const { + duelKey, + duelId, + phase, + requiredChains, + markets, + updatedAtMs, + } = input; + if (!duelKey && !duelId) return null; + + const matchedRecords = requiredChains + .map((chainKey) => + findMatchingParityRecord(chainKey, markets, duelKey, duelId), + ) + .filter((record): record is PredictionMarketLifecycleRecord => record != null); + const receipts = requiredChains.map((chainKey) => { + const record = findMatchingParityRecord(chainKey, markets, duelKey, duelId); + return record + ? buildRecoveredReceipt(record, updatedAtMs) + : buildEmptyReceipt(chainKey); + }); + const highestRank = matchedRecords.reduce( + (max, record) => Math.max(max, lifecycleRank(record.lifecycleStatus)), + 0, + ); + const state = + highestRank >= 2 ? "awaiting_confirmations" : "preparing"; + const confirmedChains = receipts + .filter((receipt) => { + if (state === "preparing") { + return receipt.lifecycleStatus != null; + } + return lifecycleRank(receipt.lifecycleStatus) >= highestRank; + }) + .map((receipt) => receipt.chainKey); + + return { + bundleId: `recovered-pending:${duelKey ?? duelId ?? "unknown"}`, + duelKey, + duelId, + revision: 0, + requiredChains: [...requiredChains], + confirmedChains, + state, + phase: phase ?? "ANNOUNCEMENT", + safeToBet: false, + openedAtMs: null, + lockedAtMs: null, + resolvedAtMs: null, + freezeReason: null, + updatedAtMs, + receipts, + }; +} diff --git a/packages/hyperbet-evm/keeper/src/predictionMarketsSurface.test.ts b/packages/hyperbet-evm/keeper/src/predictionMarketsSurface.test.ts new file mode 100644 index 00000000..0019ea5c --- /dev/null +++ b/packages/hyperbet-evm/keeper/src/predictionMarketsSurface.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, test } from "bun:test"; + +import { + extractCycleIdFromStreamingDuelId, + streamCycleAdvancedBeyondPinnedParity, +} from "./predictionMarketsSurface"; + +describe("prediction markets surface helpers", () => { + test("extracts cycle ids from streaming duel ids", () => { + expect( + extractCycleIdFromStreamingDuelId("streaming-72abb82f-abc"), + ).toBe("72abb82f-abc"); + expect( + extractCycleIdFromStreamingDuelId(" STREAMING-72abb82f-abc "), + ).toBe("72abb82f-abc"); + }); + + test("returns null for non-string or empty duel ids", () => { + expect(extractCycleIdFromStreamingDuelId(null)).toBeNull(); + expect(extractCycleIdFromStreamingDuelId("")).toBeNull(); + expect(extractCycleIdFromStreamingDuelId(" ")).toBeNull(); + }); + + test("does not treat a missing stream cycle id as advanced", () => { + expect( + streamCycleAdvancedBeyondPinnedParity({ + streamCycleId: null, + previousLiveDuelId: "streaming-3735f4d1-old", + marketParityDuelId: "streaming-3735f4d1-old", + }), + ).toBeFalse(); + }); + + test("does not treat a matching previous live cycle as advanced", () => { + expect( + streamCycleAdvancedBeyondPinnedParity({ + streamCycleId: "72abb82f-new", + previousLiveDuelId: "streaming-72abb82f-new", + marketParityDuelId: "streaming-3735f4d1-old", + }), + ).toBeFalse(); + }); + + test("does not treat a matching parity cycle as advanced", () => { + expect( + streamCycleAdvancedBeyondPinnedParity({ + streamCycleId: "72abb82f-new", + previousLiveDuelId: "streaming-3735f4d1-old", + marketParityDuelId: "streaming-72abb82f-new", + }), + ).toBeFalse(); + }); + + test("detects when the stream has advanced beyond both previous live and parity", () => { + expect( + streamCycleAdvancedBeyondPinnedParity({ + streamCycleId: "72abb82f-new", + previousLiveDuelId: "streaming-3735f4d1-old", + marketParityDuelId: "streaming-3735f4d1-old", + }), + ).toBeTrue(); + }); + + test("treats an unparseable parity duel id as lagging behind the new stream cycle", () => { + expect( + streamCycleAdvancedBeyondPinnedParity({ + streamCycleId: "72abb82f-new", + previousLiveDuelId: "streaming-3735f4d1-old", + marketParityDuelId: null, + }), + ).toBeTrue(); + }); +}); diff --git a/packages/hyperbet-evm/keeper/src/predictionMarketsSurface.ts b/packages/hyperbet-evm/keeper/src/predictionMarketsSurface.ts new file mode 100644 index 00000000..1990b648 --- /dev/null +++ b/packages/hyperbet-evm/keeper/src/predictionMarketsSurface.ts @@ -0,0 +1,35 @@ +export function extractCycleIdFromStreamingDuelId( + duelId: unknown, +): string | null { + if (typeof duelId !== "string") { + return null; + } + const stripped = duelId.trim().replace(/^streaming-/i, ""); + return stripped.length > 0 ? stripped : null; +} + +export function streamCycleAdvancedBeyondPinnedParity(params: { + streamCycleId: unknown; + previousLiveDuelId: unknown; + marketParityDuelId: unknown; +}): boolean { + const streamCycleId = + typeof params.streamCycleId === "string" && + params.streamCycleId.trim().length > 0 + ? params.streamCycleId.trim() + : null; + if (streamCycleId == null) { + return false; + } + + const previousCycleId = extractCycleIdFromStreamingDuelId( + params.previousLiveDuelId, + ); + const parityCycleId = extractCycleIdFromStreamingDuelId( + params.marketParityDuelId, + ); + return ( + (previousCycleId == null || streamCycleId !== previousCycleId) && + (parityCycleId == null || streamCycleId !== parityCycleId) + ); +} diff --git a/packages/hyperbet-evm/keeper/src/publicPerps.test.ts b/packages/hyperbet-evm/keeper/src/publicPerps.test.ts new file mode 100644 index 00000000..455dc1a8 --- /dev/null +++ b/packages/hyperbet-evm/keeper/src/publicPerps.test.ts @@ -0,0 +1,103 @@ +import { describe, expect, test } from "bun:test"; + +import { + adaptLegacySolanaPerpsMarketsPayload, + adaptLegacySolanaPerpsOracleHistoryPayload, + buildExternalPerpsUrl, + normalizePublicPerpsChainKeyParam, + resolvePublicEvmPerpsChains, +} from "./publicPerps"; + +describe("public perps helpers", () => { + test("normalizes supported public chain keys", () => { + expect(normalizePublicPerpsChainKeyParam("solana")).toBe("solana"); + expect(normalizePublicPerpsChainKeyParam("bsc")).toBe("bsc"); + expect(normalizePublicPerpsChainKeyParam("base")).toBe("base"); + expect(normalizePublicPerpsChainKeyParam("avax")).toBe("avax"); + expect(normalizePublicPerpsChainKeyParam("ethereum")).toBeNull(); + }); + + test("builds external Solana perps urls without forwarding chainKey", () => { + const searchParams = new URLSearchParams({ + chainKey: "solana", + characterId: "agent-a", + limit: "24", + }); + expect( + buildExternalPerpsUrl({ + baseUrl: "https://solana-keeper.example", + pathname: "/api/perps/oracle-history", + searchParams, + }), + ).toBe( + "https://solana-keeper.example/api/perps/oracle-history?characterId=agent-a&limit=24", + ); + }); + + test("defaults public EVM perps publishing to BSC only", () => { + expect( + resolvePublicEvmPerpsChains({ + configuredChains: ["bsc", "base", "avax"], + publicChains: undefined, + }), + ).toEqual(["bsc"]); + }); + + test("publishes only explicitly configured public EVM perps chains", () => { + expect( + resolvePublicEvmPerpsChains({ + configuredChains: ["bsc", "base", "avax"], + publicChains: "base,avax", + }), + ).toEqual(["base", "avax"]); + expect( + resolvePublicEvmPerpsChains({ + configuredChains: ["base", "avax"], + publicChains: undefined, + }), + ).toEqual([]); + }); + + test("adapts legacy Solana markets responses into chain-scoped payloads", () => { + const adapted = adaptLegacySolanaPerpsMarketsPayload({ + markets: [ + { + characterId: "agent-a", + marketId: 7, + rank: 1, + }, + ], + updatedAt: 2, + }); + + expect(adapted).toEqual({ + chainKey: "solana", + markets: [ + { + chainKey: "solana", + characterId: "agent-a", + marketId: 7, + rank: 1, + }, + ], + updatedAt: 2, + }); + }); + + test("adapts legacy Solana oracle history responses into chain-scoped payloads", () => { + const adapted = adaptLegacySolanaPerpsOracleHistoryPayload({ + characterId: "agent-a", + marketId: 7, + snapshots: [{ recordedAt: 1 }], + updatedAt: 2, + }); + + expect(adapted).toEqual({ + chainKey: "solana", + characterId: "agent-a", + marketId: 7, + snapshots: [{ recordedAt: 1 }], + updatedAt: 2, + }); + }); +}); diff --git a/packages/hyperbet-evm/keeper/src/publicPerps.ts b/packages/hyperbet-evm/keeper/src/publicPerps.ts new file mode 100644 index 00000000..44d6e67d --- /dev/null +++ b/packages/hyperbet-evm/keeper/src/publicPerps.ts @@ -0,0 +1,123 @@ +import type { EvmPerpsChainKey } from "./db"; + +export type PublicPerpsChainKey = "solana" | EvmPerpsChainKey; + +export function normalizePublicPerpsChainKeyParam( + value: string | null, +): PublicPerpsChainKey | null { + if ( + value === "solana" || + value === "bsc" || + value === "base" || + value === "avax" + ) { + return value; + } + return null; +} + +function asEvmPerpsChainKey(value: string): EvmPerpsChainKey | null { + switch (value.trim().toLowerCase()) { + case "bsc": + case "base": + case "avax": + return value.trim().toLowerCase() as EvmPerpsChainKey; + default: + return null; + } +} + +function parseEvmPerpsChainList( + value: string | null | undefined, +): EvmPerpsChainKey[] { + return Array.from( + new Set( + (value ?? "") + .split(",") + .map((entry) => asEvmPerpsChainKey(entry)) + .filter((entry): entry is EvmPerpsChainKey => entry != null), + ), + ); +} + +export function resolvePublicEvmPerpsChains(params: { + configuredChains: readonly string[]; + publicChains: string | null | undefined; +}): EvmPerpsChainKey[] { + const configured = new Set( + params.configuredChains + .map((chainKey) => asEvmPerpsChainKey(chainKey)) + .filter((chainKey): chainKey is EvmPerpsChainKey => chainKey != null), + ); + const requested = parseEvmPerpsChainList(params.publicChains); + const publishChains: EvmPerpsChainKey[] = + requested.length > 0 ? requested : ["bsc"]; + return publishChains.filter((chainKey) => configured.has(chainKey)); +} + +export function buildExternalPerpsUrl(params: { + baseUrl: string; + pathname: "/api/perps/markets" | "/api/perps/oracle-history"; + searchParams: URLSearchParams; +}): string { + const target = new URL( + params.pathname, + params.baseUrl.endsWith("/") ? params.baseUrl : `${params.baseUrl}/`, + ); + for (const [key, value] of params.searchParams.entries()) { + if (key === "chainKey") { + continue; + } + target.searchParams.set(key, value); + } + return target.toString(); +} + +export function adaptLegacySolanaPerpsMarketsPayload(value: unknown): { + chainKey: "solana"; + markets: Array>; + updatedAt: number; +} { + const candidate = asRecord(value); + const markets = Array.isArray(candidate.markets) + ? candidate.markets.flatMap((market) => { + const record = asOptionalRecord(market); + return record ? [{ ...record, chainKey: "solana" }] : []; + }) + : []; + return { + chainKey: "solana", + markets, + updatedAt: coerceTimestamp(candidate.updatedAt), + }; +} + +export function adaptLegacySolanaPerpsOracleHistoryPayload( + value: unknown, +): Record { + const candidate = asRecord(value); + return { + ...candidate, + chainKey: "solana", + snapshots: Array.isArray(candidate.snapshots) ? candidate.snapshots : [], + updatedAt: coerceTimestamp(candidate.updatedAt), + }; +} + +function asRecord(value: unknown): Record { + return typeof value === "object" && value != null + ? (value as Record) + : {}; +} + +function asOptionalRecord(value: unknown): Record | null { + return typeof value === "object" && value != null + ? (value as Record) + : null; +} + +function coerceTimestamp(value: unknown): number { + return typeof value === "number" && Number.isFinite(value) + ? value + : Date.now(); +} diff --git a/packages/hyperbet-evm/keeper/src/resultCatchupAuth.test.ts b/packages/hyperbet-evm/keeper/src/resultCatchupAuth.test.ts new file mode 100644 index 00000000..7237e0ea --- /dev/null +++ b/packages/hyperbet-evm/keeper/src/resultCatchupAuth.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, test } from "bun:test"; +import { resolveResultCatchupBearerToken } from "./resultCatchupAuth"; + +describe("resolveResultCatchupBearerToken", () => { + test("prefers the dedicated Hyperscape result lookup token", () => { + expect( + resolveResultCatchupBearerToken({ + NODE_ENV: "production", + HYPERSCAPES_RESULT_LOOKUP_BEARER_TOKEN: " result-secret ", + STREAMING_ORACLE_PROOF_TOKEN: "oracle-secret", + BETTING_FEED_ACCESS_TOKEN: "feed-secret", + }), + ).toEqual({ + token: "result-secret", + source: "hyperscapes-result", + }); + }); + + test("does not accept the provider-side alias", () => { + expect( + resolveResultCatchupBearerToken({ + NODE_ENV: "production", + STREAMING_ORACLE_PROOF_TOKEN: "oracle-secret", + BETTING_FEED_ACCESS_TOKEN: "feed-secret", + }), + ).toEqual({ + token: null, + source: null, + }); + }); + + test("does not fall back to broad feed tokens", () => { + expect( + resolveResultCatchupBearerToken({ + NODE_ENV: "production", + BETTING_FEED_ACCESS_TOKEN: "feed-secret", + STREAM_STATE_SOURCE_BEARER_TOKEN: "stream-secret", + }), + ).toEqual({ + token: null, + source: null, + }); + expect( + resolveResultCatchupBearerToken({ + NODE_ENV: "development", + BETTING_FEED_ACCESS_TOKEN: "feed-secret", + STREAM_STATE_SOURCE_BEARER_TOKEN: "stream-secret", + }), + ).toEqual({ + token: null, + source: null, + }); + + expect( + resolveResultCatchupBearerToken({ + NODE_ENV: "test", + STREAM_STATE_SOURCE_BEARER_TOKEN: "stream-secret", + }), + ).toEqual({ + token: null, + source: null, + }); + }); +}); diff --git a/packages/hyperbet-evm/keeper/src/resultCatchupAuth.ts b/packages/hyperbet-evm/keeper/src/resultCatchupAuth.ts new file mode 100644 index 00000000..53405852 --- /dev/null +++ b/packages/hyperbet-evm/keeper/src/resultCatchupAuth.ts @@ -0,0 +1,41 @@ +export type ResultCatchupBearerTokenSource = + | "hyperscapes-result" + | null; + +export type ResultCatchupBearerTokenResolution = { + token: string | null; + source: ResultCatchupBearerTokenSource; +}; + +type EnvLike = Record; + +function nonEmptyEnv(env: EnvLike, key: string): string | null { + return env[key]?.trim() || null; +} + +/** + * Resolve the bearer token used by the missed-duel-result catch-up path. + * + * Hyperscape only accepts a dedicated result lookup token for + * `/api/streaming/results/:duelId`. Broad betting-feed/source tokens are + * intentionally not accepted here because this endpoint returns settlement + * proof material (`duelKeyHex`, `seed`, `replayHash`). + */ +export function resolveResultCatchupBearerToken( + env: EnvLike, +): ResultCatchupBearerTokenResolution { + const resultLookupToken = nonEmptyEnv( + env, + "HYPERSCAPES_RESULT_LOOKUP_BEARER_TOKEN", + ); + if (resultLookupToken) { + return { + token: resultLookupToken, + source: "hyperscapes-result", + }; + } + return { + token: null, + source: null, + }; +} diff --git a/packages/hyperbet-evm/keeper/src/service.ts b/packages/hyperbet-evm/keeper/src/service.ts index 326cb8d4..291eeed0 100644 --- a/packages/hyperbet-evm/keeper/src/service.ts +++ b/packages/hyperbet-evm/keeper/src/service.ts @@ -6,8 +6,12 @@ import { request as httpsRequest } from "node:https"; import path from "node:path"; import { + BETTING_EVM_CHAIN_ORDER, normalizeChainKey, + parseBettingEvmChainList, type PredictionMarketLifecycleStatus, + resolveLifecycleFromSolanaDuelStatus, + resolveLifecycleFromSolanaMarketStatus, resolveLifecycleFromStreamPhase, toRecordedBetChain, type PredictionMarketLifecycleRecord, @@ -31,6 +35,7 @@ import { PUBLIC_EVM_RPC_READ_METHODS, PUBLIC_SOLANA_RPC_READ_METHODS, type KeeperBotHealthSnapshot, + type KeeperMarketParitySnapshot, type KeeperMarketHealthRecord, } from "@hyperbet/mm-core"; import { PublicKey } from "@solana/web3.js"; @@ -54,7 +59,8 @@ import { loadAll, loadBetSyncCheckpoint, loadPerpsMarkets, - loadPerpsOracleSnapshots, + loadChainScopedPerpsMarkets, + loadChainScopedPerpsOracleSnapshots, loadPredictionMarketsOverviewState, saveBet, saveBetSyncCheckpoint, @@ -69,6 +75,7 @@ import { saveReferral, saveInvitedWallet, saveReferralFees, + type EvmPerpsChainKey, } from "./db"; import { isBetSyncEventStaleAfterSourceReset, @@ -76,6 +83,8 @@ import { parseBetSyncBootstrapState, parseBetSyncEvent, parsePredictionMarketsOverview, + parseStreamStatePayload, + publicStreamStateChanged, resolveBetSyncReplayMode, rollPredictionMarketsOverview, selectBetSyncReplayUntilSeq, @@ -89,7 +98,25 @@ import { type PredictionMarketsSurface, type StreamState as BetSyncStreamState, } from "./betSync"; +import { + applyMarketParityReceiptsToMarkets, + buildProjectedMarketParitySnapshot, + isPublicMarketParitySnapshot, + isPublicMarketParitySnapshotForSourceDuel, + marketParityMatchesDuel, + parseRequiredParityChains, + redactPendingMarketParity, +} from "./marketParity"; import { modelMarketIdFromCharacterId } from "./modelMarkets"; +import { + adaptLegacySolanaPerpsMarketsPayload, + adaptLegacySolanaPerpsOracleHistoryPayload, + buildExternalPerpsUrl, + normalizePublicPerpsChainKeyParam, + type PublicPerpsChainKey, +} from "./publicPerps"; +import { streamCycleAdvancedBeyondPinnedParity } from "./predictionMarketsSurface"; +import { sourceStatePhaseAllowsUnmaskedDuelIdentity } from "./streamPhaseUnmask"; import { isLegacyDerivedPointsWalletKey, normalizePointsWalletInput, @@ -213,6 +240,12 @@ type SolanaKeeperContext = { marketProgramId: ReturnType["goldClobMarket"]["programId"]; }; +function asJsonRecord(value: unknown): JsonRecord | null { + return value && typeof value === "object" + ? (value as JsonRecord) + : null; +} + const encoder = new TextEncoder(); const decoder = new TextDecoder(); const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -239,6 +272,120 @@ function loadKeeperBotHealthSnapshot(): KeeperBotHealthSnapshot | null { } } +function mergeHealthSnapshots( + localSnapshot: KeeperBotHealthSnapshot | null, + externalSnapshot: KeeperBotHealthSnapshot | null, +): KeeperBotHealthSnapshot | null { + if (!localSnapshot && !externalSnapshot) { + return null; + } + + const markets = [ + ...(localSnapshot?.markets ?? []), + ...(externalSnapshot?.markets ?? []), + ]; + const dedupedMarkets = Array.from( + new Map( + markets.map((market) => [ + `${market.chainKey}:${market.marketRef ?? ""}:${market.duelKey ?? ""}`, + market, + ]), + ).values(), + ); + + return { + chainKey: localSnapshot?.chainKey ?? externalSnapshot?.chainKey ?? "solana", + updatedAtMs: Math.max( + localSnapshot?.updatedAtMs ?? 0, + externalSnapshot?.updatedAtMs ?? 0, + ), + bootedAtMs: + localSnapshot?.bootedAtMs ?? + externalSnapshot?.bootedAtMs ?? + Date.now(), + running: localSnapshot?.running ?? false, + processId: localSnapshot?.processId ?? null, + lastSuccessfulRpcAtMs: Math.max( + localSnapshot?.lastSuccessfulRpcAtMs ?? 0, + externalSnapshot?.lastSuccessfulRpcAtMs ?? 0, + ) || null, + recovery: [ + ...(localSnapshot?.recovery ?? []), + ...(externalSnapshot?.recovery ?? []), + ], + markets: dedupedMarkets, + marketParity: + localSnapshot?.marketParity ?? externalSnapshot?.marketParity ?? null, + }; +} + +let externalSolanaBotHealthState: ExternalHealthSourceState = { + sourceUrl: null, + snapshot: null, + lastFetchedAt: null, + lastError: null, +}; +const enabledEvmKeeperChains = new Set( + parseBettingEvmChainList( + process.env.EVM_KEEPER_CHAINS, + BETTING_EVM_CHAIN_ORDER, + ), +); +const requiredParityChains = parseRequiredParityChains( + process.env.MARKET_PARITY_REQUIRED_CHAINS, +); + +function effectiveKeeperBotHealthSnapshot( + localSnapshot: KeeperBotHealthSnapshot | null = loadKeeperBotHealthSnapshot(), +): KeeperBotHealthSnapshot | null { + return mergeHealthSnapshots(localSnapshot, externalSolanaBotHealthState.snapshot); +} + +async function pollExternalSolanaBotHealth(): Promise { + if (!EXTERNAL_SOLANA_KEEPER_BOT_HEALTH_URL) { + return; + } + + try { + const response = await fetch(EXTERNAL_SOLANA_KEEPER_BOT_HEALTH_URL, { + headers: + EXTERNAL_SOLANA_KEEPER_BOT_HEALTH_BEARER_TOKEN.length > 0 + ? { + authorization: `Bearer ${EXTERNAL_SOLANA_KEEPER_BOT_HEALTH_BEARER_TOKEN}`, + } + : undefined, + signal: AbortSignal.timeout(10_000), + }); + const payload = await response.json(); + const snapshot = + response.ok && + payload && + typeof payload === "object" && + "health" in payload && + payload.health && + typeof payload.health === "object" + ? (payload.health as KeeperBotHealthSnapshot) + : null; + + externalSolanaBotHealthState = { + sourceUrl: EXTERNAL_SOLANA_KEEPER_BOT_HEALTH_URL, + snapshot, + lastFetchedAt: Date.now(), + lastError: + response.ok && snapshot + ? null + : `unexpected response ${response.status}`, + }; + } catch (error) { + externalSolanaBotHealthState = { + ...externalSolanaBotHealthState, + sourceUrl: EXTERNAL_SOLANA_KEEPER_BOT_HEALTH_URL, + lastFetchedAt: Date.now(), + lastError: error instanceof Error ? error.message : "solana bot health fetch failed", + }; + } +} + function loadStreamStateSnapshotFile(): StreamState | null { if (!KEEPER_STREAM_STATE_FILE || !fs_node.existsSync(KEEPER_STREAM_STATE_FILE)) { return null; @@ -525,6 +672,65 @@ const GOLD_CLOB_READ_ABI = [ }, ] as const; +const DUEL_OUTCOME_READ_ABI = [ + { + type: "function", + name: "getDuel", + stateMutability: "view", + inputs: [{ type: "bytes32" }], + outputs: [ + { + type: "tuple", + components: [ + { name: "duelKey", type: "bytes32" }, + { name: "participantAHash", type: "bytes32" }, + { name: "participantBHash", type: "bytes32" }, + { name: "status", type: "uint8" }, + { name: "winner", type: "uint8" }, + { name: "betOpenTs", type: "uint64" }, + { name: "betCloseTs", type: "uint64" }, + { name: "duelStartTs", type: "uint64" }, + { name: "duelEndTs", type: "uint64" }, + { name: "seed", type: "uint64" }, + { name: "resultHash", type: "bytes32" }, + { name: "replayHash", type: "bytes32" }, + { name: "activeProposalId", type: "bytes32" }, + { name: "metadataUri", type: "string" }, + ], + }, + ], + }, + { + type: "function", + name: "proposals", + stateMutability: "view", + inputs: [{ type: "bytes32" }], + outputs: [ + { + type: "tuple", + components: [ + { name: "id", type: "bytes32" }, + { name: "resultHash", type: "bytes32" }, + { name: "replayHash", type: "bytes32" }, + { name: "winner", type: "uint8" }, + { name: "seed", type: "uint64" }, + { name: "duelEndTs", type: "uint64" }, + { name: "proposedAt", type: "uint64" }, + { name: "challenged", type: "bool" }, + { name: "exists", type: "bool" }, + ], + }, + ], + }, + { + type: "function", + name: "disputeWindowSeconds", + stateMutability: "view", + inputs: [], + outputs: [{ type: "uint256" }], + }, +] as const; + const defaultAgentA = { id: "agent-a", name: "Agent A", @@ -537,6 +743,8 @@ const defaultAgentB = { hp: 10, maxHp: 10, }; +const ZERO_HEX_32 = + "0x0000000000000000000000000000000000000000000000000000000000000000"; let streamSeq = 1; const persistedStreamStateFile = loadStreamStateSnapshotFile(); @@ -686,7 +894,11 @@ let betSyncReplayUntilSeq: number | null = null; if (!predictionMarketsOverview) { persistPredictionMarketsOverview( - derivePredictionMarketsOverview(loadKeeperBotHealthSnapshot(), streamState, null), + derivePredictionMarketsOverview( + effectiveKeeperBotHealthSnapshot(), + streamState, + null, + ), ); } @@ -702,6 +914,11 @@ const bscContractAddress = ( process.env.CLOB_CONTRACT_ADDRESS_BSC || "" ).trim(); +const bscDuelOracleAddress = ( + process.env.BSC_DUEL_ORACLE_ADDRESS || + process.env.ORACLE_CONTRACT_ADDRESS_BSC || + "" +).trim(); const baseRpcUrl = ( process.env.BASE_RPC_URL || process.env.BASE_SEPOLIA_RPC || @@ -719,29 +936,73 @@ const baseContractAddress = ( process.env.CLOB_CONTRACT_ADDRESS_BASE || "" ).trim(); +const baseDuelOracleAddress = ( + process.env.BASE_DUEL_ORACLE_ADDRESS || + process.env.ORACLE_CONTRACT_ADDRESS_BASE || + "" +).trim(); const avaxContractAddress = ( process.env.AVAX_GOLD_CLOB_ADDRESS || "" ).trim(); +const avaxDuelOracleAddress = ( + process.env.AVAX_DUEL_ORACLE_ADDRESS || + process.env.ORACLE_CONTRACT_ADDRESS_AVAX || + "" +).trim(); +function evmKeeperChainEnabled(chainKey: "bsc" | "base" | "avax"): boolean { + return enabledEvmKeeperChains.has(chainKey); +} const bscClient = - bscRpcUrl && bscContractAddress + evmKeeperChainEnabled("bsc") && bscRpcUrl && bscContractAddress ? createPublicClient({ transport: http(bscRpcUrl) }) : null; const baseClient = - baseRpcUrl && baseContractAddress + evmKeeperChainEnabled("base") && baseRpcUrl && baseContractAddress ? createPublicClient({ transport: http(baseRpcUrl) }) : null; const avaxClient = - avaxRpcUrl && avaxContractAddress + evmKeeperChainEnabled("avax") && avaxRpcUrl && avaxContractAddress ? createPublicClient({ transport: http(avaxRpcUrl) }) : null; const EVM_RPC_PROXY_TARGETS = { - bsc: bscRpcUrl, - base: baseRpcUrl, - avax: avaxRpcUrl, + bsc: evmKeeperChainEnabled("bsc") ? bscRpcUrl : "", + base: evmKeeperChainEnabled("base") ? baseRpcUrl : "", + avax: evmKeeperChainEnabled("avax") ? avaxRpcUrl : "", } as const; type SupportedEvmRpcChain = keyof typeof EVM_RPC_PROXY_TARGETS; +const EXTERNAL_SOLANA_KEEPER_BOT_HEALTH_URL = firstNonEmptyString( + process.env.SOLANA_KEEPER_BOT_HEALTH_URL, + process.env.HYPERBET_SOLANA_KEEPER_URL + ? `${process.env.HYPERBET_SOLANA_KEEPER_URL.replace(/\/$/, "")}/api/keeper/bot-health` + : "", + process.env.ENOOMIAN_HYPERBET_SOLANA_KEEPER_URL + ? `${process.env.ENOOMIAN_HYPERBET_SOLANA_KEEPER_URL.replace(/\/$/, "")}/api/keeper/bot-health` + : "", +); +const EXTERNAL_SOLANA_KEEPER_BASE_URL = firstNonEmptyString( + process.env.HYPERBET_SOLANA_KEEPER_URL, + process.env.ENOOMIAN_HYPERBET_SOLANA_KEEPER_URL, +); +const EXTERNAL_SOLANA_KEEPER_BOT_HEALTH_BEARER_TOKEN = ( + process.env.SOLANA_KEEPER_BOT_HEALTH_BEARER_TOKEN || + "" +).trim(); + +type ExternalHealthSourceState = { + sourceUrl: string | null; + snapshot: KeeperBotHealthSnapshot | null; + lastFetchedAt: number | null; + lastError: string | null; +}; + +externalSolanaBotHealthState = { + sourceUrl: EXTERNAL_SOLANA_KEEPER_BOT_HEALTH_URL || null, + snapshot: null, + lastFetchedAt: null, + lastError: null, +}; const SOLANA_RPC_CACHE_TTL_MS: Record = { getAccountInfo: 750, @@ -1173,6 +1434,19 @@ function applyCors(req: Request, headers: Headers): void { headers.set("access-control-max-age", "86400"); } +function buildSseHeaders(req: Request): Headers { + const headers = new Headers({ + "content-type": "text/event-stream; charset=utf-8", + "cache-control": + "no-store, no-cache, must-revalidate, proxy-revalidate, no-transform", + pragma: "no-cache", + "x-accel-buffering": "no", + ...securityHeaders(), + }); + applyCors(req, headers); + return headers; +} + function jsonResponse( req: Request, body: unknown, @@ -1185,7 +1459,12 @@ function jsonResponse( ...extraHeaders, }); applyCors(req, headers); - return new Response(JSON.stringify(body), { status, headers }); + return new Response( + JSON.stringify(body, (_key, value) => + typeof value === "bigint" ? value.toString() : value, + ), + { status, headers }, + ); } function textResponse( @@ -1412,14 +1691,107 @@ function isSupportedEvmRpcChain( return Object.hasOwn(EVM_RPC_PROXY_TARGETS, value); } -function handlePerpsOracleHistory(req: Request, url: URL): Response { +function isSupportedLocalPerpsChainKey( + value: PublicPerpsChainKey, +): value is EvmPerpsChainKey { + return value === "bsc" || value === "base" || value === "avax"; +} + +async function fetchExternalSolanaPerpsPayload( + req: Request, + url: URL, + pathname: "/api/perps/markets" | "/api/perps/oracle-history", +): Promise { + if (!EXTERNAL_SOLANA_KEEPER_BASE_URL) { + return jsonResponse( + req, + { error: "solana perps source unavailable" }, + 503, + { "cache-control": "no-store" }, + ); + } + + const upstreamUrl = buildExternalPerpsUrl({ + baseUrl: EXTERNAL_SOLANA_KEEPER_BASE_URL, + pathname, + searchParams: url.searchParams, + }); + + try { + const response = await fetch(upstreamUrl, { + headers: { Accept: "application/json" }, + signal: AbortSignal.timeout(10_000), + }); + const payload = await response.json().catch(() => null); + if (!response.ok) { + return jsonResponse( + req, + (payload && + typeof payload === "object" && + payload != null && + !Array.isArray(payload) + ? payload + : { error: `solana perps upstream returned ${response.status}` }) as Record< + string, + unknown + >, + response.status, + { "cache-control": "no-store" }, + ); + } + + return jsonResponse( + req, + pathname === "/api/perps/markets" + ? adaptLegacySolanaPerpsMarketsPayload(payload) + : adaptLegacySolanaPerpsOracleHistoryPayload(payload), + 200, + { "cache-control": "no-store" }, + ); + } catch (error) { + return jsonResponse( + req, + { + error: + error instanceof Error + ? error.message + : "solana perps upstream request failed", + }, + 502, + { "cache-control": "no-store" }, + ); + } +} + +async function handlePerpsOracleHistory(req: Request, url: URL): Promise { + const requestedChainKey = url.searchParams.get("chainKey"); + const chainKey = normalizePublicPerpsChainKeyParam(requestedChainKey); + if (!chainKey) { + if (typeof console !== "undefined") { + console.warn("[hyperbet] perps_chain_missing", { + endpoint: "/api/perps/oracle-history", + requestedChainKey, + }); + } + return jsonResponse(req, { error: "chainKey is required" }, 400); + } + if (chainKey === "solana") { + return fetchExternalSolanaPerpsPayload( + req, + url, + "/api/perps/oracle-history", + ); + } + if (!isSupportedLocalPerpsChainKey(chainKey)) { + return jsonResponse(req, { error: "unsupported chainKey" }, 400); + } const characterId = url.searchParams.get("characterId")?.trim() || ""; if (!characterId) { return jsonResponse(req, { error: "characterId is required" }, 400); } const limit = parseBoundedInteger(url.searchParams.get("limit"), 120, 1, 500); - const snapshots = loadPerpsOracleSnapshots(characterId, limit) + const snapshots = loadChainScopedPerpsOracleSnapshots(chainKey, characterId, limit) .slice() .reverse(); const marketId = @@ -1428,6 +1800,7 @@ function handlePerpsOracleHistory(req: Request, url: URL): Response { return jsonResponse( req, { + chainKey, characterId, marketId, snapshots, @@ -1440,11 +1813,15 @@ function handlePerpsOracleHistory(req: Request, url: URL): Response { ); } -function handlePerpsMarkets(req: Request): Response { - return jsonResponse( - req, - { - markets: loadPerpsMarkets().map((market) => ({ +async function handlePerpsMarkets(req: Request, url: URL): Promise { + const requestedChainKey = url.searchParams.get("chainKey"); + const normalizedChainKey = normalizePublicPerpsChainKeyParam(requestedChainKey); + if (!requestedChainKey) { + const markets = BETTING_EVM_CHAIN_ORDER.filter((chainKey) => + enabledEvmKeeperChains.has(chainKey), + ).flatMap((chainKey) => + loadChainScopedPerpsMarkets(chainKey).map((market) => ({ + chainKey: market.chainKey, characterId: market.agentId, marketId: market.marketId, rank: market.rank, @@ -1461,6 +1838,73 @@ function handlePerpsMarkets(req: Request): Response { deprecatedAt: market.deprecatedAt, updatedAt: market.updatedAt, })), + ); + if (typeof console !== "undefined") { + console.info("[hyperbet] perps_chain_defaulted", { + endpoint: "/api/perps/markets", + chainKey: null, + source: "legacy", + }); + } + return jsonResponse( + req, + { + chainKey: null, + markets, + updatedAt: Date.now(), + }, + 200, + { + "cache-control": "no-store", + }, + ); + } + const chainKey = normalizedChainKey; + if (!chainKey) { + if (typeof console !== "undefined") { + console.warn("[hyperbet] perps_chain_missing", { + endpoint: "/api/perps/markets", + requestedChainKey, + }); + } + return jsonResponse(req, { error: "chainKey is required" }, 400); + } + if (chainKey === "solana") { + return fetchExternalSolanaPerpsPayload(req, url, "/api/perps/markets"); + } + if (!isSupportedLocalPerpsChainKey(chainKey)) { + return jsonResponse(req, { error: "unsupported chainKey" }, 400); + } + + const markets = loadChainScopedPerpsMarkets(chainKey).map((market) => ({ + chainKey: market.chainKey, + characterId: market.agentId, + marketId: market.marketId, + rank: market.rank, + name: market.name, + provider: market.provider, + model: market.model, + wins: market.wins, + losses: market.losses, + winRate: market.winRate, + combatLevel: market.combatLevel, + currentStreak: market.currentStreak, + status: market.status, + lastSeenAt: market.lastSeenAt, + deprecatedAt: market.deprecatedAt, + updatedAt: market.updatedAt, + })); + if (markets.length === 0 && typeof console !== "undefined") { + console.warn("[hyperbet] empty_model_directory", { + chainKey, + endpoint: "/api/perps/markets", + }); + } + return jsonResponse( + req, + { + chainKey, + markets, updatedAt: Date.now(), }, 200, @@ -1471,6 +1915,15 @@ function handlePerpsMarkets(req: Request): Response { } function handleDuelContext(req: Request): Response { + // `sourceEmittedAt` anchors this response to the stream frame we last + // observed — the selector key viewer-clock alignment will use. + // `serverEmittedAt` is the wall-clock moment we emit this response — + // the staleness key for max-age budgets. Legacy `updatedAt` is kept + // equal to the source anchor for backwards compatibility with + // consumers that pre-date the split. + const sourceEmittedAt = + typeof streamState.emittedAt === "number" ? streamState.emittedAt : null; + const serverEmittedAt = Date.now(); return jsonResponse( req, { @@ -1478,7 +1931,9 @@ function handleDuelContext(req: Request): Response { cycle: streamState.cycle, leaderboard: streamState.leaderboard, cameraTarget: streamState.cameraTarget, - updatedAt: streamState.emittedAt, + updatedAt: sourceEmittedAt, + sourceEmittedAt, + serverEmittedAt, }, 200, { @@ -1545,22 +2000,15 @@ function enumName(value: unknown): string | null { return typeof key === "string" && key.length > 0 ? key : null; } -function resolveLifecycleFromSolanaStatus( - status: string | null, +function resolveLifecycleFromSolanaSnapshot( + duelStatus: string | null, + marketStatus: string | null, fallback: PredictionMarketLifecycleStatus, ): PredictionMarketLifecycleStatus { - switch (status?.toLowerCase()) { - case "open": - return "OPEN"; - case "locked": - return "LOCKED"; - case "resolved": - return "RESOLVED"; - case "cancelled": - return "CANCELLED"; - default: - return fallback; - } + const duelLifecycle = resolveLifecycleFromSolanaDuelStatus(duelStatus); + if (duelLifecycle !== "UNKNOWN") return duelLifecycle; + const marketLifecycle = resolveLifecycleFromSolanaMarketStatus(marketStatus); + return marketLifecycle !== "UNKNOWN" ? marketLifecycle : fallback; } function resolveWinnerFromSolanaState( @@ -1579,6 +2027,85 @@ function resolveWinnerFromSolanaState( } } +function isSyntheticSolanaLifecycleEnabled(): boolean { + return Boolean(process.env.STATE_JSON_PATH?.trim()); +} + +function syntheticSolanaDuelStatusFromCycle( + sourceState: StreamState = streamState, +): string | null { + const phase = + typeof sourceState.cycle?.phase === "string" + ? sourceState.cycle.phase.trim().toUpperCase() + : null; + const betCloseTime = currentBetCloseTime(sourceState); + const winner = currentWinnerFromCycle(sourceState); + const now = Date.now(); + switch (phase) { + case "ANNOUNCEMENT": + case "COUNTDOWN": + case "FIGHTING": + return betCloseTime == null || betCloseTime > now + ? "betting_open" + : "locked"; + case "RESOLUTION": + return winner === "NONE" ? "proposed" : "resolved"; + default: + return null; + } +} + +function syntheticSolanaWinnerFromCycle( + sourceState: StreamState = streamState, +): string { + switch (currentWinnerFromCycle(sourceState)) { + case "A": + return "a"; + case "B": + return "b"; + default: + return "none"; + } +} + +function buildSyntheticSolanaLifecycleSnapshot( + sourceState: StreamState = streamState, +): JsonRecord | null { + if (!isSyntheticSolanaLifecycleEnabled()) return null; + const duelKey = currentDuelKey(sourceState); + const duelId = currentDuelId(sourceState); + if (!duelKey && !duelId) return null; + + let currentMarketPda: string | null = null; + if (duelKey != null) { + try { + currentMarketPda = findMarketPda( + GOLD_CLOB_MARKET_PROGRAM_ID, + findDuelStatePda(FIGHT_ORACLE_PROGRAM_ID, duelKeyHexToBytes(duelKey)), + ).toBase58(); + } catch { + currentMarketPda = null; + } + } + + return { + rpc: "synthetic:e2e", + fightOracleProgram: FIGHT_ORACLE_PROGRAM_ID.toBase58(), + marketProgram: GOLD_CLOB_MARKET_PROGRAM_ID.toBase58(), + fightAccountCount: duelKey != null ? 1 : 0, + marketAccountCount: currentMarketPda != null ? 1 : 0, + latestFightAccount: null, + latestMarketAccount: currentMarketPda, + derivedMarketPda: currentMarketPda, + currentMarketPda, + currentDuelStatus: syntheticSolanaDuelStatusFromCycle(sourceState), + currentMarketStatus: null, + currentMarketWinner: syntheticSolanaWinnerFromCycle(sourceState), + recentSignature: "synthetic-stream-state", + syntheticSyncedAt: Date.now(), + }; +} + function resolvePhaseFromLifecycleStatus( lifecycleStatus: PredictionMarketLifecycleStatus | null | undefined, ): string | null { @@ -1618,9 +2145,12 @@ function buildPredictionMarketLifecycleRecords( ); const cycleWinner = duelOverride?.winner ?? currentWinnerFromCycle(); const records: PredictionMarketLifecycleRecord[] = []; + const solanaSnapshot = + (parsers.solana.snapshot as JsonRecord | null) ?? + buildSyntheticSolanaLifecycleSnapshot(); - if (parsers.solana.enabled || parsers.solana.snapshot) { - const snapshot = parsers.solana.snapshot as JsonRecord | null; + if (parsers.solana.enabled || solanaSnapshot) { + const snapshot = solanaSnapshot; const currentDerivedMarketPda = typeof snapshot?.derivedMarketPda === "string" ? snapshot.derivedMarketPda @@ -1644,7 +2174,10 @@ function buildPredictionMarketLifecycleRecords( findDuelStatePda(FIGHT_ORACLE_PROGRAM_ID, duelKeyHexToBytes(duelKey)), ).toBase58() : null; - const solanaLifecycle = resolveLifecycleFromSolanaStatus( + const solanaLifecycle = resolveLifecycleFromSolanaSnapshot( + typeof snapshot?.currentDuelStatus === "string" + ? snapshot.currentDuelStatus + : null, typeof snapshot?.currentMarketStatus === "string" ? snapshot.currentMarketStatus : null, @@ -1676,7 +2209,11 @@ function buildPredictionMarketLifecycleRecords( contractAddress: null, programId: currentMarketProgram, txRef: currentRecentSignature, - syncedAt: parsers.solana.lastSuccessAt, + syncedAt: + parsers.solana.lastSuccessAt ?? + (typeof snapshot?.syntheticSyncedAt === "number" + ? snapshot.syntheticSyncedAt + : null), metadata: { fightAccountCount: snapshot?.fightAccountCount ?? null, marketAccountCount: snapshot?.marketAccountCount ?? null, @@ -1685,12 +2222,12 @@ function buildPredictionMarketLifecycleRecords( } for (const chainKey of ["bsc", "base", "avax"] as const) { + if (!evmKeeperChainEnabled(chainKey)) continue; const parser = parsers[chainKey]; const fallbackHealth = selectBotHealthMarket(botHealthSnapshot, chainKey); if (!parser.enabled && !parser.snapshot && !fallbackHealth) continue; const snapshot = parser.snapshot as JsonRecord | null; - records.push( - buildEvmPredictionMarketLifecycleRecord({ + const record = buildEvmPredictionMarketLifecycleRecord({ chainKey, duelKey, duelId, @@ -1706,8 +2243,40 @@ function buildPredictionMarketLifecycleRecords( : avaxContractAddress ) ?? null, syncedAt: parser.lastSuccessAt ?? botHealthSnapshot?.updatedAtMs ?? null, - }), - ); + }); + const duelOracleAddress = + chainKey === "bsc" + ? bscDuelOracleAddress + : chainKey === "base" + ? baseDuelOracleAddress + : avaxDuelOracleAddress; + records.push({ + ...record, + metadata: { + ...(record.metadata ?? {}), + marketStatus: + typeof snapshot?.currentMatch === "object" && + snapshot.currentMatch && + "status" in snapshot.currentMatch + ? snapshot.currentMatch.status + : null, + duelOracleStatus: + typeof snapshot?.currentDuel === "object" && + snapshot.currentDuel && + "status" in snapshot.currentDuel + ? snapshot.currentDuel.status + : null, + marketKey: + typeof snapshot?.marketKey === "string" + ? snapshot.marketKey + : record.marketRef, + duelOracleAddress: duelOracleAddress || null, + parserLastSuccessAt: parser.lastSuccessAt, + parserLastError: parser.lastError, + recoveredFromBotHealth: + record.metadata?.recoveredFromBotHealth ?? false, + }, + }); } return records; @@ -1728,6 +2297,215 @@ function currentDuelSnapshot( }; } +function isStreamSafeForBetting(sourceState: StreamState): boolean { + const publicReadiness = asJsonRecord(sourceState.publicReadiness); + const topLevelRenderer = asJsonRecord(sourceState.rendererHealth); + const cycle = asJsonRecord(sourceState.cycle); + const cycleRenderer = asJsonRecord(cycle?.rendererHealth); + const publicReady = + typeof publicReadiness?.ready === "boolean" ? publicReadiness.ready : true; + const rendererReady = + typeof topLevelRenderer?.ready === "boolean" + ? topLevelRenderer.ready + : typeof cycleRenderer?.ready === "boolean" + ? cycleRenderer.ready + : true; + return publicReady && rendererReady; +} + +function resolvePublicMarketParity( + botHealthSnapshot: KeeperBotHealthSnapshot | null, + sourceState: StreamState, +): KeeperMarketParitySnapshot | null { + const rawParity = + botHealthSnapshot?.marketParity ?? + sourceState.marketParity ?? + buildProjectedMarketParitySnapshot({ + duelKey: currentDuelKey(sourceState), + duelId: currentDuelId(sourceState), + phase: + typeof sourceState.cycle?.phase === "string" + ? sourceState.cycle.phase + : null, + requiredChains: requiredParityChains, + markets: buildPredictionMarketLifecycleRecords( + botHealthSnapshot, + currentDuelSnapshot(sourceState), + ), + updatedAtMs: sourceState.emittedAt, + streamSafe: isStreamSafeForBetting(sourceState), + }); + if (!rawParity) return null; + const publicParity = redactPendingMarketParity(rawParity); + if (!publicParity) return null; + return { + ...publicParity, + safeToBet: + publicParity.state === "open" && + publicParity.safeToBet && + isStreamSafeForBetting(sourceState), + }; +} + +function maskNonPublicStreamChannel( + channel: StreamState["channel"] | null | undefined, +): StreamState["channel"] | null | undefined { + const channelRecord = asJsonRecord(channel); + if (!channelRecord) return channel; + return { + ...channelRecord, + activeDuelId: null, + activeDuelKey: null, + }; +} + +function sanitizeSourceTimeline( + value: unknown, +): Record | null { + const candidate = asJsonRecord(value); + if (!candidate) return null; + return { + phase: + typeof candidate.phase === "string" || candidate.phase === null + ? candidate.phase + : null, + betOpenTime: + typeof candidate.betOpenTime === "number" && + Number.isFinite(candidate.betOpenTime) + ? candidate.betOpenTime + : null, + betCloseTime: + typeof candidate.betCloseTime === "number" && + Number.isFinite(candidate.betCloseTime) + ? candidate.betCloseTime + : null, + fightStartTime: + typeof candidate.fightStartTime === "number" && + Number.isFinite(candidate.fightStartTime) + ? candidate.fightStartTime + : null, + duelEndTime: + typeof candidate.duelEndTime === "number" && + Number.isFinite(candidate.duelEndTime) + ? candidate.duelEndTime + : null, + updatedAt: + typeof candidate.updatedAt === "number" && + Number.isFinite(candidate.updatedAt) + ? candidate.updatedAt + : null, + }; +} + +function projectPublicStreamState( + sourceState: StreamState, + botHealthSnapshot: KeeperBotHealthSnapshot | null, +): StreamState { + const publicMarketParity = resolvePublicMarketParity( + botHealthSnapshot, + sourceState, + ); + if ( + isPublicMarketParitySnapshotForSourceDuel( + publicMarketParity, + currentDuelKey(sourceState), + currentDuelId(sourceState), + ) + ) { + return { + ...sourceState, + marketParity: publicMarketParity, + }; + } + + const cycle = asJsonRecord(sourceState.cycle) ?? {}; + const existingTimeline = + asJsonRecord(cycle.broadcastTimeline) ?? + asJsonRecord(sourceState.broadcastTimeline); + const sourceTimeline = + sanitizeSourceTimeline(cycle.sourceTimeline) ?? + sanitizeSourceTimeline(sourceState.sourceTimeline); + const unmaskDuelIdentity = + sourceStatePhaseAllowsUnmaskedDuelIdentity(sourceState); + const effectivePhase = unmaskDuelIdentity + ? (typeof cycle.phase === "string" + ? cycle.phase + : typeof sourceState.phase === "string" + ? sourceState.phase + : "ANNOUNCEMENT") + : "ANNOUNCEMENT"; + const effectiveTimelinePhase = unmaskDuelIdentity + ? (typeof existingTimeline?.phase === "string" + ? existingTimeline.phase + : effectivePhase) + : "ANNOUNCEMENT"; + const maskedTimeline = { + phase: effectiveTimelinePhase, + betOpenTime: null, + betCloseTime: null, + fightStartTime: null, + duelEndTime: null, + presentationDelayMs: + typeof existingTimeline?.presentationDelayMs === "number" + ? existingTimeline.presentationDelayMs + : 0, + updatedAt: + typeof existingTimeline?.updatedAt === "number" + ? existingTimeline.updatedAt + : sourceState.emittedAt, + }; + + return { + ...sourceState, + cycle: { + ...cycle, + duelId: unmaskDuelIdentity ? (cycle.duelId ?? null) : null, + duelKey: unmaskDuelIdentity ? (cycle.duelKey ?? null) : null, + duelKeyHex: unmaskDuelIdentity ? (cycle.duelKeyHex ?? null) : null, + rawCycle: null, + phase: effectivePhase, + broadcastTimeline: maskedTimeline, + ...(sourceTimeline ? { sourceTimeline } : {}), + betOpenTime: null, + betCloseTime: null, + fightStartTime: null, + duelEndTime: null, + winnerId: null, + winnerName: null, + winReason: null, + seed: null, + replayHash: null, + countdown: null, + agent1: null, + agent2: null, + }, + channel: unmaskDuelIdentity + ? sourceState.channel + : maskNonPublicStreamChannel(sourceState.channel), + phase: effectivePhase, + broadcastTimeline: maskedTimeline, + marketParity: publicMarketParity, + }; +} + +function currentPublicStreamState( + botHealthSnapshot: KeeperBotHealthSnapshot | null = effectiveKeeperBotHealthSnapshot(), + sourceState: StreamState = streamState, +): StreamState { + return projectPublicStreamState(sourceState, botHealthSnapshot); +} + +function filterEnabledPredictionMarkets( + markets: PredictionMarketLifecycleRecord[], +): PredictionMarketLifecycleRecord[] { + return markets.filter((market) => { + if (market.chainKey === "solana") return true; + return evmKeeperChainEnabled( + market.chainKey as "bsc" | "base" | "avax", + ); + }); +} + function filterPredictionMarketsForDuel( markets: PredictionMarketLifecycleRecord[], duel: PredictionMarketsSurface["duel"], @@ -1764,12 +2542,37 @@ function buildLivePredictionMarketsSurface( sourceState: StreamState = streamState, previousOverview: PredictionMarketsOverviewResponse | null = predictionMarketsOverview, ): PredictionMarketsSurface { - const effectiveBotHealth = botHealthSnapshot ?? loadKeeperBotHealthSnapshot(); - const streamDuel = currentDuelSnapshot(sourceState); + const effectiveBotHealth = + botHealthSnapshot ?? effectiveKeeperBotHealthSnapshot(); + const publicStreamState = projectPublicStreamState( + sourceState, + effectiveBotHealth, + ); + const publicMarketParity = publicStreamState.marketParity ?? null; + const streamDuel = currentDuelSnapshot(publicStreamState); const streamDuelKey = streamDuel.duelKey; const streamDuelId = streamDuel.duelId; const cyclePhase = streamDuel.phase; const previousLive = previousOverview?.live ?? null; + // Source-time anchor: the upstream stream frame that drove this + // surface derivation. Threaded through every surface return below. + const liveSourceEmittedAt = + typeof sourceState.emittedAt === "number" ? sourceState.emittedAt : null; + const nowMs = Date.now(); + + if ( + !publicMarketParity || + !isPublicMarketParitySnapshot(publicMarketParity) + ) { + return { + duel: streamDuel, + markets: [], + updatedAt: nowMs, + sourceEmittedAt: liveSourceEmittedAt, + serverEmittedAt: nowMs, + marketParity: publicMarketParity, + }; + } // Preserve the just-finished duel across the transient source IDLE gap so it // can roll into recentSettlement with intact duel identity and timing. @@ -1783,46 +2586,106 @@ function buildLivePredictionMarketsSurface( previousLive.duel, effectiveBotHealth, previousLive, + liveSourceEmittedAt, ); } - const markets = buildPredictionMarketLifecycleRecords( - effectiveBotHealth, - streamDuel, + const streamDuelMatchesParity = marketParityMatchesDuel( + publicMarketParity, + streamDuelKey, + streamDuelId, + ); + const previousLiveMatchesParity = marketParityMatchesDuel( + publicMarketParity, + previousLive?.duel.duelKey, + previousLive?.duel.duelId, + ); + if ( + streamCycleAdvancedBeyondPinnedParity({ + streamCycleId: publicStreamState.cycle?.cycleId, + previousLiveDuelId: previousLive?.duel.duelId, + marketParityDuelId: publicMarketParity.duelId, + }) + ) { + return { + duel: streamDuel, + markets: [], + updatedAt: nowMs, + sourceEmittedAt: liveSourceEmittedAt, + serverEmittedAt: nowMs, + marketParity: publicMarketParity, + }; + } + const publicDuel: PredictionMarketsSurface["duel"] = streamDuelMatchesParity + ? streamDuel + : previousLiveMatchesParity && previousLive + ? previousLive.duel + : { + duelKey: publicMarketParity.duelKey, + duelId: publicMarketParity.duelId, + phase: publicMarketParity.phase, + winner: "NONE", + betCloseTime: null, + agent1Name: null, + agent2Name: null, + }; + + const markets = applyMarketParityReceiptsToMarkets( + buildPredictionMarketLifecycleRecords( + effectiveBotHealth, + publicDuel, + ), + publicMarketParity, + publicDuel.winner, ); const fallbackMarket = markets.find((market) => market.duelKey != null || market.duelId != null) ?? null; - const cycleWinner = streamDuel.winner; + const cycleWinner = publicDuel.winner; return { duel: { - duelKey: streamDuelKey ?? fallbackMarket?.duelKey ?? null, - duelId: streamDuelId ?? fallbackMarket?.duelId ?? null, - phase: cyclePhase ?? resolvePhaseFromLifecycleStatus(fallbackMarket?.lifecycleStatus), + duelKey: publicDuel.duelKey ?? fallbackMarket?.duelKey ?? null, + duelId: publicDuel.duelId ?? fallbackMarket?.duelId ?? null, + phase: + publicDuel.phase ?? + resolvePhaseFromLifecycleStatus(fallbackMarket?.lifecycleStatus), winner: cycleWinner !== "NONE" ? cycleWinner : (fallbackMarket?.winner ?? "NONE"), betCloseTime: - streamDuel.betCloseTime ?? fallbackMarket?.betCloseTime ?? null, + publicDuel.betCloseTime ?? fallbackMarket?.betCloseTime ?? null, agent1Name: - streamDuel.agent1Name ?? + publicDuel.agent1Name ?? previousLive?.duel.agent1Name ?? null, agent2Name: - streamDuel.agent2Name ?? + publicDuel.agent2Name ?? previousLive?.duel.agent2Name ?? null, }, - markets, - updatedAt: Date.now(), + markets: filterEnabledPredictionMarkets(markets), + updatedAt: nowMs, + sourceEmittedAt: liveSourceEmittedAt, + serverEmittedAt: nowMs, + marketParity: publicMarketParity, }; } +/** + * Build a surface for a specific duel identity, using the given source + * emission anchor. `sourceEmittedAt` is the upstream + * `streamState.emittedAt` that drove this derivation; pass `null` only + * when the caller genuinely lacks a source anchor (e.g. preservation + * paths restoring a previous surface whose anchor should be kept intact + * via merge semantics). + */ function buildPredictionMarketsSurfaceForDuel( duel: PredictionMarketsSurface["duel"], botHealthSnapshot: KeeperBotHealthSnapshot | null, previous: PredictionMarketsSurface | null, + sourceEmittedAt: number | null, ): PredictionMarketsSurface { + const nowMs = Date.now(); const nextSurface: PredictionMarketsSurface = { duel, markets: filterPredictionMarketsForDuel( @@ -1834,9 +2697,16 @@ function buildPredictionMarketsSurfaceForDuel( duelId: duel.duelId ?? market.duelId ?? null, betCloseTime: duel.betCloseTime ?? market.betCloseTime ?? null, })), - updatedAt: Date.now(), + updatedAt: nowMs, + sourceEmittedAt, + serverEmittedAt: nowMs, + marketParity: botHealthSnapshot?.marketParity ?? null, + }; + const merged = mergePredictionMarketsSurface(previous, nextSurface) ?? nextSurface; + return { + ...merged, + markets: filterEnabledPredictionMarkets(merged.markets), }; - return mergePredictionMarketsSurface(previous, nextSurface) ?? nextSurface; } function derivePredictionMarketsOverview( @@ -1844,32 +2714,53 @@ function derivePredictionMarketsOverview( sourceState: StreamState = streamState, previousOverview: PredictionMarketsOverviewResponse | null = predictionMarketsOverview, ): PredictionMarketsOverviewResponse { - const effectiveBotHealth = botHealthSnapshot ?? loadKeeperBotHealthSnapshot(); + const effectiveBotHealth = + botHealthSnapshot ?? effectiveKeeperBotHealthSnapshot(); const live = buildLivePredictionMarketsSurface( effectiveBotHealth, sourceState, previousOverview, ); + const rolledAt = Date.now(); const rolled = rollPredictionMarketsOverview( previousOverview, live, - Date.now(), + rolledAt, ); + // When refreshing the rolled recentSettlement for market updates + // (lifecycle records / parity receipts may have changed), preserve + // the surface's ORIGINAL `sourceEmittedAt` — that anchor ties the + // surface back to the source frame that produced it. Re-stamping + // with the live source would lie about when that settlement + // originated from the keeper's perspective. const refreshedRecentSettlement = rolled.recentSettlement ? buildPredictionMarketsSurfaceForDuel( rolled.recentSettlement.duel, effectiveBotHealth, rolled.recentSettlement, + rolled.recentSettlement.sourceEmittedAt, ) : null; + // Recompute envelope `sourceEmittedAt` since the refreshed + // settlement may carry a different anchor than what `rolled` + // computed before the refresh. + const finalRecentSettlement = + refreshedRecentSettlement && sameDuelIdentity(refreshedRecentSettlement, live) + ? null + : refreshedRecentSettlement; + const envelopeSourceCandidates = [ + live?.sourceEmittedAt, + finalRecentSettlement?.sourceEmittedAt, + ].filter((value): value is number => typeof value === "number"); const next: PredictionMarketsOverviewResponse = { - updatedAt: Date.now(), + updatedAt: rolledAt, + sourceEmittedAt: + envelopeSourceCandidates.length > 0 + ? Math.max(...envelopeSourceCandidates) + : null, + serverEmittedAt: rolledAt, live, - recentSettlement: - refreshedRecentSettlement && - sameDuelIdentity(refreshedRecentSettlement, live) - ? null - : refreshedRecentSettlement, + recentSettlement: finalRecentSettlement, }; return next; } @@ -1888,16 +2779,18 @@ function refreshPredictionMarketsOverview( } function handlePredictionMarkets(req: Request): Response { - const overview = - predictionMarketsOverview ?? refreshPredictionMarketsOverview(); + const overview = refreshPredictionMarketsOverview( + effectiveKeeperBotHealthSnapshot(), + ); return jsonResponse(req, overview.live, 200, { "cache-control": "no-store", }); } function handlePredictionMarketsOverview(req: Request): Response { - const overview = - predictionMarketsOverview ?? refreshPredictionMarketsOverview(); + const overview = refreshPredictionMarketsOverview( + effectiveKeeperBotHealthSnapshot(), + ); return jsonResponse(req, overview, 200, { "cache-control": "no-store", }); @@ -2109,33 +3002,12 @@ async function authorizeExternalBetRecord( } function toStreamState(payload: unknown): StreamState | null { - if (!payload || typeof payload !== "object") return null; - - const candidate = payload as JsonRecord; - const cycle = candidate.cycle; - if (!cycle || typeof cycle !== "object") return null; - - return { - type: "STREAMING_STATE_UPDATE", - cycle: cycle as JsonRecord, - leaderboard: Array.isArray(candidate.leaderboard) - ? (candidate.leaderboard as JsonRecord[]) - : [], - cameraTarget: - typeof candidate.cameraTarget === "string" || - candidate.cameraTarget === null - ? candidate.cameraTarget - : null, - seq: - typeof candidate.seq === "number" && Number.isFinite(candidate.seq) - ? candidate.seq - : streamSeq + 1, - emittedAt: - typeof candidate.emittedAt === "number" && - Number.isFinite(candidate.emittedAt) - ? candidate.emittedAt - : Date.now(), - }; + return ( + parseStreamStatePayload(payload, { + seq: streamSeq + 1, + emittedAt: Date.now(), + }) as StreamState | null + ); } function sendSse( @@ -2150,9 +3022,13 @@ function sendSse( } function broadcastStreamState(nextState: StreamState, event = "state"): void { + const publicState = currentPublicStreamState( + effectiveKeeperBotHealthSnapshot(), + nextState, + ); for (const controller of sseClients) { try { - sendSse(controller, event, nextState.seq, nextState); + sendSse(controller, event, publicState.seq, publicState); } catch { sseClients.delete(controller); } @@ -2242,7 +3118,7 @@ function commitStreamProjection(params: { function publishStreamState(next: StreamState, sourceLabel: string): void { const nextOverview = derivePredictionMarketsOverview( - loadKeeperBotHealthSnapshot(), + effectiveKeeperBotHealthSnapshot(), next, predictionMarketsOverview, ); @@ -2343,10 +3219,10 @@ async function pollStreamStateSource(): Promise { return; } - const changed = - streamState.cycle?.cycleId !== nextState.cycle?.cycleId || - streamState.cycle?.phase !== nextState.cycle?.phase || - streamState.cycle?.winnerId !== nextState.cycle?.winnerId; + const changed = publicStreamStateChanged( + streamState as BetSyncStreamState, + nextState as BetSyncStreamState, + ); if (changed) { publishStreamState(nextState, "poll"); } @@ -2477,7 +3353,7 @@ async function applyBetSyncEvent( } const nextStreamState = toStreamStateFromBetSyncEvent(event); - const botHealthSnapshot = loadKeeperBotHealthSnapshot(); + const botHealthSnapshot = effectiveKeeperBotHealthSnapshot(); const nextOverview = derivePredictionMarketsOverview( botHealthSnapshot, nextStreamState, @@ -2717,6 +3593,7 @@ function startKeeperBotIfEnabled(): void { ...process.env, GAME_URL: process.env.GAME_URL || `http://127.0.0.1:${PORT}`, EVM_KEEPER_CHAINS: process.env.EVM_KEEPER_CHAINS || "bsc,base,avax", + BOT_LOOP: process.env.BOT_LOOP?.trim() || "true", KEEPER_BOT_HEALTH_FILE, }; @@ -2853,6 +3730,7 @@ async function pollEvmSnapshot( label: "bsc" | "base" | "avax", client: ReturnType | null, contractAddress: string, + duelOracleAddress: string, ): Promise { if (!client || !contractAddress) return; const parser = parsers[label]; @@ -2894,12 +3772,63 @@ async function pollEvmSnapshot( const winner = Number(market?.winner ?? 0); const yesPool = String(market?.totalAShares ?? 0n); const noPool = String(market?.totalBShares ?? 0n); + let currentDuel: Record | null = null; + if (duelOracleAddress) { + try { + const duel = await client.readContract({ + address: duelOracleAddress as Address, + abi: DUEL_OUTCOME_READ_ABI, + functionName: "getDuel", + args: [normalizedDuelKey], + }); + const activeProposalId = + typeof duel?.activeProposalId === "string" + ? duel.activeProposalId + : null; + const disputeWindowSeconds = await client.readContract({ + address: duelOracleAddress as Address, + abi: DUEL_OUTCOME_READ_ABI, + functionName: "disputeWindowSeconds", + }); + let proposal: unknown = null; + if ( + typeof activeProposalId === "string" && + /^0x[0-9a-fA-F]{64}$/.test(activeProposalId) && + activeProposalId.toLowerCase() !== ZERO_HEX_32 + ) { + proposal = await client.readContract({ + address: duelOracleAddress as Address, + abi: DUEL_OUTCOME_READ_ABI, + functionName: "proposals", + args: [activeProposalId as `0x${string}`], + }); + } + currentDuel = { + status: Number(duel?.status ?? 0), + activeProposalId, + proposalProposedAt: + proposal && typeof proposal === "object" && "proposedAt" in proposal + ? proposal.proposedAt + : null, + proposalChallenged: + proposal && typeof proposal === "object" && "challenged" in proposal + ? proposal.challenged + : null, + disputeWindowSeconds, + }; + } catch (error) { + currentDuel = { + readError: error instanceof Error ? error.message : "oracle read failed", + }; + } + } parser.snapshot = { contractAddress, duelKey, duelId, marketKey, + currentDuel, currentMatch: { status, winner, @@ -2922,11 +3851,22 @@ async function pollContractParsers(): Promise { try { await Promise.all([ pollSolanaSnapshot(), - pollEvmSnapshot("bsc", bscClient, bscContractAddress), - pollEvmSnapshot("base", baseClient, baseContractAddress), - pollEvmSnapshot("avax", avaxClient, avaxContractAddress), + pollEvmSnapshot("bsc", bscClient, bscContractAddress, bscDuelOracleAddress), + pollEvmSnapshot( + "base", + baseClient, + baseContractAddress, + baseDuelOracleAddress, + ), + pollEvmSnapshot( + "avax", + avaxClient, + avaxContractAddress, + avaxDuelOracleAddress, + ), + pollExternalSolanaBotHealth(), ]); - refreshPredictionMarketsOverview(loadKeeperBotHealthSnapshot()); + refreshPredictionMarketsOverview(effectiveKeeperBotHealthSnapshot()); } finally { contractPollInFlight = false; } @@ -3002,13 +3942,16 @@ function leaderboardResponse( }; } -function rankResponse(wallet: string): Record { +function rankResponse( + wallet: string, + scope: string | null, +): Record { const normalized = rememberWalletCase(wallet); const canonical = ensureIdentity(normalized); - const rows = leaderboardRows("linked", "alltime"); + const rows = leaderboardRows(scope, "alltime"); const rank = rows.findIndex((entry) => normalizeWallet(entry.wallet) === canonical) + 1; - const wallets = identityWallets(normalized, "linked"); + const wallets = identityWallets(normalized, scope); return { wallet: displayWallet(canonical), @@ -3022,9 +3965,10 @@ function historyResponse( limit: number, offset: number, eventType: string | null, + scope: string | null, ): Record { const normalized = rememberWalletCase(wallet); - const wallets = new Set(identityWallets(normalized, "linked")); + const wallets = new Set(identityWallets(normalized, scope)); const filtered = pointsEvents.filter((entry) => { if (!wallets.has(entry.wallet)) return false; if (eventType && entry.eventType !== eventType) return false; @@ -3056,8 +4000,11 @@ function historyResponse( }; } -function multiplierResponse(wallet: string): Record { - const wallets = identityWallets(wallet, "linked"); +function multiplierResponse( + wallet: string, + scope: string | null, +): Record { + const wallets = identityWallets(wallet, scope); const detail = multiplierDetailForWallets(wallets); return { wallet: wallet.trim(), @@ -3778,9 +4725,11 @@ const server = Bun.serve({ if (url.pathname === "/status") { const botHealthSnapshotRaw = loadKeeperBotHealthSnapshot(); + const effectiveBotHealthSnapshot = + effectiveKeeperBotHealthSnapshot(botHealthSnapshotRaw); const overview = predictionMarketsOverview ?? - refreshPredictionMarketsOverview(botHealthSnapshotRaw); + refreshPredictionMarketsOverview(effectiveBotHealthSnapshot); const predictionMarkets = overview.live?.markets ?? []; const botHealthSnapshot = botHealthSnapshotRaw ? { @@ -3790,7 +4739,7 @@ const server = Bun.serve({ : null; const marketStatuses = mergePredictionMarketsWithHealth( predictionMarkets, - botHealthSnapshot, + effectiveBotHealthSnapshot, ); return jsonResponse(req, { ok: true, @@ -3820,10 +4769,22 @@ const server = Bun.serve({ }, bot: { enabled: ENABLE_KEEPER_BOT, + scope: "bsc", running: Boolean(botSubprocess), lastExitCode: botExitCode, lastExitAt: botLastExitAt, health: botHealthSnapshot, + externalSources: { + solana: { + url: externalSolanaBotHealthState.sourceUrl, + lastFetchedAt: externalSolanaBotHealthState.lastFetchedAt, + lastError: externalSolanaBotHealthState.lastError, + healthUpdatedAt: + externalSolanaBotHealthState.snapshot?.updatedAtMs ?? null, + marketCount: + externalSolanaBotHealthState.snapshot?.markets?.length ?? 0, + }, + }, }, stats: { trackedBets: bets.length, @@ -3833,8 +4794,16 @@ const server = Bun.serve({ predictionMarkets: { activeDuelKey: overview.live?.duel.duelKey ?? currentDuelKey(), marketCount: predictionMarkets.length, - botHealthUpdatedAt: botHealthSnapshot?.updatedAtMs ?? null, + botHealthUpdatedAt: + effectiveBotHealthSnapshot?.updatedAtMs ?? null, overviewUpdatedAt: overview.updatedAt ?? null, + solanaHealthSource: { + url: externalSolanaBotHealthState.sourceUrl, + lastFetchedAt: externalSolanaBotHealthState.lastFetchedAt, + lastError: externalSolanaBotHealthState.lastError, + healthUpdatedAt: + externalSolanaBotHealthState.snapshot?.updatedAtMs ?? null, + }, chains: marketStatuses.map((market) => ({ chainKey: market.chainKey, marketRef: market.marketRef, @@ -3857,10 +4826,27 @@ const server = Bun.serve({ ); } - if (req.method === "GET" && url.pathname === "/api/streaming/state") { - return jsonResponse(req, streamState, 200, { + // `/api/streaming/session` is an alias for `/api/streaming/state`. The + // overhaul bets bundle's `useCanonicalStreamSession` hook fetches the + // `/session` URL but the canonical envelope it consumes (cycle, + // canonicalAuthority, rendererHealth, deliveryHealth, publicReadiness, + // sourceRuntime, channel, marketParity, seq, emittedAt, ...) is exactly + // what `currentPublicStreamState()` already emits on `/state`. Exposing + // both URL names keeps the existing `/state` consumers working while + // letting the canonical-session hook light up with zero client changes. + if ( + req.method === "GET" && + (url.pathname === "/api/streaming/state" || + url.pathname === "/api/streaming/session") + ) { + return jsonResponse( + req, + currentPublicStreamState(effectiveKeeperBotHealthSnapshot()), + 200, + { "cache-control": "no-store", - }); + }, + ); } if ( @@ -3904,7 +4890,32 @@ const server = Bun.serve({ !syncStatus.enabled || (betSyncConsumerRunning && betSyncLastError == null); const botHealthy = Boolean(botSubprocess); - const healthy = betSyncHealthy && botHealthy; + + // Canonical stream health: previously /api/health only reflected + // bet-sync + bot subprocess liveness, which produces false-positive + // `ok: true` responses while the public stream is completely blocked + // (e.g. source_unready / provider_not_live / manifest missing). + // Route this endpoint through projectPublicStreamState so that when + // the canonical stream is not actually serving, /api/health reports + // unhealthy and the response body exposes which layer failed. + const publicStreamState = currentPublicStreamState( + effectiveKeeperBotHealthSnapshot(), + ); + const publicReadinessReady = + publicStreamState.publicReadiness?.ready === true; + const canonicalAuthorityReady = + publicStreamState.canonicalAuthority?.decision === "ready"; + const sourceRuntimeReady = + publicStreamState.sourceRuntime?.ready === true; + const rendererHealthReady = + publicStreamState.rendererHealth?.ready === true; + const streamHealthy = + publicReadinessReady && + canonicalAuthorityReady && + sourceRuntimeReady && + rendererHealthReady; + + const healthy = betSyncHealthy && botHealthy && streamHealthy; return jsonResponse( req, { @@ -3921,6 +4932,23 @@ const server = Bun.serve({ running: botHealthy, exitCode: botExitCode, }, + stream: { + healthy: streamHealthy, + publicReadinessReady, + publicReadinessReason: + publicStreamState.publicReadiness?.reason ?? null, + canonicalAuthorityReady, + canonicalAuthorityDecision: + publicStreamState.canonicalAuthority?.decision ?? null, + canonicalAuthorityReason: + publicStreamState.canonicalAuthority?.reason ?? null, + sourceRuntimeReady, + sourceDegradedReason: + publicStreamState.sourceRuntime?.degradedReason ?? null, + rendererHealthReady, + rendererDegradedReason: + publicStreamState.rendererHealth?.degradedReason ?? null, + }, sseClients: connectedSseCount(), }, healthy ? 200 : 503, @@ -3933,12 +4961,24 @@ const server = Bun.serve({ return jsonResponse(req, { ok: true, running: Boolean(botSubprocess), + scope: "bsc", health: botHealthSnapshotRaw ? { ...botHealthSnapshotRaw, running: Boolean(botSubprocess), } : null, + externalSources: { + solana: { + url: externalSolanaBotHealthState.sourceUrl, + lastFetchedAt: externalSolanaBotHealthState.lastFetchedAt, + lastError: externalSolanaBotHealthState.lastError, + healthUpdatedAt: + externalSolanaBotHealthState.snapshot?.updatedAtMs ?? null, + marketCount: + externalSolanaBotHealthState.snapshot?.markets?.length ?? 0, + }, + }, }); } @@ -3949,9 +4989,15 @@ const server = Bun.serve({ return handleStreamingLeaderboardDetails(req, url); } + // `/api/streaming/session/events` is an alias for + // `/api/streaming/state/events` — same SSE stream, same canonical + // envelope. The overhaul bets bundle's canonical-session hook + // subscribes to the `/session/events` URL; the existing bundles that + // consume `useStreamingState` keep using `/state/events`. if ( req.method === "GET" && - url.pathname === "/api/streaming/state/events" + (url.pathname === "/api/streaming/state/events" || + url.pathname === "/api/streaming/session/events") ) { let sseController: ReadableStreamDefaultController | null = null; @@ -3959,7 +5005,10 @@ const server = Bun.serve({ start(controller) { sseController = controller; sseClients.add(controller); - sendSse(controller, "reset", streamState.seq, streamState); + const publicState = currentPublicStreamState( + effectiveKeeperBotHealthSnapshot(), + ); + sendSse(controller, "reset", publicState.seq, publicState); controller.enqueue(encoder.encode(": connected\n\n")); }, cancel() { @@ -3970,15 +5019,10 @@ const server = Bun.serve({ }, }); - const headers = new Headers({ - "content-type": "text/event-stream; charset=utf-8", - "cache-control": - "no-store, no-cache, must-revalidate, proxy-revalidate", - connection: "keep-alive", - ...securityHeaders(), + return new Response(stream, { + status: 200, + headers: buildSseHeaders(req), }); - applyCors(req, headers); - return new Response(stream, { status: 200, headers }); } if ( @@ -4022,7 +5066,7 @@ const server = Bun.serve({ } if (req.method === "GET" && url.pathname === "/api/perps/markets") { - return handlePerpsMarkets(req); + return handlePerpsMarkets(req, url); } if ( @@ -4035,7 +5079,7 @@ const server = Bun.serve({ if (!wallet) { return jsonResponse(req, { error: "Wallet is required" }, 400); } - return jsonResponse(req, rankResponse(wallet), 200, { + return jsonResponse(req, rankResponse(wallet, url.searchParams.get("scope")), 200, { "cache-control": "no-store", }); } @@ -4066,6 +5110,7 @@ const server = Bun.serve({ limit, offset, url.searchParams.get("eventType"), + url.searchParams.get("scope"), ), 200, { @@ -4086,7 +5131,7 @@ const server = Bun.serve({ if (!wallet) { return jsonResponse(req, { error: "Wallet is required" }, 400); } - return jsonResponse(req, multiplierResponse(wallet), 200, { + return jsonResponse(req, multiplierResponse(wallet, url.searchParams.get("scope")), 200, { "cache-control": "no-store", }); } diff --git a/packages/hyperbet-evm/keeper/src/streamPhaseUnmask.test.ts b/packages/hyperbet-evm/keeper/src/streamPhaseUnmask.test.ts new file mode 100644 index 00000000..e04163c2 --- /dev/null +++ b/packages/hyperbet-evm/keeper/src/streamPhaseUnmask.test.ts @@ -0,0 +1,93 @@ +import { describe, expect, test } from "bun:test"; + +import { sourceStatePhaseAllowsUnmaskedDuelIdentity } from "./streamPhaseUnmask"; + +// `StreamState` is structurally loose enough for these tests that we +// drive the predicate with a minimal inline shape. The helper only +// reads `cycle.phase` and top-level `phase` off the source state. +const build = (overrides: { + cyclePhase?: string | null; + topPhase?: string | null; +}): Parameters[0] => { + const cycle: Record = {}; + if (overrides.cyclePhase !== undefined) cycle.phase = overrides.cyclePhase; + return { + cycle: overrides.cyclePhase !== undefined ? cycle : null, + phase: overrides.topPhase ?? null, + } as unknown as Parameters[0]; +}; + +describe("sourceStatePhaseAllowsUnmaskedDuelIdentity", () => { + test("returns true when cycle.phase is FIGHTING", () => { + expect( + sourceStatePhaseAllowsUnmaskedDuelIdentity(build({ cyclePhase: "FIGHTING" })), + ).toBe(true); + }); + + test("returns true when cycle.phase is RESOLUTION", () => { + expect( + sourceStatePhaseAllowsUnmaskedDuelIdentity( + build({ cyclePhase: "RESOLUTION" }), + ), + ).toBe(true); + }); + + test("returns true when cycle.phase is missing but top-level phase is FIGHTING", () => { + expect( + sourceStatePhaseAllowsUnmaskedDuelIdentity(build({ topPhase: "FIGHTING" })), + ).toBe(true); + }); + + test("returns false when cycle.phase is IDLE", () => { + expect( + sourceStatePhaseAllowsUnmaskedDuelIdentity(build({ cyclePhase: "IDLE" })), + ).toBe(false); + }); + + test("returns false when cycle.phase is ANNOUNCEMENT", () => { + expect( + sourceStatePhaseAllowsUnmaskedDuelIdentity( + build({ cyclePhase: "ANNOUNCEMENT" }), + ), + ).toBe(false); + }); + + test("returns false when cycle.phase is COUNTDOWN", () => { + expect( + sourceStatePhaseAllowsUnmaskedDuelIdentity( + build({ cyclePhase: "COUNTDOWN" }), + ), + ).toBe(false); + }); + + test("returns false when both phases are null/undefined", () => { + expect( + sourceStatePhaseAllowsUnmaskedDuelIdentity(build({})), + ).toBe(false); + }); + + test("returns false for unknown phase strings", () => { + expect( + sourceStatePhaseAllowsUnmaskedDuelIdentity( + build({ cyclePhase: "MYSTERY_PHASE" }), + ), + ).toBe(false); + }); + + test("cycle.phase wins over top-level phase when both are set", () => { + expect( + sourceStatePhaseAllowsUnmaskedDuelIdentity( + build({ cyclePhase: "IDLE", topPhase: "FIGHTING" }), + ), + ).toBe(false); + }); + + test("ignores non-string cycle.phase", () => { + const sourceState = { + cycle: { phase: 42 }, + phase: "FIGHTING", + } as unknown as Parameters[0]; + // cycle.phase is non-string so it falls through to top-level phase + expect(sourceStatePhaseAllowsUnmaskedDuelIdentity(sourceState)).toBe(true); + }); +}); diff --git a/packages/hyperbet-evm/keeper/src/streamPhaseUnmask.ts b/packages/hyperbet-evm/keeper/src/streamPhaseUnmask.ts new file mode 100644 index 00000000..d126b644 --- /dev/null +++ b/packages/hyperbet-evm/keeper/src/streamPhaseUnmask.ts @@ -0,0 +1,47 @@ +import type { StreamState as BetSyncStreamState } from "./betSync"; + +type StreamState = BetSyncStreamState; + +function asJsonRecord(value: unknown): Record | null { + return value && typeof value === "object" + ? (value as Record) + : null; +} + +/** + * Policy predicate for `projectPublicStreamState`: should the mask + * branch pass `cycle.phase`, `cycle.duelId`, `cycle.duelKey`, + * `cycle.duelKeyHex`, the `channel`, `broadcastTimeline.phase`, and + * the top-level `phase` through to the public projection even when + * market parity hasn't confirmed yet? + * + * Rule: true if the source state reports `FIGHTING` or `RESOLUTION` + * on either `cycle.phase` or top-level `phase`. During those phases + * the stream viewport is already showing live combat and the on-chain + * market-parity check trails the duel-api by 5-10 minutes while + * Solana/BSC prediction markets settle — the bets UI needs the phase + * and duel identifiers to label the fight correctly and to silence + * the `streamDriftDetected` drift banner. Betting controls stay + * disabled because `canTrade` is gated on the on-chain market being + * in the `OPEN` lifecycle status, which it cannot be during FIGHTING + * regardless of what the streaming envelope says. Outcome metadata + * (winnerId, winnerName, winReason, seed, replayHash) and agent + * identities remain masked in the mask branch of + * `projectPublicStreamState` even when this predicate returns true, + * so the outcome cannot leak before parity confirms. + * + * Pre-fight phases (`IDLE`, `ANNOUNCEMENT`, `COUNTDOWN`) always + * return false — the existing parity gate stays active for them so + * pre-match betting UI cannot surface before markets are open. + */ +export function sourceStatePhaseAllowsUnmaskedDuelIdentity( + sourceState: StreamState, +): boolean { + const cycleRecord = asJsonRecord(sourceState.cycle); + const cyclePhase = + typeof cycleRecord?.phase === "string" ? cycleRecord.phase : null; + const topPhase = + typeof sourceState.phase === "string" ? sourceState.phase : null; + const phase = cyclePhase ?? topPhase ?? null; + return phase === "FIGHTING" || phase === "RESOLUTION"; +} diff --git a/packages/hyperbet-mm-core/src/index.ts b/packages/hyperbet-mm-core/src/index.ts index 29b431fe..a9f3c69e 100644 --- a/packages/hyperbet-mm-core/src/index.ts +++ b/packages/hyperbet-mm-core/src/index.ts @@ -211,6 +211,47 @@ export interface KeeperMarketHealthRecord { recovery: string[]; } +export type KeeperParityBundleState = + | "preparing" + | "awaiting_confirmations" + | "open" + | "locked" + | "resolved" + | "cancelled" + | "frozen" + | "aborted"; + +export interface KeeperParityChainReceipt { + chainKey: BettingChainKey; + preparedAtMs: number | null; + openedAtMs: number | null; + lockedAtMs: number | null; + resolvedAtMs: number | null; + cancelledAtMs: number | null; + confirmedAtMs: number | null; + lifecycleStatus: PredictionMarketLifecycleStatus | null; + txRef: string | null; + note: string | null; +} + +export interface KeeperMarketParitySnapshot { + bundleId: string; + duelKey: string | null; + duelId: string | null; + revision: number; + requiredChains: BettingChainKey[]; + confirmedChains: BettingChainKey[]; + state: KeeperParityBundleState; + phase: string | null; + safeToBet: boolean; + openedAtMs: number | null; + lockedAtMs: number | null; + resolvedAtMs: number | null; + freezeReason: string | null; + updatedAtMs: number; + receipts: KeeperParityChainReceipt[]; +} + export interface KeeperBotHealthSnapshot { chainKey: BettingChainKey; updatedAtMs: number; @@ -220,6 +261,7 @@ export interface KeeperBotHealthSnapshot { lastSuccessfulRpcAtMs: number | null; recovery: KeeperRecoveryState[]; markets: KeeperMarketHealthRecord[]; + marketParity?: KeeperMarketParitySnapshot | null; } export interface PredictionMarketStatusRecord diff --git a/packages/hyperbet-sdk/package.json b/packages/hyperbet-sdk/package.json index 908fe0ee..6d888d3d 100644 --- a/packages/hyperbet-sdk/package.json +++ b/packages/hyperbet-sdk/package.json @@ -30,6 +30,7 @@ "ethers": "^6.13.4" }, "devDependencies": { + "@biomejs/biome": "^2.4.11", "@types/bn.js": "^5.2.0", "@types/node": "^22.9.0", "tsup": "^8.3.5", diff --git a/packages/hyperbet-solana/anchor/Anchor.toml b/packages/hyperbet-solana/anchor/Anchor.toml index 0837dae4..aee292b5 100644 --- a/packages/hyperbet-solana/anchor/Anchor.toml +++ b/packages/hyperbet-solana/anchor/Anchor.toml @@ -6,20 +6,20 @@ resolution = true skip-lint = false [programs.devnet] -fight_oracle = "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM" -gold_clob_market = "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy" +fight_oracle = "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB" +gold_clob_market = "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt" gold_perps_market = "BFbmQbSbf3R6fMDdXKMKQZCTyMhMs9MCcjAhGDBLETXS" lvr_amm = "12E8Lz5w8Qxyj8Fh6LgsCgPDQNJMCLMV1y43LhPrH66w" [programs.localnet] -fight_oracle = "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM" -gold_clob_market = "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy" +fight_oracle = "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB" +gold_clob_market = "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt" gold_perps_market = "BFbmQbSbf3R6fMDdXKMKQZCTyMhMs9MCcjAhGDBLETXS" lvr_amm = "12E8Lz5w8Qxyj8Fh6LgsCgPDQNJMCLMV1y43LhPrH66w" [programs.testnet] -fight_oracle = "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM" -gold_clob_market = "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy" +fight_oracle = "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB" +gold_clob_market = "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt" gold_perps_market = "BFbmQbSbf3R6fMDdXKMKQZCTyMhMs9MCcjAhGDBLETXS" lvr_amm = "12E8Lz5w8Qxyj8Fh6LgsCgPDQNJMCLMV1y43LhPrH66w" diff --git a/packages/hyperbet-solana/anchor/programs/fight_oracle/src/lib.rs b/packages/hyperbet-solana/anchor/programs/fight_oracle/src/lib.rs index 7c71d8ae..09adf83f 100644 --- a/packages/hyperbet-solana/anchor/programs/fight_oracle/src/lib.rs +++ b/packages/hyperbet-solana/anchor/programs/fight_oracle/src/lib.rs @@ -3,7 +3,7 @@ use anchor_lang::prelude::*; -declare_id!("GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM"); +declare_id!("7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB"); pub const ORACLE_CONFIG_SEED: &[u8] = b"oracle_config"; pub const DUEL_SEED: &[u8] = b"duel"; @@ -172,12 +172,13 @@ pub mod fight_oracle { duel_state.winner = MarketSide::None; } - // FIX-4: Lock participant identity and bet timing once betting opens - if is_initialized && duel_status_rank(duel_state.status) >= duel_status_rank(DuelStatus::BettingOpen) { + // FIX-5: Once a duel is prestaged, its manifest becomes immutable. + if is_initialized { require!(participant_a_hash == duel_state.participant_a_hash, ErrorCode::ParticipantHashImmutable); require!(participant_b_hash == duel_state.participant_b_hash, ErrorCode::ParticipantHashImmutable); require!(bet_open_ts == duel_state.bet_open_ts, ErrorCode::TimingImmutable); require!(bet_close_ts == duel_state.bet_close_ts, ErrorCode::TimingImmutable); + require!(duel_start_ts == duel_state.duel_start_ts, ErrorCode::TimingImmutable); } duel_state.duel_key = duel_key; @@ -721,9 +722,9 @@ pub enum ErrorCode { BettingWindowActive, #[msg("Duel must be in Challenged status for reproposal")] NotChallenged, - #[msg("Participant hashes are immutable after betting opens")] + #[msg("Participant hashes are immutable after duel prepare")] ParticipantHashImmutable, - #[msg("Bet timing is immutable after betting opens")] + #[msg("Bet timing is immutable after duel prepare")] TimingImmutable, #[msg("Config is already initialized")] AlreadyInitialized, diff --git a/packages/hyperbet-solana/anchor/programs/gold_clob_market/src/lib.rs b/packages/hyperbet-solana/anchor/programs/gold_clob_market/src/lib.rs index adac5f1e..5d06fadb 100644 --- a/packages/hyperbet-solana/anchor/programs/gold_clob_market/src/lib.rs +++ b/packages/hyperbet-solana/anchor/programs/gold_clob_market/src/lib.rs @@ -7,7 +7,7 @@ use fight_oracle::{ self, DuelState as OracleDuelState, DuelStatus as OracleDuelStatus, MarketSide, }; -declare_id!("3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy"); +declare_id!("EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt"); const CONFIG_SEED: &[u8] = b"config"; const MARKET_SEED: &[u8] = b"market"; @@ -180,7 +180,8 @@ pub mod gold_clob_market { ErrorCode::DuelMismatch ); require!( - ctx.accounts.duel_state.status == OracleDuelStatus::BettingOpen + ctx.accounts.duel_state.status == OracleDuelStatus::Scheduled + || ctx.accounts.duel_state.status == OracleDuelStatus::BettingOpen || ctx.accounts.duel_state.status == OracleDuelStatus::Locked, ErrorCode::MarketCreationClosed ); diff --git a/packages/hyperbet-solana/anchor/target/idl/fight_oracle.json b/packages/hyperbet-solana/anchor/target/idl/fight_oracle.json index d0c45c69..0c8cba6a 100644 --- a/packages/hyperbet-solana/anchor/target/idl/fight_oracle.json +++ b/packages/hyperbet-solana/anchor/target/idl/fight_oracle.json @@ -1,5 +1,5 @@ { - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM", + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB", "metadata": { "name": "fight_oracle", "version": "0.1.0", @@ -343,7 +343,7 @@ }, { "name": "program", - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM" + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB" }, { "name": "program_data" @@ -1051,12 +1051,12 @@ { "code": 6021, "name": "ParticipantHashImmutable", - "msg": "Participant hashes are immutable after betting opens" + "msg": "Participant hashes are immutable after duel prepare" }, { "code": 6022, "name": "TimingImmutable", - "msg": "Bet timing is immutable after betting opens" + "msg": "Bet timing is immutable after duel prepare" }, { "code": 6023, diff --git a/packages/hyperbet-solana/anchor/target/idl/gold_clob_market.json b/packages/hyperbet-solana/anchor/target/idl/gold_clob_market.json index d2bb65f3..550fae7e 100644 --- a/packages/hyperbet-solana/anchor/target/idl/gold_clob_market.json +++ b/packages/hyperbet-solana/anchor/target/idl/gold_clob_market.json @@ -1,5 +1,5 @@ { - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy", + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt", "metadata": { "name": "gold_clob_market", "version": "0.1.0", @@ -433,7 +433,7 @@ }, { "name": "program", - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy" + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt" }, { "name": "program_data" diff --git a/packages/hyperbet-solana/anchor/target/types/fight_oracle.ts b/packages/hyperbet-solana/anchor/target/types/fight_oracle.ts index 4f3b083d..7183391c 100644 --- a/packages/hyperbet-solana/anchor/target/types/fight_oracle.ts +++ b/packages/hyperbet-solana/anchor/target/types/fight_oracle.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/fight_oracle.json`. */ export type FightOracle = { - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM", + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB", "metadata": { "name": "fightOracle", "version": "0.1.0", @@ -349,7 +349,7 @@ export type FightOracle = { }, { "name": "program", - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM" + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB" }, { "name": "programData" @@ -1057,12 +1057,12 @@ export type FightOracle = { { "code": 6021, "name": "participantHashImmutable", - "msg": "Participant hashes are immutable after betting opens" + "msg": "Participant hashes are immutable after duel prepare" }, { "code": 6022, "name": "timingImmutable", - "msg": "Bet timing is immutable after betting opens" + "msg": "Bet timing is immutable after duel prepare" }, { "code": 6023, diff --git a/packages/hyperbet-solana/anchor/target/types/gold_clob_market.ts b/packages/hyperbet-solana/anchor/target/types/gold_clob_market.ts index 16e1d101..aea963e3 100644 --- a/packages/hyperbet-solana/anchor/target/types/gold_clob_market.ts +++ b/packages/hyperbet-solana/anchor/target/types/gold_clob_market.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/gold_clob_market.json`. */ export type GoldClobMarket = { - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy", + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt", "metadata": { "name": "goldClobMarket", "version": "0.1.0", @@ -439,7 +439,7 @@ export type GoldClobMarket = { }, { "name": "program", - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy" + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt" }, { "name": "programData" diff --git a/packages/hyperbet-solana/anchor/tests/gold_clob_market.anchor.ts b/packages/hyperbet-solana/anchor/tests/gold_clob_market.anchor.ts index b18d8eae..3909d8b9 100644 --- a/packages/hyperbet-solana/anchor/tests/gold_clob_market.anchor.ts +++ b/packages/hyperbet-solana/anchor/tests/gold_clob_market.anchor.ts @@ -17,6 +17,7 @@ import { createOpenMarketFixture, duelStatusBettingOpen, duelStatusLocked, + duelStatusScheduled, ensureClobConfig, ensureOracleReady, finalizeDuelResult, @@ -121,6 +122,33 @@ describe("gold_clob_market (native SOL settlement)", () => { } }); + it("allows scheduled prestage and keeps the initialized market locked", async () => { + await ensureOracleReady(fightProgram, authority, authority.publicKey); + const config = await ensureClobConfig(clobProgram, authority); + + const duelKey = uniqueDuelKey("scheduled-market"); + const now = Math.floor(Date.now() / 1000); + const duelState = await upsertDuel(fightProgram, authority, duelKey, { + status: duelStatusScheduled(), + betOpenTs: now + 30, + betCloseTs: now + 600, + duelStartTs: now + 660, + metadataUri: "https://hyperscape.gg/tests/clob/scheduled", + }); + const market = await initializeCanonicalMarket( + clobProgram, + authority, + duelState, + duelKey, + config, + ); + + const marketState = await clobProgram.account.marketState.fetch( + market.marketState, + ); + assert.deepStrictEqual(marketState.status, { locked: {} }); + }); + it("charges trade fees only on executed taker size and enforces FIFO at a shared price level", async () => { const treasury = Keypair.generate(); const marketMaker = Keypair.generate(); diff --git a/packages/hyperbet-solana/anchor/tests/oracle-finality.anchor.ts b/packages/hyperbet-solana/anchor/tests/oracle-finality.anchor.ts index 6ac8e947..a131c18a 100644 --- a/packages/hyperbet-solana/anchor/tests/oracle-finality.anchor.ts +++ b/packages/hyperbet-solana/anchor/tests/oracle-finality.anchor.ts @@ -12,10 +12,12 @@ import { createOpenMarketFixture, challengeDuelResult, duelStatusLocked, + duelStatusScheduled, deriveDuelStatePda, deriveOracleConfigPda, ensureOracleReady, finalizeDuelResult, + hashLabel, hasProgramError, marketSideA, placeClobOrder, @@ -37,6 +39,59 @@ describe("oracle finality truth (solana)", () => { const clobProgram = anchor.workspace.GoldClobMarket as Program; const authority = (provider.wallet as anchor.Wallet & { payer: Keypair }).payer; + it("freezes the scheduled duel manifest after prepare", async () => { + const duelKey = uniqueDuelKey("sol-scheduled-immutable"); + const now = Math.floor(Date.now() / 1000); + const participantAHash = hashLabel("sol-agent-a"); + const participantBHash = hashLabel("sol-agent-b"); + + await upsertDuel(fightProgram, authority, duelKey, { + status: duelStatusScheduled(), + betOpenTs: now + 30, + betCloseTs: now + 90, + duelStartTs: now + 150, + participantAHash, + participantBHash, + metadataUri: "https://hyperscape.gg/tests/prepare/scheduled", + }); + + try { + await upsertDuel(fightProgram, authority, duelKey, { + status: duelStatusScheduled(), + betOpenTs: now + 30, + betCloseTs: now + 90, + duelStartTs: now + 151, + participantAHash, + participantBHash, + metadataUri: "https://hyperscape.gg/tests/prepare/scheduled-shifted", + }); + assert.fail("scheduled duel timing mutated after prepare"); + } catch (error: unknown) { + assert.ok( + hasProgramError(error, "TimingImmutable"), + `expected TimingImmutable, got ${String(error)}`, + ); + } + + try { + await upsertDuel(fightProgram, authority, duelKey, { + status: duelStatusScheduled(), + betOpenTs: now + 30, + betCloseTs: now + 90, + duelStartTs: now + 150, + participantAHash: hashLabel("sol-agent-a-mutated"), + participantBHash, + metadataUri: "https://hyperscape.gg/tests/prepare/scheduled-mutated", + }); + assert.fail("scheduled duel participant mutated after prepare"); + } catch (error: unknown) { + assert.ok( + hasProgramError(error, "ParticipantHashImmutable"), + `expected ParticipantHashImmutable, got ${String(error)}`, + ); + } + }); + it("rejects settlement before terminal oracle states", async () => { const maker = Keypair.generate(); const taker = Keypair.generate(); diff --git a/packages/hyperbet-solana/app/hls-player.html b/packages/hyperbet-solana/app/hls-player.html new file mode 100644 index 00000000..cacfaa27 --- /dev/null +++ b/packages/hyperbet-solana/app/hls-player.html @@ -0,0 +1,27 @@ + + + + + + Hyperscape Live Stream + + + + +
+ + + diff --git a/packages/hyperbet-solana/app/src/App.tsx b/packages/hyperbet-solana/app/src/App.tsx index 71eaf522..19462979 100644 --- a/packages/hyperbet-solana/app/src/App.tsx +++ b/packages/hyperbet-solana/app/src/App.tsx @@ -28,13 +28,34 @@ import { captureInviteCodeFromLocation, getStoredInviteCode, } from "@hyperbet/ui/lib/invite"; +import { ENABLE_STREAM_SOURCE_OVERRIDE } from "@hyperbet/ui/lib/config"; import { normalizePredictionMarketDuelKeyHex, + selectPredictionMarketLifecycleRecord, usePredictionMarketLifecycle, + type PredictionMarketsResponse, } from "@hyperbet/ui/lib/predictionMarkets"; +import { + describeCanonicalRendererDegradedReason, + resolveCanonicalPlaybackDeliveryMode, + selectBetSurfaceStreamUrl, +} from "@hyperbet/ui/lib/streamSession"; import { useAppConnection, useAppWallet, useAppWalletModal } from "./lib/appWallet"; -import { StreamPlayer } from "@hyperbet/ui/components/StreamPlayer"; +import { + DEFAULT_SYNC_TOLERANCE_MS, + StreamPlayer, + type StreamPlayerStatus, +} from "@hyperbet/ui/components/StreamPlayer"; import { PointsDisplay } from "@hyperbet/ui/components/PointsDisplay"; +import { useCanonicalStreamSession } from "@hyperbet/ui/spectator/useCanonicalStreamSession"; +import { + logViewerAlignmentDivergence, + projectCanonicalSessionToSourceTimeline, + projectDuelContextToSourceTimeline, + resolveAlignedCountdownDisplay, + resolveAlignedSessionPhase, + useViewerAlignedBetState, +} from "@hyperbet/ui/lib/viewerAlignment"; import type { SolanaClobMarketSnapshot } from "@hyperbet/ui/components/SolanaClobPanel"; import { getDuelStateDecoder } from "./generated/fight-oracle/accounts"; import { @@ -49,6 +70,7 @@ import { FIGHT_ORACLE_PROGRAM_ID } from "./lib/programIds"; import { useStreamingState } from "./spectator/useStreamingState"; import { useDuelContext } from "@hyperbet/ui/spectator/useDuelContext"; import type { LeaderboardEntry } from "./spectator/types"; +import { useMeasuredContentBox } from "@hyperbet/ui/lib/useMeasuredContentBox"; import { useResizePanel, useIsMobile } from "@hyperbet/ui/lib/useResizePanel"; import { ResizeHandle } from "@hyperbet/ui/components/ResizeHandle"; import { @@ -57,7 +79,6 @@ import { XAxis, YAxis, Tooltip, - ResponsiveContainer, ReferenceLine, } from "recharts"; @@ -594,17 +615,6 @@ function normalizeTimestamp(value: number): number { return Math.floor(value); } -function formatCountdown(seconds: number): string { - if (seconds <= 0) return "00:00"; - const m = Math.floor(seconds / 60) - .toString() - .padStart(2, "0"); - const s = Math.floor(seconds % 60) - .toString() - .padStart(2, "0"); - return `${m}:${s}`; -} - function enumIs(value: unknown, variant: string): boolean { if (!value || typeof value !== "object") return false; const key = Object.keys(value as Record)[0]; @@ -745,36 +755,161 @@ export function App() { >("leaderboard"); const appRootRef = useRef(null); const bettingDockInnerRef = useRef(null); + const chartContainerRef = useRef(null); + const chartSize = useMeasuredContentBox(chartContainerRef, !isMobile, 2); const { state: streamingState } = useStreamingState(); + const { + session: canonicalStreamSession, + playback: _canonicalPlayback, + rawSession: rawCanonicalStreamSession, + rendererHealth: canonicalRendererHealth, + deliveryHealth: canonicalDeliveryHealth, + publicReadiness: canonicalPublicReadiness, + authorityHealth: canonicalAuthorityHealth, + presentationDelayMs: canonicalPresentationDelayMs, + } = useCanonicalStreamSession(); const { context: duelContext } = useDuelContext(); - const liveCycle = streamingState?.cycle ?? null; - const { market: lifecycleMarket } = usePredictionMarketLifecycle("solana"); + const freshestCycle = streamingState?.cycle ?? null; + const { data: lifecyclePayload, market: lifecycleMarket } = + usePredictionMarketLifecycle("solana"); const runtimeE2eOverride = useMemo( () => isE2eMode ? readSolanaE2eRuntimeOverride() - : { + : { duelKey: null, duelId: null, marketRef: null, }, [isE2eMode], ); + const allowStreamSourceOverride = + ENABLE_STREAM_SOURCE_OVERRIDE || isE2eDebugMode; const streamSources = STREAM_URLS; - const activeStreamUrl = streamSources[streamSourceIndex] ?? ""; + const lifecycleDuelKey = normalizePredictionMarketDuelKeyHex( + lifecycleMarket?.duelKey ?? null, + ); + const { activeStreamUrl, preloadStreamUrl } = selectBetSurfaceStreamUrl({ + allowFallbackOverride: allowStreamSourceOverride, + authorityHealth: canonicalAuthorityHealth, + fallbackStreamIndex: streamSourceIndex, + fallbackStreamSources: streamSources, + lifecycleDuelId: lifecycleMarket?.duelId ?? null, + lifecycleDuelKey, + rendererReady: canonicalRendererHealth?.ready ?? null, + session: canonicalStreamSession, + }); + const mountedStreamUrl = activeStreamUrl || preloadStreamUrl; + const streamDeliveryMode = resolveCanonicalPlaybackDeliveryMode( + canonicalStreamSession, + ); + const [streamPlayerStatus, setStreamPlayerStatus] = + useState(null); + + // Viewer-alignment shadow composition. When + // `VITE_ENABLE_VIEWER_ALIGNED_BET_STATE` is off the inner hook is a + // passthrough (no timers, no buffer pushes) and the panel sees the + // canonical live overrides unchanged. When the flag is on, the + // aligned envelope's `duel` / selected market are used as the + // panel's override payloads so display copy tracks the viewer's + // video frame. Divergence events are emitted as `[viewer-align]` + // shadow-logs. + const viewerAligned = useViewerAlignedBetState< + typeof canonicalStreamSession, + PredictionMarketsResponse | null, + typeof duelContext + >({ + latestSession: rawCanonicalStreamSession ?? canonicalStreamSession, + latestMarket: lifecyclePayload, + latestDuelContext: duelContext, + sessionPresentationDelayMs: canonicalPresentationDelayMs, + streamPlayerStatus, + currentDisplayPhase: + canonicalStreamSession?.cycle.broadcastTimeline?.phase ?? + canonicalStreamSession?.phase ?? + freshestCycle?.phase ?? + null, + extractAlignedPhase: resolveAlignedSessionPhase, + onDivergence: logViewerAlignmentDivergence, + }); + const alignedLifecyclePayload = viewerAligned.enabled + ? viewerAligned.marketOverview ?? null + : null; + const alignedLifecycleDuel = alignedLifecyclePayload?.duel ?? null; + const alignedLifecycleMarket = alignedLifecyclePayload + ? selectPredictionMarketLifecycleRecord( + alignedLifecyclePayload, + "solana", + ) + : null; + const alignedSession = viewerAligned.enabled + ? projectCanonicalSessionToSourceTimeline( + viewerAligned.session ?? canonicalStreamSession, + ) + : null; + const displaySession = alignedSession ?? canonicalStreamSession; + const displayDuelContext = viewerAligned.enabled + ? projectDuelContextToSourceTimeline( + viewerAligned.duelContext ?? duelContext, + ) ?? duelContext + : duelContext; + const liveCycle = + (displaySession?.cycle as typeof freshestCycle) ?? freshestCycle; + const displayLifecycleMarket = alignedLifecycleMarket ?? lifecycleMarket ?? null; + + const streamPlaceholderMessage = useMemo(() => { + if (!canonicalStreamSession) { + return "Connecting to live session..."; + } + if (canonicalAuthorityHealth?.ready === false) { + return "Stream authority unavailable. Waiting for session state."; + } + if (canonicalRendererHealth?.ready === false) { + return describeCanonicalRendererDegradedReason( + canonicalRendererHealth.degradedReason, + "Waiting for stream...", + ); + } + if ( + canonicalPublicReadiness?.ready === false || + canonicalDeliveryHealth?.ready === false + ) { + return describeCanonicalRendererDegradedReason( + canonicalPublicReadiness?.reason ?? + canonicalDeliveryHealth?.degradedReason, + "Waiting for live delivery...", + ); + } + return "Waiting for stream..."; + }, [ + canonicalPublicReadiness?.reason, + canonicalPublicReadiness?.ready, + canonicalDeliveryHealth?.degradedReason, + canonicalDeliveryHealth?.ready, + canonicalAuthorityHealth?.ready, + canonicalRendererHealth?.degradedReason, + canonicalRendererHealth?.ready, + canonicalStreamSession, + ]); const switchToBackupStream = useCallback(() => { + if (!allowStreamSourceOverride) { + return; + } setStreamSourceIndex((current) => current + 1 < streamSources.length ? current + 1 : current, ); - }, [streamSources.length]); + }, [allowStreamSourceOverride, streamSources.length]); const cycleStreamSource = useCallback(() => { + if (!allowStreamSourceOverride) { + return; + } setStreamSourceIndex((current) => streamSources.length > 1 ? (current + 1) % streamSources.length : current, ); - }, [streamSources.length]); + }, [allowStreamSourceOverride, streamSources.length]); useEffect(() => { if (streamSourceIndex < streamSources.length) return; @@ -1103,8 +1238,8 @@ export function App() { return "rgba(255,255,255,0.78)"; })(); const effStatus = status; - const contextAgent1 = duelContext?.cycle.agent1 ?? null; - const contextAgent2 = duelContext?.cycle.agent2 ?? null; + const contextAgent1 = displayDuelContext?.cycle.agent1 ?? null; + const contextAgent2 = displayDuelContext?.cycle.agent2 ?? null; // Agent context from live SSE + duel-context polling const effA1 = { @@ -1182,17 +1317,36 @@ export function App() { const activeLifecycleMarket = runtimeE2eOverride.duelKey && - normalizePredictionMarketDuelKeyHex(lifecycleMarket?.duelKey ?? null) !== + normalizePredictionMarketDuelKeyHex(displayLifecycleMarket?.duelKey ?? null) !== runtimeE2eOverride.duelKey ? null - : lifecycleMarket; + : displayLifecycleMarket; const marketStatusText = _getMarketStatusLabel( activeLifecycleMarket?.lifecycleStatus ?? solanaClobSnapshot.marketStatus, copy, ); - const countdownText = formatCountdown( - currentMatch ? Math.max(0, currentMatch.closeTs - nowTs) : 0, - ); + const liveStartDisplay = liveCycle + ? resolveAlignedCountdownDisplay({ + phase: liveCycle.phase ?? null, + viewerClock: viewerAligned.viewerClock, + betCloseTime: liveCycle.betCloseTime ?? null, + fightStartTime: liveCycle.fightStartTime ?? null, + fallbackTimeRemaining: liveCycle.timeRemaining, + }) + : currentMatch + ? resolveAlignedCountdownDisplay({ + phase: "ANNOUNCEMENT", + viewerClock: null, + fallbackTimeRemaining: Math.max(0, currentMatch.closeTs - nowTs), + }) + : null; + const displayPhaseLabel = + liveStartDisplay?.holdState === "preparing_arena" + ? "Preparing arena" + : liveStartDisplay?.holdState === "starting" + ? "Starting..." + : effPhaseLabel; + const displayCountdownText = ""; // Sidebar bet state const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [_hmSide, _setHmSide] = useState("YES"); @@ -1378,7 +1532,7 @@ export function App() { {/* Non-compact points summary */}
- +
{/* Tab Content */} @@ -1395,12 +1549,12 @@ export function App() { } > - + )} {pointsDrawerTab === "history" && ( }> - + )} {pointsDrawerTab === "referral" && ( @@ -1453,7 +1607,7 @@ export function App() { YES pool: {goldDisplay(yesPot)} GOLD | NO pool: {goldDisplay(noPot)}{" "} GOLD
-
{countdownText}
+
{displayCountdownText}
{status}
- {streamSources.length > 1 && ( + {activeStreamUrl ? ( +
- )} -
+ {allowStreamSourceOverride && streamSources.length > 1 && ( + + )} +
+ ) : null} + {!activeStreamUrl ? ( +
+
+ + {streamPlaceholderMessage} + +
+ ) : null} ) : (
- - Waiting for stream… - + {streamPlaceholderMessage}
)}
{/* Odds Chart */} -
-
- - - -
-
- - {(effYesPercent / 100).toFixed(1)} - -
-
- - - { - const d = new Date(v); - return `${d.getHours()}:${String(d.getMinutes()).padStart(2, "0")}`; - }} - /> - `${v}%`} - /> - - active && payload?.length ? ( -
- {payload[0].value}% -
- ) : null - } - /> - - -
-
+ {!isMobile && ( +
+
+ + + +
+
+ + {(effYesPercent / 100).toFixed(1)} + +
+
+ {chartSize ? ( + + { + const d = new Date(v); + return `${d.getHours()}:${String(d.getMinutes()).padStart(2, "0")}`; + }} + /> + `${v}%`} + /> + + active && payload?.length ? ( +
+ {payload[0].value}% +
+ ) : null + } + /> + + +
+ ) : null} +
-
+ )}
- {effPhaseLabel} + {displayPhaseLabel}
diff --git a/packages/hyperbet-solana/app/src/AppRoot.tsx b/packages/hyperbet-solana/app/src/AppRoot.tsx index dce2567a..ade0d0d6 100644 --- a/packages/hyperbet-solana/app/src/AppRoot.tsx +++ b/packages/hyperbet-solana/app/src/AppRoot.tsx @@ -1,7 +1,6 @@ -import { useEffect, useMemo, useRef, useState } from "react"; +import { useMemo } from "react"; import { Buffer } from "buffer"; import { SolanaProvider } from "@solana/react-hooks"; -import { watchWalletStandardConnectors } from "@solana/client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { AppWalletProvider } from "./lib/appWallet"; @@ -19,47 +18,14 @@ if (!(globalThis as { Buffer?: typeof Buffer }).Buffer) { const queryClient = new QueryClient(); export default function AppRoot() { - const [walletScanVersion, setWalletScanVersion] = useState(0); const headlessWallets = useMemo( () => createHeadlessWalletConnectorsFromEnv(), [], ); - const frameworkClient = useMemo( - () => createFrameworkClient(), - [walletScanVersion], - ); - const knownConnectorIdsRef = useRef(""); + const frameworkClient = useMemo(() => createFrameworkClient(), []); const autoConnectHeadlessConnectorId = headlessWallets.find((entry) => entry.autoConnect)?.connector.id ?? null; - useEffect(() => { - const stopWatchingWallets = watchWalletStandardConnectors((connectors) => { - const nextConnectorIds = connectors - .map((connector) => connector.id) - .sort() - .join("|"); - if (nextConnectorIds === knownConnectorIdsRef.current) return; - knownConnectorIdsRef.current = nextConnectorIds; - - const walletStatus = frameworkClient.store.getState().wallet.status; - if (walletStatus === "disconnected") { - setWalletScanVersion((value) => value + 1); - } - }); - - const delayedRescanId = window.setTimeout(() => { - const walletStatus = frameworkClient.store.getState().wallet.status; - if (walletStatus === "disconnected") { - setWalletScanVersion((value) => value + 1); - } - }, 500); - - return () => { - stopWatchingWallets(); - window.clearTimeout(delayedRescanId); - }; - }, [frameworkClient]); - return ( ; - -export function ModelsMarketView(props: SharedProps) { - const { connection } = useAppConnection(); - const wallet = useAppWallet(); - const walletModal = useAppWalletModal(); - - return ( - - ); -} +export { SolanaModelsMarketView as ModelsMarketView } from "@hyperbet/ui/components/SolanaModelsMarketView"; diff --git a/packages/hyperbet-solana/app/src/hlsPlayerMain.tsx b/packages/hyperbet-solana/app/src/hlsPlayerMain.tsx new file mode 100644 index 00000000..3c73354c --- /dev/null +++ b/packages/hyperbet-solana/app/src/hlsPlayerMain.tsx @@ -0,0 +1,3 @@ +import { mountHlsPlayerApp } from "@hyperbet/ui/player"; + +mountHlsPlayerApp(document.getElementById("root")!); diff --git a/packages/hyperbet-solana/app/src/idl/fight_oracle.json b/packages/hyperbet-solana/app/src/idl/fight_oracle.json index d0c45c69..0c8cba6a 100644 --- a/packages/hyperbet-solana/app/src/idl/fight_oracle.json +++ b/packages/hyperbet-solana/app/src/idl/fight_oracle.json @@ -1,5 +1,5 @@ { - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM", + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB", "metadata": { "name": "fight_oracle", "version": "0.1.0", @@ -343,7 +343,7 @@ }, { "name": "program", - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM" + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB" }, { "name": "program_data" @@ -1051,12 +1051,12 @@ { "code": 6021, "name": "ParticipantHashImmutable", - "msg": "Participant hashes are immutable after betting opens" + "msg": "Participant hashes are immutable after duel prepare" }, { "code": 6022, "name": "TimingImmutable", - "msg": "Bet timing is immutable after betting opens" + "msg": "Bet timing is immutable after duel prepare" }, { "code": 6023, diff --git a/packages/hyperbet-solana/app/src/idl/fight_oracle.ts b/packages/hyperbet-solana/app/src/idl/fight_oracle.ts index 4f3b083d..7183391c 100644 --- a/packages/hyperbet-solana/app/src/idl/fight_oracle.ts +++ b/packages/hyperbet-solana/app/src/idl/fight_oracle.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/fight_oracle.json`. */ export type FightOracle = { - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM", + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB", "metadata": { "name": "fightOracle", "version": "0.1.0", @@ -349,7 +349,7 @@ export type FightOracle = { }, { "name": "program", - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM" + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB" }, { "name": "programData" @@ -1057,12 +1057,12 @@ export type FightOracle = { { "code": 6021, "name": "participantHashImmutable", - "msg": "Participant hashes are immutable after betting opens" + "msg": "Participant hashes are immutable after duel prepare" }, { "code": 6022, "name": "timingImmutable", - "msg": "Bet timing is immutable after betting opens" + "msg": "Bet timing is immutable after duel prepare" }, { "code": 6023, diff --git a/packages/hyperbet-solana/app/src/idl/gold_clob_market.json b/packages/hyperbet-solana/app/src/idl/gold_clob_market.json index d2bb65f3..550fae7e 100644 --- a/packages/hyperbet-solana/app/src/idl/gold_clob_market.json +++ b/packages/hyperbet-solana/app/src/idl/gold_clob_market.json @@ -1,5 +1,5 @@ { - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy", + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt", "metadata": { "name": "gold_clob_market", "version": "0.1.0", @@ -433,7 +433,7 @@ }, { "name": "program", - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy" + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt" }, { "name": "program_data" diff --git a/packages/hyperbet-solana/app/src/idl/gold_clob_market.ts b/packages/hyperbet-solana/app/src/idl/gold_clob_market.ts index 16e1d101..aea963e3 100644 --- a/packages/hyperbet-solana/app/src/idl/gold_clob_market.ts +++ b/packages/hyperbet-solana/app/src/idl/gold_clob_market.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/gold_clob_market.json`. */ export type GoldClobMarket = { - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy", + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt", "metadata": { "name": "goldClobMarket", "version": "0.1.0", @@ -439,7 +439,7 @@ export type GoldClobMarket = { }, { "name": "program", - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy" + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt" }, { "name": "programData" diff --git a/packages/hyperbet-solana/app/src/lib/appWallet.tsx b/packages/hyperbet-solana/app/src/lib/appWallet.tsx index ead4fcdd..c186f81b 100644 --- a/packages/hyperbet-solana/app/src/lib/appWallet.tsx +++ b/packages/hyperbet-solana/app/src/lib/appWallet.tsx @@ -1,206 +1,17 @@ -import { - createContext, - useContext, - useEffect, - useMemo, - type ReactNode, -} from "react"; -import { useWalletConnection, useWalletModalState } from "@solana/react-hooks"; -import type { WalletConnector, WalletSession } from "@solana/client"; -import type { Address } from "@solana/kit"; -import { - Connection, - PublicKey, - type Transaction, - type VersionedTransaction, -} from "@solana/web3.js"; +import type { ReactNode } from "react"; import { getRpcUrl, getWsUrl } from "./config"; +import { + AppWalletProvider as SharedAppWalletProvider, + useAppConnection, + useAppWallet, + useAppWalletModal, +} from "@hyperbet/ui/lib/solanaRuntime"; -type WalletLike = { - id: string; - name: string; - icon?: string; - ready: boolean; -}; - -export type AppWallet = { - address: Address | null; - connect: () => Promise; - connected: boolean; - connecting: boolean; - disconnect: () => Promise; - publicKey: PublicKey | null; - session: WalletSession | null; - select: (connectorId: string | null) => void; - signAllTransactions?: >( - txs: T, - ) => Promise; - signTransaction?: ( - tx: T, - ) => Promise; - wallet: WalletLike | null; - wallets: WalletLike[]; -}; - -type AppWalletConnection = { - connection: Connection; -}; - -type AppWalletModalState = { - setVisible: (visible: boolean) => void; - visible: boolean; -}; - -type AppWalletContextValue = { - modal: AppWalletModalState; - wallet: AppWallet; -}; - -const AppWalletContext = createContext(null); - -const connectionCache = new Map(); - -function getSharedConnection(rpcUrl: string, wsUrl: string): Connection { - const cacheKey = `${rpcUrl}|${wsUrl}`; - const cached = connectionCache.get(cacheKey); - if (cached) return cached; - - const connection = new Connection(rpcUrl, { - commitment: "confirmed", - disableRetryOnRateLimit: true, - wsEndpoint: wsUrl, - }); - connectionCache.set(cacheKey, connection); - return connection; -} - -function walletReady(connector: WalletConnector): boolean { - return connector.ready !== false; -} - -function walletSessionPublicKey( - session: WalletSession | null | undefined, -): PublicKey | null { - if (!session) return null; - return new PublicKey(session.account.publicKey); -} - -function createAppWallet( - connectionState: ReturnType, - modalState: ReturnType, -): AppWallet { - const session = connectionState.wallet ?? null; - const publicKey = walletSessionPublicKey(session); - const wallets = connectionState.connectors.map((connector) => ({ - id: connector.id, - name: connector.name, - icon: connector.icon, - ready: walletReady(connector), - })); - const activeWallet = connectionState.currentConnector - ? { - id: connectionState.currentConnector.id, - name: connectionState.currentConnector.name, - icon: connectionState.currentConnector.icon, - ready: walletReady(connectionState.currentConnector), - } - : null; - - const signTransaction = session?.signTransaction - ? async (tx: T): Promise => - (await session.signTransaction!( - tx as unknown as Parameters>[0], - )) as unknown as T - : undefined; - - const signAllTransactions = signTransaction - ? async >( - txs: T, - ): Promise => { - const signed: Array = []; - for (const tx of txs) { - signed.push(await signTransaction(tx)); - } - return signed as T; - } - : undefined; - - return { - address: session?.account.address ?? null, - connect: async () => { - if (connectionState.connectorId) { - await connectionState.connect(connectionState.connectorId); - return; - } - modalState.open(); - }, - connected: connectionState.connected, - connecting: connectionState.connecting, - disconnect: async () => { - await connectionState.disconnect(); - }, - publicKey, - session, - select: (connectorId) => modalState.select(connectorId), - signAllTransactions, - signTransaction, - wallet: activeWallet, - wallets, - }; -} +export type { AppWallet } from "@hyperbet/ui/lib/solanaRuntime"; -function WalletSelectionModal({ - state, -}: { - state: ReturnType; -}) { - if (!state.isOpen) return null; - - return ( -
-
-
event.stopPropagation()}> - -

Connect Solana Wallet

-
    - {state.connectors.map((connector) => { - const disabled = state.connecting; - return ( -
  • - -
  • - ); - })} -
-
-
-
- ); -} +export type AppWalletConnection = ReturnType; +export type AppWalletModalState = ReturnType; export function AppWalletProvider({ children, @@ -209,74 +20,15 @@ export function AppWalletProvider({ children: ReactNode; headlessAutoConnectorId: string | null; }) { - const connectionState = useWalletConnection(); - const modalState = useWalletModalState(); - - useEffect(() => { - if (!headlessAutoConnectorId) return; - if (connectionState.connected || connectionState.connecting) return; - const connector = connectionState.connectors.find( - (entry) => entry.id === headlessAutoConnectorId, - ); - if (!connector || !walletReady(connector)) return; - void connectionState.connect(headlessAutoConnectorId, { - allowInteractiveFallback: false, - autoConnect: true, - }); - }, [ - connectionState.connected, - connectionState.connect, - connectionState.connecting, - connectionState.connectors, - headlessAutoConnectorId, - ]); - - const value = useMemo( - () => ({ - modal: { - setVisible: (visible) => { - if (visible) { - modalState.open(); - } else { - modalState.close(); - } - }, - visible: modalState.isOpen, - }, - wallet: createAppWallet(connectionState, modalState), - }), - [connectionState, modalState], - ); - return ( - + {children} - - + ); } -export function useAppConnection(): AppWalletConnection { - const rpcUrl = getRpcUrl(); - const wsUrl = getWsUrl() ?? rpcUrl.replace(/^http/i, "ws"); - return useMemo( - () => ({ connection: getSharedConnection(rpcUrl, wsUrl) }), - [rpcUrl, wsUrl], - ); -} - -export function useAppWallet(): AppWallet { - const context = useContext(AppWalletContext); - if (!context) { - throw new Error("useAppWallet must be used inside AppWalletProvider."); - } - return context.wallet; -} - -export function useAppWalletModal(): AppWalletModalState { - const context = useContext(AppWalletContext); - if (!context) { - throw new Error("useAppWalletModal must be used inside AppWalletProvider."); - } - return context.modal; -} +export { useAppConnection, useAppWallet, useAppWalletModal }; diff --git a/packages/hyperbet-solana/app/src/lib/config.ts b/packages/hyperbet-solana/app/src/lib/config.ts index faa815ae..3974b654 100644 --- a/packages/hyperbet-solana/app/src/lib/config.ts +++ b/packages/hyperbet-solana/app/src/lib/config.ts @@ -1,6 +1,7 @@ import type { Address } from "@solana/kit"; import { resolveBettingSolanaDeployment } from "../../../deployments"; +import { sanitizeResolvedStreamSources } from "@hyperbet/ui/lib/streamSession"; export type SolanaCluster = "localnet" | "devnet" | "testnet" | "mainnet-beta"; @@ -306,7 +307,7 @@ const defaultPrimaryStreamUrl = envStreamUrl ?? (suppressDefaultStreamFallback ? "" : baseEnvConfig.streamUrl); const resolvedStreamSources = (() => { if (envStreamSources.length > 0) { - return uniqueList(envStreamSources); + return sanitizeResolvedStreamSources(envStreamSources); } const envFallbackUrl = readEnvString("VITE_STREAM_FALLBACK_URL"); const fallbackUrl = @@ -314,8 +315,10 @@ const resolvedStreamSources = (() => { (defaultPrimaryStreamUrl && !suppressDefaultStreamFallback ? DEFAULT_STREAM_FALLBACK_URL : ""); - return uniqueList([defaultPrimaryStreamUrl, fallbackUrl ?? ""]).filter( - (value) => value.length > 0, + return sanitizeResolvedStreamSources( + uniqueList([defaultPrimaryStreamUrl, fallbackUrl ?? ""]).filter( + (value) => value.length > 0, + ), ); })(); const resolvedStreamUrl = resolvedStreamSources[0] ?? ""; diff --git a/packages/hyperbet-solana/app/src/lib/frameworkClient.ts b/packages/hyperbet-solana/app/src/lib/frameworkClient.ts index f842052a..c0a1aa3c 100644 --- a/packages/hyperbet-solana/app/src/lib/frameworkClient.ts +++ b/packages/hyperbet-solana/app/src/lib/frameworkClient.ts @@ -1,59 +1,10 @@ -import { - createClient, - defaultWalletConnectors, - type WalletConnector, -} from "@solana/client"; -import { connectorKit } from "@solana/client/connectorkit"; - import { getCluster, getRpcUrl, getWsUrl } from "./config"; -import { createHeadlessWalletConnectorsFromEnv } from "./headlessWallet"; - -function resolveConnectorKitNetwork() { - const cluster = getCluster(); - if (cluster === "mainnet-beta") { - return "mainnet-beta" as const; - } - if (cluster === "devnet" || cluster === "testnet" || cluster === "localnet") { - return cluster; - } - return "devnet" as const; -} - -function dedupeConnectors(connectors: readonly WalletConnector[]) { - const seen = new Set(); - const unique: WalletConnector[] = []; - for (const connector of connectors) { - if (seen.has(connector.id)) continue; - seen.add(connector.id); - unique.push(connector); - } - return unique; -} +import { createFrameworkClient as createSharedFrameworkClient } from "@hyperbet/ui/lib/solanaRuntime"; export function createFrameworkClient() { - const headlessConnectors = createHeadlessWalletConnectorsFromEnv().map( - (entry) => entry.connector, - ); - const detectedConnectors = defaultWalletConnectors(); - const interactiveConnectors = connectorKit({ - defaultConfig: { - appName: "Hyperbet Solana", - appUrl: - typeof window !== "undefined" - ? window.location.origin - : "https://hyperbet.ai", - autoConnect: true, - enableMobile: true, - network: resolveConnectorKitNetwork(), - }, - }); - return createClient({ - endpoint: getRpcUrl(), - websocketEndpoint: getWsUrl(), - walletConnectors: dedupeConnectors([ - ...headlessConnectors, - ...detectedConnectors, - ...interactiveConnectors, - ]), + return createSharedFrameworkClient({ + getCluster, + getRpcUrl, + getWsUrl, }); } diff --git a/packages/hyperbet-solana/app/src/lib/headlessWallet.ts b/packages/hyperbet-solana/app/src/lib/headlessWallet.ts index 0f8bcbc4..97bf0284 100644 --- a/packages/hyperbet-solana/app/src/lib/headlessWallet.ts +++ b/packages/hyperbet-solana/app/src/lib/headlessWallet.ts @@ -1,271 +1,6 @@ -import { ed25519 } from "@noble/curves/ed25519.js"; -import type { WalletConnector, WalletSession } from "@solana/client"; -import type { Address } from "@solana/kit"; -import type { Transaction, VersionedTransaction } from "@solana/web3.js"; -import { Keypair } from "@solana/web3.js"; -import { Buffer } from "buffer"; - -import bs58 from "bs58"; - -import { CONFIG } from "./config"; - -const DEFAULT_HEADLESS_WALLET_NAME = "Headless Test Wallet"; -const HEADLESS_ICON = - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40'%3E%3Crect width='40' height='40' rx='8' fill='%230d58a6'/%3E%3Cpath d='M10 20h20M10 14h20M10 26h20' stroke='white' stroke-width='2'/%3E%3C/svg%3E"; - -type HeadlessWalletEntry = { - name?: string; - secretKey: string; - autoConnect?: boolean; -}; - -export type HeadlessWalletDescriptor = { - autoConnect: boolean; - connector: WalletConnector; -}; - -function validateSecretKeyBytes(bytes: Uint8Array): Uint8Array { - if (bytes.length !== 32 && bytes.length !== 64) { - throw new Error( - `Headless wallet secret key must be 32 or 64 bytes (received ${bytes.length})`, - ); - } - return bytes; -} - -function keypairFromSecret(secretKey: Uint8Array): Keypair { - return secretKey.length === 32 - ? Keypair.fromSeed(secretKey) - : Keypair.fromSecretKey(secretKey); -} - -function parseSecretKey(secret: string): Uint8Array { - const trimmed = secret.trim(); - if (!trimmed) { - throw new Error("Headless wallet secret key is empty"); - } - - if (trimmed.startsWith("[") && trimmed.endsWith("]")) { - const parsed = JSON.parse(trimmed) as unknown; - if ( - !Array.isArray(parsed) || - !parsed.every( - (value) => Number.isInteger(value) && value >= 0 && value <= 255, - ) - ) { - throw new Error("Invalid JSON byte array secret key"); - } - return validateSecretKeyBytes(Uint8Array.from(parsed)); - } - - if (trimmed.includes(",")) { - const values = trimmed.split(",").map((value) => Number(value.trim())); - if ( - values.length === 0 || - !values.every( - (value) => Number.isInteger(value) && value >= 0 && value <= 255, - ) - ) { - throw new Error("Invalid comma-separated byte secret key"); - } - return validateSecretKeyBytes(Uint8Array.from(values)); - } - - try { - const decoded = bs58.decode(trimmed); - if (decoded.length === 32 || decoded.length === 64) { - return validateSecretKeyBytes(decoded); - } - } catch { - // Continue to other formats. - } - - try { - if (/^[A-Za-z0-9+/=\s]+$/.test(trimmed)) { - const decoded = Uint8Array.from(Buffer.from(trimmed, "base64")); - if (decoded.length === 32 || decoded.length === 64) { - return validateSecretKeyBytes(decoded); - } - } - } catch { - // Continue to error. - } - - throw new Error( - "Unsupported secret key format (expected JSON array, comma-separated bytes, bs58, or base64)", - ); -} - -function parseHeadlessWalletEntries(): HeadlessWalletEntry[] { - const fromJson = CONFIG.headlessWalletsJson.trim(); - if (!fromJson) { - const legacySecret = CONFIG.headlessWalletSecretKey.trim(); - if (!legacySecret) return []; - return [ - { - name: getHeadlessWalletName(), - secretKey: legacySecret, - autoConnect: CONFIG.headlessWalletAutoConnect, - }, - ]; - } - - try { - const parsed = JSON.parse(fromJson) as unknown; - if (!Array.isArray(parsed)) { - throw new Error("VITE_HEADLESS_WALLETS must be a JSON array"); - } - - return parsed - .map((value, index) => { - if (typeof value === "string") { - return { - name: `${DEFAULT_HEADLESS_WALLET_NAME} ${index + 1}`, - secretKey: value, - autoConnect: index === 0 && CONFIG.headlessWalletAutoConnect, - }; - } - - if (value && typeof value === "object") { - const candidate = value as Partial; - return { - name: - typeof candidate.name === "string" ? candidate.name : undefined, - secretKey: - typeof candidate.secretKey === "string" - ? candidate.secretKey - : "", - autoConnect: - typeof candidate.autoConnect === "boolean" - ? candidate.autoConnect - : false, - }; - } - - return { - name: undefined, - secretKey: "", - autoConnect: false, - }; - }) - .filter((entry) => entry.secretKey.trim().length > 0); - } catch (error) { - console.error( - "[headless-wallet] Failed to parse VITE_HEADLESS_WALLETS:", - (error as Error).message, - ); - return []; - } -} - -function createHeadlessSession( - connectorId: string, - connectorName: string, - keypair: Keypair, - onDisconnect: () => Promise, -): WalletSession { - return { - account: { - address: keypair.publicKey.toBase58() as Address, - label: connectorName, - publicKey: keypair.publicKey.toBytes(), - }, - connector: { - canAutoConnect: true, - icon: HEADLESS_ICON, - id: connectorId, - kind: "headless", - name: connectorName, - ready: true, - }, - disconnect: onDisconnect, - signMessage: async (message: Uint8Array) => - ed25519.sign(message, keypair.secretKey.slice(0, 32)), - signTransaction: async (transaction) => { - const web3Transaction = transaction as unknown as - | Transaction - | VersionedTransaction; - if ("version" in web3Transaction) { - web3Transaction.sign([keypair]); - } else { - web3Transaction.partialSign(keypair); - } - return transaction; - }, - }; -} - -function createHeadlessConnector( - secretKey: Uint8Array, - name: string, - index: number, -): WalletConnector { - const fixedKeypair = keypairFromSecret(secretKey); - let activeSession: WalletSession | null = null; - const id = `headless:${index}:${fixedKeypair.publicKey.toBase58()}`; - - const disconnect = async () => { - activeSession = null; - }; - - return { - canAutoConnect: true, - connect: async () => { - activeSession = - activeSession ?? - createHeadlessSession( - id, - name, - Keypair.fromSecretKey(fixedKeypair.secretKey), - disconnect, - ); - return activeSession; - }, - disconnect, - icon: HEADLESS_ICON, - id, - isSupported: () => true, - kind: "headless", - name, - ready: true, - }; -} - -function getHeadlessWalletName(): string { - return CONFIG.headlessWalletName || DEFAULT_HEADLESS_WALLET_NAME; -} - -export function isHeadlessWalletEnabled(): boolean { - return parseHeadlessWalletEntries().length > 0; -} - -export function shouldAutoConnectHeadlessWallet(): boolean { - return parseHeadlessWalletEntries().some((entry) => - Boolean(entry.autoConnect), - ); -} - -export function createHeadlessWalletConnectorsFromEnv(): HeadlessWalletDescriptor[] { - const entries = parseHeadlessWalletEntries(); - if (entries.length === 0) return []; - - return entries - .map((entry, index) => { - try { - const secret = parseSecretKey(entry.secretKey); - const name = - entry.name?.trim() || `${DEFAULT_HEADLESS_WALLET_NAME} ${index + 1}`; - return { - autoConnect: Boolean(entry.autoConnect), - connector: createHeadlessConnector(secret, name, index), - } as HeadlessWalletDescriptor; - } catch (error) { - console.error( - `[headless-wallet] Failed to load wallet #${index + 1}:`, - (error as Error).message, - ); - return null; - } - }) - .filter((entry): entry is HeadlessWalletDescriptor => entry !== null); -} +export { + createHeadlessWalletConnectorsFromEnv, + isHeadlessWalletEnabled, + shouldAutoConnectHeadlessWallet, + type HeadlessWalletDescriptor, +} from "@hyperbet/ui/lib/solanaRuntime"; diff --git a/packages/hyperbet-solana/app/src/spectator/useStreamingState.ts b/packages/hyperbet-solana/app/src/spectator/useStreamingState.ts index e417753e..684d278d 100644 --- a/packages/hyperbet-solana/app/src/spectator/useStreamingState.ts +++ b/packages/hyperbet-solana/app/src/spectator/useStreamingState.ts @@ -1,200 +1 @@ -import { useEffect, useRef, useState, useCallback } from "react"; -import type { StreamingStateUpdate } from "./types"; -import { UI_SYNC_DELAY_MS, CONFIG } from "../lib/config"; - -const API_URL = CONFIG.gameApiUrl.replace(/\/$/, ""); - -const SSE_URL = `${API_URL}/api/streaming/state/events`; -const POLL_URL = `${API_URL}/api/streaming/state`; -const FALLBACK_POLL_INTERVAL_MS = 5000; - -type SseSource = { - onopen: (() => void) | null; - onerror: (() => void) | null; - close: () => void; - addEventListener: ( - type: string, - listener: (event: MessageEvent) => void, - ) => void; -}; - -function normalizeState(payload: unknown): StreamingStateUpdate | null { - if (!payload || typeof payload !== "object") return null; - const candidate = payload as Partial & { - cycle?: unknown; - leaderboard?: unknown; - }; - if (!candidate.cycle || !Array.isArray(candidate.leaderboard)) return null; - return { - type: "STREAMING_STATE_UPDATE", - cycle: candidate.cycle as StreamingStateUpdate["cycle"], - leaderboard: candidate.leaderboard as StreamingStateUpdate["leaderboard"], - cameraTarget: - typeof candidate.cameraTarget === "string" || - candidate.cameraTarget === null - ? candidate.cameraTarget - : null, - seq: - typeof candidate.seq === "number" && Number.isFinite(candidate.seq) - ? candidate.seq - : undefined, - emittedAt: - typeof candidate.emittedAt === "number" && - Number.isFinite(candidate.emittedAt) - ? candidate.emittedAt - : undefined, - }; -} - -export function useStreamingState(options: { disabled?: boolean } = {}) { - const { disabled = false } = options; - const [state, setState] = useState(null); - const [isConnected, setIsConnected] = useState(false); - const pollTimer = useRef | null>(null); - const eventSourceRef = useRef(null); - const lastEventIdRef = useRef(0); - const closedRef = useRef(false); - - const clearPollTimer = () => { - if (pollTimer.current) { - clearInterval(pollTimer.current); - pollTimer.current = null; - } - }; - - const applyState = useCallback((nextState: StreamingStateUpdate) => { - // Determine if we need to update the event ID sequence right away - if ( - typeof nextState.seq === "number" && - Number.isFinite(nextState.seq) && - nextState.seq > lastEventIdRef.current - ) { - lastEventIdRef.current = nextState.seq; - } - - // Delay UI state application to synchronize with public stream latency - setTimeout(() => { - setState(nextState); - setIsConnected(true); - }, UI_SYNC_DELAY_MS); - }, []); - - const poll = useCallback(async () => { - try { - const res = await fetch(POLL_URL, { cache: "no-store" }); - if (!res.ok) throw new Error("Failed to fetch"); - const data = normalizeState(await res.json()); - if (data) { - applyState(data); - } - } catch { - setIsConnected(false); - } - }, [applyState]); - - const startFallbackPolling = useCallback(() => { - if (pollTimer.current) return; - void poll(); - pollTimer.current = setInterval(() => { - void poll(); - }, FALLBACK_POLL_INTERVAL_MS); - }, [poll]); - - const connectSse = useCallback(() => { - if ( - typeof window === "undefined" || - typeof window.EventSource === "undefined" - ) { - startFallbackPolling(); - return; - } - - const url = new URL(SSE_URL); - if (lastEventIdRef.current > 0) { - url.searchParams.set("since", String(lastEventIdRef.current)); - } - - const source = new window.EventSource( - url.toString(), - ) as unknown as SseSource; - eventSourceRef.current = source; - - source.onopen = () => { - setIsConnected(true); - clearPollTimer(); - }; - - source.addEventListener("state", (event: MessageEvent) => { - try { - const parsed = normalizeState(JSON.parse(event.data)); - if (parsed) { - applyState(parsed); - } - const eventId = Number.parseInt(event.lastEventId || "", 10); - if (Number.isFinite(eventId) && eventId > lastEventIdRef.current) { - lastEventIdRef.current = eventId; - } - clearPollTimer(); - } catch { - // Ignore malformed SSE payloads and wait for the next frame - } - }); - - source.addEventListener("reset", (event: MessageEvent) => { - try { - const parsed = normalizeState(JSON.parse(event.data)); - if (parsed) { - applyState(parsed); - } - const eventId = Number.parseInt(event.lastEventId || "", 10); - if (Number.isFinite(eventId) && eventId > lastEventIdRef.current) { - lastEventIdRef.current = eventId; - } - } catch { - // Ignore malformed reset payloads - } - }); - - source.addEventListener("unavailable", () => { - setIsConnected(false); - startFallbackPolling(); - }); - - source.onerror = () => { - setIsConnected(false); - if (!closedRef.current) { - // Close the EventSource explicitly to stop the browser's built-in - // auto-reconnect loop, which floods the console with connection errors. - // The fallback poll timer will keep checking the server, and SSE will - // be re-established on the next page load or reconnect. - source.close(); - eventSourceRef.current = null; - startFallbackPolling(); - } - }; - }, [applyState, startFallbackPolling]); - - useEffect(() => { - closedRef.current = false; - if (!disabled) { - connectSse(); - } else { - clearPollTimer(); - if (eventSourceRef.current) { - eventSourceRef.current.close(); - eventSourceRef.current = null; - } - } - - return () => { - closedRef.current = true; - clearPollTimer(); - if (eventSourceRef.current) { - eventSourceRef.current.close(); - eventSourceRef.current = null; - } - }; - }, [connectSse, disabled]); - - return { state, isConnected }; -} +export { useStreamingState } from "@hyperbet/ui/spectator/useStreamingState"; diff --git a/packages/hyperbet-solana/app/tests/e2e/setup-localnet.ts b/packages/hyperbet-solana/app/tests/e2e/setup-localnet.ts index 12948b25..a6543ab0 100644 --- a/packages/hyperbet-solana/app/tests/e2e/setup-localnet.ts +++ b/packages/hyperbet-solana/app/tests/e2e/setup-localnet.ts @@ -71,6 +71,58 @@ function lamportsBn(sol: number): BN { return new BN(Math.round(sol * LAMPORTS_PER_SOL).toString()); } +function parseDotEnv(body: string): Record { + const result: Record = {}; + for (const rawLine of body.split("\n")) { + const line = rawLine.trim(); + if (!line || line.startsWith("#")) continue; + const equals = line.indexOf("="); + if (equals <= 0) continue; + const key = line.slice(0, equals).trim(); + const value = line.slice(equals + 1).trim(); + result[key] = value; + } + return result; +} + +function serializeDotEnv(values: Record): string { + return `${Object.entries(values) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([key, value]) => `${key}=${value}`) + .join("\n")}\n`; +} + +async function readJsonFile(filepath: string): Promise { + try { + const body = await fs.readFile(filepath, "utf8"); + return JSON.parse(body) as T; + } catch { + return null; + } +} + +async function readEnvFile(filepath: string): Promise> { + try { + return parseDotEnv(await fs.readFile(filepath, "utf8")); + } catch { + return {}; + } +} + +function parseOptionalPositiveInteger(value: string | undefined): number | null { + const parsed = Number.parseInt(value?.trim() || "", 10); + return Number.isFinite(parsed) && parsed > 0 ? parsed : null; +} + +function decodeHex32(value: string | undefined, label: string): number[] | null { + const normalized = value?.trim().replace(/^0x/i, "").toLowerCase() || ""; + if (!normalized) return null; + if (!/^[0-9a-f]{64}$/.test(normalized)) { + throw new Error(`Invalid ${label}; expected 32-byte hex string`); + } + return Array.from(Buffer.from(normalized, "hex")); +} + async function loadBootstrapAuthority(): Promise<{ keypair: Keypair; keypairPath: string; @@ -326,9 +378,15 @@ function attachReliableSendAndConfirm( async function main(): Promise { const __dirname = path.dirname(fileURLToPath(import.meta.url)); - const appDir = path.resolve(__dirname, "../.."); - const statePath = path.resolve(__dirname, "./state.json"); - const envPath = path.resolve(appDir, ".env.e2e"); + const appDir = process.env.E2E_TARGET_APP_DIR?.trim() + ? path.resolve(process.env.E2E_TARGET_APP_DIR) + : path.resolve(__dirname, "../.."); + const statePath = process.env.E2E_TARGET_STATE_PATH?.trim() + ? path.resolve(process.env.E2E_TARGET_STATE_PATH) + : path.resolve(__dirname, "./state.json"); + const envPath = process.env.E2E_TARGET_ENV_PATH?.trim() + ? path.resolve(process.env.E2E_TARGET_ENV_PATH) + : path.resolve(appDir, ".env.e2e"); const solanaRpcUrl = process.env.E2E_SOLANA_RPC_URL || "http://127.0.0.1:8899"; const solanaWsUrl = process.env.E2E_SOLANA_WS_URL || "ws://127.0.0.1:8900"; @@ -336,6 +394,8 @@ async function main(): Promise { process.env.E2E_BROWSER_SOLANA_RPC_URL || solanaRpcUrl; const browserSolanaWsUrl = process.env.E2E_BROWSER_SOLANA_WS_URL || solanaWsUrl; + const headlessWalletAutoConnect = + process.env.E2E_HEADLESS_WALLET_AUTO_CONNECT?.trim() || "true"; const clobProgramId = resolveIdlAddress( goldClobIdl as unknown as IdlWithAddress, "gold_clob_market", @@ -348,6 +408,13 @@ async function main(): Promise { const bootstrapAuthority = await loadBootstrapAuthority(); const authority = bootstrapAuthority.keypair; const trader = Keypair.fromSeed(E2E_TRADER_SEED); + const existingState = + (await readJsonFile>(statePath)) ?? {}; + const fixedMatchId = parseOptionalPositiveInteger(process.env.E2E_FIXED_MATCH_ID); + const fixedDuelKey = decodeHex32( + process.env.E2E_FIXED_DUEL_KEY_HEX, + "E2E_FIXED_DUEL_KEY_HEX", + ); const provider = new AnchorProvider(connection, toWallet(authority), { commitment: "confirmed", preflightCommitment: "confirmed", @@ -383,7 +450,7 @@ async function main(): Promise { const e2ePerpsMarketId = modelMarketIdFromCharacterId(e2eModelCharacterId); const hostNow = Math.floor(Date.now() / 1000); const now = await getChainUnixTimestamp(connection, hostNow); - const currentMatchId = Math.max(Date.now(), now * 1000); + const currentMatchId = fixedMatchId ?? Math.max(Date.now(), now * 1000); const currentDuelMetadata = JSON.stringify({ duelId: currentMatchId, matchId: currentMatchId, @@ -395,9 +462,11 @@ async function main(): Promise { clobProgram as never, authority, { - duelKey: uniqueDuelKey( - `e2e-current-duel:${Date.now()}:${Math.random().toString(16).slice(2)}`, - ), + duelKey: + fixedDuelKey ?? + uniqueDuelKey( + `e2e-current-duel:${Date.now()}:${Math.random().toString(16).slice(2)}`, + ), betOpenTs: now - 30, // Use validator time, not host wall clock, or the local chain can reject // orders as already closed while the UI still thinks the market is open. @@ -482,52 +551,74 @@ async function main(): Promise { ); const oracleRecordedAt = Date.now(); - - const envBody = [ - "VITE_SOLANA_CLUSTER=localnet", - `VITE_SOLANA_RPC_URL=${browserSolanaRpcUrl}`, - `VITE_SOLANA_WS_URL=${browserSolanaWsUrl}`, - "VITE_USE_LOCAL_SOLANA_RPC_PROXY=true", - `VITE_FIGHT_ORACLE_PROGRAM_ID=${fightProgram.programId.toBase58()}`, - `VITE_GOLD_CLOB_MARKET_PROGRAM_ID=${clobProgramId}`, - `VITE_GOLD_BINARY_MARKET_PROGRAM_ID=${clobProgramId}`, - `VITE_GOLD_MINT=${goldMint.toBase58()}`, - `VITE_ACTIVE_MATCH_ID=${currentMatchId}`, - "VITE_BET_WINDOW_SECONDS=300", - "VITE_NEW_ROUND_BET_WINDOW_SECONDS=300", - "VITE_AUTO_SEED_DELAY_SECONDS=10", - "VITE_MARKET_MAKER_SEED_GOLD=1", - "VITE_BET_FEE_BPS=200", - "VITE_GOLD_DECIMALS=9", - "VITE_REFRESH_INTERVAL_MS=1500", - "VITE_ENABLE_AUTO_SEED=false", - "VITE_E2E_FORCE_WINNER=YES", - `VITE_E2E_MODEL_CHARACTER_ID=${e2eModelCharacterId}`, - `VITE_E2E_MODEL_NAME=${e2eModelName}`, - `VITE_E2E_MODEL_PROVIDER=${e2eModelProvider}`, - `VITE_E2E_MODEL_SLUG=${e2eModelSlug}`, - `VITE_E2E_MODEL_WINS=${e2eModelWins}`, - `VITE_E2E_MODEL_LOSSES=${e2eModelLosses}`, - `VITE_E2E_MODEL_COMBAT_LEVEL=${e2eModelCombatLevel}`, - `VITE_E2E_MODEL_STREAK=${e2eModelCurrentStreak}`, - `VITE_E2E_MODEL_SPOT_INDEX=${e2eModelSpotIndex}`, - `VITE_E2E_MODEL_MU=${e2eModelMu}`, - `VITE_E2E_MODEL_SIGMA=${e2eModelSigma}`, - "VITE_E2E_MODEL_INSURANCE=12", - `VITE_E2E_MODEL_ORACLE_RECORDED_AT=${oracleRecordedAt}`, - `VITE_BINARY_MARKET_MAKER_WALLET=${authority.publicKey.toBase58()}`, - `VITE_BINARY_TRADE_TREASURY_WALLET=${authority.publicKey.toBase58()}`, - `VITE_BINARY_TRADE_MARKET_MAKER_WALLET=${authority.publicKey.toBase58()}`, - `VITE_HEADLESS_WALLET_SECRET_KEY=${Array.from(trader.secretKey).join(",")}`, - "VITE_HEADLESS_WALLET_NAME=E2E Trader", - "VITE_HEADLESS_WALLET_AUTO_CONNECT=true", - ].join("\n"); - - await fs.writeFile(envPath, `${envBody}\n`, "utf8"); + const envValues = { + VITE_SOLANA_CLUSTER: "localnet", + VITE_SOLANA_RPC_URL: browserSolanaRpcUrl, + VITE_SOLANA_WS_URL: browserSolanaWsUrl, + VITE_USE_LOCAL_SOLANA_RPC_PROXY: "true", + VITE_FIGHT_ORACLE_PROGRAM_ID: fightProgram.programId.toBase58(), + VITE_GOLD_CLOB_MARKET_PROGRAM_ID: clobProgramId, + VITE_GOLD_BINARY_MARKET_PROGRAM_ID: clobProgramId, + VITE_GOLD_MINT: goldMint.toBase58(), + VITE_ACTIVE_MATCH_ID: String(currentMatchId), + VITE_BET_WINDOW_SECONDS: "300", + VITE_NEW_ROUND_BET_WINDOW_SECONDS: "300", + VITE_AUTO_SEED_DELAY_SECONDS: "10", + VITE_MARKET_MAKER_SEED_GOLD: "1", + VITE_BET_FEE_BPS: "200", + VITE_GOLD_DECIMALS: "9", + VITE_REFRESH_INTERVAL_MS: "1500", + VITE_ENABLE_AUTO_SEED: "false", + VITE_E2E_FORCE_WINNER: "YES", + VITE_E2E_MODEL_CHARACTER_ID: e2eModelCharacterId, + VITE_E2E_MODEL_NAME: e2eModelName, + VITE_E2E_MODEL_PROVIDER: e2eModelProvider, + VITE_E2E_MODEL_SLUG: e2eModelSlug, + VITE_E2E_MODEL_WINS: String(e2eModelWins), + VITE_E2E_MODEL_LOSSES: String(e2eModelLosses), + VITE_E2E_MODEL_COMBAT_LEVEL: String(e2eModelCombatLevel), + VITE_E2E_MODEL_STREAK: String(e2eModelCurrentStreak), + VITE_E2E_MODEL_SPOT_INDEX: String(e2eModelSpotIndex), + VITE_E2E_MODEL_MU: String(e2eModelMu), + VITE_E2E_MODEL_SIGMA: String(e2eModelSigma), + VITE_E2E_MODEL_INSURANCE: "12", + VITE_E2E_MODEL_ORACLE_RECORDED_AT: String(oracleRecordedAt), + VITE_BINARY_MARKET_MAKER_WALLET: authority.publicKey.toBase58(), + VITE_BINARY_TRADE_TREASURY_WALLET: authority.publicKey.toBase58(), + VITE_BINARY_TRADE_MARKET_MAKER_WALLET: authority.publicKey.toBase58(), + VITE_HEADLESS_WALLET_SECRET_KEY: Array.from(trader.secretKey).join(","), + VITE_HEADLESS_WALLET_NAME: "E2E Trader", + VITE_HEADLESS_WALLET_AUTO_CONNECT: headlessWalletAutoConnect, + } satisfies Record; + const existingEnv = await readEnvFile(envPath); + await fs.writeFile( + envPath, + serializeDotEnv({ + ...existingEnv, + ...envValues, + }), + "utf8", + ); + const existingCurrentMatchId = + typeof existingState.currentMatchId === "number" && + Number.isFinite(existingState.currentMatchId) + ? existingState.currentMatchId + : null; + const existingCurrentDuelId = + typeof existingState.currentDuelId === "string" && + existingState.currentDuelId.trim().length > 0 + ? existingState.currentDuelId.trim() + : null; + const existingCurrentDuelKeyHex = + typeof existingState.currentDuelKeyHex === "string" && + existingState.currentDuelKeyHex.trim().length > 0 + ? existingState.currentDuelKeyHex.trim() + : null; await fs.writeFile( statePath, JSON.stringify( { + ...existingState, mode: "localnet", cluster: "localnet", solanaRpcUrl, @@ -535,9 +626,9 @@ async function main(): Promise { bootstrapWalletPath: bootstrapAuthority.keypairPath, solanaTraderPublicKey: trader.publicKey.toBase58(), goldMint: goldMint.toBase58(), - currentMatchId, - currentDuelId: String(currentMatchId), - currentDuelKeyHex, + currentMatchId: existingCurrentMatchId ?? currentMatchId, + currentDuelId: existingCurrentDuelId ?? String(currentMatchId), + currentDuelKeyHex: existingCurrentDuelKeyHex ?? currentDuelKeyHex, clobConfig: currentMarket.config.toBase58(), clobMarketState: currentMarket.marketState.toBase58(), clobDuelState: currentMarket.duelState.toBase58(), diff --git a/packages/hyperbet-solana/app/vite.config.ts b/packages/hyperbet-solana/app/vite.config.ts index 26aca58c..6846feee 100644 --- a/packages/hyperbet-solana/app/vite.config.ts +++ b/packages/hyperbet-solana/app/vite.config.ts @@ -386,6 +386,10 @@ export default defineConfig(async ({ mode }) => { sourcemap: env.VITE_BUILD_SOURCEMAP === "true", chunkSizeWarningLimit: 3000, rollupOptions: { + input: { + index: path.resolve(__dirname, "index.html"), + "hls-player": path.resolve(__dirname, "hls-player.html"), + }, onwarn(warning, warn) { if ( warning.code === "SOURCEMAP_ERROR" || diff --git a/packages/hyperbet-solana/deployments/contracts.json b/packages/hyperbet-solana/deployments/contracts.json index 31a0e8a9..6bd209c7 100644 --- a/packages/hyperbet-solana/deployments/contracts.json +++ b/packages/hyperbet-solana/deployments/contracts.json @@ -2,8 +2,8 @@ "solana": { "localnet": { "cluster": "localnet", - "fightOracleProgramId": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM", - "goldClobMarketProgramId": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy", + "fightOracleProgramId": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB", + "goldClobMarketProgramId": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt", "goldPerpsMarketProgramId": "BFbmQbSbf3R6fMDdXKMKQZCTyMhMs9MCcjAhGDBLETXS", "goldAmmMarketProgramId": "12E8Lz5w8Qxyj8Fh6LgsCgPDQNJMCLMV1y43LhPrH66w", "goldMint": "DK9nBUMfdu4XprPRWeh8f6KnQiGWD8Z4xz3yzs9gpump", @@ -11,8 +11,8 @@ }, "devnet": { "cluster": "devnet", - "fightOracleProgramId": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM", - "goldClobMarketProgramId": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy", + "fightOracleProgramId": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB", + "goldClobMarketProgramId": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt", "goldPerpsMarketProgramId": "BFbmQbSbf3R6fMDdXKMKQZCTyMhMs9MCcjAhGDBLETXS", "goldAmmMarketProgramId": "12E8Lz5w8Qxyj8Fh6LgsCgPDQNJMCLMV1y43LhPrH66w", "goldMint": "DK9nBUMfdu4XprPRWeh8f6KnQiGWD8Z4xz3yzs9gpump", @@ -20,8 +20,8 @@ }, "testnet": { "cluster": "testnet", - "fightOracleProgramId": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM", - "goldClobMarketProgramId": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy", + "fightOracleProgramId": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB", + "goldClobMarketProgramId": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt", "goldPerpsMarketProgramId": "BFbmQbSbf3R6fMDdXKMKQZCTyMhMs9MCcjAhGDBLETXS", "goldAmmMarketProgramId": "12E8Lz5w8Qxyj8Fh6LgsCgPDQNJMCLMV1y43LhPrH66w", "goldMint": "", diff --git a/packages/hyperbet-solana/keeper/src/betSync.test.ts b/packages/hyperbet-solana/keeper/src/betSync.test.ts new file mode 100644 index 00000000..c1ed2d74 --- /dev/null +++ b/packages/hyperbet-solana/keeper/src/betSync.test.ts @@ -0,0 +1,335 @@ +import { describe, expect, test } from "bun:test"; + +import { + parseBetSyncBootstrapState, + parseBetSyncEvent, + resolveBetSyncBootstrapCursor, + toStreamStateFromBetSyncEvent, +} from "./betSync"; + +describe("resolveBetSyncBootstrapCursor", () => { + test("rebases the applied seq when the source epoch changes", () => { + const next = resolveBetSyncBootstrapCursor({ + previousSourceEpoch: 100, + nextSourceEpoch: 200, + previousLastAppliedSeq: 91210, + latestSeq: 433, + }); + + expect(next.sourceEpochChanged).toBe(true); + expect(next.rebasedLastAppliedSeq).toBe(0); + expect(next.replayUntilSeq).toBeNull(); + expect(next.replayMode).toBe("live"); + }); + + test("preserves replay semantics when the source epoch is unchanged", () => { + const next = resolveBetSyncBootstrapCursor({ + previousSourceEpoch: 200, + nextSourceEpoch: 200, + previousLastAppliedSeq: 25, + latestSeq: 40, + }); + + expect(next.sourceEpochChanged).toBe(false); + expect(next.rebasedLastAppliedSeq).toBe(25); + expect(next.replayUntilSeq).toBe(40); + expect(next.replayMode).toBe("replay"); + }); +}); + +describe("parseBetSyncEvent", () => { + test("preserves channel delivery contract fields", () => { + const event = parseBetSyncEvent({ + sourceEpoch: 7, + seq: 12, + emittedAt: 1_712_345_678_000, + channel: { + id: "main", + mode: "always_on", + }, + publicReadiness: { + ready: false, + reason: "manifest_stale", + }, + canonicalDestination: { + id: "canonical-cloudflare", + playbackReady: false, + }, + fallbackDestination: { + id: "fallback-self-hls", + playbackReady: true, + }, + canonicalAuthority: { + providerLive: true, + playbackProbeReady: false, + decision: "blocked", + reason: "probe_unready", + revision: 9, + updatedAt: 1_712_345_678_100, + liveInputId: "live-input-123", + videoUid: "video-456", + lifecycleStatus: "connected", + playbackUrl: "https://video.example/live.m3u8?protocol=llhls", + playbackProbeStatusCode: 503, + playbackManifestStatus: "stale", + }, + sourceRuntime: { + ready: false, + statusSource: "external_worker", + captureMode: "cdp", + degradedReason: "worker_missing", + currentSceneUrl: "https://example.com/stream", + activeBundle: "bundle-a", + lastFrameAt: 1000, + lastRenderTickAt: 1001, + lastVisualChangeAt: 1002, + lastRecoveryAt: 1003, + recoveryCount: 2, + workerHeartbeatAt: 1004, + }, + deliveryHealth: { + ready: false, + degradedReason: "manifest_stale", + }, + }); + + expect(event).not.toBeNull(); + expect(event?.channel).toEqual({ + id: "main", + mode: "always_on", + }); + expect(event?.publicReadiness).toEqual({ + ready: false, + reason: "manifest_stale", + }); + expect(event?.canonicalDestination).toEqual({ + id: "canonical-cloudflare", + playbackReady: false, + }); + expect(event?.fallbackDestination).toEqual({ + id: "fallback-self-hls", + playbackReady: true, + }); + expect(event?.canonicalAuthority).toEqual({ + providerLive: true, + playbackProbeReady: false, + decision: "blocked", + reason: "probe_unready", + revision: 9, + updatedAt: 1_712_345_678_100, + liveInputId: "live-input-123", + videoUid: "video-456", + lifecycleStatus: "connected", + playbackUrl: "https://video.example/live.m3u8?protocol=llhls", + playbackProbeStatusCode: 503, + playbackManifestStatus: "stale", + }); + expect(event?.sourceRuntime).toEqual({ + ready: false, + statusSource: "external_worker", + captureMode: "cdp", + degradedReason: "worker_missing", + currentSceneUrl: "https://example.com/stream", + activeBundle: "bundle-a", + lastFrameAt: 1000, + lastRenderTickAt: 1001, + lastVisualChangeAt: 1002, + lastRecoveryAt: 1003, + recoveryCount: 2, + workerHeartbeatAt: 1004, + }); + expect(event?.deliveryHealth).toEqual({ + ready: false, + degradedReason: "manifest_stale", + }); + }); + + test("parses and republishes broadcastTimeline without disturbing legacy fields", () => { + const event = parseBetSyncEvent({ + schemaVersion: 3, + sourceEpoch: 8, + seq: 13, + emittedAt: 1_712_345_679_000, + duelId: "duel-2", + duelKey: "22".repeat(32), + phase: "FIGHTING", + betOpenTime: 1_000, + betCloseTime: 2_000, + fightStartTime: 3_000, + duelEndTime: 9_000, + broadcastTimeline: { + phase: "COUNTDOWN", + betOpenTime: 5_000, + betCloseTime: 6_000, + fightStartTime: 7_000, + duelEndTime: 13_000, + presentationDelayMs: 4_000, + updatedAt: 1_712_345_679_000, + }, + }); + + expect(event?.broadcastTimeline).toEqual({ + phase: "COUNTDOWN", + betOpenTime: 5_000, + betCloseTime: 6_000, + fightStartTime: 7_000, + duelEndTime: 13_000, + presentationDelayMs: 4_000, + updatedAt: 1_712_345_679_000, + }); + expect(event?.betCloseTime).toBe(2_000); + + const nextState = toStreamStateFromBetSyncEvent(event!); + expect(nextState.cycle.broadcastTimeline).toEqual({ + phase: "COUNTDOWN", + betOpenTime: 5_000, + betCloseTime: 6_000, + fightStartTime: 7_000, + duelEndTime: 13_000, + presentationDelayMs: 4_000, + updatedAt: 1_712_345_679_000, + }); + expect(nextState.cycle.betCloseTime).toBe(2_000); + expect(nextState.phase).toBe("FIGHTING"); + expect(nextState.phaseVersion).toBeNull(); + expect(nextState.broadcastTimeline).toEqual({ + phase: "COUNTDOWN", + betOpenTime: 5_000, + betCloseTime: 6_000, + fightStartTime: 7_000, + duelEndTime: 13_000, + presentationDelayMs: 4_000, + updatedAt: 1_712_345_679_000, + }); + }); + + test("preserves sourceTimeline alongside the projected timeline", () => { + const event = parseBetSyncEvent({ + schemaVersion: 3, + sourceEpoch: 8, + seq: 14, + emittedAt: 1_712_345_680_000, + duelId: "duel-3", + duelKey: "33".repeat(32), + phase: "COUNTDOWN", + broadcastTimeline: { + phase: "COUNTDOWN", + betOpenTime: 5_000, + betCloseTime: 6_000, + fightStartTime: 7_000, + duelEndTime: 13_000, + presentationDelayMs: 4_000, + updatedAt: 1_712_345_680_000, + }, + sourceTimeline: { + phase: "FIGHTING", + betOpenTime: 1_000, + betCloseTime: 2_000, + fightStartTime: 3_000, + duelEndTime: 9_000, + updatedAt: 1_712_345_676_000, + }, + }); + + expect(event?.sourceTimeline).toEqual({ + phase: "FIGHTING", + betOpenTime: 1_000, + betCloseTime: 2_000, + fightStartTime: 3_000, + duelEndTime: 9_000, + updatedAt: 1_712_345_676_000, + }); + + const nextState = toStreamStateFromBetSyncEvent(event!); + expect(nextState.cycle.sourceTimeline).toEqual(event?.sourceTimeline); + expect(nextState.sourceTimeline).toEqual(event?.sourceTimeline); + }); +}); + +describe("parseBetSyncBootstrapState", () => { + test("retains latest event channel fields during bootstrap", () => { + const state = parseBetSyncBootstrapState({ + sourceEpoch: 9, + latestSeq: 101, + latestEvent: { + sourceEpoch: 9, + seq: 101, + emittedAt: 1_712_345_678_999, + channel: { + id: "main", + canonicalDestinationId: "canonical-cloudflare", + }, + publicReadiness: { + ready: true, + reason: null, + }, + canonicalAuthority: { + providerLive: true, + playbackProbeReady: true, + decision: "ready", + reason: null, + revision: 5, + updatedAt: 1_712_345_679_111, + liveInputId: "live-input-123", + videoUid: "video-456", + lifecycleStatus: "connected", + playbackUrl: "https://video.example/live.m3u8?protocol=llhls", + playbackProbeStatusCode: 200, + playbackManifestStatus: "ok", + }, + sourceRuntime: { + ready: true, + statusSource: "external_worker", + captureMode: "webcodecs", + degradedReason: null, + currentSceneUrl: "https://example.com/stream", + activeBundle: "bundle-b", + lastFrameAt: 1001, + lastRenderTickAt: 1002, + lastVisualChangeAt: 1003, + lastRecoveryAt: 1004, + recoveryCount: 0, + workerHeartbeatAt: 1005, + }, + }, + }); + + expect(state).not.toBeNull(); + expect(state?.latestEvent?.channel).toEqual({ + id: "main", + canonicalDestinationId: "canonical-cloudflare", + }); + expect(state?.latestEvent?.publicReadiness).toEqual({ + ready: true, + reason: null, + }); + expect(state?.latestEvent?.canonicalAuthority).toEqual({ + providerLive: true, + playbackProbeReady: true, + decision: "ready", + reason: null, + revision: 5, + updatedAt: 1_712_345_679_111, + liveInputId: "live-input-123", + videoUid: "video-456", + lifecycleStatus: "connected", + playbackUrl: "https://video.example/live.m3u8?protocol=llhls", + playbackProbeStatusCode: 200, + playbackManifestStatus: "ok", + }); + expect(state?.latestEvent?.sourceRuntime).toEqual({ + ready: true, + statusSource: "external_worker", + captureMode: "webcodecs", + degradedReason: null, + currentSceneUrl: "https://example.com/stream", + activeBundle: "bundle-b", + lastFrameAt: 1001, + lastRenderTickAt: 1002, + lastVisualChangeAt: 1003, + lastRecoveryAt: 1004, + recoveryCount: 0, + workerHeartbeatAt: 1005, + }); + }); +}); diff --git a/packages/hyperbet-solana/keeper/src/betSync.ts b/packages/hyperbet-solana/keeper/src/betSync.ts new file mode 100644 index 00000000..e9585ca2 --- /dev/null +++ b/packages/hyperbet-solana/keeper/src/betSync.ts @@ -0,0 +1,729 @@ +import type { + PredictionMarketLifecycleRecord, + PredictionMarketLifecycleStatus, + PredictionMarketWinner, +} from "../../../hyperbet-chain-registry/src/index"; +import { + normalizePredictionMarketTimestamp, + normalizePredictionMarketWinner, +} from "../../../hyperbet-chain-registry/src/index"; + +type JsonRecord = Record; + +export type BetSyncRendererHealth = { + ready: boolean; + degradedReason: string | null; + updatedAt: number | null; +}; + +export type BetSyncHlsManifest = { + updatedAt: number | null; + mediaSequence: number | null; +}; + +export type BetSyncRendererMetrics = { + captureFps: number | null; + encodeFps: number | null; + droppedFrames: number | null; + renderTick: number | null; + duelStateTick: number | null; + latestFrameAt: number | null; + latestRenderTickAt: number | null; + latestDuelStateTickAt: number | null; + latestVisualChangeAt: number | null; + visualChangeAgeMs: number | null; + hlsManifest: BetSyncHlsManifest | null; +}; + +export type BetSyncDelivery = { + mode: "self_hls" | "external_hls"; + provider: string | null; + playbackUrl: string | null; + hlsUrl: string | null; + llhlsUrl: string | null; + ingestUrl: string | null; +}; + +export type BetSyncBroadcastTimeline = { + phase: string | null; + betOpenTime: number | null; + betCloseTime: number | null; + fightStartTime: number | null; + duelEndTime: number | null; + presentationDelayMs: number; + updatedAt: number | null; +}; + +export type BetSyncSourceTimeline = { + phase: string | null; + betOpenTime: number | null; + betCloseTime: number | null; + fightStartTime: number | null; + duelEndTime: number | null; + updatedAt: number | null; +}; + +export type BetSyncCanonicalAuthority = { + providerLive: boolean; + playbackProbeReady: boolean; + decision: string | null; + reason: string | null; + revision: number | null; + updatedAt: number | null; + liveInputId: string | null; + videoUid: string | null; + lifecycleStatus: string | null; + playbackUrl: string | null; + playbackProbeStatusCode: number | null; + playbackManifestStatus: string | null; +}; + +export type BetSyncEvent = { + schemaVersion: number; + sourceEpoch: number; + seq: number; + emittedAt: number; + cycle: JsonRecord | null; + duelId: string | null; + duelKey: string | null; + phase: string | null; + phaseVersion: number | null; + broadcastTimeline: BetSyncBroadcastTimeline | null; + sourceTimeline: BetSyncSourceTimeline | null; + betOpenTime: number | null; + betCloseTime: number | null; + fightStartTime: number | null; + duelEndTime: number | null; + winnerId: string | null; + winnerName: string | null; + winReason: string | null; + seed: string | null; + replayHash: string | null; + agent1: JsonRecord | null; + agent2: JsonRecord | null; + arenaPositions: JsonRecord | null; + leaderboard: JsonRecord[]; + cameraTarget: string | null; + rendererHealth: BetSyncRendererHealth | null; + rendererMetrics: BetSyncRendererMetrics | null; + delivery: BetSyncDelivery | null; + sourceRuntime: JsonRecord | null; + channel: JsonRecord | null; + publicReadiness: JsonRecord | null; + canonicalDestination: JsonRecord | null; + fallbackDestination: JsonRecord | null; + canonicalAuthority: BetSyncCanonicalAuthority | null; + deliveryHealth: JsonRecord | null; +}; + +export type BetSyncBootstrapState = { + sourceEpoch: number; + latestSeq: number; + oldestReplaySeq: number | null; + latestEvent: BetSyncEvent | null; +}; + +export type StreamState = { + type: "STREAMING_STATE_UPDATE"; + cycle: JsonRecord; + leaderboard: JsonRecord[]; + cameraTarget: string | null; + seq: number; + emittedAt: number; + phase?: string | null; + phaseVersion?: number | null; + broadcastTimeline?: BetSyncBroadcastTimeline | null; + sourceTimeline?: BetSyncSourceTimeline | null; + rendererHealth?: BetSyncRendererHealth | null; + canonicalAuthority?: BetSyncCanonicalAuthority | null; + sourceRuntime?: JsonRecord | null; +}; + +export type PredictionMarketsDuelSnapshot = { + duelKey: string | null; + duelId: string | null; + phase: string | null; + winner: PredictionMarketWinner; + betCloseTime: number | null; + agent1Name: string | null; + agent2Name: string | null; +}; + +export type PredictionMarketsSurface = { + duel: PredictionMarketsDuelSnapshot; + markets: PredictionMarketLifecycleRecord[]; + updatedAt: number | null; +}; + +export type PredictionMarketsOverviewResponse = { + updatedAt: number | null; + live: PredictionMarketsSurface | null; + recentSettlement: PredictionMarketsSurface | null; +}; + +export type BetSyncReplayMode = "bootstrap" | "replay" | "reset" | "live"; + +function asRecord(value: unknown): JsonRecord | null { + return value && typeof value === "object" ? (value as JsonRecord) : null; +} + +function asFiniteNumber(value: unknown): number | null { + return typeof value === "number" && Number.isFinite(value) ? value : null; +} + +function asString(value: unknown): string | null { + return typeof value === "string" && value.trim().length > 0 ? value : null; +} + +function normalizeDuelKey(value: unknown): string | null { + const raw = asString(value); + if (!raw) return null; + const normalized = raw.replace(/^0x/i, "").trim().toLowerCase(); + return /^[0-9a-f]{64}$/.test(normalized) ? normalized : null; +} + +const PREDICTION_MARKET_STATUS_RANK: Record< + PredictionMarketLifecycleStatus, + number +> = { + UNKNOWN: -1, + PENDING: 0, + OPEN: 1, + LOCKED: 2, + PROPOSED: 3, + CHALLENGED: 4, + RESOLVED: 5, + CANCELLED: 5, +}; + +function statusRank(status: PredictionMarketLifecycleStatus): number { + return PREDICTION_MARKET_STATUS_RANK[status] ?? -1; +} + +function preferPreviousMarket( + previous: PredictionMarketLifecycleRecord, + next: PredictionMarketLifecycleRecord, +): boolean { + const previousRank = statusRank(previous.lifecycleStatus); + const nextRank = statusRank(next.lifecycleStatus); + if (nextRank < previousRank) { + return true; + } + if ( + nextRank === previousRank && + previous.winner !== "NONE" && + next.winner === "NONE" + ) { + return true; + } + return false; +} + +function mergeMarketRecord( + previous: PredictionMarketLifecycleRecord, + next: PredictionMarketLifecycleRecord, +): PredictionMarketLifecycleRecord { + const keepPrevious = preferPreviousMarket(previous, next); + const preferred = keepPrevious ? previous : next; + const fallback = keepPrevious ? next : previous; + + return { + ...fallback, + ...preferred, + duelKey: preferred.duelKey ?? fallback.duelKey ?? null, + duelId: preferred.duelId ?? fallback.duelId ?? null, + betCloseTime: preferred.betCloseTime ?? fallback.betCloseTime ?? null, + winner: preferred.winner !== "NONE" ? preferred.winner : fallback.winner, + syncedAt: + preferred.syncedAt != null + ? Math.max(preferred.syncedAt, fallback.syncedAt ?? preferred.syncedAt) + : (fallback.syncedAt ?? null), + metadata: + previous.metadata || next.metadata + ? { + ...(keepPrevious ? next.metadata ?? {} : previous.metadata ?? {}), + ...(keepPrevious ? previous.metadata ?? {} : next.metadata ?? {}), + } + : undefined, + }; +} + +function mergeDuelSnapshot( + previous: PredictionMarketsDuelSnapshot, + next: PredictionMarketsDuelSnapshot, +): PredictionMarketsDuelSnapshot { + const nextPhase = + next.phase === "IDLE" && previous.phase && previous.phase !== "IDLE" + ? previous.phase + : (next.phase ?? previous.phase ?? null); + + return { + duelKey: next.duelKey ?? previous.duelKey ?? null, + duelId: next.duelId ?? previous.duelId ?? null, + phase: nextPhase, + winner: next.winner !== "NONE" ? next.winner : previous.winner, + betCloseTime: next.betCloseTime ?? previous.betCloseTime ?? null, + agent1Name: next.agent1Name ?? previous.agent1Name ?? null, + agent2Name: next.agent2Name ?? previous.agent2Name ?? null, + }; +} + +function normalizeRendererHealth(value: unknown): BetSyncRendererHealth | null { + const candidate = asRecord(value); + if (!candidate) return null; + return { + ready: candidate.ready === true, + degradedReason: asString(candidate.degradedReason), + updatedAt: normalizePredictionMarketTimestamp(candidate.updatedAt), + }; +} + +function normalizeHlsManifest(value: unknown): BetSyncHlsManifest | null { + const candidate = asRecord(value); + if (!candidate) return null; + return { + updatedAt: normalizePredictionMarketTimestamp(candidate.updatedAt), + mediaSequence: asFiniteNumber(candidate.mediaSequence), + }; +} + +function normalizeRendererMetrics( + value: unknown, +): BetSyncRendererMetrics | null { + const candidate = asRecord(value); + if (!candidate) return null; + return { + captureFps: asFiniteNumber(candidate.captureFps), + encodeFps: asFiniteNumber(candidate.encodeFps), + droppedFrames: asFiniteNumber(candidate.droppedFrames), + renderTick: asFiniteNumber(candidate.renderTick), + duelStateTick: asFiniteNumber(candidate.duelStateTick), + latestFrameAt: normalizePredictionMarketTimestamp(candidate.latestFrameAt), + latestRenderTickAt: normalizePredictionMarketTimestamp( + candidate.latestRenderTickAt, + ), + latestDuelStateTickAt: normalizePredictionMarketTimestamp( + candidate.latestDuelStateTickAt, + ), + latestVisualChangeAt: normalizePredictionMarketTimestamp( + candidate.latestVisualChangeAt, + ), + visualChangeAgeMs: asFiniteNumber(candidate.visualChangeAgeMs), + hlsManifest: normalizeHlsManifest(candidate.hlsManifest), + }; +} + +function normalizeDelivery(value: unknown): BetSyncDelivery | null { + const candidate = asRecord(value); + if (!candidate) return null; + const mode = asString(candidate.mode); + if (mode !== "self_hls" && mode !== "external_hls") { + return null; + } + return { + mode, + provider: asString(candidate.provider), + playbackUrl: asString(candidate.playbackUrl), + hlsUrl: asString(candidate.hlsUrl), + llhlsUrl: asString(candidate.llhlsUrl), + ingestUrl: asString(candidate.ingestUrl), + }; +} + +function normalizeBroadcastTimeline( + value: unknown, +): BetSyncBroadcastTimeline | null { + const candidate = asRecord(value); + if (!candidate) return null; + + return { + phase: asString(candidate.phase), + betOpenTime: normalizePredictionMarketTimestamp(candidate.betOpenTime), + betCloseTime: normalizePredictionMarketTimestamp(candidate.betCloseTime), + fightStartTime: normalizePredictionMarketTimestamp(candidate.fightStartTime), + duelEndTime: normalizePredictionMarketTimestamp(candidate.duelEndTime), + presentationDelayMs: Math.max(0, asFiniteNumber(candidate.presentationDelayMs) ?? 0), + updatedAt: normalizePredictionMarketTimestamp(candidate.updatedAt), + }; +} + +function normalizeSourceTimeline(value: unknown): BetSyncSourceTimeline | null { + const candidate = asRecord(value); + if (!candidate) return null; + + return { + phase: asString(candidate.phase), + betOpenTime: normalizePredictionMarketTimestamp(candidate.betOpenTime), + betCloseTime: normalizePredictionMarketTimestamp(candidate.betCloseTime), + fightStartTime: normalizePredictionMarketTimestamp(candidate.fightStartTime), + duelEndTime: normalizePredictionMarketTimestamp(candidate.duelEndTime), + updatedAt: normalizePredictionMarketTimestamp(candidate.updatedAt), + }; +} + +function normalizeCanonicalAuthority( + value: unknown, +): BetSyncCanonicalAuthority | null { + const candidate = asRecord(value); + if (!candidate) return null; + + return { + providerLive: candidate.providerLive === true, + playbackProbeReady: candidate.playbackProbeReady === true, + decision: asString(candidate.decision), + reason: asString(candidate.reason), + revision: asFiniteNumber(candidate.revision), + updatedAt: normalizePredictionMarketTimestamp(candidate.updatedAt), + liveInputId: asString(candidate.liveInputId), + videoUid: asString(candidate.videoUid), + lifecycleStatus: asString(candidate.lifecycleStatus), + playbackUrl: asString(candidate.playbackUrl), + playbackProbeStatusCode: asFiniteNumber(candidate.playbackProbeStatusCode), + playbackManifestStatus: asString(candidate.playbackManifestStatus), + }; +} + +export function parseBetSyncEvent(payload: unknown): BetSyncEvent | null { + const candidate = asRecord(payload); + if (!candidate) return null; + + const seq = asFiniteNumber(candidate.seq); + const emittedAt = normalizePredictionMarketTimestamp(candidate.emittedAt); + const sourceEpoch = asFiniteNumber(candidate.sourceEpoch); + + if (seq == null || emittedAt == null || sourceEpoch == null) { + return null; + } + + const broadcastTimeline = normalizeBroadcastTimeline( + candidate.broadcastTimeline, + ); + const sourceTimeline = normalizeSourceTimeline(candidate.sourceTimeline); + + return { + schemaVersion: asFiniteNumber(candidate.schemaVersion) ?? 1, + sourceEpoch, + seq, + emittedAt, + cycle: asRecord(candidate.cycle), + duelId: asString(candidate.duelId), + duelKey: normalizeDuelKey(candidate.duelKey), + phase: asString(candidate.phase), + phaseVersion: asFiniteNumber(candidate.phaseVersion), + broadcastTimeline, + sourceTimeline, + betOpenTime: normalizePredictionMarketTimestamp(candidate.betOpenTime), + betCloseTime: normalizePredictionMarketTimestamp(candidate.betCloseTime), + fightStartTime: normalizePredictionMarketTimestamp(candidate.fightStartTime), + duelEndTime: normalizePredictionMarketTimestamp(candidate.duelEndTime), + winnerId: asString(candidate.winnerId), + winnerName: asString(candidate.winnerName), + winReason: asString(candidate.winReason), + seed: asString(candidate.seed), + replayHash: asString(candidate.replayHash), + agent1: asRecord(candidate.agent1), + agent2: asRecord(candidate.agent2), + arenaPositions: asRecord(candidate.arenaPositions), + leaderboard: Array.isArray(candidate.leaderboard) + ? candidate.leaderboard + .map((entry) => asRecord(entry)) + .filter((entry): entry is JsonRecord => entry !== null) + : [], + cameraTarget: asString(candidate.cameraTarget), + rendererHealth: normalizeRendererHealth(candidate.rendererHealth), + rendererMetrics: normalizeRendererMetrics(candidate.rendererMetrics), + delivery: normalizeDelivery(candidate.delivery), + sourceRuntime: asRecord(candidate.sourceRuntime), + channel: asRecord(candidate.channel), + publicReadiness: asRecord(candidate.publicReadiness), + canonicalDestination: asRecord(candidate.canonicalDestination), + fallbackDestination: asRecord(candidate.fallbackDestination), + canonicalAuthority: normalizeCanonicalAuthority(candidate.canonicalAuthority), + deliveryHealth: asRecord(candidate.deliveryHealth), + }; +} + +export function parseBetSyncBootstrapState( + payload: unknown, +): BetSyncBootstrapState | null { + const candidate = asRecord(payload); + if (!candidate) return null; + + const replay = asRecord(candidate.replay); + const sourceEpoch = + asFiniteNumber(candidate.sourceEpoch) ?? + asFiniteNumber(replay?.sourceEpoch); + const latestSeq = + asFiniteNumber(candidate.latestSeq) ?? + asFiniteNumber(replay?.latestSeq) ?? + asFiniteNumber(candidate.seq); + if (sourceEpoch == null || latestSeq == null) { + return null; + } + + return { + sourceEpoch, + latestSeq, + oldestReplaySeq: + asFiniteNumber(candidate.oldestReplaySeq) ?? + asFiniteNumber(replay?.oldestSeq), + latestEvent: + parseBetSyncEvent(candidate.latestEvent) ?? parseBetSyncEvent(candidate), + }; +} + +export function toStreamStateFromBetSyncEvent(event: BetSyncEvent): StreamState { + const rawCycle = event.cycle; + return { + type: "STREAMING_STATE_UPDATE", + cycle: { + cycleId: + asString(rawCycle?.cycleId) ?? + event.duelId ?? + `bet-sync-${event.sourceEpoch}-${event.seq}`, + duelId: event.duelId, + duelKey: event.duelKey, + duelKeyHex: event.duelKey ? `0x${event.duelKey}` : null, + phase: event.phase ?? "IDLE", + phaseVersion: event.phaseVersion, + rawCycle, + broadcastTimeline: event.broadcastTimeline, + sourceTimeline: event.sourceTimeline, + betOpenTime: event.betOpenTime, + betCloseTime: event.betCloseTime, + fightStartTime: event.fightStartTime, + duelEndTime: event.duelEndTime, + winnerId: event.winnerId, + winnerName: event.winnerName, + winReason: event.winReason, + seed: event.seed, + replayHash: event.replayHash, + agent1: event.agent1, + agent2: event.agent2, + arenaPositions: event.arenaPositions, + rendererHealth: event.rendererHealth, + }, + leaderboard: event.leaderboard, + cameraTarget: event.cameraTarget, + seq: event.seq, + emittedAt: event.emittedAt, + phase: event.phase, + phaseVersion: event.phaseVersion, + broadcastTimeline: event.broadcastTimeline, + sourceTimeline: event.sourceTimeline, + rendererHealth: event.rendererHealth, + canonicalAuthority: event.canonicalAuthority, + sourceRuntime: event.sourceRuntime, + }; +} + +export function parsePredictionMarketsSurface( + payload: unknown, +): PredictionMarketsSurface | null { + const candidate = asRecord(payload); + const duel = asRecord(candidate?.duel); + if (!candidate || !duel || !Array.isArray(candidate.markets)) { + return null; + } + + return { + duel: { + duelKey: normalizeDuelKey(duel.duelKey), + duelId: asString(duel.duelId), + phase: asString(duel.phase), + winner: normalizePredictionMarketWinner(duel.winner), + betCloseTime: normalizePredictionMarketTimestamp(duel.betCloseTime), + agent1Name: asString(duel.agent1Name), + agent2Name: asString(duel.agent2Name), + }, + markets: candidate.markets.filter( + (market): market is PredictionMarketLifecycleRecord => + Boolean(market) && typeof market === "object", + ) as PredictionMarketLifecycleRecord[], + updatedAt: normalizePredictionMarketTimestamp(candidate.updatedAt), + }; +} + +export function parsePredictionMarketsOverview( + payload: unknown, +): PredictionMarketsOverviewResponse | null { + const candidate = asRecord(payload); + if (!candidate) return null; + return { + updatedAt: normalizePredictionMarketTimestamp(candidate.updatedAt), + live: parsePredictionMarketsSurface(candidate.live), + recentSettlement: parsePredictionMarketsSurface(candidate.recentSettlement), + }; +} + +export function hasMeaningfulSurface( + surface: PredictionMarketsSurface | null | undefined, +): boolean { + if (!surface) return false; + return Boolean( + surface.duel.duelKey || surface.duel.duelId || surface.markets.length, + ); +} + +export function sameDuelIdentity( + left: PredictionMarketsSurface | null | undefined, + right: PredictionMarketsSurface | null | undefined, +): boolean { + if (!left || !right) return false; + if (left.duel.duelKey && right.duel.duelKey) { + return left.duel.duelKey === right.duel.duelKey; + } + if (left.duel.duelId && right.duel.duelId) { + return left.duel.duelId === right.duel.duelId; + } + return false; +} + +export function mergePredictionMarketsSurface( + previous: PredictionMarketsSurface | null, + next: PredictionMarketsSurface | null, +): PredictionMarketsSurface | null { + if (!next) return previous; + if (!previous) return next; + + const byChain = new Map(); + for (const market of previous.markets) { + byChain.set(market.chainKey, market); + } + for (const market of next.markets) { + const existing = byChain.get(market.chainKey); + byChain.set( + market.chainKey, + existing ? mergeMarketRecord(existing, market) : market, + ); + } + + return { + duel: mergeDuelSnapshot(previous.duel, next.duel), + markets: Array.from(byChain.values()), + updatedAt: next.updatedAt, + }; +} + +export function rollPredictionMarketsOverview( + previous: PredictionMarketsOverviewResponse | null, + live: PredictionMarketsSurface | null, + updatedAt: number, +): PredictionMarketsOverviewResponse { + const nextLive = live; + let recentSettlement = previous?.recentSettlement ?? null; + + if ( + hasMeaningfulSurface(previous?.live) && + hasMeaningfulSurface(nextLive) && + !sameDuelIdentity(previous?.live, nextLive) + ) { + recentSettlement = previous?.live ?? null; + } + + return { + updatedAt, + live: nextLive, + recentSettlement, + }; +} + +export function selectBetSyncResumeSeq(params: { + lastAppliedSeq: number; +}): number { + return Math.max(0, params.lastAppliedSeq); +} + +export function selectBetSyncReplayUntilSeq(params: { + resumeSeq: number; + latestSeq: number | null; +}): number | null { + const resumeSeq = Math.max(0, params.resumeSeq); + if (resumeSeq <= 0 || params.latestSeq == null) { + return null; + } + return params.latestSeq > resumeSeq ? params.latestSeq : null; +} + +export function resolveBetSyncBootstrapCursor(params: { + previousSourceEpoch: number | null; + nextSourceEpoch: number; + previousLastAppliedSeq: number; + latestSeq: number | null; +}): { + sourceEpochChanged: boolean; + rebasedLastAppliedSeq: number; + replayUntilSeq: number | null; + replayMode: BetSyncReplayMode; +} { + const sourceEpochChanged = + params.previousSourceEpoch != null && + params.previousSourceEpoch !== params.nextSourceEpoch; + const rebasedLastAppliedSeq = sourceEpochChanged + ? 0 + : Math.max(0, params.previousLastAppliedSeq); + const replayUntilSeq = selectBetSyncReplayUntilSeq({ + resumeSeq: selectBetSyncResumeSeq({ + lastAppliedSeq: rebasedLastAppliedSeq, + }), + latestSeq: params.latestSeq, + }); + + return { + sourceEpochChanged, + rebasedLastAppliedSeq, + replayUntilSeq, + replayMode: + rebasedLastAppliedSeq > 0 && replayUntilSeq != null ? "replay" : "live", + }; +} + +export function isBetSyncEventStaleAfterSourceReset(params: { + sourceEpochChanged: boolean; + currentStreamEmittedAt: number | null; + eventEmittedAt: number; + toleranceMs: number; +}): boolean { + if (!params.sourceEpochChanged || params.currentStreamEmittedAt == null) { + return false; + } + return ( + params.eventEmittedAt + Math.max(0, params.toleranceMs) < + params.currentStreamEmittedAt + ); +} + +export function resolveBetSyncReplayMode(params: { + eventName: string; + eventSeq: number; + replayUntilSeq: number | null; + sourceEpochChanged: boolean; +}): { + replayMode: BetSyncReplayMode; + replayUntilSeq: number | null; +} { + if (params.sourceEpochChanged || params.eventName === "reset") { + return { + replayMode: "reset", + replayUntilSeq: null, + }; + } + + if (params.replayUntilSeq != null) { + if (params.eventSeq < params.replayUntilSeq) { + return { + replayMode: "replay", + replayUntilSeq: params.replayUntilSeq, + }; + } + return { + replayMode: "live", + replayUntilSeq: null, + }; + } + + return { + replayMode: "live", + replayUntilSeq: null, + }; +} diff --git a/packages/hyperbet-solana/keeper/src/bot.ts b/packages/hyperbet-solana/keeper/src/bot.ts index 863c207b..0c4606fd 100644 --- a/packages/hyperbet-solana/keeper/src/bot.ts +++ b/packages/hyperbet-solana/keeper/src/bot.ts @@ -45,6 +45,14 @@ import type { PredictionMarketWinner } from "@hyperbet/chain-registry"; import { buildResultHash } from "./resultHash"; const DEFAULT_DISPUTE_WINDOW_SECS = 3600; +const configuredDisputeWindowSecs = Number( + process.env.SOLANA_ORACLE_DISPUTE_WINDOW_SECS ?? DEFAULT_DISPUTE_WINDOW_SECS, +); +const SOLANA_ORACLE_DISPUTE_WINDOW_SECS = Number.isFinite( + configuredDisputeWindowSecs, +) + ? Math.max(60, Math.floor(configuredDisputeWindowSecs)) + : DEFAULT_DISPUTE_WINDOW_SECS; function asNum(value: unknown, fallback = 0): number { if (typeof value === "number") return value; @@ -347,8 +355,48 @@ const botKeypair = readKeypair( process.env.MARKET_MAKER_KEYPAIR || requireEnv("ORACLE_AUTHORITY_KEYPAIR"), ); +function readOptionalKeypair(name: string): Keypair | null { + const value = process.env[name]?.trim(); + return value ? readKeypair(value) : null; +} + +const hasExplicitOracleConfigAuthority = Boolean( + process.env.ORACLE_CONFIG_AUTHORITY_KEYPAIR?.trim(), +); +const hasExplicitClobConfigAuthority = Boolean( + process.env.CLOB_CONFIG_AUTHORITY_KEYPAIR?.trim() || + process.env.CLOB_AUTHORITY_KEYPAIR?.trim(), +); +const oracleConfigAuthorityKeypair = + readOptionalKeypair("ORACLE_CONFIG_AUTHORITY_KEYPAIR") || + readOptionalKeypair("ORACLE_AUTHORITY_KEYPAIR") || + botKeypair; +const oracleReporterKeypair = + readOptionalKeypair("ORACLE_REPORTER_KEYPAIR") || + readOptionalKeypair("ORACLE_AUTHORITY_KEYPAIR") || + botKeypair; +const oracleFinalizerKeypair = + readOptionalKeypair("ORACLE_FINALIZER_KEYPAIR") || + readOptionalKeypair("ORACLE_AUTHORITY_KEYPAIR") || + oracleReporterKeypair; +const oracleChallengerKeypair = + readOptionalKeypair("ORACLE_CHALLENGER_KEYPAIR") || + readOptionalKeypair("ORACLE_AUTHORITY_KEYPAIR") || + oracleReporterKeypair; +const clobConfigAuthorityKeypair = + readOptionalKeypair("CLOB_CONFIG_AUTHORITY_KEYPAIR") || + readOptionalKeypair("CLOB_AUTHORITY_KEYPAIR") || + readOptionalKeypair("ORACLE_CONFIG_AUTHORITY_KEYPAIR") || + readOptionalKeypair("ORACLE_AUTHORITY_KEYPAIR") || + botKeypair; +const clobMarketOperatorKeypair = + readOptionalKeypair("CLOB_MARKET_OPERATOR_KEYPAIR") || + readOptionalKeypair("MARKET_OPERATOR_KEYPAIR") || + readOptionalKeypair("ORACLE_REPORTER_KEYPAIR") || + readOptionalKeypair("ORACLE_AUTHORITY_KEYPAIR") || + botKeypair; const { connection, provider, fightOracle, goldClobMarket, goldPerpsMarket } = - createPrograms(botKeypair); + createPrograms(botKeypair, { usePollingSendAndConfirm: true }); const fightProgram = fightOracle as unknown as Program; const marketProgram = goldClobMarket as unknown as Program; const perpsProgram = goldPerpsMarket as unknown as Program; @@ -1524,6 +1572,10 @@ async function ensureWalletAccountReady( } const ensureOracleReady = async (): Promise => { + const expectedAuthority = oracleConfigAuthorityKeypair.publicKey; + const expectedReporter = oracleReporterKeypair.publicKey; + const expectedFinalizer = oracleFinalizerKeypair.publicKey; + const expectedChallenger = oracleChallengerKeypair.publicKey; let config = await fightProgram.account.oracleConfig.fetchNullable(oracleConfigPda); if (!config) { @@ -1531,18 +1583,19 @@ const ensureOracleReady = async (): Promise => { () => fightProgram.methods .initializeOracle( - botKeypair.publicKey, - botKeypair.publicKey, - botKeypair.publicKey, - new BN(DEFAULT_DISPUTE_WINDOW_SECS), + expectedReporter, + expectedFinalizer, + expectedChallenger, + new BN(SOLANA_ORACLE_DISPUTE_WINDOW_SECS), ) .accountsPartial({ - authority: botKeypair.publicKey, + authority: expectedAuthority, oracleConfig: oracleConfigPda, program: fightProgram.programId, programData: deriveProgramDataAddress(fightProgram.programId), systemProgram: SystemProgram.programId, }) + .signers([oracleConfigAuthorityKeypair]) .rpc(), connection, ); @@ -1554,31 +1607,38 @@ const ensureOracleReady = async (): Promise => { `Oracle config ${oracleConfigPda.toBase58()} was not created`, ); } - if (!(config.authority as PublicKey).equals(botKeypair.publicKey)) { + const actualAuthority = config.authority as PublicKey; + if (hasExplicitOracleConfigAuthority && !actualAuthority.equals(expectedAuthority)) { throw new Error( - `Bot wallet ${botKeypair.publicKey.toBase58()} is not oracle authority`, + `Configured oracle authority ${expectedAuthority.toBase58()} does not match on-chain oracle authority ${actualAuthority.toBase58()}`, ); } const configNeedsUpdate = - !(config.reporter as PublicKey).equals(botKeypair.publicKey) || - !(config.finalizer as PublicKey).equals(botKeypair.publicKey) || - !(config.challenger as PublicKey).equals(botKeypair.publicKey); + !(config.reporter as PublicKey).equals(expectedReporter) || + !(config.finalizer as PublicKey).equals(expectedFinalizer) || + !(config.challenger as PublicKey).equals(expectedChallenger) || + asNum(config.disputeWindowSecs) !== SOLANA_ORACLE_DISPUTE_WINDOW_SECS; if (configNeedsUpdate) { - const disputeWindowSecs = asNum(config.disputeWindowSecs); + if (!actualAuthority.equals(expectedAuthority)) { + throw new Error( + `Oracle role update requires config authority ${actualAuthority.toBase58()}, but keeper has ${expectedAuthority.toBase58()}`, + ); + } await runWithRecovery( () => fightProgram.methods .updateOracleConfig( - botKeypair.publicKey, - botKeypair.publicKey, - botKeypair.publicKey, - botKeypair.publicKey, - new BN(disputeWindowSecs), + expectedAuthority, + expectedReporter, + expectedFinalizer, + expectedChallenger, + new BN(SOLANA_ORACLE_DISPUTE_WINDOW_SECS), ) .accountsPartial({ - authority: botKeypair.publicKey, + authority: expectedAuthority, oracleConfig: oracleConfigPda, }) + .signers([oracleConfigAuthorityKeypair]) .rpc(), connection, ); @@ -1597,6 +1657,8 @@ const ensureMarketConfigReady = async (): Promise => { const existingConfig = await marketProgram.account.marketConfig.fetchNullable(marketConfigPda); const expectedConfig = { + authority: clobConfigAuthorityKeypair.publicKey, + marketOperator: clobMarketOperatorKeypair.publicKey, treasury: configuredTradeTreasuryWallet, marketMaker: configuredTradeMarketMakerWallet, tradeTreasuryFeeBps, @@ -1609,7 +1671,7 @@ const ensureMarketConfigReady = async (): Promise => { () => marketProgram.methods .initializeConfig( - botKeypair.publicKey, + expectedConfig.marketOperator, expectedConfig.treasury, expectedConfig.marketMaker, expectedConfig.tradeTreasuryFeeBps, @@ -1617,12 +1679,13 @@ const ensureMarketConfigReady = async (): Promise => { expectedConfig.winningsMarketMakerFeeBps, ) .accountsPartial({ - authority: botKeypair.publicKey, + authority: expectedConfig.authority, config: marketConfigPda, program: marketProgram.programId, programData: deriveProgramDataAddress(marketProgram.programId), systemProgram: SystemProgram.programId, }) + .signers([clobConfigAuthorityKeypair]) .rpc(), connection, ); @@ -1632,7 +1695,17 @@ const ensureMarketConfigReady = async (): Promise => { return; } + const actualAuthority = existingConfig.authority as PublicKey; + if (hasExplicitClobConfigAuthority && !actualAuthority.equals(expectedConfig.authority)) { + throw new Error( + `Configured CLOB authority ${expectedConfig.authority.toBase58()} does not match on-chain CLOB authority ${actualAuthority.toBase58()}`, + ); + } + const configNeedsUpdate = + !(existingConfig.marketOperator as PublicKey).equals( + expectedConfig.marketOperator, + ) || !(existingConfig.treasury as PublicKey).equals(expectedConfig.treasury) || !(existingConfig.marketMaker as PublicKey).equals( expectedConfig.marketMaker, @@ -1645,12 +1718,17 @@ const ensureMarketConfigReady = async (): Promise => { expectedConfig.winningsMarketMakerFeeBps; if (configNeedsUpdate) { + if (!actualAuthority.equals(expectedConfig.authority)) { + throw new Error( + `CLOB config update requires authority ${actualAuthority.toBase58()}, but keeper has ${expectedConfig.authority.toBase58()}`, + ); + } await runWithRecovery( () => marketProgram.methods .updateConfig( - botKeypair.publicKey, - botKeypair.publicKey, + expectedConfig.authority, + expectedConfig.marketOperator, expectedConfig.treasury, expectedConfig.marketMaker, expectedConfig.tradeTreasuryFeeBps, @@ -1658,9 +1736,10 @@ const ensureMarketConfigReady = async (): Promise => { expectedConfig.winningsMarketMakerFeeBps, ) .accountsPartial({ - authority: botKeypair.publicKey, + authority: expectedConfig.authority, config: marketConfigPda, }) + .signers([clobConfigAuthorityKeypair]) .rpc(), connection, ); @@ -1788,13 +1867,14 @@ async function ensureClobVaultReady(vault: PublicKey): Promise { } function buildDuelMetadata(data: DuelLifecycleEvent): string { - return JSON.stringify({ - cycleId: data.cycleId, - duelId: data.duelId, - duelKeyHex: data.duelKeyHex, - agent1: data.agent1?.name ?? "Agent A", - agent2: data.agent2?.name ?? "Agent B", - }); + const duelKeyRef = data.duelKeyHex.trim().replace(/^0x/i, "").toLowerCase(); + const metadataUri = `hyperscapes://duel/${duelKeyRef}`; + if (Buffer.byteLength(metadataUri, "utf8") > 200) { + throw new Error( + `duel metadata URI exceeds Solana oracle limit for duel ${data.duelId}`, + ); + } + return metadataUri; } function duelStatusEnum( @@ -1835,20 +1915,26 @@ async function upsertDuelLifecycle( const duelKey = duelKeyHexToBytes(data.duelKeyHex); const duelState = findDuelStatePda(fightProgram.programId, duelKey); const existingDuelState = await getDuelState(duelState); - const nowSeconds = Math.floor(Date.now() / 1000); - const betOpenTs = Math.floor((data.betOpenTime ?? Date.now()) / 1000); - const betCloseTs = Math.max( - betOpenTs + 1, + const derivedBetOpenTs = Math.floor((data.betOpenTime ?? Date.now()) / 1000); + const derivedBetCloseTs = Math.max( + derivedBetOpenTs + 1, Math.floor( (data.betCloseTime ?? data.fightStartTime ?? Date.now() + 1_000) / 1000, ), ); - const duelStartTs = Math.max( - betCloseTs, + const derivedDuelStartTs = Math.max( + derivedBetCloseTs, Math.floor((data.fightStartTime ?? data.betCloseTime ?? Date.now()) / 1000), ); - const requestedStatus = - status === "scheduled" && betOpenTs <= nowSeconds ? "bettingOpen" : status; + const betOpenTs = existingDuelState + ? asNum(existingDuelState.betOpenTs, derivedBetOpenTs) + : derivedBetOpenTs; + const betCloseTs = existingDuelState + ? asNum(existingDuelState.betCloseTs, derivedBetCloseTs) + : derivedBetCloseTs; + const duelStartTs = existingDuelState + ? asNum(existingDuelState.duelStartTs, derivedDuelStartTs) + : derivedDuelStartTs; const participantAHash = toByteArray32(existingDuelState?.participantAHash) ?? hashParticipant(data.agent1); const participantBHash = @@ -1865,14 +1951,15 @@ async function upsertDuelLifecycle( new BN(betCloseTs), new BN(duelStartTs), buildDuelMetadata(data), - duelStatusEnum(requestedStatus), + duelStatusEnum(status), ) .accountsPartial({ - reporter: botKeypair.publicKey, + reporter: oracleReporterKeypair.publicKey, oracleConfig: oracleConfigPda, duelState, systemProgram: SystemProgram.programId, }) + .signers([oracleReporterKeypair]) .rpc(), connection, ); @@ -2266,13 +2353,14 @@ async function createOrSyncRound( marketProgram.methods .initializeMarket(Array.from(duelKey), DUEL_WINNER_MARKET_KIND) .accountsPartial({ - operator: botKeypair.publicKey, + operator: clobMarketOperatorKeypair.publicKey, config: marketConfigPda, duelState, marketState, vault, systemProgram: SystemProgram.programId, }) + .signers([clobMarketOperatorKeypair]) .rpc(), connection, ); @@ -2602,10 +2690,11 @@ async function maybeFinalizeTrackedProposal( metadataUri, ) .accountsPartial({ - finalizer: botKeypair.publicKey, + finalizer: oracleFinalizerKeypair.publicKey, oracleConfig: oracleConfigPda, duelState: trackedMatch.duelState, }) + .signers([oracleFinalizerKeypair]) .rpc(), connection, ); @@ -2699,10 +2788,11 @@ async function reportRoundResult(data: DuelLifecycleEvent): Promise { buildDuelMetadata(data), ) .accountsPartial({ - reporter: botKeypair.publicKey, + reporter: oracleReporterKeypair.publicKey, oracleConfig: oracleConfigPda, duelState: trackedMatch.duelState, }) + .signers([oracleReporterKeypair]) .rpc(), connection, ); diff --git a/packages/hyperbet-solana/keeper/src/canonicalDelivery.test.ts b/packages/hyperbet-solana/keeper/src/canonicalDelivery.test.ts new file mode 100644 index 00000000..7283309a --- /dev/null +++ b/packages/hyperbet-solana/keeper/src/canonicalDelivery.test.ts @@ -0,0 +1,75 @@ +import { describe, expect, test } from "bun:test"; + +import { + mergeCanonicalDeliveryOverride, + type CanonicalStreamDelivery, +} from "./canonicalDelivery"; + +const BASE_DELIVERY: CanonicalStreamDelivery = { + mode: "external_hls", + provider: "cloudflare_stream", + playbackUrl: "https://videodelivery.net/live.m3u8", + hlsUrl: null, + llhlsUrl: "https://videodelivery.net/live.m3u8?protocol=llhls", + ingestUrl: "srt://live.cloudflare.example/input", +}; + +const OVERRIDE_DELIVERY: CanonicalStreamDelivery = { + mode: "external_hls", + provider: "cloudflare_stream", + playbackUrl: "https://override.example/video.m3u8", + hlsUrl: "https://override.example/video.m3u8", + llhlsUrl: null, + ingestUrl: "rtmps://override.example/live", +}; + +describe("mergeCanonicalDeliveryOverride", () => { + test("preserves authoritative canonical transport metadata", () => { + const delivery = mergeCanonicalDeliveryOverride({ + baseDelivery: BASE_DELIVERY, + overrideDelivery: OVERRIDE_DELIVERY, + hasAuthoritativeCanonicalDestination: true, + }); + + expect(delivery).toEqual({ + mode: "external_hls", + provider: "cloudflare_stream", + playbackUrl: "https://videodelivery.net/live.m3u8", + hlsUrl: "https://override.example/video.m3u8", + llhlsUrl: "https://videodelivery.net/live.m3u8?protocol=llhls", + ingestUrl: "srt://live.cloudflare.example/input", + }); + }); + + test("allows override delivery when no authoritative canonical destination exists", () => { + const delivery = mergeCanonicalDeliveryOverride({ + baseDelivery: BASE_DELIVERY, + overrideDelivery: OVERRIDE_DELIVERY, + hasAuthoritativeCanonicalDestination: false, + }); + + expect(delivery).toEqual(OVERRIDE_DELIVERY); + }); + + test("fills missing playback fields from the override without replacing ingest", () => { + const delivery = mergeCanonicalDeliveryOverride({ + baseDelivery: { + ...BASE_DELIVERY, + playbackUrl: null, + hlsUrl: null, + llhlsUrl: null, + }, + overrideDelivery: OVERRIDE_DELIVERY, + hasAuthoritativeCanonicalDestination: true, + }); + + expect(delivery).toEqual({ + mode: "external_hls", + provider: "cloudflare_stream", + playbackUrl: "https://override.example/video.m3u8", + hlsUrl: "https://override.example/video.m3u8", + llhlsUrl: null, + ingestUrl: "srt://live.cloudflare.example/input", + }); + }); +}); diff --git a/packages/hyperbet-solana/keeper/src/canonicalDelivery.ts b/packages/hyperbet-solana/keeper/src/canonicalDelivery.ts new file mode 100644 index 00000000..d2bd2195 --- /dev/null +++ b/packages/hyperbet-solana/keeper/src/canonicalDelivery.ts @@ -0,0 +1,41 @@ +export type CanonicalStreamDelivery = { + mode: "self_hls" | "external_hls"; + provider: string | null; + playbackUrl: string | null; + hlsUrl: string | null; + llhlsUrl: string | null; + ingestUrl: string | null; +}; + +export function mergeCanonicalDeliveryOverride(params: { + baseDelivery: CanonicalStreamDelivery | null; + overrideDelivery: CanonicalStreamDelivery | null; + hasAuthoritativeCanonicalDestination: boolean; +}): CanonicalStreamDelivery | null { + const { + baseDelivery, + overrideDelivery, + hasAuthoritativeCanonicalDestination, + } = params; + + if (!hasAuthoritativeCanonicalDestination) { + return overrideDelivery ?? baseDelivery; + } + + if (!baseDelivery) { + return overrideDelivery; + } + + if (!overrideDelivery) { + return baseDelivery; + } + + return { + mode: baseDelivery.mode, + provider: baseDelivery.provider, + playbackUrl: baseDelivery.playbackUrl ?? overrideDelivery.playbackUrl, + hlsUrl: baseDelivery.hlsUrl ?? overrideDelivery.hlsUrl, + llhlsUrl: baseDelivery.llhlsUrl ?? overrideDelivery.llhlsUrl, + ingestUrl: baseDelivery.ingestUrl, + }; +} diff --git a/packages/hyperbet-solana/keeper/src/common.ts b/packages/hyperbet-solana/keeper/src/common.ts index 9ab242ec..fa365948 100644 --- a/packages/hyperbet-solana/keeper/src/common.ts +++ b/packages/hyperbet-solana/keeper/src/common.ts @@ -11,6 +11,7 @@ import { } from "@coral-xyz/anchor"; import { Connection, + type ConfirmOptions, Keypair, PublicKey, Transaction, @@ -51,6 +52,14 @@ dotenv.config({ path: path.join(envRoot, `.env.${envClusterSuffix}`) }); dotenv.config({ path: path.join(envRoot, ".env") }); type SignableTx = Transaction | VersionedTransaction; +type CommitmentLevel = "processed" | "confirmed" | "finalized"; + +type CreateProgramsOptions = { + usePollingSendAndConfirm?: boolean; + commitment?: CommitmentLevel; + preflightCommitment?: CommitmentLevel; + confirmTimeoutMs?: number; +}; type AnchorLikeWallet = Wallet & { payer: Keypair; @@ -79,6 +88,247 @@ function toAnchorWallet(signer: Keypair): AnchorLikeWallet { }; } +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function cloneTransaction(transaction: Transaction): Transaction { + const clone = new Transaction(); + clone.instructions = [...transaction.instructions]; + clone.feePayer = transaction.feePayer; + clone.nonceInfo = transaction.nonceInfo; + clone.minNonceContextSlot = transaction.minNonceContextSlot; + return clone; +} + +async function getLatestBlockhashWithRetries( + connection: Connection, + commitment: CommitmentLevel, + maxAttempts = 4, +): Promise<{ blockhash: string; lastValidBlockHeight: number }> { + let lastError: unknown = null; + for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { + try { + return await connection.getLatestBlockhash(commitment); + } catch (error) { + lastError = error; + if (attempt < maxAttempts) { + await sleep(Math.min(2_000, 250 * attempt)); + } + } + } + + throw new Error( + `failed to fetch latest blockhash after ${maxAttempts} attempts: ${String(lastError)}`, + ); +} + +export async function confirmSignatureByPolling( + connection: Connection, + signature: string, + lastValidBlockHeight?: number, + timeoutMs = Number.parseInt( + process.env.HYPERBET_SOLANA_CONFIRM_TIMEOUT_MS ?? "180000", + 10, + ), + commitment: CommitmentLevel = "confirmed", +): Promise { + const deadline = Date.now() + timeoutMs; + let lastRpcError: unknown = null; + let pollCount = 0; + + while (Date.now() < deadline) { + pollCount += 1; + try { + const statuses = await connection.getSignatureStatuses([signature], { + searchTransactionHistory: true, + }); + const status = statuses.value[0]; + + if (status?.err) { + throw new Error( + `transaction ${signature} failed: ${JSON.stringify(status.err)}`, + ); + } + + if ( + status && + (status.confirmationStatus === "confirmed" || + status.confirmationStatus === "finalized") + ) { + return; + } + + if (lastValidBlockHeight && pollCount % 8 === 0) { + const currentBlockHeight = await connection.getBlockHeight(commitment); + if (currentBlockHeight > lastValidBlockHeight) { + throw new Error( + `transaction ${signature} expired at block height ${lastValidBlockHeight}`, + ); + } + } + } catch (error) { + if ( + error instanceof Error && + (error.message.includes(`transaction ${signature} failed`) || + error.message.includes("expired at block height")) + ) { + throw error; + } + lastRpcError = error; + } + + await sleep(250); + } + + const reason = + lastRpcError instanceof Error ? ` (${lastRpcError.message})` : ""; + throw new Error( + `timed out waiting for confirmation for ${signature}${reason}`, + ); +} + +async function sendAndConfirmWithPolling( + provider: AnchorProvider, + transaction: Transaction, + signers: Keypair[] = [], + options?: ConfirmOptions, +): Promise { + const opts = { + ...provider.opts, + ...options, + }; + const commitment = + (opts.preflightCommitment ?? + opts.commitment ?? + "confirmed") as CommitmentLevel; + let lastError: unknown = null; + + for (let attempt = 1; attempt <= 4; attempt += 1) { + try { + const tx = cloneTransaction(transaction); + tx.feePayer = tx.feePayer ?? provider.wallet.publicKey; + + const { blockhash, lastValidBlockHeight } = + await getLatestBlockhashWithRetries(provider.connection, commitment); + tx.recentBlockhash = blockhash; + + if (signers.length > 0) { + tx.partialSign(...signers); + } + + const signedTx = await provider.wallet.signTransaction(tx); + const signature = await provider.connection.sendRawTransaction( + signedTx.serialize(), + { + maxRetries: 8, + preflightCommitment: commitment, + skipPreflight: opts.skipPreflight ?? false, + }, + ); + + await confirmSignatureByPolling( + provider.connection, + signature, + lastValidBlockHeight, + undefined, + commitment, + ); + return signature; + } catch (error) { + lastError = error; + if (attempt < 4) { + await sleep(250 * attempt); + } + } + } + + throw lastError instanceof Error ? lastError : new Error(String(lastError)); +} + +async function sendVersionedTransactionWithPolling( + provider: AnchorProvider, + transaction: VersionedTransaction, + signers: Keypair[] = [], + options?: ConfirmOptions, +): Promise { + const opts = { + ...provider.opts, + ...options, + }; + const commitment = + (opts.preflightCommitment ?? + opts.commitment ?? + "confirmed") as CommitmentLevel; + let lastError: unknown = null; + + for (let attempt = 1; attempt <= 4; attempt += 1) { + try { + if (signers.length > 0) { + transaction.sign(signers); + } + const signedTx = await provider.wallet.signTransaction(transaction); + const signature = await provider.connection.sendRawTransaction( + signedTx.serialize(), + { + maxRetries: 8, + preflightCommitment: commitment, + skipPreflight: opts.skipPreflight ?? false, + }, + ); + + await confirmSignatureByPolling( + provider.connection, + signature, + undefined, + undefined, + commitment, + ); + return signature; + } catch (error) { + lastError = error; + if (attempt < 4) { + await sleep(250 * attempt); + } + } + } + + throw lastError instanceof Error ? lastError : new Error(String(lastError)); +} + +export function createPollingProvider( + connection: Connection, + wallet: Wallet, + options?: Partial, +): AnchorProvider { + const provider = new AnchorProvider(connection, wallet, { + commitment: "confirmed", + preflightCommitment: "confirmed", + ...(options ?? {}), + }); + const defaultSendAndConfirm = provider.sendAndConfirm.bind(provider); + provider.sendAndConfirm = async (tx, signers, overrideOptions) => { + if (tx instanceof VersionedTransaction) { + return sendVersionedTransactionWithPolling( + provider, + tx, + (signers ?? []) as Keypair[], + overrideOptions, + ); + } + if (tx instanceof Transaction) { + return sendAndConfirmWithPolling( + provider, + tx, + (signers ?? []) as Keypair[], + overrideOptions, + ); + } + return defaultSendAndConfirm(tx, signers, overrideOptions); + }; + return provider; +} + export function getRpcUrl(): string { if (process.env.SOLANA_RPC_URL) return process.env.SOLANA_RPC_URL; @@ -274,19 +524,31 @@ export type KeeperPrograms = { goldBinaryMarket: null; }; -export function createPrograms(signer: Keypair): KeeperPrograms { +export function createPrograms( + signer: Keypair, + options?: CreateProgramsOptions, +): KeeperPrograms { const fightOracleProgramId = resolveFightOracleProgramId(); const goldClobMarketProgramId = resolveGoldClobMarketProgramId(); const goldPerpsMarketProgramId = resolveGoldPerpsMarketProgramId(); const goldAmmMarketProgramId = resolveGoldAmmMarketProgramId(); + const commitment = options?.commitment ?? "confirmed"; + const preflightCommitment = + options?.preflightCommitment ?? options?.commitment ?? "confirmed"; const connection = new Connection(getRpcUrl(), { - commitment: "confirmed", + commitment, + confirmTransactionInitialTimeout: options?.confirmTimeoutMs, }); const wallet = toAnchorWallet(signer); - const provider = new AnchorProvider(connection, wallet, { - commitment: "confirmed", - preflightCommitment: "confirmed", - }); + const provider = options?.usePollingSendAndConfirm + ? createPollingProvider(connection, wallet, { + commitment, + preflightCommitment, + }) + : new AnchorProvider(connection, wallet, { + commitment, + preflightCommitment, + }); const fightOracle = new Program( resolveFightOracleIdl(fightOracleProgramId), diff --git a/packages/hyperbet-solana/keeper/src/db.test.ts b/packages/hyperbet-solana/keeper/src/db.test.ts index f9f0a31b..5dde764a 100644 --- a/packages/hyperbet-solana/keeper/src/db.test.ts +++ b/packages/hyperbet-solana/keeper/src/db.test.ts @@ -245,6 +245,32 @@ describe("keeper db persistence", () => { ]); }); + test("round-trips bet-sync checkpoint state through SQLite", async () => { + const db = (await import( + `./db.ts?case=${Date.now()}-bet-sync-checkpoint` + )) as typeof import("./db.ts"); + loadedModules.push(db); + + db.saveBetSyncCheckpoint({ + sourceEpoch: 7, + lastSeenSeq: 101, + lastAppliedSeq: 99, + replayMode: "replay", + degradedReason: "upstream reset", + updatedAt: 1_700_000_000_123, + }); + + const state = db.loadAll(); + expect(state.betSyncCheckpoint).toEqual({ + sourceEpoch: 7, + lastSeenSeq: 101, + lastAppliedSeq: 99, + replayMode: "replay", + degradedReason: "upstream reset", + updatedAt: 1_700_000_000_123, + }); + }); + test("quarantines duplicate recorded bets before enforcing uniqueness", async () => { seedDuplicateBets(process.env.KEEPER_DB_PATH!); diff --git a/packages/hyperbet-solana/keeper/src/db.ts b/packages/hyperbet-solana/keeper/src/db.ts index f384426d..5571ab8a 100644 --- a/packages/hyperbet-solana/keeper/src/db.ts +++ b/packages/hyperbet-solana/keeper/src/db.ts @@ -94,6 +94,15 @@ export type DbPerpsMarketRecord = { updatedAt: number; }; +export type DbBetSyncCheckpoint = { + sourceEpoch: number; + lastSeenSeq: number; + lastAppliedSeq: number; + replayMode: string | null; + degradedReason: string | null; + updatedAt: number; +}; + // ── DB singleton ────────────────────────────────────────────────────────────── const db = new Database(DB_PATH, { create: true }); @@ -388,6 +397,16 @@ db.run(`CREATE TABLE IF NOT EXISTS perps_markets ( db.run(`CREATE INDEX IF NOT EXISTS idx_perps_markets_status_seen ON perps_markets (status, last_seen_at DESC)`); +db.run(`CREATE TABLE IF NOT EXISTS bet_sync_checkpoint ( + id INTEGER PRIMARY KEY CHECK (id = 1), + source_epoch INTEGER NOT NULL DEFAULT 0, + last_seen_seq INTEGER NOT NULL DEFAULT 0, + last_applied_seq INTEGER NOT NULL DEFAULT 0, + replay_mode TEXT, + degraded_reason TEXT, + updated_at INTEGER NOT NULL DEFAULT 0 +)`); + // ── Prepared statements ─────────────────────────────────────────────────────── const insertBet = db.prepare(`INSERT OR IGNORE INTO bets @@ -491,6 +510,17 @@ const upsertPerpsMarket = db.prepare(`INSERT INTO perps_markets deprecated_at = excluded.deprecated_at, updated_at = excluded.updated_at`); +const upsertBetSyncCheckpoint = db.prepare(`INSERT INTO bet_sync_checkpoint + (id, source_epoch, last_seen_seq, last_applied_seq, replay_mode, degraded_reason, updated_at) + VALUES (1, $sourceEpoch, $lastSeenSeq, $lastAppliedSeq, $replayMode, $degradedReason, $updatedAt) + ON CONFLICT(id) DO UPDATE SET + source_epoch = excluded.source_epoch, + last_seen_seq = excluded.last_seen_seq, + last_applied_seq = excluded.last_applied_seq, + replay_mode = excluded.replay_mode, + degraded_reason = excluded.degraded_reason, + updated_at = excluded.updated_at`); + // ── Load (hydrate in-memory state from DB at startup) ───────────────────────── export type HydratedState = { @@ -507,6 +537,7 @@ export type HydratedState = { invitedWalletsByWallet: Map>; referralFeeShareGoldByWallet: Map; treasuryFeesFromReferralsByWallet: Map; + betSyncCheckpoint: DbBetSyncCheckpoint | null; }; export function loadAll(betLimit = 5000): HydratedState { @@ -655,6 +686,30 @@ export function loadAll(betLimit = 5000): HydratedState { ); } + const checkpointRow = db + .prepare( + `SELECT source_epoch, last_seen_seq, last_applied_seq, replay_mode, degraded_reason, updated_at + FROM bet_sync_checkpoint + WHERE id = 1`, + ) + .get() as Record | null; + const betSyncCheckpoint = checkpointRow + ? { + sourceEpoch: Number(checkpointRow.source_epoch ?? 0), + lastSeenSeq: Number(checkpointRow.last_seen_seq ?? 0), + lastAppliedSeq: Number(checkpointRow.last_applied_seq ?? 0), + replayMode: + checkpointRow.replay_mode == null + ? null + : String(checkpointRow.replay_mode), + degradedReason: + checkpointRow.degraded_reason == null + ? null + : String(checkpointRow.degraded_reason), + updatedAt: Number(checkpointRow.updated_at ?? 0), + } + : null; + console.log( `[db] loaded ${bets.length} bets, ${walletDisplay.size} wallets, ${pointsByWallet.size} point records from ${DB_PATH}`, ); @@ -673,6 +728,7 @@ export function loadAll(betLimit = 5000): HydratedState { invitedWalletsByWallet, referralFeeShareGoldByWallet, treasuryFeesFromReferralsByWallet, + betSyncCheckpoint, }; } @@ -795,6 +851,17 @@ export function saveReferralFees( }); } +export function saveBetSyncCheckpoint(checkpoint: DbBetSyncCheckpoint): void { + upsertBetSyncCheckpoint.run({ + $sourceEpoch: checkpoint.sourceEpoch, + $lastSeenSeq: checkpoint.lastSeenSeq, + $lastAppliedSeq: checkpoint.lastAppliedSeq, + $replayMode: checkpoint.replayMode, + $degradedReason: checkpoint.degradedReason, + $updatedAt: checkpoint.updatedAt, + }); +} + export function loadAgentRatings(): Record { const ratings: Record = {}; for (const row of db diff --git a/packages/hyperbet-solana/keeper/src/idl/fight_oracle.json b/packages/hyperbet-solana/keeper/src/idl/fight_oracle.json index d0c45c69..0c8cba6a 100644 --- a/packages/hyperbet-solana/keeper/src/idl/fight_oracle.json +++ b/packages/hyperbet-solana/keeper/src/idl/fight_oracle.json @@ -1,5 +1,5 @@ { - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM", + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB", "metadata": { "name": "fight_oracle", "version": "0.1.0", @@ -343,7 +343,7 @@ }, { "name": "program", - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM" + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB" }, { "name": "program_data" @@ -1051,12 +1051,12 @@ { "code": 6021, "name": "ParticipantHashImmutable", - "msg": "Participant hashes are immutable after betting opens" + "msg": "Participant hashes are immutable after duel prepare" }, { "code": 6022, "name": "TimingImmutable", - "msg": "Bet timing is immutable after betting opens" + "msg": "Bet timing is immutable after duel prepare" }, { "code": 6023, diff --git a/packages/hyperbet-solana/keeper/src/idl/fight_oracle.ts b/packages/hyperbet-solana/keeper/src/idl/fight_oracle.ts index 4f3b083d..7183391c 100644 --- a/packages/hyperbet-solana/keeper/src/idl/fight_oracle.ts +++ b/packages/hyperbet-solana/keeper/src/idl/fight_oracle.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/fight_oracle.json`. */ export type FightOracle = { - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM", + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB", "metadata": { "name": "fightOracle", "version": "0.1.0", @@ -349,7 +349,7 @@ export type FightOracle = { }, { "name": "program", - "address": "GFdnu7kUnZGiXh4ejWiJSBCUxvq4UfdEeUv9jjFzr5EM" + "address": "7EXSmAP4fcabojJBQbxN7GcBE4hinqfZao1tJcGLTrfB" }, { "name": "programData" @@ -1057,12 +1057,12 @@ export type FightOracle = { { "code": 6021, "name": "participantHashImmutable", - "msg": "Participant hashes are immutable after betting opens" + "msg": "Participant hashes are immutable after duel prepare" }, { "code": 6022, "name": "timingImmutable", - "msg": "Bet timing is immutable after betting opens" + "msg": "Bet timing is immutable after duel prepare" }, { "code": 6023, diff --git a/packages/hyperbet-solana/keeper/src/idl/gold_clob_market.json b/packages/hyperbet-solana/keeper/src/idl/gold_clob_market.json index d2bb65f3..550fae7e 100644 --- a/packages/hyperbet-solana/keeper/src/idl/gold_clob_market.json +++ b/packages/hyperbet-solana/keeper/src/idl/gold_clob_market.json @@ -1,5 +1,5 @@ { - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy", + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt", "metadata": { "name": "gold_clob_market", "version": "0.1.0", @@ -433,7 +433,7 @@ }, { "name": "program", - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy" + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt" }, { "name": "program_data" diff --git a/packages/hyperbet-solana/keeper/src/idl/gold_clob_market.ts b/packages/hyperbet-solana/keeper/src/idl/gold_clob_market.ts index 16e1d101..aea963e3 100644 --- a/packages/hyperbet-solana/keeper/src/idl/gold_clob_market.ts +++ b/packages/hyperbet-solana/keeper/src/idl/gold_clob_market.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/gold_clob_market.json`. */ export type GoldClobMarket = { - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy", + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt", "metadata": { "name": "goldClobMarket", "version": "0.1.0", @@ -439,7 +439,7 @@ export type GoldClobMarket = { }, { "name": "program", - "address": "3QUVoaKJqo1rg9eXe7vyFewJrY75NWdtH8JZfvTb79Uy" + "address": "EHBdndoQUXZXDtoTcb5D5AimJ1x715vKWzbqLH8entTt" }, { "name": "programData" diff --git a/packages/hyperbet-solana/keeper/src/service.ts b/packages/hyperbet-solana/keeper/src/service.ts index 768af283..08434091 100644 --- a/packages/hyperbet-solana/keeper/src/service.ts +++ b/packages/hyperbet-solana/keeper/src/service.ts @@ -48,6 +48,7 @@ import { import type { FightOracle } from "./idl/fight_oracle"; import type { GoldClobMarket } from "./idl/gold_clob_market"; import { + type DbBetSyncCheckpoint, deleteIdentityMembers, loadAll, loadPerpsMarkets, @@ -63,7 +64,21 @@ import { saveReferral, saveInvitedWallet, saveReferralFees, + saveBetSyncCheckpoint, } from "./db"; +import { + isBetSyncEventStaleAfterSourceReset, + parseBetSyncBootstrapState, + parseBetSyncEvent, + resolveBetSyncBootstrapCursor, + resolveBetSyncReplayMode, + selectBetSyncReplayUntilSeq, + selectBetSyncResumeSeq, + toStreamStateFromBetSyncEvent, + type BetSyncEvent, + type BetSyncReplayMode, +} from "./betSync"; +import { mergeCanonicalDeliveryOverride } from "./canonicalDelivery"; import { buildKeeperBotChildEnv } from "./keeperBot"; import { modelMarketIdFromCharacterId } from "./modelMarkets"; import { @@ -78,6 +93,193 @@ type StreamState = { cameraTarget: string | null; seq: number; emittedAt: number; + phase?: string | null; + phaseVersion?: number | null; + broadcastTimeline?: Record | null; + sourceTimeline?: Record | null; + rendererHealth?: Record | null; + channel?: Record | null; + publicReadiness?: Record | null; + canonicalDestination?: Record | null; + fallbackDestination?: Record | null; + canonicalAuthority?: Record | null; + sourceRuntime?: Record | null; + deliveryHealth?: Record | null; + delivery?: Record | null; +}; + +type CanonicalStreamHealth = { + ready: boolean; + degradedReason: string | null; + updatedAt: number | null; +}; + +type CanonicalSourceRuntimeStatusSource = + | "external_worker" + | "in_process_bridge" + | "none"; + +type CanonicalSourceRuntimeCaptureMode = + | "cdp" + | "webcodecs" + | "mediarecorder" + | "none"; + +type CanonicalSourceRuntimeDegradedReason = + | "worker_missing" + | "browser_missing" + | "page_not_ready" + | "unexpected_navigation" + | "capture_stalled" + | "encoder_stalled" + | "manifest_stale" + | "destination_disconnected" + | "status_stale" + | "unknown"; + +type CanonicalSourceRuntime = { + ready: boolean; + statusSource: CanonicalSourceRuntimeStatusSource; + captureMode: CanonicalSourceRuntimeCaptureMode; + degradedReason: CanonicalSourceRuntimeDegradedReason | string | null; + currentSceneUrl: string | null; + activeBundle: string | null; + lastFrameAt: number | null; + lastRenderTickAt: number | null; + lastVisualChangeAt: number | null; + lastRecoveryAt: number | null; + recoveryCount: number; + workerHeartbeatAt: number | null; +}; + +type CanonicalStreamPlayback = { + url: string | null; + kind: string | null; + renderSessionId: string | null; + presentationDelayMs: number; +}; + +type CanonicalStreamDestinationRole = "canonical" | "fallback" | "mirror"; +type CanonicalStreamDestinationProvider = + | "cloudflare_stream" + | "self_hls" + | "twitch" + | "kick" + | "youtube" + | "custom"; +type CanonicalStreamDeliveryTransport = + | "llhls" + | "hls" + | "rtmps" + | "srt" + | "unknown"; +type CanonicalStreamManifestStatus = "ok" | "stale" | "missing" | "unknown"; +type CanonicalStreamPublicReadiness = { + ready: boolean; + reason: string | null; + updatedAt: number | null; +}; +type CanonicalAuthorityInfo = { + providerLive: boolean; + playbackProbeReady: boolean; + decision: string | null; + reason: string | null; + revision: number | null; + updatedAt: number | null; + liveInputId: string | null; + videoUid: string | null; + lifecycleStatus: string | null; + playbackUrl: string | null; + playbackProbeStatusCode: number | null; + playbackManifestStatus: string | null; +}; +type CanonicalStreamDestination = { + id: string; + name: string; + role: CanonicalStreamDestinationRole; + provider: CanonicalStreamDestinationProvider; + transport: CanonicalStreamDeliveryTransport; + playbackUrl: string | null; + ingestUrl: string | null; + connected: boolean; + transportHealthy: boolean; + playbackReady: boolean; + manifestStatus: CanonicalStreamManifestStatus; + lastError: string | null; + updatedAt: number | null; +}; +type CanonicalStreamChannel = { + id: string; + mode: "always_on"; + presentationDelayMs: number; + activeDuelId: string | null; + activeDuelKey: string | null; + canonicalDestinationId: string; + fallbackDestinationId: string | null; + publicPlaybackUrl: string | null; + publicReadiness: CanonicalStreamPublicReadiness; + destinations: CanonicalStreamDestination[]; +}; + +type CanonicalHlsManifest = { + updatedAt: number | null; + mediaSequence: number | null; +}; + +type CanonicalRendererMetrics = { + captureFps: number | null; + encodeFps: number | null; + droppedFrames: number | null; + renderTick: number | null; + duelStateTick: number | null; + latestFrameAt: number | null; + latestRenderTickAt: number | null; + latestDuelStateTickAt: number | null; + latestVisualChangeAt: number | null; + visualChangeAgeMs: number | null; + hlsManifest: CanonicalHlsManifest | null; +}; + +type CanonicalStreamDelivery = { + mode: "self_hls" | "external_hls"; + provider: string | null; + playbackUrl: string | null; + hlsUrl: string | null; + llhlsUrl: string | null; + ingestUrl: string | null; +}; + +type CanonicalStreamSession = { + schemaVersion: number; + sourceEpoch: number | null; + seq: number; + emittedAt: number; + duelId: string | null; + duelKey: string | null; + phase: string | null; + phaseVersion: number | null; + cycle: Record; + leaderboard: unknown[]; + cameraTarget: string | null; + playback: CanonicalStreamPlayback | null; + rendererHealth: CanonicalStreamHealth | null; + sourceRuntime: CanonicalSourceRuntime | null; + deliveryHealth: CanonicalStreamHealth | null; + channel: CanonicalStreamChannel | null; + publicReadiness: CanonicalStreamPublicReadiness | null; + canonicalDestination: CanonicalStreamDestination | null; + fallbackDestination: CanonicalStreamDestination | null; + canonicalAuthority: CanonicalAuthorityInfo | null; + rendererMetrics: CanonicalRendererMetrics | null; + delivery: CanonicalStreamDelivery | null; + authorityHealth: CanonicalStreamHealth; + status: { + authority: CanonicalStreamHealth; + renderer: CanonicalStreamHealth | null; + sourceRuntime: CanonicalSourceRuntime | null; + delivery: CanonicalStreamDelivery | null; + deliveryHealth: CanonicalStreamHealth | null; + }; }; type BetRecord = { @@ -143,6 +345,7 @@ type JsonRpcRequestPayload = Record & { }; const encoder = new TextEncoder(); +const decoder = new TextDecoder(); const __dirname = path.dirname(fileURLToPath(import.meta.url)); const keeperRoot = path.resolve(__dirname, ".."); const KEEPER_BOT_HEALTH_FILE = ( @@ -213,6 +416,18 @@ function readEnvBoolean(name: string, fallback: boolean): boolean { return raw === "1" || raw === "true" || raw === "yes" || raw === "on"; } +function deriveBetSyncStateUrl(eventsUrl: string): string { + if (!eventsUrl) return ""; + try { + const url = new URL(eventsUrl); + url.pathname = url.pathname.replace(/\/events$/, "/state"); + url.search = ""; + return url.toString(); + } catch { + return ""; + } +} + const PORT = Number(process.env.PORT || 8080); const ARENA_WRITE_KEY = process.env.ARENA_EXTERNAL_BET_WRITE_KEY?.trim() || ""; const STREAM_PUBLISH_KEY = @@ -227,6 +442,18 @@ const STREAM_STATE_SOURCE_URL = process.env.STREAM_STATE_SOURCE_URL?.trim() || ""; const STREAM_STATE_SOURCE_BEARER_TOKEN = process.env.STREAM_STATE_SOURCE_BEARER_TOKEN?.trim() || ""; +const BET_SYNC_SOURCE_EVENTS_URL = + process.env.BET_SYNC_SOURCE_EVENTS_URL?.trim() || ""; +const BET_SYNC_SOURCE_STATE_URL = + process.env.BET_SYNC_SOURCE_STATE_URL?.trim() || + deriveBetSyncStateUrl(BET_SYNC_SOURCE_EVENTS_URL); +const BET_SYNC_SOURCE_BEARER_TOKEN = + process.env.BET_SYNC_SOURCE_BEARER_TOKEN?.trim() || + STREAM_STATE_SOURCE_BEARER_TOKEN; +const ACTIVE_STREAM_STATE_SOURCE_URL = + BET_SYNC_SOURCE_STATE_URL || STREAM_STATE_SOURCE_URL; +const ACTIVE_STREAM_SOURCE_BEARER_TOKEN = + BET_SYNC_SOURCE_BEARER_TOKEN || STREAM_STATE_SOURCE_BEARER_TOKEN; const STREAM_STATE_POLL_MS = Math.max( 1_000, Number(process.env.STREAM_STATE_POLL_MS || 2_000), @@ -239,6 +466,39 @@ const STREAM_STATE_SOURCE_MAX_BACKOFF_MS = Math.max( STREAM_STATE_POLL_MS, Number(process.env.STREAM_STATE_SOURCE_MAX_BACKOFF_MS || 30_000), ); +const BET_SYNC_CONNECT_TIMEOUT_MS = Math.max( + 1_000, + Number(process.env.BET_SYNC_CONNECT_TIMEOUT_MS || 10_000), +); +const BET_SYNC_RECONNECT_MIN_MS = Math.max( + 500, + Number(process.env.BET_SYNC_RECONNECT_MIN_MS || 1_000), +); +const BET_SYNC_RECONNECT_MAX_MS = Math.max( + BET_SYNC_RECONNECT_MIN_MS, + Number(process.env.BET_SYNC_RECONNECT_MAX_MS || 30_000), +); +const BET_SYNC_STALE_EVENT_TOLERANCE_MS = Math.max( + 0, + Number(process.env.BET_SYNC_STALE_EVENT_TOLERANCE_MS || 5_000), +); +const DEFAULT_EXTERNAL_PRESENTATION_DELAY_MS = 4_000; +const STREAM_RENDERER_HEALTH_URL = + process.env.STREAM_RENDERER_HEALTH_URL?.trim() || ""; +const STREAM_RENDERER_HEALTH_BEARER_TOKEN = + process.env.STREAM_RENDERER_HEALTH_BEARER_TOKEN?.trim() || ""; +const STREAM_RENDERER_HEALTH_POLL_MS = Math.max( + 1_000, + Number(process.env.STREAM_RENDERER_HEALTH_POLL_MS || 2_000), +); +const STREAM_RENDERER_HEALTH_TIMEOUT_MS = Math.max( + 500, + Number(process.env.STREAM_RENDERER_HEALTH_TIMEOUT_MS || 3_000), +); +const STREAM_RENDERER_HLS_FRESHNESS_MS = Math.max( + STREAM_RENDERER_HEALTH_POLL_MS, + Number(process.env.STREAM_RENDERER_HLS_FRESHNESS_MS || 15_000), +); const CONTRACT_POLL_MS = Math.max( 5_000, Number(process.env.CONTRACT_POLL_MS || 15_000), @@ -341,8 +601,25 @@ let streamLastSourceError: string | null = null; let streamSourcePollInFlight = false; let streamSourceConsecutiveFailures = 0; let streamSourceBackoffUntil = 0; +let streamRendererHealthOverride: CanonicalStreamHealth | null = null; +let streamRendererMetricsOverride: CanonicalRendererMetrics | null = null; +let streamDeliveryOverride: CanonicalStreamDelivery | null = null; +let streamRendererHealthPollInFlight = false; +let canonicalStreamSession: CanonicalStreamSession; +let betSyncSourceLatestSeq = 0; +let betSyncOldestReplaySeq: number | null = null; +let betSyncLastEventReceivedAt: number | null = null; +let betSyncLastAppliedAt: number | null = null; +let betSyncLastError: string | null = null; +let betSyncConnectedAt: number | null = null; +let betSyncConsumerRunning = false; +let betSyncReplayMode: BetSyncReplayMode = "bootstrap"; +let betSyncReplayUntilSeq: number | null = null; +let betSyncLastAppliedSeq = 0; +let betSyncSourceEpoch: number | null = null; const sseClients = new Set>(); +const sessionSseClients = new Set>(); const manifestCache = new Map(); const rateBuckets = new Map(); @@ -368,6 +645,22 @@ const referralFeeShareGoldByWallet: Map = _db.referralFeeShareGoldByWallet; const treasuryFeesFromReferralsByWallet: Map = _db.treasuryFeesFromReferralsByWallet; +const persistedBetSyncCheckpoint = _db.betSyncCheckpoint; + +if (persistedBetSyncCheckpoint) { + betSyncSourceEpoch = persistedBetSyncCheckpoint.sourceEpoch; + betSyncSourceLatestSeq = persistedBetSyncCheckpoint.lastSeenSeq; + betSyncLastAppliedSeq = persistedBetSyncCheckpoint.lastAppliedSeq; + betSyncLastAppliedAt = + persistedBetSyncCheckpoint.updatedAt > 0 + ? persistedBetSyncCheckpoint.updatedAt + : null; + betSyncReplayMode = + (persistedBetSyncCheckpoint.replayMode as BetSyncReplayMode | null) ?? + (persistedBetSyncCheckpoint.lastAppliedSeq > 0 ? "live" : "bootstrap"); + betSyncLastError = persistedBetSyncCheckpoint.degradedReason; + streamLastSourceError = persistedBetSyncCheckpoint.degradedReason; +} const parsers: { solana: ParserState; @@ -379,6 +672,7 @@ const parsers: { snapshot: null, }, }; +canonicalStreamSession = buildCanonicalStreamSessionFromStreamState(streamState); function nowIso(): string { return new Date().toISOString(); @@ -769,6 +1063,19 @@ function applyCors(req: Request, headers: Headers): void { headers.set("access-control-max-age", "86400"); } +function buildSseHeaders(req: Request): Headers { + const headers = new Headers({ + "content-type": "text/event-stream; charset=utf-8", + "cache-control": + "no-store, no-cache, must-revalidate, proxy-revalidate, no-transform", + pragma: "no-cache", + "x-accel-buffering": "no", + ...securityHeaders(), + }); + applyCors(req, headers); + return headers; +} + function normalizeOriginLike(value: string | null): string | null { if (!value) return null; try { @@ -1145,14 +1452,24 @@ function handlePerpsMarkets(req: Request): Response { } function handleDuelContext(req: Request): Response { + // Commit 2b (viewer-aligned-bet-state): emit both source-time and + // server-time anchors so the hyperbet-ui viewer-clock selector can + // key duel-context history independently from staleness budgets. + // See docs/frontier_duel_bet_stream_sync_prd_sow.md. + const state = toStreamStateFromCanonicalSession(canonicalStreamSession); + const sourceEmittedAt = + typeof state.emittedAt === "number" ? state.emittedAt : null; + const serverEmittedAt = Date.now(); return jsonResponse( req, { type: "STREAMING_DUEL_CONTEXT", - cycle: streamState.cycle, - leaderboard: streamState.leaderboard, - cameraTarget: streamState.cameraTarget, - updatedAt: streamState.emittedAt, + cycle: state.cycle, + leaderboard: state.leaderboard, + cameraTarget: state.cameraTarget, + updatedAt: sourceEmittedAt, + sourceEmittedAt, + serverEmittedAt, }, 200, { @@ -1173,7 +1490,16 @@ function currentDuelId(): string | null { return typeof raw === "string" && raw.trim().length > 0 ? raw.trim() : null; } +function currentBroadcastTimeline(): Record | null { + return asRecord(streamState.cycle?.broadcastTimeline); +} + function currentBetCloseTime(): number | null { + const projected = currentBroadcastTimeline()?.betCloseTime; + if (typeof projected === "number" && Number.isFinite(projected)) { + return projected; + } + const raw = streamState.cycle?.betCloseTime; return typeof raw === "number" && Number.isFinite(raw) ? raw : null; } @@ -1438,6 +1764,12 @@ function handlePredictionMarkets(req: Request): Response { const markets = buildPredictionMarketLifecycleRecords(); const fallbackMarket = markets.find((market) => market.duelKey != null || market.duelId != null) ?? null; + // Commit 2b: `sourceEmittedAt` is the stream frame we observed when + // deriving this surface (selector key); `serverEmittedAt` is the + // wall clock at emission (staleness key). + const sourceEmittedAt = + typeof streamState.emittedAt === "number" ? streamState.emittedAt : null; + const serverEmittedAt = Date.now(); return jsonResponse( req, { @@ -1455,7 +1787,9 @@ function handlePredictionMarkets(req: Request): Response { betCloseTime: currentBetCloseTime() ?? fallbackMarket?.betCloseTime ?? null, }, markets, - updatedAt: Date.now(), + updatedAt: serverEmittedAt, + sourceEmittedAt, + serverEmittedAt, }, 200, { @@ -1756,6 +2090,879 @@ function toStreamState(payload: unknown): StreamState | null { Number.isFinite(candidate.emittedAt) ? candidate.emittedAt : Date.now(), + phase: + typeof candidate.phase === "string" || candidate.phase === null + ? candidate.phase + : null, + phaseVersion: + typeof candidate.phaseVersion === "number" && + Number.isFinite(candidate.phaseVersion) + ? candidate.phaseVersion + : null, + broadcastTimeline: asRecord(candidate.broadcastTimeline), + sourceTimeline: asRecord(candidate.sourceTimeline), + rendererHealth: asRecord(candidate.rendererHealth), + canonicalAuthority: asRecord(candidate.canonicalAuthority), + sourceRuntime: asRecord(candidate.sourceRuntime), + }; +} + +function asRecord(value: unknown): Record | null { + return value && typeof value === "object" + ? (value as Record) + : null; +} + +function asFiniteNumber(value: unknown): number | null { + return typeof value === "number" && Number.isFinite(value) ? value : null; +} + +function asNonEmptyString(value: unknown): string | null { + return typeof value === "string" && value.trim().length > 0 + ? value.trim() + : null; +} + +function normalizeCanonicalRendererMetrics( + value: unknown, +): CanonicalRendererMetrics | null { + const candidate = asRecord(value); + if (!candidate) return null; + const hlsManifest = asRecord(candidate.hlsManifest); + return { + captureFps: asFiniteNumber(candidate.captureFps), + encodeFps: asFiniteNumber(candidate.encodeFps), + droppedFrames: asFiniteNumber(candidate.droppedFrames), + renderTick: asFiniteNumber(candidate.renderTick), + duelStateTick: asFiniteNumber(candidate.duelStateTick), + latestFrameAt: asFiniteNumber(candidate.latestFrameAt), + latestRenderTickAt: asFiniteNumber(candidate.latestRenderTickAt), + latestDuelStateTickAt: asFiniteNumber(candidate.latestDuelStateTickAt), + latestVisualChangeAt: asFiniteNumber(candidate.latestVisualChangeAt), + visualChangeAgeMs: asFiniteNumber(candidate.visualChangeAgeMs), + hlsManifest: hlsManifest + ? { + updatedAt: asFiniteNumber(hlsManifest.updatedAt), + mediaSequence: asFiniteNumber(hlsManifest.mediaSequence), + } + : null, + }; +} + +function normalizeCanonicalSourceRuntime( + value: unknown, +): CanonicalSourceRuntime | null { + const candidate = asRecord(value); + if (!candidate) return null; + + const statusSource = asNonEmptyString(candidate.statusSource); + const captureMode = asNonEmptyString(candidate.captureMode); + if ( + statusSource !== "external_worker" && + statusSource !== "in_process_bridge" && + statusSource !== "none" + ) { + return null; + } + if ( + captureMode !== "cdp" && + captureMode !== "webcodecs" && + captureMode !== "mediarecorder" && + captureMode !== "none" + ) { + return null; + } + + return { + ready: candidate.ready === true, + statusSource, + captureMode, + degradedReason: + typeof candidate.degradedReason === "string" + ? candidate.degradedReason + : null, + currentSceneUrl: asNonEmptyString(candidate.currentSceneUrl), + activeBundle: asNonEmptyString(candidate.activeBundle), + lastFrameAt: asFiniteNumber(candidate.lastFrameAt), + lastRenderTickAt: asFiniteNumber(candidate.lastRenderTickAt), + lastVisualChangeAt: asFiniteNumber(candidate.lastVisualChangeAt), + lastRecoveryAt: asFiniteNumber(candidate.lastRecoveryAt), + recoveryCount: Math.max(0, asFiniteNumber(candidate.recoveryCount) ?? 0), + workerHeartbeatAt: asFiniteNumber(candidate.workerHeartbeatAt), + }; +} + +function normalizeCanonicalDelivery( + value: unknown, +): CanonicalStreamDelivery | null { + const candidate = asRecord(value); + if (!candidate) return null; + const mode = asNonEmptyString(candidate.mode); + if (mode !== "self_hls" && mode !== "external_hls") { + return null; + } + return { + mode, + provider: asNonEmptyString(candidate.provider), + playbackUrl: asNonEmptyString(candidate.playbackUrl), + hlsUrl: asNonEmptyString(candidate.hlsUrl), + llhlsUrl: asNonEmptyString(candidate.llhlsUrl), + ingestUrl: asNonEmptyString(candidate.ingestUrl), + }; +} + +function normalizeCanonicalPublicReadiness( + value: unknown, + fallbackUpdatedAt: number, +): CanonicalStreamPublicReadiness | null { + const candidate = asRecord(value); + if (!candidate) return null; + return { + ready: candidate.ready === true, + reason: asNonEmptyString(candidate.reason), + updatedAt: asFiniteNumber(candidate.updatedAt) ?? fallbackUpdatedAt, + }; +} + +function normalizeCanonicalAuthority( + value: unknown, + fallbackUpdatedAt: number, +): CanonicalAuthorityInfo | null { + const candidate = asRecord(value); + if (!candidate) return null; + return { + providerLive: candidate.providerLive === true, + playbackProbeReady: candidate.playbackProbeReady === true, + decision: asNonEmptyString(candidate.decision), + reason: asNonEmptyString(candidate.reason), + revision: asFiniteNumber(candidate.revision), + updatedAt: asFiniteNumber(candidate.updatedAt) ?? fallbackUpdatedAt, + liveInputId: asNonEmptyString(candidate.liveInputId), + videoUid: asNonEmptyString(candidate.videoUid), + lifecycleStatus: asNonEmptyString(candidate.lifecycleStatus), + playbackUrl: asNonEmptyString(candidate.playbackUrl), + playbackProbeStatusCode: asFiniteNumber(candidate.playbackProbeStatusCode), + playbackManifestStatus: asNonEmptyString(candidate.playbackManifestStatus), + }; +} + +function toAuthorityHealthFromCanonicalAuthority( + authority: CanonicalAuthorityInfo | null, +): CanonicalStreamHealth | null { + if (!authority) { + return null; + } + return { + ready: authority.decision === "ready", + degradedReason: authority.reason, + updatedAt: authority.updatedAt, + }; +} + +function normalizeCanonicalDestinationState( + value: unknown, +): CanonicalStreamDestination | null { + const candidate = asRecord(value); + if (!candidate) return null; + const id = asNonEmptyString(candidate.id); + const role = asNonEmptyString(candidate.role); + const provider = asNonEmptyString(candidate.provider); + const transport = asNonEmptyString(candidate.transport); + const manifestStatus = asNonEmptyString(candidate.manifestStatus); + if (!id) return null; + if (role !== "canonical" && role !== "fallback" && role !== "mirror") { + return null; + } + if ( + provider !== "cloudflare_stream" && + provider !== "self_hls" && + provider !== "twitch" && + provider !== "kick" && + provider !== "youtube" && + provider !== "custom" + ) { + return null; + } + if ( + transport !== "llhls" && + transport !== "hls" && + transport !== "rtmps" && + transport !== "srt" && + transport !== "unknown" + ) { + return null; + } + if ( + manifestStatus !== "ok" && + manifestStatus !== "stale" && + manifestStatus !== "missing" && + manifestStatus !== "unknown" + ) { + return null; + } + return { + id, + name: asNonEmptyString(candidate.name) ?? id, + role, + provider, + transport, + playbackUrl: asNonEmptyString(candidate.playbackUrl), + ingestUrl: asNonEmptyString(candidate.ingestUrl), + connected: candidate.connected === true, + transportHealthy: candidate.transportHealthy === true, + playbackReady: candidate.playbackReady === true, + manifestStatus, + lastError: asNonEmptyString(candidate.lastError), + updatedAt: asFiniteNumber(candidate.updatedAt), + }; +} + +function normalizeCanonicalChannel( + value: unknown, + fallbackUpdatedAt: number, +): CanonicalStreamChannel | null { + const candidate = asRecord(value); + if (!candidate) return null; + const id = asNonEmptyString(candidate.id); + const mode = asNonEmptyString(candidate.mode); + const canonicalDestinationId = asNonEmptyString(candidate.canonicalDestinationId); + if (!id || mode !== "always_on" || !canonicalDestinationId) { + return null; + } + + const destinations = Array.isArray(candidate.destinations) + ? candidate.destinations + .map((destination) => normalizeCanonicalDestinationState(destination)) + .filter((destination): destination is CanonicalStreamDestination => { + return destination != null; + }) + : []; + const publicReadiness = normalizeCanonicalPublicReadiness( + candidate.publicReadiness, + fallbackUpdatedAt, + ); + if (!publicReadiness) { + return null; + } + + return { + id, + mode: "always_on", + presentationDelayMs: Math.max( + 0, + asFiniteNumber(candidate.presentationDelayMs) ?? 0, + ), + activeDuelId: asNonEmptyString(candidate.activeDuelId), + activeDuelKey: asNonEmptyString(candidate.activeDuelKey), + canonicalDestinationId, + fallbackDestinationId: asNonEmptyString(candidate.fallbackDestinationId), + publicPlaybackUrl: asNonEmptyString(candidate.publicPlaybackUrl), + publicReadiness, + destinations, + }; +} + +function findCanonicalDestinationInChannel( + channel: CanonicalStreamChannel | null, +): CanonicalStreamDestination | null { + if (!channel) return null; + return ( + channel.destinations.find( + (destination) => destination.id === channel.canonicalDestinationId, + ) ?? null + ); +} + +function findFallbackDestinationInChannel( + channel: CanonicalStreamChannel | null, +): CanonicalStreamDestination | null { + if (!channel?.fallbackDestinationId) return null; + return ( + channel.destinations.find( + (destination) => destination.id === channel.fallbackDestinationId, + ) ?? null + ); +} + +function toDeliveryFromDestination( + destination: CanonicalStreamDestination | null, + channel: CanonicalStreamChannel | null, +): CanonicalStreamDelivery | null { + if (!destination) { + return null; + } + + const playbackUrl = + channel?.publicPlaybackUrl ?? destination.playbackUrl ?? null; + const mode = + destination.provider === "self_hls" ? "self_hls" : "external_hls"; + return { + mode, + provider: destination.provider, + playbackUrl, + hlsUrl: + destination.transport === "hls" && + destination.provider !== "self_hls" + ? destination.playbackUrl + : null, + llhlsUrl: + destination.transport === "llhls" ? destination.playbackUrl : null, + ingestUrl: destination.ingestUrl, + }; +} + +function toDeliveryHealthFromPublicReadiness( + publicReadiness: CanonicalStreamPublicReadiness | null, +): CanonicalStreamHealth | null { + if (!publicReadiness) { + return null; + } + return { + ready: publicReadiness.ready, + degradedReason: publicReadiness.reason, + updatedAt: publicReadiness.updatedAt, + }; +} + +function buildCanonicalStreamDelivery( + value: unknown, + channel: CanonicalStreamChannel | null = null, +): CanonicalStreamDelivery | null { + return ( + toDeliveryFromDestination(findCanonicalDestinationInChannel(channel), channel) ?? + normalizeCanonicalDelivery(value) + ); +} + +function normalizeCanonicalStreamHealth( + value: unknown, + fallbackUpdatedAt: number, + fallbackReady: boolean, +): CanonicalStreamHealth { + const candidate = + value && typeof value === "object" + ? (value as Record) + : null; + return { + ready: + candidate?.ready === true || + (candidate?.ready !== false && fallbackReady), + degradedReason: + typeof candidate?.degradedReason === "string" + ? candidate.degradedReason + : null, + updatedAt: + typeof candidate?.updatedAt === "number" && + Number.isFinite(candidate.updatedAt) + ? candidate.updatedAt + : fallbackUpdatedAt, + }; +} + +function canonicalStreamHealthEquals( + left: CanonicalStreamHealth | null, + right: CanonicalStreamHealth | null, +): boolean { + return ( + left?.ready === right?.ready && + left?.degradedReason === right?.degradedReason && + left?.updatedAt === right?.updatedAt + ); +} + +function extractFreshHlsManifestUpdatedAt(payload: unknown): number | null { + if (!payload || typeof payload !== "object") return null; + const candidate = payload as Record; + const hlsManifest = + candidate.hlsManifest && typeof candidate.hlsManifest === "object" + ? (candidate.hlsManifest as Record) + : null; + const updatedAt = + typeof hlsManifest?.updatedAt === "number" && + Number.isFinite(hlsManifest.updatedAt) + ? hlsManifest.updatedAt + : null; + const mediaSequence = + typeof hlsManifest?.mediaSequence === "number" && + Number.isFinite(hlsManifest.mediaSequence) + ? hlsManifest.mediaSequence + : null; + if (updatedAt == null || mediaSequence == null) return null; + return Date.now() - updatedAt <= STREAM_RENDERER_HLS_FRESHNESS_MS + ? updatedAt + : null; +} + +function toRendererHealthOverride(payload: unknown): CanonicalStreamHealth | null { + if (!payload || typeof payload !== "object") return null; + const candidate = payload as Record; + const statusCandidate = + candidate.status && typeof candidate.status === "object" + ? (candidate.status as Record) + : null; + const healthCandidate = + (candidate.rendererHealth && + typeof candidate.rendererHealth === "object" + ? candidate.rendererHealth + : null) ?? + (statusCandidate?.renderer && + typeof statusCandidate.renderer === "object" + ? statusCandidate.renderer + : null) ?? + (typeof candidate.ready === "boolean" || + typeof candidate.degradedReason === "string" || + typeof candidate.updatedAt === "number" + ? candidate + : null); + + if (!healthCandidate) return null; + const normalized = normalizeCanonicalStreamHealth( + healthCandidate, + Date.now(), + false, + ); + const freshHlsUpdatedAt = extractFreshHlsManifestUpdatedAt(candidate); + if (freshHlsUpdatedAt != null && !normalized.ready) { + return { + ready: true, + degradedReason: null, + updatedAt: freshHlsUpdatedAt, + }; + } + return normalized; +} + +function toRendererMetricsOverride( + payload: unknown, +): CanonicalRendererMetrics | null { + if (!payload || typeof payload !== "object") return null; + const candidate = payload as Record; + const metricsCandidate = + (candidate.metrics && typeof candidate.metrics === "object" + ? candidate.metrics + : null) ?? + (candidate.rendererMetrics && typeof candidate.rendererMetrics === "object" + ? candidate.rendererMetrics + : null); + + if (!metricsCandidate) return null; + const normalized = normalizeCanonicalRendererMetrics(metricsCandidate); + if (!normalized) return null; + const candidateRecord = candidate as Record; + const topLevelHlsManifest = asRecord(candidateRecord.hlsManifest); + if (topLevelHlsManifest) { + normalized.hlsManifest = { + updatedAt: asFiniteNumber(topLevelHlsManifest.updatedAt), + mediaSequence: asFiniteNumber(topLevelHlsManifest.mediaSequence), + }; + } else if (!normalized.hlsManifest) { + normalized.hlsManifest = { + updatedAt: extractFreshHlsManifestUpdatedAt(candidate), + mediaSequence: null, + }; + } + return normalized; +} + +function toDeliveryOverride(payload: unknown): CanonicalStreamDelivery | null { + if (!payload || typeof payload !== "object") return null; + const candidate = payload as Record; + return buildCanonicalStreamDelivery(candidate.delivery); +} + +function resolveCanonicalRendererHealth( + value: unknown, + fallbackUpdatedAt: number, +): CanonicalStreamHealth | null { + return ( + streamRendererHealthOverride ?? + normalizeCanonicalStreamHealth(value, fallbackUpdatedAt, false) + ); +} + +function resolveCanonicalRendererMetrics( + value: unknown, +): CanonicalRendererMetrics | null { + return ( + streamRendererMetricsOverride ?? + normalizeCanonicalRendererMetrics(value) + ); +} + +function resolveCanonicalDelivery( + value: unknown, + channel: CanonicalStreamChannel | null = null, +): CanonicalStreamDelivery | null { + return mergeCanonicalDeliveryOverride({ + baseDelivery: buildCanonicalStreamDelivery(value, channel), + overrideDelivery: streamDeliveryOverride, + hasAuthoritativeCanonicalDestination: + findCanonicalDestinationInChannel(channel) != null, + }); +} + +function resolveCanonicalDeliveryHealth( + value: unknown, + fallbackUpdatedAt: number, + channel: CanonicalStreamChannel | null, + publicReadiness: CanonicalStreamPublicReadiness | null, + delivery: CanonicalStreamDelivery | null, +): CanonicalStreamHealth | null { + if (publicReadiness) { + return toDeliveryHealthFromPublicReadiness(publicReadiness); + } + if (!delivery && (value == null || typeof value !== "object")) { + return null; + } + + const fallbackReady = + channel == null && + delivery?.mode === "self_hls" && + Boolean(delivery.playbackUrl); + + return normalizeCanonicalStreamHealth( + value, + fallbackUpdatedAt, + fallbackReady, + ); +} + +function buildCanonicalStreamPlayback( + cycle: Record, + playbackCandidate: Record | null, + delivery: CanonicalStreamDelivery | null, + channel: CanonicalStreamChannel | null, + fallbackRenderSessionId: string, +): CanonicalStreamPlayback | null { + const canonicalDestination = findCanonicalDestinationInChannel(channel); + const candidateUrl = + typeof playbackCandidate?.url === "string" + ? playbackCandidate.url.trim() + : ""; + const playbackUrl = + channel?.publicPlaybackUrl || + canonicalDestination?.playbackUrl || + delivery?.playbackUrl || + candidateUrl; + if (!playbackUrl) return null; + + const presentationDelayMs = + typeof playbackCandidate?.presentationDelayMs === "number" && + Number.isFinite(playbackCandidate.presentationDelayMs) + ? Math.max(0, playbackCandidate.presentationDelayMs) + : channel?.presentationDelayMs ?? + (delivery?.mode === "external_hls" + ? DEFAULT_EXTERNAL_PRESENTATION_DELAY_MS + : 0); + + return { + url: playbackUrl, + kind: + canonicalDestination?.transport === "llhls" + ? "llhls" + : canonicalDestination?.transport === "hls" + ? "hls" + : delivery?.mode === "external_hls" && delivery.llhlsUrl + ? "llhls" + : delivery?.mode === "external_hls" && delivery.hlsUrl + ? "hls" + : typeof playbackCandidate?.kind === "string" + ? playbackCandidate.kind + : null, + renderSessionId: + typeof playbackCandidate?.renderSessionId === "string" + ? playbackCandidate.renderSessionId + : ((typeof cycle.duelId === "string" && cycle.duelId) || + (typeof cycle.cycleId === "string" && cycle.cycleId) || + fallbackRenderSessionId), + presentationDelayMs, + }; +} + +function buildCanonicalStreamSessionFromStreamState( + next: StreamState, +): CanonicalStreamSession { + const emittedAt = + typeof next.emittedAt === "number" && Number.isFinite(next.emittedAt) + ? next.emittedAt + : Date.now(); + const nextRecord = next as Record; + const cycle = next.cycle as Record; + const duelKeyHex = + typeof cycle.duelKeyHex === "string" ? cycle.duelKeyHex.trim() : null; + const channel = normalizeCanonicalChannel( + nextRecord.channel ?? cycle.channel, + emittedAt, + ); + const publicReadiness = + channel?.publicReadiness ?? + normalizeCanonicalPublicReadiness( + nextRecord.publicReadiness ?? cycle.publicReadiness, + emittedAt, + ); + const sourceRuntime = + normalizeCanonicalSourceRuntime( + nextRecord.sourceRuntime ?? + cycle.sourceRuntime ?? + asRecord(nextRecord.status)?.sourceRuntime, + ) ?? null; + const canonicalDestination = + findCanonicalDestinationInChannel(channel) ?? + normalizeCanonicalDestinationState( + nextRecord.canonicalDestination ?? cycle.canonicalDestination, + ); + const fallbackDestination = + findFallbackDestinationInChannel(channel) ?? + normalizeCanonicalDestinationState( + nextRecord.fallbackDestination ?? cycle.fallbackDestination, + ); + const canonicalAuthority = normalizeCanonicalAuthority( + nextRecord.canonicalAuthority ?? cycle.canonicalAuthority, + emittedAt, + ); + const delivery = resolveCanonicalDelivery( + nextRecord.delivery ?? cycle.delivery, + channel, + ); + const deliveryHealth = resolveCanonicalDeliveryHealth( + nextRecord.deliveryHealth ?? cycle.deliveryHealth, + emittedAt, + channel, + publicReadiness, + delivery, + ); + const playback = buildCanonicalStreamPlayback( + cycle, + null, + delivery, + channel, + `stream-${next.seq}`, + ); + const rendererHealth = resolveCanonicalRendererHealth( + cycle.rendererHealth, + emittedAt, + ); + const rendererMetrics = resolveCanonicalRendererMetrics(cycle.rendererMetrics); + const authorityHealth = + toAuthorityHealthFromCanonicalAuthority(canonicalAuthority) ?? + toDeliveryHealthFromPublicReadiness(publicReadiness) ?? + normalizeCanonicalStreamHealth( + { + ready: Boolean(playback?.url), + degradedReason: playback?.url ? null : "playback_unconfigured", + updatedAt: emittedAt, + }, + emittedAt, + Boolean(playback?.url), + ); + + return { + schemaVersion: 1, + sourceEpoch: null, + seq: next.seq, + emittedAt, + duelId: typeof cycle.duelId === "string" ? cycle.duelId : null, + duelKey: duelKeyHex ? duelKeyHex.replace(/^0x/i, "").toLowerCase() : null, + phase: typeof cycle.phase === "string" ? cycle.phase : null, + phaseVersion: + typeof cycle.phaseVersion === "number" && Number.isFinite(cycle.phaseVersion) + ? cycle.phaseVersion + : null, + cycle, + leaderboard: Array.isArray(next.leaderboard) ? next.leaderboard : [], + cameraTarget: + typeof next.cameraTarget === "string" || next.cameraTarget === null + ? next.cameraTarget + : null, + playback, + rendererHealth, + sourceRuntime, + deliveryHealth, + channel, + publicReadiness, + canonicalDestination, + fallbackDestination, + canonicalAuthority, + rendererMetrics, + delivery, + authorityHealth, + status: { + authority: authorityHealth, + renderer: rendererHealth, + sourceRuntime, + delivery, + deliveryHealth, + }, + }; +} + +function toCanonicalStreamSession(payload: unknown): CanonicalStreamSession | null { + if (!payload || typeof payload !== "object") return null; + const candidate = payload as Record; + const cycle = + candidate.cycle && typeof candidate.cycle === "object" + ? (candidate.cycle as Record) + : null; + if (!cycle) return null; + + const emittedAt = + typeof candidate.emittedAt === "number" && Number.isFinite(candidate.emittedAt) + ? candidate.emittedAt + : Date.now(); + const statusCandidate = + candidate.status && typeof candidate.status === "object" + ? (candidate.status as Record) + : null; + const channel = normalizeCanonicalChannel(candidate.channel, emittedAt); + const publicReadiness = + channel?.publicReadiness ?? + normalizeCanonicalPublicReadiness(candidate.publicReadiness, emittedAt); + const sourceRuntime = + normalizeCanonicalSourceRuntime( + candidate.sourceRuntime ?? asRecord(statusCandidate?.sourceRuntime), + ) ?? null; + const canonicalDestination = + findCanonicalDestinationInChannel(channel) ?? + normalizeCanonicalDestinationState(candidate.canonicalDestination); + const fallbackDestination = + findFallbackDestinationInChannel(channel) ?? + normalizeCanonicalDestinationState(candidate.fallbackDestination); + const canonicalAuthority = normalizeCanonicalAuthority( + candidate.canonicalAuthority, + emittedAt, + ); + const rendererHealth = resolveCanonicalRendererHealth( + candidate.rendererHealth ?? statusCandidate?.renderer, + emittedAt, + ); + const rendererMetrics = resolveCanonicalRendererMetrics( + candidate.rendererMetrics, + ); + const delivery = resolveCanonicalDelivery( + candidate.delivery ?? statusCandidate?.delivery, + channel, + ); + const deliveryHealth = resolveCanonicalDeliveryHealth( + candidate.deliveryHealth ?? statusCandidate?.deliveryHealth, + emittedAt, + channel, + publicReadiness, + delivery, + ); + const playbackCandidate = + candidate.playback && typeof candidate.playback === "object" + ? (candidate.playback as Record) + : null; + const playback = buildCanonicalStreamPlayback( + cycle, + playbackCandidate, + delivery, + channel, + "", + ); + const authorityHealth = + toAuthorityHealthFromCanonicalAuthority(canonicalAuthority) ?? + toDeliveryHealthFromPublicReadiness(publicReadiness) ?? + normalizeCanonicalStreamHealth( + candidate.authorityHealth ?? statusCandidate?.authority, + emittedAt, + Boolean(playback?.url), + ); + + return { + schemaVersion: + typeof candidate.schemaVersion === "number" && + Number.isFinite(candidate.schemaVersion) + ? candidate.schemaVersion + : 1, + sourceEpoch: + typeof candidate.sourceEpoch === "number" && + Number.isFinite(candidate.sourceEpoch) + ? candidate.sourceEpoch + : null, + seq: + typeof candidate.seq === "number" && Number.isFinite(candidate.seq) + ? candidate.seq + : streamSeq + 1, + emittedAt, + duelId: + typeof candidate.duelId === "string" + ? candidate.duelId + : typeof cycle.duelId === "string" + ? cycle.duelId + : null, + duelKey: + typeof candidate.duelKey === "string" + ? candidate.duelKey.replace(/^0x/i, "").toLowerCase() + : typeof cycle.duelKeyHex === "string" + ? cycle.duelKeyHex.replace(/^0x/i, "").toLowerCase() + : null, + phase: + typeof candidate.phase === "string" + ? candidate.phase + : typeof cycle.phase === "string" + ? cycle.phase + : null, + phaseVersion: + typeof candidate.phaseVersion === "number" && + Number.isFinite(candidate.phaseVersion) + ? candidate.phaseVersion + : typeof cycle.phaseVersion === "number" && + Number.isFinite(cycle.phaseVersion) + ? cycle.phaseVersion + : null, + cycle, + leaderboard: Array.isArray(candidate.leaderboard) ? candidate.leaderboard : [], + cameraTarget: + typeof candidate.cameraTarget === "string" || candidate.cameraTarget === null + ? (candidate.cameraTarget as string | null) + : null, + playback, + rendererHealth, + sourceRuntime, + deliveryHealth, + channel, + publicReadiness, + canonicalDestination, + fallbackDestination, + canonicalAuthority, + rendererMetrics, + delivery, + authorityHealth, + status: { + authority: authorityHealth, + renderer: rendererHealth, + sourceRuntime, + delivery, + deliveryHealth, + }, + }; +} + +function toStreamStateFromCanonicalSession( + session: CanonicalStreamSession, +): StreamState { + return { + type: "STREAMING_STATE_UPDATE", + cycle: { + ...session.cycle, + rendererHealth: session.rendererHealth, + sourceRuntime: session.sourceRuntime, + delivery: session.delivery, + deliveryHealth: session.deliveryHealth, + }, + leaderboard: session.leaderboard, + cameraTarget: session.cameraTarget, + seq: session.seq, + emittedAt: session.emittedAt, + channel: session.channel, + publicReadiness: session.publicReadiness, + canonicalDestination: session.canonicalDestination, + fallbackDestination: session.fallbackDestination, + canonicalAuthority: session.canonicalAuthority, + sourceRuntime: session.sourceRuntime, + deliveryHealth: session.deliveryHealth, + delivery: session.delivery, }; } @@ -1780,23 +2987,470 @@ function broadcastStreamState(nextState: StreamState, event = "state"): void { } } -function publishStreamState(next: StreamState, sourceLabel: string): void { +function broadcastCanonicalStreamSession( + nextSession: CanonicalStreamSession, + event = "session", +): void { + for (const controller of sessionSseClients) { + try { + sendSse(controller, event, nextSession.seq, nextSession); + } catch { + sessionSseClients.delete(controller); + } + } +} + +function publishCanonicalStreamSession( + next: CanonicalStreamSession, + sourceLabel: string, +): void { + const emittedAt = + typeof next.emittedAt === "number" && Number.isFinite(next.emittedAt) + ? next.emittedAt + : Date.now(); + const resolvedRendererHealth = resolveCanonicalRendererHealth( + next.rendererHealth ?? next.status?.renderer, + emittedAt, + ); + const resolvedRendererMetrics = resolveCanonicalRendererMetrics( + next.rendererMetrics, + ); + const resolvedChannel = normalizeCanonicalChannel(next.channel, emittedAt); + const resolvedPublicReadiness = + resolvedChannel?.publicReadiness ?? + normalizeCanonicalPublicReadiness(next.publicReadiness, emittedAt); + const resolvedSourceRuntime = + normalizeCanonicalSourceRuntime( + next.sourceRuntime ?? next.status?.sourceRuntime, + ) ?? null; + const resolvedCanonicalDestination = + findCanonicalDestinationInChannel(resolvedChannel) ?? + normalizeCanonicalDestinationState(next.canonicalDestination); + const resolvedFallbackDestination = + findFallbackDestinationInChannel(resolvedChannel) ?? + normalizeCanonicalDestinationState(next.fallbackDestination); + const resolvedCanonicalAuthority = normalizeCanonicalAuthority( + next.canonicalAuthority, + emittedAt, + ); + const resolvedDelivery = resolveCanonicalDelivery( + next.delivery ?? next.status?.delivery, + resolvedChannel, + ); + const resolvedDeliveryHealth = resolveCanonicalDeliveryHealth( + next.deliveryHealth ?? next.status?.deliveryHealth, + emittedAt, + resolvedChannel, + resolvedPublicReadiness, + resolvedDelivery, + ); + const resolvedPlayback = buildCanonicalStreamPlayback( + next.cycle, + next.playback as Record | null, + resolvedDelivery, + resolvedChannel, + next.duelId || `stream-${streamSeq + 1}`, + ); + const resolvedAuthorityHealth = + toAuthorityHealthFromCanonicalAuthority(resolvedCanonicalAuthority) ?? + toDeliveryHealthFromPublicReadiness(resolvedPublicReadiness) ?? + next.authorityHealth; streamSeq = Math.max(streamSeq + 1, next.seq || streamSeq + 1); - streamState = { + canonicalStreamSession = { ...next, - type: "STREAMING_STATE_UPDATE", + cycle: { + ...next.cycle, + rendererHealth: resolvedRendererHealth, + }, seq: streamSeq, emittedAt: Date.now(), + playback: resolvedPlayback, + rendererHealth: resolvedRendererHealth, + sourceRuntime: resolvedSourceRuntime, + deliveryHealth: resolvedDeliveryHealth, + channel: resolvedChannel, + publicReadiness: resolvedPublicReadiness, + canonicalDestination: resolvedCanonicalDestination, + fallbackDestination: resolvedFallbackDestination, + canonicalAuthority: resolvedCanonicalAuthority, + rendererMetrics: resolvedRendererMetrics, + delivery: resolvedDelivery, + authorityHealth: resolvedAuthorityHealth, + status: { + authority: resolvedAuthorityHealth, + renderer: resolvedRendererHealth, + sourceRuntime: resolvedSourceRuntime, + delivery: resolvedDelivery, + deliveryHealth: resolvedDeliveryHealth, + }, }; + streamState = toStreamStateFromCanonicalSession(canonicalStreamSession); streamLastUpdatedAt = Date.now(); streamLastSourceError = null; persistStreamStateSnapshot(streamState); broadcastStreamState(streamState, "state"); + broadcastCanonicalStreamSession(canonicalStreamSession, "session"); console.log( `[${nowIso()}] [stream] updated from ${sourceLabel} cycle=${streamState.cycle?.cycleId ?? "unknown"} phase=${streamState.cycle?.phase ?? "unknown"}`, ); } +function publishStreamState(next: StreamState, sourceLabel: string): void { + publishCanonicalStreamSession( + buildCanonicalStreamSessionFromStreamState({ + ...next, + type: "STREAMING_STATE_UPDATE", + seq: + typeof next.seq === "number" && Number.isFinite(next.seq) + ? next.seq + : streamSeq + 1, + emittedAt: + typeof next.emittedAt === "number" && Number.isFinite(next.emittedAt) + ? next.emittedAt + : Date.now(), + }), + sourceLabel, + ); +} + +function nextBetSyncCheckpointState( + next: Partial = {}, +): DbBetSyncCheckpoint { + return { + sourceEpoch: betSyncSourceEpoch ?? 0, + lastSeenSeq: Math.max(betSyncSourceLatestSeq, betSyncLastAppliedSeq), + lastAppliedSeq: betSyncLastAppliedSeq, + replayMode: betSyncReplayMode, + degradedReason: betSyncLastError, + updatedAt: Date.now(), + ...next, + }; +} + +function persistBetSyncCheckpointState( + next: Partial = {}, +): void { + saveBetSyncCheckpoint(nextBetSyncCheckpointState(next)); +} + +function buildBetSyncHeaders(resumeSeq?: number): Record { + const headers: Record = { + connection: "close", + }; + if (BET_SYNC_SOURCE_BEARER_TOKEN) { + headers.authorization = `Bearer ${BET_SYNC_SOURCE_BEARER_TOKEN}`; + } + if (typeof resumeSeq === "number" && Number.isFinite(resumeSeq) && resumeSeq > 0) { + headers["last-event-id"] = String(resumeSeq); + } + return headers; +} + +function buildCanonicalStreamSessionFromBetSyncEvent( + event: BetSyncEvent, +): CanonicalStreamSession { + const nextState = toStreamStateFromBetSyncEvent(event) as unknown as StreamState; + const base = buildCanonicalStreamSessionFromStreamState(nextState); + const eventRecord = event as unknown as Record; + const channel = normalizeCanonicalChannel(eventRecord.channel, event.emittedAt); + const publicReadiness = + channel?.publicReadiness ?? + normalizeCanonicalPublicReadiness(eventRecord.publicReadiness, event.emittedAt); + const sourceRuntime = + normalizeCanonicalSourceRuntime( + eventRecord.sourceRuntime ?? base.sourceRuntime, + ) ?? base.sourceRuntime; + const canonicalDestination = + findCanonicalDestinationInChannel(channel) ?? + normalizeCanonicalDestinationState(eventRecord.canonicalDestination); + const fallbackDestination = + findFallbackDestinationInChannel(channel) ?? + normalizeCanonicalDestinationState(eventRecord.fallbackDestination); + const canonicalAuthority = normalizeCanonicalAuthority( + eventRecord.canonicalAuthority, + event.emittedAt, + ); + const delivery = resolveCanonicalDelivery(event.delivery, channel); + const deliveryHealth = resolveCanonicalDeliveryHealth( + eventRecord.deliveryHealth, + event.emittedAt, + channel, + publicReadiness, + delivery, + ); + return { + ...base, + schemaVersion: event.schemaVersion, + sourceEpoch: event.sourceEpoch, + seq: event.seq, + emittedAt: event.emittedAt, + duelId: event.duelId, + duelKey: event.duelKey, + phase: event.phase, + phaseVersion: event.phaseVersion, + channel, + publicReadiness, + canonicalDestination, + fallbackDestination, + canonicalAuthority, + sourceRuntime, + rendererMetrics: resolveCanonicalRendererMetrics(event.rendererMetrics), + delivery, + deliveryHealth, + authorityHealth: + toAuthorityHealthFromCanonicalAuthority(canonicalAuthority) ?? + toDeliveryHealthFromPublicReadiness(publicReadiness) ?? + base.authorityHealth, + status: { + authority: + toAuthorityHealthFromCanonicalAuthority(canonicalAuthority) ?? + toDeliveryHealthFromPublicReadiness(publicReadiness) ?? + base.authorityHealth, + renderer: base.rendererHealth, + sourceRuntime, + delivery, + deliveryHealth, + }, + }; +} + +function applyBetSyncEvent( + event: BetSyncEvent, + sourceLabel: string, +): void { + betSyncSourceLatestSeq = Math.max(betSyncSourceLatestSeq, event.seq); + betSyncLastEventReceivedAt = Date.now(); + const sourceEpochChanged = + betSyncSourceEpoch != null && event.sourceEpoch !== betSyncSourceEpoch; + const currentAppliedEmittedAt = + typeof canonicalStreamSession.emittedAt === "number" && + Number.isFinite(canonicalStreamSession.emittedAt) + ? canonicalStreamSession.emittedAt + : null; + + if ( + isBetSyncEventStaleAfterSourceReset({ + sourceEpochChanged, + currentStreamEmittedAt: currentAppliedEmittedAt, + eventEmittedAt: event.emittedAt, + toleranceMs: BET_SYNC_STALE_EVENT_TOLERANCE_MS, + }) + ) { + betSyncLastError = "stale_source_reset_event"; + streamLastSourceError = betSyncLastError; + persistBetSyncCheckpointState({ + sourceEpoch: event.sourceEpoch, + lastSeenSeq: betSyncSourceLatestSeq, + degradedReason: betSyncLastError, + }); + return; + } + + if (!sourceEpochChanged && event.seq <= betSyncLastAppliedSeq) { + persistBetSyncCheckpointState({ + sourceEpoch: event.sourceEpoch, + lastSeenSeq: betSyncSourceLatestSeq, + degradedReason: null, + }); + return; + } + + publishCanonicalStreamSession( + buildCanonicalStreamSessionFromBetSyncEvent(event), + sourceLabel, + ); + betSyncSourceEpoch = event.sourceEpoch; + betSyncLastAppliedSeq = event.seq; + betSyncLastAppliedAt = Date.now(); + if (sourceEpochChanged) { + betSyncReplayUntilSeq = null; + } + betSyncLastError = null; + streamLastSourceError = null; + persistBetSyncCheckpointState({ + sourceEpoch: event.sourceEpoch, + lastSeenSeq: betSyncSourceLatestSeq, + lastAppliedSeq: betSyncLastAppliedSeq, + replayMode: betSyncReplayMode, + degradedReason: null, + }); + resetStreamSourceFailures(); +} + +async function bootstrapBetSyncState(): Promise { + if (!BET_SYNC_SOURCE_STATE_URL) return; + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), BET_SYNC_CONNECT_TIMEOUT_MS); + try { + const response = await fetch(BET_SYNC_SOURCE_STATE_URL, { + cache: "no-store", + headers: buildBetSyncHeaders(), + signal: controller.signal, + }); + streamLastSourcePollAt = Date.now(); + if (!response.ok) { + throw new Error(`bootstrap failed (${response.status})`); + } + + const state = parseBetSyncBootstrapState(await response.json()); + if (!state) { + throw new Error("bootstrap payload was invalid"); + } + + const bootstrapCursor = resolveBetSyncBootstrapCursor({ + previousSourceEpoch: betSyncSourceEpoch, + nextSourceEpoch: state.sourceEpoch, + previousLastAppliedSeq: betSyncLastAppliedSeq, + latestSeq: state.latestSeq, + }); + + if (bootstrapCursor.sourceEpochChanged) { + betSyncLastAppliedAt = null; + } + + betSyncSourceEpoch = state.sourceEpoch; + betSyncSourceLatestSeq = state.latestSeq; + betSyncOldestReplaySeq = state.oldestReplaySeq; + betSyncLastAppliedSeq = bootstrapCursor.rebasedLastAppliedSeq; + betSyncReplayUntilSeq = bootstrapCursor.replayUntilSeq; + betSyncReplayMode = bootstrapCursor.replayMode; + persistBetSyncCheckpointState({ + sourceEpoch: state.sourceEpoch, + lastSeenSeq: state.latestSeq, + lastAppliedSeq: betSyncLastAppliedSeq, + replayMode: betSyncReplayMode, + degradedReason: null, + }); + + if (state.latestEvent && betSyncReplayUntilSeq == null) { + applyBetSyncEvent(state.latestEvent, "bet-sync-bootstrap"); + } + } finally { + clearTimeout(timeoutId); + } +} + +async function consumeBetSyncEventsOnce(): Promise { + if (!BET_SYNC_SOURCE_EVENTS_URL) return; + + const url = new URL(BET_SYNC_SOURCE_EVENTS_URL); + const resumeSeq = selectBetSyncResumeSeq({ + lastAppliedSeq: betSyncLastAppliedSeq, + }); + if (resumeSeq > 0) { + url.searchParams.set("since", String(resumeSeq)); + } + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), BET_SYNC_CONNECT_TIMEOUT_MS); + const response = await fetch(url, { + cache: "no-store", + headers: { + ...buildBetSyncHeaders(resumeSeq), + accept: "text/event-stream", + }, + signal: controller.signal, + }); + clearTimeout(timeoutId); + + if (!response.ok || !response.body) { + throw new Error(`bet-sync stream failed (${response.status})`); + } + + betSyncConnectedAt = Date.now(); + let buffer = ""; + for await (const chunk of response.body as unknown as AsyncIterable) { + buffer += decoder.decode(chunk, { stream: true }); + let boundary = buffer.indexOf("\n\n"); + while (boundary >= 0) { + const frame = buffer.slice(0, boundary); + buffer = buffer.slice(boundary + 2); + boundary = buffer.indexOf("\n\n"); + + if (!frame.trim() || frame.startsWith(":")) { + continue; + } + + const lines = frame.split(/\r?\n/); + let eventName = "message"; + let data = ""; + for (const line of lines) { + if (line.startsWith("event:")) { + eventName = line.slice(6).trim(); + continue; + } + if (line.startsWith("data:")) { + data += `${line.slice(5).trim()}\n`; + } + } + + if (eventName === "heartbeat" || !data.trim()) { + continue; + } + + let decodedPayload: unknown; + try { + decodedPayload = JSON.parse(data); + } catch { + continue; + } + + const parsed = parseBetSyncEvent(decodedPayload); + if (!parsed) { + continue; + } + + const replayDecision = resolveBetSyncReplayMode({ + eventName, + eventSeq: parsed.seq, + replayUntilSeq: betSyncReplayUntilSeq, + sourceEpochChanged: + betSyncSourceEpoch != null && parsed.sourceEpoch !== betSyncSourceEpoch, + }); + betSyncReplayMode = replayDecision.replayMode; + betSyncReplayUntilSeq = replayDecision.replayUntilSeq; + if (eventName === "reset") { + betSyncOldestReplaySeq = parsed.seq; + } + + applyBetSyncEvent(parsed, `bet-sync-${eventName}`); + } + } +} + +function startBetSyncConsumer(): void { + if (!BET_SYNC_SOURCE_EVENTS_URL || betSyncConsumerRunning) return; + betSyncConsumerRunning = true; + void (async () => { + let reconnectDelayMs = BET_SYNC_RECONNECT_MIN_MS; + while (true) { + try { + await bootstrapBetSyncState(); + await consumeBetSyncEventsOnce(); + reconnectDelayMs = BET_SYNC_RECONNECT_MIN_MS; + betSyncLastError = null; + } catch (error) { + betSyncLastError = + error instanceof Error ? error.message : "bet-sync consumer failed"; + streamLastSourceError = betSyncLastError; + persistBetSyncCheckpointState({ + replayMode: betSyncReplayMode, + degradedReason: betSyncLastError, + }); + registerStreamSourceFailure(streamLastSourceError); + console.warn( + `[${nowIso()}] [bet-sync] consumer error: ${betSyncLastError}; reconnecting in ${reconnectDelayMs}ms`, + ); + await new Promise((resolve) => setTimeout(resolve, reconnectDelayMs)); + reconnectDelayMs = Math.min( + BET_SYNC_RECONNECT_MAX_MS, + reconnectDelayMs * 2, + ); + } + } + })(); +} + function nextStreamSourceBackoffMs(): number { const step = Math.min(streamSourceConsecutiveFailures, 5); return Math.min( @@ -1826,7 +3480,7 @@ function resetStreamSourceFailures(): void { } async function pollStreamStateSource(): Promise { - if (!STREAM_STATE_SOURCE_URL) return; + if (!ACTIVE_STREAM_STATE_SOURCE_URL) return; if (streamSourcePollInFlight) return; if (Date.now() < streamSourceBackoffUntil) return; @@ -1839,12 +3493,12 @@ async function pollStreamStateSource(): Promise { try { const headers: Record = {}; - if (STREAM_STATE_SOURCE_BEARER_TOKEN) { - headers.authorization = `Bearer ${STREAM_STATE_SOURCE_BEARER_TOKEN}`; + if (ACTIVE_STREAM_SOURCE_BEARER_TOKEN) { + headers.authorization = `Bearer ${ACTIVE_STREAM_SOURCE_BEARER_TOKEN}`; } headers.connection = "close"; - const response = await fetch(STREAM_STATE_SOURCE_URL, { + const response = await fetch(ACTIVE_STREAM_STATE_SOURCE_URL, { cache: "no-store", headers, signal: controller.signal, @@ -1862,6 +3516,48 @@ async function pollStreamStateSource(): Promise { } const payload = await response.json(); + const nextSession = + toCanonicalStreamSession(payload) || + toCanonicalStreamSession((payload as Record)?.data); + + if (nextSession) { + const changed = + canonicalStreamSession.seq !== nextSession.seq || + canonicalStreamSession.duelId !== nextSession.duelId || + canonicalStreamSession.phase !== nextSession.phase || + canonicalStreamSession.playback?.url !== nextSession.playback?.url || + canonicalStreamSession.canonicalAuthority?.revision !== + nextSession.canonicalAuthority?.revision || + canonicalStreamSession.canonicalAuthority?.decision !== + nextSession.canonicalAuthority?.decision || + canonicalStreamSession.canonicalAuthority?.reason !== + nextSession.canonicalAuthority?.reason || + canonicalStreamSession.canonicalAuthority?.providerLive !== + nextSession.canonicalAuthority?.providerLive || + canonicalStreamSession.canonicalAuthority?.playbackProbeReady !== + nextSession.canonicalAuthority?.playbackProbeReady || + canonicalStreamSession.canonicalAuthority?.playbackManifestStatus !== + nextSession.canonicalAuthority?.playbackManifestStatus || + canonicalStreamSession.publicReadiness?.ready !== + nextSession.publicReadiness?.ready || + canonicalStreamSession.publicReadiness?.reason !== + nextSession.publicReadiness?.reason || + canonicalStreamSession.sourceRuntime?.ready !== + nextSession.sourceRuntime?.ready || + canonicalStreamSession.sourceRuntime?.degradedReason !== + nextSession.sourceRuntime?.degradedReason || + canonicalStreamSession.rendererHealth?.ready !== + nextSession.rendererHealth?.ready || + canonicalStreamSession.rendererHealth?.degradedReason !== + nextSession.rendererHealth?.degradedReason; + if (changed) { + publishCanonicalStreamSession(nextSession, "poll"); + } + streamLastSourceError = null; + resetStreamSourceFailures(); + return; + } + const nextState = toStreamState(payload) || toStreamState((payload as Record)?.data); @@ -1891,6 +3587,69 @@ async function pollStreamStateSource(): Promise { } } +async function pollRendererHealthSource(): Promise { + if (!STREAM_RENDERER_HEALTH_URL || streamRendererHealthPollInFlight) return; + + streamRendererHealthPollInFlight = true; + const controller = new AbortController(); + const timeoutId = setTimeout( + () => controller.abort(), + STREAM_RENDERER_HEALTH_TIMEOUT_MS, + ); + + try { + const headers: Record = { connection: "close" }; + if (STREAM_RENDERER_HEALTH_BEARER_TOKEN) { + headers.authorization = `Bearer ${STREAM_RENDERER_HEALTH_BEARER_TOKEN}`; + } + const response = await fetch(STREAM_RENDERER_HEALTH_URL, { + cache: "no-store", + headers, + signal: controller.signal, + }); + + if (!response.ok) { + try { + await response.body?.cancel(); + } catch { + // Ignore cancellation issues for already-closed streams. + } + return; + } + + const payload = await response.json(); + const nextHealth = + toRendererHealthOverride(payload) || + toRendererHealthOverride((payload as Record)?.data); + const nextMetrics = + toRendererMetricsOverride(payload) || + toRendererMetricsOverride((payload as Record)?.data); + const nextDelivery = + toDeliveryOverride(payload) || + toDeliveryOverride((payload as Record)?.data); + if (!nextHealth && !nextMetrics && !nextDelivery) return; + + if ( + canonicalStreamHealthEquals(streamRendererHealthOverride, nextHealth) && + JSON.stringify(streamRendererMetricsOverride) === + JSON.stringify(nextMetrics) && + JSON.stringify(streamDeliveryOverride) === JSON.stringify(nextDelivery) + ) { + return; + } + + streamRendererHealthOverride = nextHealth; + streamRendererMetricsOverride = nextMetrics; + streamDeliveryOverride = nextDelivery; + publishCanonicalStreamSession(canonicalStreamSession, "renderer-health"); + } catch { + // Defer to the canonical authority when the override source is unavailable. + } finally { + clearTimeout(timeoutId); + streamRendererHealthPollInFlight = false; + } +} + function connectedSseCount(): number { return sseClients.size; } @@ -2163,13 +3922,16 @@ function leaderboardResponse( }; } -function rankResponse(wallet: string): Record { +function rankResponse( + wallet: string, + scope: string | null, +): Record { const normalized = rememberWalletCase(wallet); const canonical = ensureIdentity(normalized); - const rows = leaderboardRows("linked", "alltime"); + const rows = leaderboardRows(scope, "alltime"); const rank = rows.findIndex((entry) => normalizeWallet(entry.wallet) === canonical) + 1; - const wallets = identityWallets(normalized, "linked"); + const wallets = identityWallets(normalized, scope); return { wallet: displayWallet(canonical), @@ -2183,9 +3945,10 @@ function historyResponse( limit: number, offset: number, eventType: string | null, + scope: string | null, ): Record { const normalized = rememberWalletCase(wallet); - const wallets = new Set(identityWallets(normalized, "linked")); + const wallets = new Set(identityWallets(normalized, scope)); const filtered = pointsEvents.filter((entry) => { if (!wallets.has(entry.wallet)) return false; if (eventType && entry.eventType !== eventType) return false; @@ -2217,8 +3980,11 @@ function historyResponse( }; } -function multiplierResponse(wallet: string): Record { - const wallets = identityWallets(wallet, "linked"); +function multiplierResponse( + wallet: string, + scope: string | null, +): Record { + const wallets = identityWallets(wallet, scope); const detail = multiplierDetailForWallets(wallets); return { wallet: wallet.trim(), @@ -2974,11 +4740,25 @@ const server = Bun.serve({ cycleId: streamState.cycle?.cycleId ?? null, phase: streamState.cycle?.phase ?? null, lastUpdatedAt: streamLastUpdatedAt, - sourceUrl: STREAM_STATE_SOURCE_URL - ? sanitizeUrlForStatus(STREAM_STATE_SOURCE_URL) + sourceUrl: BET_SYNC_SOURCE_EVENTS_URL + ? sanitizeUrlForStatus(BET_SYNC_SOURCE_EVENTS_URL) + : ACTIVE_STREAM_STATE_SOURCE_URL + ? sanitizeUrlForStatus(ACTIVE_STREAM_STATE_SOURCE_URL) : null, lastSourcePollAt: streamLastSourcePollAt, - lastSourceError: streamLastSourceError, + lastSourceError: betSyncLastError ?? streamLastSourceError, + betSync: { + enabled: Boolean(BET_SYNC_SOURCE_EVENTS_URL), + replayMode: betSyncReplayMode, + sourceEpoch: betSyncSourceEpoch, + sourceLatestSeq: betSyncSourceLatestSeq, + oldestReplaySeq: betSyncOldestReplaySeq, + lastAppliedSeq: betSyncLastAppliedSeq, + connectedAt: betSyncConnectedAt, + lastEventReceivedAt: betSyncLastEventReceivedAt, + lastAppliedAt: betSyncLastAppliedAt, + lastError: betSyncLastError, + }, sseClients: connectedSseCount(), }, parsers, @@ -3025,7 +4805,18 @@ const server = Bun.serve({ } if (req.method === "GET" && url.pathname === "/api/streaming/state") { - return jsonResponse(req, streamState, 200, { + return jsonResponse( + req, + toStreamStateFromCanonicalSession(canonicalStreamSession), + 200, + { + "cache-control": "no-store", + }, + ); + } + + if (req.method === "GET" && url.pathname === "/api/streaming/session") { + return jsonResponse(req, canonicalStreamSession, 200, { "cache-control": "no-store", }); } @@ -3084,15 +4875,36 @@ const server = Bun.serve({ }, }); - const headers = new Headers({ - "content-type": "text/event-stream; charset=utf-8", - "cache-control": - "no-store, no-cache, must-revalidate, proxy-revalidate", - connection: "keep-alive", - ...securityHeaders(), + return new Response(stream, { + status: 200, + headers: buildSseHeaders(req), + }); + } + + if ( + req.method === "GET" && + url.pathname === "/api/streaming/session/events" + ) { + const stream = new ReadableStream({ + start(controller) { + sessionSseClients.add(controller); + sendSse( + controller, + "reset", + canonicalStreamSession.seq, + canonicalStreamSession, + ); + controller.enqueue(encoder.encode(": connected\n\n")); + }, + cancel(reason) { + void reason; + }, + }); + + return new Response(stream, { + status: 200, + headers: buildSseHeaders(req), }); - applyCors(req, headers); - return new Response(stream, { status: 200, headers }); } if ( @@ -3149,7 +4961,7 @@ const server = Bun.serve({ if (!wallet) { return jsonResponse(req, { error: "Wallet is required" }, 400); } - return jsonResponse(req, rankResponse(wallet), 200, { + return jsonResponse(req, rankResponse(wallet, url.searchParams.get("scope")), 200, { "cache-control": "no-store", }); } @@ -3180,6 +4992,7 @@ const server = Bun.serve({ limit, offset, url.searchParams.get("eventType"), + url.searchParams.get("scope"), ), 200, { @@ -3200,7 +5013,7 @@ const server = Bun.serve({ if (!wallet) { return jsonResponse(req, { error: "Wallet is required" }, 400); } - return jsonResponse(req, multiplierResponse(wallet), 200, { + return jsonResponse(req, multiplierResponse(wallet, url.searchParams.get("scope")), 200, { "cache-control": "no-store", }); } @@ -3277,9 +5090,14 @@ setInterval(() => { } }, 20_000); -if (STREAM_STATE_SOURCE_URL) { +if (BET_SYNC_SOURCE_EVENTS_URL) { console.log( - `[${nowIso()}] [stream] polling source ${STREAM_STATE_SOURCE_URL}`, + `[${nowIso()}] [bet-sync] consuming ${BET_SYNC_SOURCE_EVENTS_URL}`, + ); + startBetSyncConsumer(); +} else if (ACTIVE_STREAM_STATE_SOURCE_URL) { + console.log( + `[${nowIso()}] [stream] polling source ${ACTIVE_STREAM_STATE_SOURCE_URL}`, ); setInterval(() => { void pollStreamStateSource(); @@ -3287,6 +5105,16 @@ if (STREAM_STATE_SOURCE_URL) { void pollStreamStateSource(); } +if (STREAM_RENDERER_HEALTH_URL) { + console.log( + `[${nowIso()}] [renderer-health] polling ${STREAM_RENDERER_HEALTH_URL}`, + ); + setInterval(() => { + void pollRendererHealthSource(); + }, STREAM_RENDERER_HEALTH_POLL_MS); + void pollRendererHealthSource(); +} + setInterval(() => { void pollContractParsers(); }, CONTRACT_POLL_MS); diff --git a/packages/hyperbet-solana/keeper/src/staged-proof-solana.ts b/packages/hyperbet-solana/keeper/src/staged-proof-solana.ts index 86935310..b017abbd 100644 --- a/packages/hyperbet-solana/keeper/src/staged-proof-solana.ts +++ b/packages/hyperbet-solana/keeper/src/staged-proof-solana.ts @@ -342,21 +342,43 @@ async function ensureRentExemptSystemAccount(args: { provider: Awaited>["provider"]; target: PublicKey; }): Promise { - const minimumBalance = - await args.connection.getMinimumBalanceForRentExemption(0); - const currentBalance = await args.connection.getBalance(args.target, "confirmed"); - if (currentBalance >= minimumBalance) { + const minimumBalance = BigInt( + await args.connection.getMinimumBalanceForRentExemption(0), + ); + await ensureSystemAccountBalance({ + ...args, + targetLamports: minimumBalance, + }); +} + +async function ensureSystemAccountBalance(args: { + connection: Awaited>["connection"]; + payer: PublicKey; + signers: Parameters>["provider"]["sendAndConfirm"]>[1]; + provider: Awaited>["provider"]; + target: PublicKey; + targetLamports: bigint; + label?: string; +}): Promise { + const currentBalance = BigInt( + await args.connection.getBalance(args.target, "confirmed"), + ); + if (currentBalance >= args.targetLamports) { return; } + const topUpLamports = args.targetLamports - currentBalance; const tx = new Transaction().add( SystemProgram.transfer({ fromPubkey: args.payer, toPubkey: args.target, - lamports: minimumBalance - currentBalance, + lamports: Number(topUpLamports), }), ); await args.provider.sendAndConfirm(tx, args.signers); + console.error( + `[solana-canary] topped up ${args.label ?? args.target.toBase58()} by ${lamportsToSolString(topUpLamports)} SOL`, + ); } async function topUpPerpsInsurance(args: { @@ -455,17 +477,53 @@ async function main(): Promise { const trader = readKeypair(runtime.canaryKeypair); const matcher = readKeypair(runtime.marketMakerKeypair); - const authorityPrograms = createPrograms(authority); - const traderPrograms = createPrograms(trader); - const matcherPrograms = createPrograms(matcher); + const authorityPrograms = createPrograms(authority, { + usePollingSendAndConfirm: true, + }); + const traderPrograms = createPrograms(trader, { + usePollingSendAndConfirm: true, + }); + const matcherPrograms = createPrograms(matcher, { + usePollingSendAndConfirm: true, + }); const oracleFundingKeypair = runtime.oracleAuthorityKeypair && runtime.oracleAuthorityKeypair !== authorityKeypairPath ? readKeypair(runtime.oracleAuthorityKeypair) : null; const oracleFundingPrograms = oracleFundingKeypair - ? createPrograms(oracleFundingKeypair) + ? createPrograms(oracleFundingKeypair, { + usePollingSendAndConfirm: true, + }) : null; + const canaryMinLamports = BigInt( + ( + maybeAcceptanceEnv("CANARY_SOLANA_TRADER_MIN_LAMPORTS") ?? "200000000" + ).trim(), + ); + const matcherMinLamports = BigInt( + ( + maybeAcceptanceEnv("CANARY_SOLANA_MATCHER_MIN_LAMPORTS") ?? "200000000" + ).trim(), + ); + await ensureSystemAccountBalance({ + connection: authorityPrograms.connection, + payer: authority.publicKey, + signers: [authority], + provider: authorityPrograms.provider, + target: trader.publicKey, + targetLamports: canaryMinLamports, + label: "solana-canary-trader", + }); + await ensureSystemAccountBalance({ + connection: authorityPrograms.connection, + payer: authority.publicKey, + signers: [authority], + provider: authorityPrograms.provider, + target: matcher.publicKey, + targetLamports: matcherMinLamports, + label: "solana-canary-matcher", + }); const fightOracleProgramId = resolveFightOracleProgramId(); const fightOracle = authorityPrograms.fightOracle; const clobProgram = traderPrograms.goldClobMarket; @@ -785,10 +843,17 @@ async function main(): Promise { .signers([authority]) .rpc(); - const perpsMinInsuranceLamports = BigInt( - ( - maybeAcceptanceEnv("CANARY_PERPS_MIN_INSURANCE_LAMPORTS") ?? "12000000000" - ).trim(), + const configuredPerpsMinInsuranceLamports = maybeAcceptanceEnv( + "CANARY_PERPS_MIN_INSURANCE_LAMPORTS", + ); + const requiredPerpsMinInsuranceLamports = asBigInt( + perpsConfig.minMarketInsuranceLamports, + ); + const perpsMinInsuranceLamports = maxBigInt( + configuredPerpsMinInsuranceLamports + ? BigInt(configuredPerpsMinInsuranceLamports.trim()) + : 0n, + requiredPerpsMinInsuranceLamports, ); const insuranceFundingSources: InsuranceFundingSource[] = [ { diff --git a/packages/hyperbet-ui/package.json b/packages/hyperbet-ui/package.json index af6edf3a..4b08484e 100644 --- a/packages/hyperbet-ui/package.json +++ b/packages/hyperbet-ui/package.json @@ -14,6 +14,7 @@ }, "exports": { ".": "./src/index.ts", + "./createHybridAppRoot": "./src/createHybridAppRoot.tsx", "./i18n": "./src/i18n.ts", "./StreamUIApp": "./src/StreamUIApp.tsx", "./lib/birdeye": "./src/lib/birdeye.ts", @@ -37,6 +38,8 @@ "./lib/programs": "./src/lib/programs.ts", "./lib/solanaCompat": "./src/lib/solanaCompat.ts", "./lib/solanaRpc": "./src/lib/solanaRpc.ts", + "./lib/solanaRuntime": "./src/lib/solanaRuntime.tsx", + "./lib/streamSession": "./src/lib/streamSession.ts", "./lib/token": "./src/lib/token.ts", "./lib/useMockStreamingEngine": "./src/lib/useMockStreamingEngine.ts", "./lib/useResizePanel": "./src/lib/useResizePanel.ts", @@ -60,15 +63,18 @@ "./components/ResizeHandle": "./src/components/ResizeHandle.tsx", "./components/Sidebar": "./src/components/Sidebar.tsx", "./components/SolanaClobPanel": "./src/components/SolanaClobPanel.tsx", + "./components/SolanaModelsMarketView": "./src/components/SolanaModelsMarketView.tsx", "./components/StreamPlayer": "./src/components/StreamPlayer.tsx", "./components/Tabs": "./src/components/Tabs.tsx", "./components/ThemeSelector": "./src/components/ThemeSelector.tsx", "./components/WalletLinkCard": "./src/components/WalletLinkCard.tsx", + "./player": "./src/player/index.ts", "./spectator/AgentCard": "./src/spectator/AgentCard.tsx", "./spectator/index": "./src/spectator/index.ts", "./spectator/Leaderboard": "./src/spectator/Leaderboard.tsx", "./spectator/SpectatorPanel": "./src/spectator/SpectatorPanel.tsx", "./spectator/types": "./src/spectator/types.ts", + "./spectator/useCanonicalStreamSession": "./src/spectator/useCanonicalStreamSession.ts", "./spectator/useDuelContext": "./src/spectator/useDuelContext.ts", "./spectator/useStreamingState": "./src/spectator/useStreamingState.ts" }, @@ -79,6 +85,9 @@ "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "@rainbow-me/rainbowkit": "^2.2.10", + "@solana/client": "latest", + "@solana/kit": "latest", + "@solana/react-hooks": "latest", "@solana/spl-token": "0.4.14", "@solana/wallet-adapter-base": "0.9.27", "@solana/wallet-adapter-phantom": "0.9.28", diff --git a/packages/hyperbet-ui/src/assets/hyperscape-loader/app_background.png b/packages/hyperbet-ui/src/assets/hyperscape-loader/app_background.png new file mode 100644 index 00000000..afab35ac Binary files /dev/null and b/packages/hyperbet-ui/src/assets/hyperscape-loader/app_background.png differ diff --git a/packages/hyperbet-ui/src/assets/hyperscape-loader/loading_bar.png b/packages/hyperbet-ui/src/assets/hyperscape-loader/loading_bar.png new file mode 100644 index 00000000..6630671c Binary files /dev/null and b/packages/hyperbet-ui/src/assets/hyperscape-loader/loading_bar.png differ diff --git a/packages/hyperbet-ui/src/assets/hyperscape-loader/logo.png b/packages/hyperbet-ui/src/assets/hyperscape-loader/logo.png new file mode 100644 index 00000000..7f9b0186 Binary files /dev/null and b/packages/hyperbet-ui/src/assets/hyperscape-loader/logo.png differ diff --git a/packages/hyperbet-ui/src/assets/hyperscape-loader/rubik.woff2 b/packages/hyperbet-ui/src/assets/hyperscape-loader/rubik.woff2 new file mode 100644 index 00000000..c533eec6 Binary files /dev/null and b/packages/hyperbet-ui/src/assets/hyperscape-loader/rubik.woff2 differ diff --git a/packages/hyperbet-ui/src/components/EvmBettingPanel.tsx b/packages/hyperbet-ui/src/components/EvmBettingPanel.tsx index 84df9d16..e70c913f 100644 --- a/packages/hyperbet-ui/src/components/EvmBettingPanel.tsx +++ b/packages/hyperbet-ui/src/components/EvmBettingPanel.tsx @@ -31,20 +31,20 @@ import { secp256k1 } from "@noble/curves/secp256k1.js"; import { useChain } from "../lib/ChainContext"; import { getEvmChainConfig } from "../lib/chainConfig"; +import { ENABLE_LIFECYCLE_MISMATCH_CONSOLE } from "../lib/config"; import { claimWinnings, type ContractWriteAccount, createEvmPublicClient, createSignedRpcWalletClient, createUnlockedRpcWalletClient, - getFeeBps, getMarketMeta, + getMarketReadSnapshot, getNativeBalance, - getOrderBook, - getPosition, getRecentTrades, ORDER_FLAG_GTC, placeOrder, + RateLimitError, toDuelKeyHex, type MarketMeta, type MarketStatus, @@ -57,6 +57,11 @@ import { normalizePredictionMarketDuelKeyHex, usePredictionMarketLifecycle, } from "../lib/predictionMarkets"; +import { + deriveMarketParityLabel, + type MarketParityInfo, +} from "../lib/marketParity"; +import type { TradeGate } from "../lib/viewerAlignment"; import { selectConfiguredEvmPrivateKey } from "../lib/evmPrivateKey"; import { derivePredictionMarketUiState, @@ -77,6 +82,33 @@ import { type Trade } from "./RecentTrades"; type BetSide = "YES" | "NO"; const MARKET_KIND_DUEL_WINNER = 0; +const MIN_RPC_BACKOFF_MS = 15_000; + +export type EvmPanelStatusSource = "base" | "transient"; + +export function shouldSkipEvmRpcRefresh( + backoffUntilMs: number, + nowMs = Date.now(), +): boolean { + return nowMs < backoffUntilMs; +} + +export function deriveEvmPanelBaseStatus(params: { + parityStatusLabel?: string | null; + lifecycleStatusLabel?: string | null; + fallback: string; +}): string { + return ( + params.parityStatusLabel ?? params.lifecycleStatusLabel ?? params.fallback + ); +} + +export function shouldApplyEvmPanelBaseStatus( + statusSource: EvmPanelStatusSource, + force = false, +): boolean { + return force || statusSource === "base"; +} function createStrictPrivateKeyAccount( address: Address, @@ -177,6 +209,8 @@ interface EvmBettingPanelProps { locale?: UiLocale; lifecycleDuelOverride?: PredictionMarketsDuelSnapshot | null; lifecycleMarketOverride?: PredictionMarketLifecycleRecord | null; + lifecycleMarketParityOverride?: MarketParityInfo | null; + viewerAlignmentTradeGate?: TradeGate | null; onLifecycleRefreshRequested?: (() => void | Promise) | null; } @@ -192,8 +226,18 @@ function getEvmPanelCopy(locale: UiLocale) { resolutionProposed: "结果已提交,等待挑战期结束", resolutionChallenged: "结果已被挑战,结算已暂停", marketOpen: "市场开放中", + parityPreparing: "全链市场准备中", + parityAwaitingConfirmations: "等待最终确认", + parityBettingOpen: "投注已开启", + parityLocked: "已锁定", + parityResolved: "已结算", + parityFrozen: "已冻结", + parityCancelled: "已取消", + parityAborted: "启动已中止", refreshFailed: (message: string) => `刷新失败:${message}`, streamDriftDetected: "即将开放下注", + streamSyncing: "正在同步直播画面", + streamVerifying: "正在校验市场状态", walletNotConnected: "钱包未连接", amountTooLow: "数量必须大于 0", placingOrder: "正在下单...", @@ -250,8 +294,18 @@ function getEvmPanelCopy(locale: UiLocale) { resolutionProposed: "Result proposed; challenge window active", resolutionChallenged: "Result challenged; settlement paused", marketOpen: "Market open", + parityPreparing: "Preparing markets on all chains", + parityAwaitingConfirmations: "Awaiting final confirmations", + parityBettingOpen: "Betting open", + parityLocked: "Locked", + parityResolved: "Resolved", + parityFrozen: "Frozen", + parityCancelled: "Cancelled", + parityAborted: "Start aborted", refreshFailed: (message: string) => `Refresh failed: ${message}`, streamDriftDetected: "Betting starts soon", + streamSyncing: "Syncing stream...", + streamVerifying: "Verifying market state...", walletNotConnected: "Wallet not connected", amountTooLow: "Amount must be greater than zero", placingOrder: "Placing order...", @@ -392,6 +446,31 @@ function getLifecycleStatusLabel( } } +function toRateLimitError(error: unknown): RateLimitError | null { + return error instanceof RateLimitError ? error : null; +} + +function describeRefreshError(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + +function describeViewerAlignmentTradeGate( + tradeGate: TradeGate | null | undefined, + copy: ReturnType, +): string | null { + switch (tradeGate?.reason) { + case "clock-confidence-low": + case "clock-frozen": + return copy.streamSyncing; + case "market-stale": + case "market-missing": + case "session-missing": + return copy.streamVerifying; + default: + return null; + } +} + export function EvmBettingPanel({ agent1Name, agent2Name, @@ -399,6 +478,8 @@ export function EvmBettingPanel({ locale, lifecycleDuelOverride = null, lifecycleMarketOverride = null, + lifecycleMarketParityOverride = null, + viewerAlignmentTradeGate = null, onLifecycleRefreshRequested = null, }: EvmBettingPanelProps) { const resolvedLocale = resolveUiLocale(locale); @@ -500,7 +581,7 @@ export function EvmBettingPanel({ : (walletClient ?? e2eWalletClient); const walletConnected = Boolean(effectiveWalletClient && effectiveAddress); - const [status, setStatus] = useState(copy.waitingForLiveDuel); + const [status, setStatusState] = useState(copy.waitingForLiveDuel); const [side, setSide] = useState("YES"); const [amountInput, setAmountInput] = useState("1"); const [priceInput, setPriceInput] = useState("500"); @@ -527,6 +608,8 @@ export function EvmBettingPanel({ const lastSnapshotRef = useRef<{ a: bigint; b: bigint }>({ a: 0n, b: 0n }); const refreshDataRef = useRef<() => Promise>(async () => {}); const refreshDataInFlightRef = useRef | null>(null); + const rpcBackoffUntilRef = useRef(0); + const statusSourceRef = useRef("base"); const cycle = streamingState?.cycle ?? null; const streamedDuelKeyHex = @@ -539,18 +622,19 @@ export function EvmBettingPanel({ ? activeChain : null; const { - duel: lifecycleDuel, - market: lifecycleMarket, + duel: fetchedLifecycleDuel, + market: fetchedLifecycleMarket, + marketParity: fetchedLifecycleMarketParity, refresh: refreshLifecycle, } = usePredictionMarketLifecycle(lifecycleChainKey, { - disabled: - !chainConfig || - lifecycleDuelOverride != null || - lifecycleMarketOverride != null, + disabled: !chainConfig, }); - const effectiveLifecycleDuel = lifecycleDuelOverride ?? lifecycleDuel; - const effectiveLifecycleMarket = lifecycleMarketOverride ?? lifecycleMarket; + const effectiveLifecycleDuel = lifecycleDuelOverride ?? fetchedLifecycleDuel; + const effectiveLifecycleMarket = + lifecycleMarketOverride ?? fetchedLifecycleMarket; + const effectiveLifecycleMarketParity = + lifecycleMarketParityOverride ?? fetchedLifecycleMarketParity; const pinnedE2eDuelKey = isE2eMode ? runtimeE2eOverride.duelKey ?? configuredE2eDuelKey @@ -570,6 +654,18 @@ export function EvmBettingPanel({ () => normalizePredictionMarketDuelKeyHex(streamedDuelKeyHex), [streamedDuelKeyHex], ); + const authoritativeLifecycleDuelKey = useMemo( + () => + pinnedE2eDuelKey ?? + normalizePredictionMarketDuelKeyHex( + effectiveLifecycleMarket?.duelKey ?? effectiveLifecycleDuel?.duelKey, + ), + [ + effectiveLifecycleDuel?.duelKey, + effectiveLifecycleMarket?.duelKey, + pinnedE2eDuelKey, + ], + ); const lifecycleMatchesActiveDuel = lifecycleDuelKey == null || lifecycleDuelKey === liveLifecycleDuelKey; const activeLifecycleDuel = lifecycleMatchesActiveDuel @@ -579,9 +675,16 @@ export function EvmBettingPanel({ ? effectiveLifecycleMarket : null; const streamDriftDetected = - Boolean(liveLifecycleDuelKey) && + Boolean(authoritativeLifecycleDuelKey) && pinnedE2eDuelKey == null && - (streamedDuelKey == null || streamedDuelKey !== liveLifecycleDuelKey); + (streamedDuelKey == null || streamedDuelKey !== authoritativeLifecycleDuelKey); + const fetchedLifecycleDuelKey = useMemo( + () => + normalizePredictionMarketDuelKeyHex( + fetchedLifecycleMarket?.duelKey ?? fetchedLifecycleDuel?.duelKey, + ), + [fetchedLifecycleDuel?.duelKey, fetchedLifecycleMarket?.duelKey], + ); const nativeDecimals = chainConfig?.nativeCurrency.decimals ?? 18; const chainNativeSymbol: Record = { bsc: "BNB", base: "ETH", avax: "AVAX" }; const nativeSymbol = chainConfig?.nativeCurrency.symbol ?? chainNativeSymbol[activeChain] ?? "ETH"; @@ -622,6 +725,16 @@ export function EvmBettingPanel({ ), [activeLifecycleMarket, marketMeta, walletSnapshot], ); + const viewerAlignmentStatusLabel = useMemo( + () => describeViewerAlignmentTradeGate(viewerAlignmentTradeGate, copy), + [copy, viewerAlignmentTradeGate], + ); + const viewerAlignmentBlocksDisplay = + viewerAlignmentTradeGate?.canDisplayOpen === false && + uiState.lifecycleStatus !== "RESOLVED" && + uiState.lifecycleStatus !== "CANCELLED"; + const viewerAlignmentBlocksSubmit = + viewerAlignmentTradeGate?.canSubmitTrade === false; const lifecycleStatusLabel = useMemo( () => getLifecycleStatusLabel( @@ -633,6 +746,97 @@ export function EvmBettingPanel({ ), [copy, cycleAgent1, cycleAgent2, uiState.lifecycleStatus, uiState.winner], ); + const parityStatusLabel = useMemo( + () => deriveMarketParityLabel(effectiveLifecycleMarketParity, copy), + [copy, effectiveLifecycleMarketParity], + ); + const baseStatusLabel = useMemo( + () => + viewerAlignmentBlocksDisplay && viewerAlignmentStatusLabel + ? viewerAlignmentStatusLabel + : deriveEvmPanelBaseStatus({ + parityStatusLabel, + lifecycleStatusLabel, + fallback: copy.waitingForLiveDuel, + }), + [ + copy.waitingForLiveDuel, + lifecycleStatusLabel, + parityStatusLabel, + viewerAlignmentBlocksDisplay, + viewerAlignmentStatusLabel, + ], + ); + const lastLifecycleMismatchSignatureRef = useRef(null); + + const setBaseStatus = useCallback( + (nextStatus: string, options: { force?: boolean } = {}) => { + if ( + !shouldApplyEvmPanelBaseStatus( + statusSourceRef.current, + options.force ?? false, + ) + ) { + return; + } + statusSourceRef.current = "base"; + setStatusState(nextStatus); + }, + [], + ); + + const setTransientStatus = useCallback((nextStatus: string) => { + statusSourceRef.current = "transient"; + setStatusState(nextStatus); + }, []); + + useEffect(() => { + setBaseStatus(baseStatusLabel); + }, [baseStatusLabel, setBaseStatus]); + + useEffect(() => { + if (!ENABLE_LIFECYCLE_MISMATCH_CONSOLE) { + lastLifecycleMismatchSignatureRef.current = null; + return; + } + if ( + authoritativeLifecycleDuelKey == null || + fetchedLifecycleDuelKey == null || + authoritativeLifecycleDuelKey === fetchedLifecycleDuelKey + ) { + lastLifecycleMismatchSignatureRef.current = null; + return; + } + const mismatchSignature = [ + activeChain, + authoritativeLifecycleDuelKey, + fetchedLifecycleDuelKey, + effectiveLifecycleMarket?.duelId ?? effectiveLifecycleDuel?.duelId ?? "", + fetchedLifecycleMarket?.duelId ?? fetchedLifecycleDuel?.duelId ?? "", + ].join("|"); + if (lastLifecycleMismatchSignatureRef.current === mismatchSignature) { + return; + } + lastLifecycleMismatchSignatureRef.current = mismatchSignature; + console.warn("[hyperbet] lifecycle_mismatch", { + chain: activeChain, + visibleDuelKey: authoritativeLifecycleDuelKey, + fetchedDuelKey: fetchedLifecycleDuelKey, + visibleDuelId: + effectiveLifecycleMarket?.duelId ?? effectiveLifecycleDuel?.duelId ?? null, + fetchedDuelId: + fetchedLifecycleMarket?.duelId ?? fetchedLifecycleDuel?.duelId ?? null, + }); + }, [ + ENABLE_LIFECYCLE_MISMATCH_CONSOLE, + activeChain, + authoritativeLifecycleDuelKey, + effectiveLifecycleDuel?.duelId, + effectiveLifecycleMarket?.duelId, + fetchedLifecycleDuel?.duelId, + fetchedLifecycleDuelKey, + fetchedLifecycleMarket?.duelId, + ]); const publicClient = useMemo(() => { if (!chainConfig) return null; @@ -698,14 +902,70 @@ export function EvmBettingPanel({ const refreshData = useCallback(async () => { if (!publicClient || !chainConfig) return; + const applyRateLimitBackoff = (error: unknown): boolean => { + const rateLimitError = toRateLimitError(error); + if (!rateLimitError) return false; + rpcBackoffUntilRef.current = Math.max( + rpcBackoffUntilRef.current, + Date.now() + Math.max(rateLimitError.retryAfterMs, MIN_RPC_BACKOFF_MS), + ); + setLastRefreshError(rateLimitError.message); + return true; + }; + + const updateStatusFromMarket = (market: MarketMeta, nextPosition: Position | null) => { + const nextUiState = derivePredictionMarketUiState( + activeLifecycleMarket, + nextPosition + ? { + aShares: nextPosition.aShares, + bShares: nextPosition.bShares, + aStake: nextPosition.aStake, + bStake: nextPosition.bStake, + refundableAmount: nextPosition.aStake + nextPosition.bStake, + } + : EMPTY_PREDICTION_MARKET_WALLET_SNAPSHOT, + { + lifecycleStatus: getFallbackLifecycleStatus(market.status), + winner: getFallbackWinner(market.winner), + }, + ); + setBaseStatus( + deriveEvmPanelBaseStatus({ + parityStatusLabel, + lifecycleStatusLabel: getLifecycleStatusLabel( + nextUiState.lifecycleStatus, + nextUiState.winner, + cycleAgent1, + cycleAgent2, + copy, + ), + fallback: copy.waitingForMarketOperator, + }), + { force: true }, + ); + }; + try { if (!duelKeyHex) { - setLastRefreshError("missing-duel-key"); + setLastRefreshError(null); setMarketMeta(null); setPosition(null); setBids([]); setAsks([]); - setStatus(lifecycleStatusLabel ?? copy.waitingForLiveDuel); + setBaseStatus( + deriveEvmPanelBaseStatus({ + parityStatusLabel, + lifecycleStatusLabel, + fallback: copy.waitingForLiveDuel, + }), + { force: true }, + ); + return; + } + + if (shouldSkipEvmRpcRefresh(rpcBackoffUntilRef.current)) { + setBaseStatus(baseStatusLabel); return; } @@ -720,127 +980,90 @@ export function EvmBettingPanel({ ); if (!market.exists) { - setLastRefreshError("missing-market"); + setLastRefreshError(null); setMarketMeta(null); setPosition(null); setBids([]); setAsks([]); - setStatus(lifecycleStatusLabel ?? copy.waitingForMarketOperator); + setBaseStatus( + deriveEvmPanelBaseStatus({ + parityStatusLabel, + lifecycleStatusLabel, + fallback: copy.waitingForMarketOperator, + }), + { force: true }, + ); return; } setMarketMeta(market); setLastRefreshError(null); updateChartAndTrades(market.totalAShares, market.totalBShares); - const feeBpsPromise = getFeeBps(publicClient, contractAddr); - const orderBookPromise = getOrderBook( - publicClient, + + if (!effectiveAddress) { + setPosition(null); + setNativeBalance(0n); + updateStatusFromMarket(market, null); + } else { + updateStatusFromMarket(market, effectivePosition); + } + + const marketReadPromise = getMarketReadSnapshot( + chainConfig, contractAddr, duelKey, MARKET_KIND_DUEL_WINNER, market, + effectiveAddress, ); const tradesPromise = getRecentTrades( publicClient, contractAddr, market.marketKey, ); + const balancePromise = effectiveAddress + ? getNativeBalance(publicClient, effectiveAddress) + : Promise.resolve(0n); - if (effectiveAddress) { - const [userPosition, balance] = await Promise.all([ - getPosition( - publicClient, - contractAddr, - market.marketKey, - effectiveAddress, - ), - getNativeBalance(publicClient, effectiveAddress), - ]); - setPosition(userPosition); - setOptimisticPosition((current) => { - if (!current) return null; - const hasCaughtUp = - userPosition.aShares >= current.aShares && - userPosition.bShares >= current.bShares && - userPosition.aStake >= current.aStake && - userPosition.bStake >= current.bStake; - return hasCaughtUp ? null : current; - }); - setNativeBalance(balance); - const nextUiState = derivePredictionMarketUiState( - activeLifecycleMarket, - { - aShares: userPosition.aShares, - bShares: userPosition.bShares, - aStake: userPosition.aStake, - bStake: userPosition.bStake, - refundableAmount: userPosition.aStake + userPosition.bStake, - }, - { - lifecycleStatus: getFallbackLifecycleStatus(market.status), - winner: getFallbackWinner(market.winner), - }, - ); - setStatus( - getLifecycleStatusLabel( - nextUiState.lifecycleStatus, - nextUiState.winner, - cycleAgent1, - cycleAgent2, - copy, - ) ?? copy.waitingForMarketOperator, - ); - } else { - setPosition(null); - setNativeBalance(0n); - const nextUiState = derivePredictionMarketUiState( - activeLifecycleMarket, - EMPTY_PREDICTION_MARKET_WALLET_SNAPSHOT, - { - lifecycleStatus: getFallbackLifecycleStatus(market.status), - winner: getFallbackWinner(market.winner), - }, - ); - setStatus( - getLifecycleStatusLabel( - nextUiState.lifecycleStatus, - nextUiState.winner, - cycleAgent1, - cycleAgent2, - copy, - ) ?? copy.waitingForMarketOperator, - ); - } - - const [feeBpsResult, orderBookResult, tradesResult] = + const [marketReadResult, tradesResult, balanceResult] = await Promise.allSettled([ - feeBpsPromise, - orderBookPromise, + marketReadPromise, tradesPromise, + balancePromise, ]); - if (feeBpsResult.status === "fulfilled") { - setTradeFeeBps(feeBpsResult.value); - } - - if (orderBookResult.status === "fulfilled") { + if (marketReadResult.status === "fulfilled") { + setTradeFeeBps(marketReadResult.value.feeBps); setBids( - orderBookResult.value.bids.map((entry) => ({ + marketReadResult.value.orderBook.bids.map((entry) => ({ price: entry.price, amount: Number(formatUnits(entry.amount, nativeDecimals)), total: Number(formatUnits(entry.total, nativeDecimals)), })), ); setAsks( - orderBookResult.value.asks.map((entry) => ({ + marketReadResult.value.orderBook.asks.map((entry) => ({ price: entry.price, amount: Number(formatUnits(entry.amount, nativeDecimals)), total: Number(formatUnits(entry.total, nativeDecimals)), })), ); - } else { - setBids([]); - setAsks([]); + if (effectiveAddress && marketReadResult.value.position) { + const userPosition = marketReadResult.value.position; + setPosition(userPosition); + setOptimisticPosition((current) => { + if (!current) return null; + const hasCaughtUp = + userPosition.aShares >= current.aShares && + userPosition.bShares >= current.bShares && + userPosition.aStake >= current.aStake && + userPosition.bStake >= current.bStake; + return hasCaughtUp ? null : current; + }); + updateStatusFromMarket(market, userPosition); + } + } else if (!applyRateLimitBackoff(marketReadResult.reason)) { + setLastRefreshError(describeRefreshError(marketReadResult.reason)); } if (tradesResult.status === "fulfilled") { @@ -853,25 +1076,40 @@ export function EvmBettingPanel({ time: trade.time, })), ); - } else { - setRecentTrades([]); + } else if (!applyRateLimitBackoff(tradesResult.reason)) { + setLastRefreshError(describeRefreshError(tradesResult.reason)); + } + + if (balanceResult.status === "fulfilled") { + setNativeBalance(balanceResult.value); + } else if (!applyRateLimitBackoff(balanceResult.reason)) { + setLastRefreshError(describeRefreshError(balanceResult.reason)); } } catch (error) { + if (applyRateLimitBackoff(error)) { + setBaseStatus(baseStatusLabel); + return; + } const message = (error as Error).message; setLastRefreshError(message); - setStatus(copy.refreshFailed(message)); + setTransientStatus(copy.refreshFailed(message)); } }, [ + baseStatusLabel, chainConfig, copy, cycleAgent1, cycleAgent2, duelKeyHex, effectiveAddress, + effectivePosition, activeLifecycleMarket, lifecycleStatusLabel, + parityStatusLabel, nativeDecimals, publicClient, + setBaseStatus, + setTransientStatus, updateChartAndTrades, ]); @@ -961,6 +1199,10 @@ export function EvmBettingPanel({ const handlePlaceOrder = useCallback(async () => { + if (viewerAlignmentBlocksSubmit) { + setTransientStatus(viewerAlignmentStatusLabel ?? copy.streamVerifying); + return; + } if (isSubmitting) return; if ( !effectiveWalletClient || @@ -969,7 +1211,7 @@ export function EvmBettingPanel({ !chainConfig || !duelKeyHex ) { - setStatus(copy.walletNotConnected); + setTransientStatus(copy.walletNotConnected); return; } setIsSubmitting(true); @@ -977,7 +1219,7 @@ export function EvmBettingPanel({ try { const amount = parseUnits(amountInput, nativeDecimals); if (amount <= 0n) { - setStatus(copy.amountTooLow); + setTransientStatus(copy.amountTooLow); return; } @@ -1005,7 +1247,7 @@ export function EvmBettingPanel({ bStake: cost, }; - setStatus(copy.placingOrder); + setTransientStatus(copy.placingOrder); const tx = await placeOrder( effectiveWalletClient, chainConfig.goldClobAddress as Address, @@ -1039,7 +1281,7 @@ export function EvmBettingPanel({ optimisticDelta, ), ); - setStatus(copy.orderPlaced); + setTransientStatus(copy.orderPlaced); setIsSubmitting(false); void recordPredictionMarketTrade(trackingInput); void requestRefreshData(); @@ -1047,7 +1289,7 @@ export function EvmBettingPanel({ setLastOrderErrorDetail( error instanceof Error ? error.stack ?? error.message : String(error), ); - setStatus(copy.orderFailed((error as Error).message)); + setTransientStatus(copy.orderFailed((error as Error).message)); } finally { setIsSubmitting(false); } @@ -1067,10 +1309,13 @@ export function EvmBettingPanel({ publicClient, requestRefreshData, side, + setTransientStatus, tradeFeeBps, activeLifecycleMarket?.marketRef, marketMeta?.marketKey, duelId, + viewerAlignmentBlocksSubmit, + viewerAlignmentStatusLabel, ]); @@ -1083,13 +1328,13 @@ export function EvmBettingPanel({ !chainConfig || !duelKeyHex ) { - setStatus(copy.walletNotConnected); + setTransientStatus(copy.walletNotConnected); return; } try { const duelKey = toDuelKeyHex(duelKeyHex); - setStatus(copy.claimingSettlement); + setTransientStatus(copy.claimingSettlement); const tx = await claimWinnings( effectiveWalletClient, chainConfig.goldClobAddress as Address, @@ -1100,10 +1345,10 @@ export function EvmBettingPanel({ setLastClaimTx(tx); await publicClient?.waitForTransactionReceipt({ hash: tx }); setOptimisticPosition(null); - setStatus(copy.claimComplete); + setTransientStatus(copy.claimComplete); await requestRefreshData(); } catch (error) { - setStatus(copy.claimFailed((error as Error).message)); + setTransientStatus(copy.claimFailed((error as Error).message)); } }, [ chainConfig, @@ -1114,6 +1359,7 @@ export function EvmBettingPanel({ effectiveWalletClient, publicClient, requestRefreshData, + setTransientStatus, ]); const yesPercent = @@ -1159,10 +1405,16 @@ export function EvmBettingPanel({ ? `${formatCompactTokenAmount(uiState.claimableAmount, nativeDecimals)} ${nativeSymbol}` : null; const programsReady = Boolean( - chainConfig && duelKeyHex && uiState.canTrade && !streamDriftDetected, + chainConfig && + duelKeyHex && + uiState.canTrade && + !streamDriftDetected && + !viewerAlignmentBlocksSubmit, ); const panelStatusNote = - !streamDriftDetected && lastRefreshError != null + viewerAlignmentBlocksDisplay || viewerAlignmentBlocksSubmit + ? viewerAlignmentStatusLabel + : !streamDriftDetected && lastRefreshError != null ? copy.refreshFailed(lastRefreshError ?? "unknown") : null; const e2eWalletDebug = isE2eMode @@ -1200,6 +1452,55 @@ export function EvmBettingPanel({ ].join(" ") : ""; + useEffect(() => { + if (typeof window === "undefined") { + return; + } + ( + window as typeof window & { + __HYPERBET_EVM_MARKET_EVIDENCE__?: Record | null; + } + ).__HYPERBET_EVM_MARKET_EVIDENCE__ = + activeChain === "bsc" || activeChain === "base" || activeChain === "avax" + ? { + activeChain, + duelKey: duelKeyHex ?? null, + duelId: duelId ?? null, + lifecycleStatus: activeLifecycleMarket?.lifecycleStatus ?? null, + winner: activeLifecycleMarket?.winner ?? null, + marketRef: activeLifecycleMarket?.marketRef ?? null, + marketStatus: marketMeta?.status ?? null, + marketWinner: marketMeta?.winner ?? null, + marketKey: marketMeta?.marketKey ?? null, + streamAligned: !streamDriftDetected, + viewerAlignmentReason: viewerAlignmentTradeGate?.reason ?? null, + canClaim: uiState.canClaim, + claimKind: uiState.claimKind, + } + : null; + return () => { + ( + window as typeof window & { + __HYPERBET_EVM_MARKET_EVIDENCE__?: Record | null; + } + ).__HYPERBET_EVM_MARKET_EVIDENCE__ = null; + }; + }, [ + activeChain, + activeLifecycleMarket?.lifecycleStatus, + activeLifecycleMarket?.marketRef, + activeLifecycleMarket?.winner, + duelId, + duelKeyHex, + marketMeta?.marketKey, + marketMeta?.status, + marketMeta?.winner, + streamDriftDetected, + viewerAlignmentTradeGate?.reason, + uiState.canClaim, + uiState.claimKind, + ]); + return (
({ + chainKey: market.chainKey as EvmPerpsChainKey, + characterId: market.characterId, + marketId: market.marketId, rank: market.rank, name: market.name, provider: market.provider, @@ -65,13 +80,22 @@ function toDisplayEntries( wins: market.wins, losses: market.losses, winRate: market.winRate, + combatLevel: market.combatLevel, currentStreak: market.currentStreak, status: market.status, + lastSeenAt: market.lastSeenAt, + updatedAt: market.updatedAt, })); } -function toMockEntries(mockData: EvmModelsMarketMockData): DisplayEntry[] { +function toMockEntries( + mockData: EvmModelsMarketMockData, + chainKey: EvmPerpsChainKey, +): DisplayEntry[] { return mockData.leaderboard.map((entry) => ({ + chainKey, + characterId: entry.agentName.trim().toLowerCase().replace(/\s+/g, "-"), + marketId: entry.rank, rank: entry.rank, name: entry.agentName, provider: entry.provider, @@ -79,8 +103,11 @@ function toMockEntries(mockData: EvmModelsMarketMockData): DisplayEntry[] { wins: entry.wins, losses: entry.losses, winRate: entry.winRate, + combatLevel: 0, currentStreak: entry.currentStreak, status: "ACTIVE", + lastSeenAt: Date.now(), + updatedAt: Date.now(), })); } @@ -93,77 +120,184 @@ function findEntry(entries: DisplayEntry[], agentName: string): DisplayEntry | n ); } +function formatTimestamp(value: number | null): string { + if (!value) return "Pending"; + return new Date(value).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + }); +} + +function formatShortRelative(value: number | null): string { + if (!value) return "Pending"; + const ageMs = Math.max(0, Date.now() - value); + const ageMinutes = Math.floor(ageMs / 60_000); + if (ageMinutes <= 0) return "moments ago"; + if (ageMinutes === 1) return "1 minute ago"; + if (ageMinutes < 60) return `${ageMinutes} minutes ago`; + const ageHours = Math.floor(ageMinutes / 60); + if (ageHours === 1) return "1 hour ago"; + return `${ageHours} hours ago`; +} + +function isEntryInCurrentMatch( + entry: DisplayEntry, + fightingAgentA: string, + fightingAgentB: string, +): boolean { + const normalized = entry.name.trim().toLowerCase(); + return ( + normalized === fightingAgentA.trim().toLowerCase() || + normalized === fightingAgentB.trim().toLowerCase() + ); +} + +const DIRECTORY_POLL_INTERVAL_MS = 5_000; +const ORACLE_HISTORY_POLL_INTERVAL_MS = 15_000; + export function EvmModelsMarketView({ fightingAgentA, fightingAgentB, gameApiUrl, mockData, collateralSymbol = "USDC", + chainKey, chainLabel = "EVM", theme, }: EvmModelsMarketViewProps) { const themeDefinition = useResolvedHyperbetTheme(theme); const [entries, setEntries] = useState(() => - mockData ? toMockEntries(mockData) : [], + mockData && chainKey ? toMockEntries(mockData, chainKey) : [], ); const [updatedAt, setUpdatedAt] = useState( mockData ? Date.now() : null, ); + const [searchValue, setSearchValue] = useState(""); + const [selectedCharacterId, setSelectedCharacterId] = useState( + null, + ); + const [oracleSnapshots, setOracleSnapshots] = useState( + [], + ); + const [oracleUpdatedAt, setOracleUpdatedAt] = useState(null); + const [oracleLoading, setOracleLoading] = useState(false); + const [oracleError, setOracleError] = useState(""); const [loading, setLoading] = useState(!mockData); const [error, setError] = useState(""); + const resolvedChainKey = chainKey ?? null; + const lastDirectoryWarningKeyRef = useRef(null); useEffect(() => { if (mockData) { - setEntries(toMockEntries(mockData)); + setEntries(chainKey ? toMockEntries(mockData, chainKey) : []); setUpdatedAt(Date.now()); setLoading(false); setError(""); return; } + const activeChainKey = resolvedChainKey; + if (!activeChainKey) { + setEntries([]); + setUpdatedAt(null); + setLoading(false); + setError("missing chain key"); + return; + } - let cancelled = false; + const controller = new AbortController(); + let activePollController: AbortController | null = null; - async function loadMarkets() { + async function loadMarkets(signal?: AbortSignal) { try { setLoading(true); setError(""); - const response = await fetch(`${gameApiUrl}/api/perps/markets`, { + const endpoint = buildPerpsMarketsEndpoint(gameApiUrl, activeChainKey!); + const response = await fetch(endpoint, { + cache: "no-store", headers: { Accept: "application/json" }, + signal, }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const json = await response.json(); - const sanitized = sanitizePerpsMarketsResponse(json); - if (cancelled) return; + const sanitized = sanitizePerpsMarketsResponse(json, activeChainKey); + if (signal?.aborted) return; + const warningKey = + sanitized.markets.length === 0 + ? `empty:${activeChainKey}` + : `ready:${activeChainKey}`; + if ( + sanitized.markets.length === 0 && + typeof console !== "undefined" && + lastDirectoryWarningKeyRef.current !== warningKey + ) { + console.warn("[hyperbet] empty_model_directory", { + chainKey: activeChainKey, + chain: chainLabel, + endpoint, + }); + } + lastDirectoryWarningKeyRef.current = warningKey; setEntries(toDisplayEntries(sanitized.markets)); setUpdatedAt(sanitized.updatedAt); } catch (nextError) { - if (cancelled) return; + if (signal?.aborted) return; setError( nextError instanceof Error ? nextError.message : "Failed to load model markets", ); } finally { - if (!cancelled) { + if (!signal?.aborted) { setLoading(false); } } } - void loadMarkets(); + void loadMarkets(controller.signal); + const intervalId = window.setInterval(() => { + activePollController?.abort(); + activePollController = new AbortController(); + void loadMarkets(activePollController.signal); + }, DIRECTORY_POLL_INTERVAL_MS); return () => { - cancelled = true; + controller.abort(); + activePollController?.abort(); + window.clearInterval(intervalId); }; - }, [gameApiUrl, mockData]); + }, [chainKey, chainLabel, gameApiUrl, mockData, resolvedChainKey]); const matchup = useMemo(() => { const left = findEntry(entries, fightingAgentA); const right = findEntry(entries, fightingAgentB); return { left, right }; }, [entries, fightingAgentA, fightingAgentB]); + const filteredEntries = useMemo(() => { + const query = searchValue.trim().toLowerCase(); + if (!query) return entries; + return entries.filter((entry) => { + const haystack = [ + entry.name, + entry.provider, + entry.model, + entry.status, + ] + .join(" ") + .toLowerCase(); + return haystack.includes(query); + }); + }, [entries, searchValue]); + const selectedEntry = useMemo(() => { + return ( + filteredEntries.find((entry) => entry.characterId === selectedCharacterId) ?? + matchup.left ?? + matchup.right ?? + filteredEntries[0] ?? + null + ); + }, [filteredEntries, matchup.left, matchup.right, selectedCharacterId]); const totals = useMemo(() => { return entries.reduce( @@ -172,25 +306,139 @@ export function EvmModelsMarketView({ acc.totalWins += entry.wins; acc.totalLosses += entry.losses; if (entry.status === "ACTIVE") acc.active += 1; + if (entry.rank != null) { + acc.ranked += 1; + } return acc; }, - { markets: 0, totalWins: 0, totalLosses: 0, active: 0 }, + { markets: 0, totalWins: 0, totalLosses: 0, active: 0, ranked: 0 }, ); }, [entries]); + const currentMatchupLabel = useMemo(() => { + const left = matchup.left?.name ?? fightingAgentA; + const right = matchup.right?.name ?? fightingAgentB; + if (!left && !right) return "Stand by"; + return `${left || "Agent A"} vs ${right || "Agent B"}`; + }, [fightingAgentA, fightingAgentB, matchup.left?.name, matchup.right?.name]); + + useEffect(() => { + if (!selectedEntry) { + setSelectedCharacterId(null); + return; + } + if ( + selectedCharacterId == null || + !filteredEntries.some((entry) => entry.characterId === selectedCharacterId) + ) { + setSelectedCharacterId(selectedEntry.characterId); + } + }, [filteredEntries, selectedCharacterId, selectedEntry]); + + useEffect(() => { + if (!selectedEntry || mockData) { + setOracleSnapshots([]); + setOracleUpdatedAt(null); + setOracleError(""); + setOracleLoading(false); + return; + } + const activeChainKey = resolvedChainKey; + if (!activeChainKey) { + setOracleSnapshots([]); + setOracleUpdatedAt(null); + setOracleError("missing chain key"); + setOracleLoading(false); + return; + } + + const controller = new AbortController(); + let activePollController: AbortController | null = null; + + async function loadOracleHistory(signal?: AbortSignal) { + try { + setOracleLoading(true); + setOracleError(""); + const endpoint = buildPerpsOracleHistoryEndpoint({ + gameApiUrl, + chainKey: activeChainKey!, + characterId: selectedEntry.characterId, + limit: 24, + }); + const response = await fetch( + endpoint, + { + cache: "no-store", + headers: { Accept: "application/json" }, + signal, + }, + ); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + const json = await response.json(); + const sanitized = sanitizePerpsOracleHistoryResponse( + json, + selectedEntry.characterId, + activeChainKey, + ); + if (signal?.aborted) return; + setOracleSnapshots(sanitized.snapshots); + setOracleUpdatedAt(sanitized.updatedAt); + } catch (nextError) { + if (signal?.aborted) return; + setOracleError( + nextError instanceof Error + ? nextError.message + : "Failed to load oracle history", + ); + } finally { + if (!signal?.aborted) { + setOracleLoading(false); + } + } + } + + void loadOracleHistory(controller.signal); + const intervalId = window.setInterval(() => { + activePollController?.abort(); + activePollController = new AbortController(); + void loadOracleHistory(activePollController.signal); + }, ORACLE_HISTORY_POLL_INTERVAL_MS); + + return () => { + controller.abort(); + activePollController?.abort(); + window.clearInterval(intervalId); + }; + }, [gameApiUrl, mockData, resolvedChainKey, selectedEntry]); + + const latestOracleSnapshot = + oracleSnapshots.length > 0 ? oracleSnapshots[oracleSnapshots.length - 1] : null; + const latestOracleBasis = + latestOracleSnapshot != null + ? latestOracleSnapshot.spotIndex.toFixed(2) + : "Pending"; + const tradingAvailabilityMessage = + chainLabel.toLowerCase().includes("bnb") || chainLabel.toLowerCase().includes("bsc") + ? "Trading on BSC is still rolling out. This surface is keeper-backed and read-only for now." + : `Trading on ${chainLabel} is still rolling out. This surface is keeper-backed and read-only for now.`; + const emptyDirectoryMessage = + searchValue.trim().length > 0 + ? "No models matched the current filter." + : `No ${chainLabel} models are indexed yet.`; return (

- Canonical EVM Runtime + Keeper-backed directory

-

Model markets on {chainLabel}

+

Model Perps

- This additive view uses the current shared runtime and market directory - surfaces. It keeps the sprint branch’s canonical lifecycle and deploy - assumptions intact while surfacing the sweep branch’s EVM models - market direction. + Chain-aware model market coverage for {chainLabel}. The public shell now + mirrors the Solana surface: searchable directory, live matchup context, + selected model detail, and keeper-backed oracle snapshots.

@@ -199,14 +447,21 @@ export function EvmModelsMarketView({ {formatCompactNumber(totals.markets)} - {totals.active} active + {totals.ranked} ranked
- Recorded bouts + Current matchup - {formatCompactNumber(totals.totalWins + totals.totalLosses)} + {currentMatchupLabel} - {collateralSymbol} collateral + {totals.active} active listings +
+
+ Oracle basis + {latestOracleBasis} + + {oracleUpdatedAt ? `Updated ${formatTimestamp(oracleUpdatedAt)}` : `${collateralSymbol} collateral`} +
@@ -215,53 +470,34 @@ export function EvmModelsMarketView({
-

Current matchup

+

Models

- The active duel can be cross-checked against the current EVM model - market directory. + Keeper-backed model directory for {chainLabel}, aligned to the live + duel shell instead of a separate placeholder runtime.

- {updatedAt ? ( - - Updated {new Date(updatedAt).toLocaleTimeString()} - - ) : null} -
-
- {[matchup.left ?? { name: fightingAgentA, provider: "Unknown", model: "Unknown", wins: 0, losses: 0, winRate: 0, currentStreak: 0, rank: null, status: "PENDING" }, matchup.right ?? { name: fightingAgentB, provider: "Unknown", model: "Unknown", wins: 0, losses: 0, winRate: 0, currentStreak: 0, rank: null, status: "PENDING" }].map( - (entry, index) => ( -
- - {index === 0 ? "Agent A" : "Agent B"} - - {entry.name} - - {entry.provider} · {entry.model} - - - WR {entry.winRate.toFixed(1)}% · {entry.wins}-{entry.losses} - -
- ), - )} -
-
- -
-
-
-

Market directory

-

- Ranked model entries from the current keeper-backed market index. -

+
+ setSearchValue(event.target.value)} + placeholder="Search models" + aria-label="Search models" + /> + {updatedAt ? ( + + Updated {formatTimestamp(updatedAt)} + + ) : null}
{loading ? (
Loading model markets…
) : error ? (
Directory unavailable: {error}
- ) : entries.length === 0 ? ( -
No EVM model markets are indexed yet.
+ ) : filteredEntries.length === 0 ? ( +
{emptyDirectoryMessage}
) : (
@@ -276,42 +512,229 @@ export function EvmModelsMarketView({ - {entries.slice(0, 16).map((entry) => ( - - - setSelectedCharacterId(entry.characterId)} + onKeyDown={(event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + setSelectedCharacterId(entry.characterId); + } + }} + tabIndex={0} + role="button" + aria-pressed={isSelected} + > + + + + + + - - - - - - ))} + + + ); + })}
- {entry.rank ?? "—"} - -
- {entry.name} - - {entry.provider} · {entry.model} + {filteredEntries.slice(0, 18).map((entry) => { + const isSelected = selectedEntry?.characterId === entry.characterId; + const isLive = isEntryInCurrentMatch( + entry, + fightingAgentA, + fightingAgentB, + ); + return ( +
+ {entry.rank ?? "—"} + +
+ + {entry.name} + {isLive ? ( + Live + ) : null} + + + {entry.provider} · {entry.model} + +
+
+ {entry.wins}-{entry.losses} + + {entry.winRate.toFixed(1)}% + + {entry.currentStreak} + + + {entry.status} - - - {entry.wins}-{entry.losses} - - {entry.winRate.toFixed(1)}% - - {entry.currentStreak} - - - {entry.status} - -
)}
+ +
+
+
+
+

Selected model

+

+ Live matchup alignment, keeper market identity, and recent oracle + evidence. +

+
+ {selectedEntry?.rank != null ? ( + Rank #{selectedEntry.rank} + ) : ( + + Unranked + + )} +
+ {selectedEntry ? ( +
+
+
+ Model + {selectedEntry.name} + + {selectedEntry.provider} · {selectedEntry.model} + +
+
+ Current status + + {selectedEntry.status} + + + Market #{selectedEntry.marketId} + +
+
+ Record + + {selectedEntry.wins}-{selectedEntry.losses} + + + Win rate {selectedEntry.winRate.toFixed(1)}% + +
+
+ Combat profile + + Lv.{selectedEntry.combatLevel} + + + Streak {selectedEntry.currentStreak} + +
+
+ +
+
+
+

Chain market summary

+

+ {currentMatchupLabel} · {chainLabel} +

+
+ + Seen {formatShortRelative(selectedEntry.lastSeenAt)} + +
+
+
+ Oracle basis + + {latestOracleBasis} + + + {oracleUpdatedAt ? `Updated ${formatTimestamp(oracleUpdatedAt)}` : "Waiting for oracle snapshots"} + +
+
+ Directory sync + + {formatTimestamp(selectedEntry.updatedAt)} + + + Last seen {formatShortRelative(selectedEntry.lastSeenAt)} + +
+
+ {oracleLoading ? ( +
Loading oracle snapshots…
+ ) : oracleError ? ( +
Oracle history unavailable: {oracleError}
+ ) : oracleSnapshots.length === 0 ? ( +
Waiting for keeper oracle snapshots.
+ ) : ( +
+ {oracleSnapshots.slice(-5).reverse().map((snapshot) => ( +
+ + {formatTimestamp(snapshot.recordedAt)} + + + {snapshot.spotIndex.toFixed(2)} + + + Conservative skill {snapshot.conservativeSkill.toFixed(2)} + +
+ ))} +
+ )} +
+
+ ) : ( +
+ Select a model to inspect and compare against the current duel. +
+ )} +
+ +
+
+
+

Trading status

+

+ Product-shell parity is live now; chain-specific order entry can + follow without downgrading the models surface. +

+
+
+
+
+
+

Read-only mode

+

{tradingAvailabilityMessage}

+
+ {chainLabel} +
+
+
+ Highlighted matchup + {currentMatchupLabel} + Public shell aligned with the live duel surface. +
+
+ Directory health + + {loading ? "Loading" : error ? "Degraded" : "Ready"} + + + {error + ? `Index error: ${error}` + : `${totals.markets} keeper-indexed models available`} + +
+
+
+
+
); diff --git a/packages/hyperbet-ui/src/components/FightOverlay.tsx b/packages/hyperbet-ui/src/components/FightOverlay.tsx index 3b642203..ffccd204 100644 --- a/packages/hyperbet-ui/src/components/FightOverlay.tsx +++ b/packages/hyperbet-ui/src/components/FightOverlay.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import type { MockAgentContext } from "../lib/useMockStreamingEngine"; import type { StreamingPhase } from "../spectator/types"; -import { GAME_API_URL } from "../lib/config"; +import { GAME_API_URL, PUBLIC_CDN_URL } from "../lib/config"; import { resolveUiLocale, getUiCopy } from "../i18n"; interface FightOverlayProps { @@ -48,13 +48,21 @@ let cachedItemIconMap: Record | null = null; let itemIconMapPromise: Promise> | null = null; function resolveManifestIconPath(iconPath: string): string { - const base = GAME_API_URL.replace(/\/$/, ""); + if (/^https?:\/\//i.test(iconPath)) { + return iconPath; + } + + const assetBase = PUBLIC_CDN_URL.replace(/\/$/, ""); + const apiBase = GAME_API_URL.replace(/\/$/, ""); if (iconPath.startsWith("asset://")) { const relativePath = iconPath.replace("asset://", ""); - return `${base}/game-assets/${relativePath}`; + return `${assetBase}/${relativePath}`; + } + if (iconPath.startsWith("/game-assets/")) { + return `${assetBase}/${iconPath.replace(/^\/game-assets\//, "")}`; } - if (iconPath.startsWith("/")) return `${base}${iconPath}`; - return `${base}/${iconPath}`; + if (iconPath.startsWith("/")) return `${apiBase}${iconPath}`; + return `${assetBase}/${iconPath}`; } async function loadItemIconMap(): Promise> { @@ -65,7 +73,7 @@ async function loadItemIconMap(): Promise> { const responses = await Promise.all( ITEM_MANIFEST_FILES.map(async (fileName) => { const response = await fetch( - `${GAME_API_URL}/game-assets/manifests/items/${fileName}`, + `${PUBLIC_CDN_URL.replace(/\/$/, "")}/manifests/items/${fileName}`, { cache: "force-cache" }, ); if (!response.ok) return [] as ManifestItemRecord[]; diff --git a/packages/hyperbet-ui/src/components/ModelsMarketView.tsx b/packages/hyperbet-ui/src/components/ModelsMarketView.tsx index 2136e9d7..0768c2b6 100644 --- a/packages/hyperbet-ui/src/components/ModelsMarketView.tsx +++ b/packages/hyperbet-ui/src/components/ModelsMarketView.tsx @@ -14,7 +14,6 @@ import { Line, LineChart, ReferenceLine, - ResponsiveContainer, Tooltip, XAxis, YAxis, @@ -25,11 +24,14 @@ import goldPerpsIdl from "../idl/gold_perps_market.json"; import { useChain } from "../lib/ChainContext"; import { CONFIG, GAME_API_URL } from "../lib/config"; import { + buildPerpsMarketsEndpoint, + buildPerpsOracleHistoryEndpoint, modelMarketIdFromCharacterId, sanitizePerpsOracleHistoryResponse, sanitizePerpsMarketsResponse, toWinRatePercent, type PerpsMarketDirectoryEntry, + type PerpsChainKey, type PerpsOracleHistorySnapshot, type PerpsMarketsResponse, } from "../lib/modelMarkets"; @@ -45,6 +47,7 @@ import { resolveUiLocale, type UiLocale, } from "@hyperbet/ui/i18n"; +import { useMeasuredContentBox } from "../lib/useMeasuredContentBox"; const PROGRAM_ID = new PublicKey( CONFIG.goldPerpsMarketProgramId || goldPerpsIdl.address, @@ -60,6 +63,7 @@ const ORACLE_HISTORY_POLL_INTERVAL_MS = 15_000; const ORACLE_HISTORY_LIMIT = 120; const TOTAL_TRADE_FEE_RATE = 50 / 10_000; const IS_E2E_MODE = import.meta.env.MODE === "e2e"; +const SOLANA_PERPS_CHAIN_KEY: PerpsChainKey = "solana"; function readE2eString(value: unknown): string { return typeof value === "string" ? value.trim() : ""; @@ -74,28 +78,35 @@ function readE2eNumber(value: unknown, fallback: number): number { type TradeDirection = "LONG" | "SHORT"; -interface ModelsMarketViewProps { +export interface ModelsMarketViewProps { activeMatchup: string; connectionOverride?: Connection; walletOverride?: ModelsWalletLike; walletModalOverride?: ModelsWalletModalLike; } -type ModelsWalletLike = { +export type ModelsWalletLike = { connected: boolean; publicKey: PublicKey | null; - signAllTransactions?: >( - txs: T, - ) => Promise; - signTransaction?: ( - tx: T, - ) => Promise; + signAllTransactions?: ( + txs: Array, + ) => Promise>; + signTransaction?: ( + tx: Transaction | VersionedTransaction, + ) => Promise; }; -type ModelsWalletModalLike = { +export type ModelsWalletModalLike = { setVisible: (visible: boolean) => void; }; +type ModelsMarketViewRuntimeProps = { + activeMatchup: string; + connection: Connection; + wallet: ModelsWalletLike; + setWalletModalVisible: (visible: boolean) => void; +}; + interface ConfigAccountState { authority: PublicKey; keeperAuthority: PublicKey; @@ -544,6 +555,7 @@ const E2E_MODEL_MARKET_ID = E2E_MODEL_CHARACTER_ID const E2E_MODEL_ENTRY: PerpsMarketDirectoryEntry | null = IS_E2E_MODE && E2E_MODEL_CHARACTER_ID ? { + chainKey: "solana", rank: 1, characterId: E2E_MODEL_CHARACTER_ID, marketId: E2E_MODEL_MARKET_ID, @@ -663,15 +675,29 @@ export function ModelsMarketView({ walletOverride, walletModalOverride, }: ModelsMarketViewProps) { - const locale = resolveUiLocale(); - const copy = getModelsMarketCopy(locale); const adapterConnection = useConnection(); const adapterWallet = useWallet(); const adapterWalletModal = useWalletModal(); - const connection = connectionOverride ?? adapterConnection.connection; - const wallet = walletOverride ?? adapterWallet; - const setWalletModalVisible = - walletModalOverride?.setVisible ?? adapterWalletModal.setVisible; + return ( + + ); +} + +export function ModelsMarketViewRuntime({ + activeMatchup, + connection, + wallet, + setWalletModalVisible, +}: ModelsMarketViewRuntimeProps) { + const locale = resolveUiLocale(); + const copy = getModelsMarketCopy(locale); const { activeChain, setActiveChain } = useChain(); const [data, setData] = React.useState(null); @@ -720,6 +746,12 @@ export function ModelsMarketView({ const [oracleHistoryError, setOracleHistoryError] = React.useState< string | null >(null); + const oracleHistoryChartRef = React.useRef(null); + const oracleHistoryChartSize = useMeasuredContentBox( + oracleHistoryChartRef, + true, + 2, + ); const effectiveLeverage = Math.min( configuredMaxLeverage, Math.max(1, Math.round(leverage)), @@ -740,6 +772,7 @@ export function ModelsMarketView({ React.useEffect(() => { if (E2E_MODEL_ENTRY) { setData({ + chainKey: "solana", markets: [E2E_MODEL_ENTRY], updatedAt: Date.now(), }); @@ -756,16 +789,22 @@ export function ModelsMarketView({ inFlight = new AbortController(); try { - const response = await fetch(`${GAME_API_URL}/api/perps/markets`, { - cache: "no-store", - signal: inFlight.signal, - }); + const response = await fetch( + buildPerpsMarketsEndpoint(GAME_API_URL, SOLANA_PERPS_CHAIN_KEY), + { + cache: "no-store", + signal: inFlight.signal, + }, + ); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } - const payload = sanitizePerpsMarketsResponse(await response.json()); + const payload = sanitizePerpsMarketsResponse( + await response.json(), + SOLANA_PERPS_CHAIN_KEY, + ); if (!mounted) return; setData(payload); @@ -848,7 +887,12 @@ export function ModelsMarketView({ try { const response = await fetch( - `${GAME_API_URL}/api/perps/oracle-history?characterId=${encodeURIComponent(selectedCharacterId)}&limit=${ORACLE_HISTORY_LIMIT}`, + buildPerpsOracleHistoryEndpoint({ + gameApiUrl: GAME_API_URL, + chainKey: SOLANA_PERPS_CHAIN_KEY, + characterId: selectedCharacterId, + limit: ORACLE_HISTORY_LIMIT, + }), { cache: "no-store", signal: inFlight.signal, @@ -861,6 +905,7 @@ export function ModelsMarketView({ const payload = sanitizePerpsOracleHistoryResponse( await response.json(), selectedCharacterId, + SOLANA_PERPS_CHAIN_KEY, ); if (!mounted) return; @@ -1245,6 +1290,7 @@ export function ModelsMarketView({ const refreshChainState = React.useCallback(async () => { if (!data?.markets.length) return; const freshResponse = sanitizePerpsMarketsResponse({ + chainKey: data.chainKey ?? "solana", markets: data.markets, updatedAt: Date.now(), }); @@ -1756,7 +1802,10 @@ export function ModelsMarketView({
-
+
{oracleHistoryError ? (
{copy.oracleHistoryError(oracleHistoryError)} @@ -1769,81 +1818,82 @@ export function ModelsMarketView({
{copy.waitingForSnapshots}
- ) : ( - - - - - formatUsd(value, locale, 0) - } - /> - { - if (!active || !payload?.length) return null; - const point = payload[0] - ?.payload as OracleHistoryPoint; - return ( -
- {formatUsd(point.spotIndex, locale)} - - {copy.skill}{" "} - {formatLocaleNumber( - point.conservativeSkill, - locale, - { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }, - )}{" "} - · μ{" "} - {formatLocaleNumber(point.mu, locale, { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - })}{" "} - · σ{" "} - {formatLocaleNumber(point.sigma, locale, { + ) : oracleHistoryChartSize ? ( + + + + formatUsd(value, locale, 0) + } + /> + { + if (!active || !payload?.length) return null; + const point = payload[0]?.payload as OracleHistoryPoint; + return ( +
+ {formatUsd(point.spotIndex, locale)} + + {copy.skill}{" "} + {formatLocaleNumber( + point.conservativeSkill, + locale, + { minimumFractionDigits: 2, maximumFractionDigits: 2, - })} - -
- ); - }} - /> - {selectedMarket?.spotIndex && ( - - )} - +
+ ); + }} + /> + {selectedMarket?.spotIndex && ( + -
-
- )} + )} + + + ) : null}
diff --git a/packages/hyperbet-ui/src/components/PointsDisplay.tsx b/packages/hyperbet-ui/src/components/PointsDisplay.tsx index 26ed9e2f..4b92085c 100644 --- a/packages/hyperbet-ui/src/components/PointsDisplay.tsx +++ b/packages/hyperbet-ui/src/components/PointsDisplay.tsx @@ -10,6 +10,7 @@ interface PointsDisplayProps { walletAddress: string | null; compact?: boolean; locale?: UiLocale; + scope?: "wallet" | "linked"; } interface PointsData { @@ -215,6 +216,7 @@ export function PointsDisplay({ walletAddress, compact = false, locale, + scope = "linked", }: PointsDisplayProps) { const resolvedLocale = resolveUiLocale(locale); const copy = getPointsDisplayCopy(resolvedLocale); @@ -241,13 +243,13 @@ export function PointsDisplay({ const [pointsRes, rankRes, multiplierRes] = await Promise.all([ fetch( - `${GAME_API_URL}/api/arena/points/${walletAddress}?scope=linked`, + `${GAME_API_URL}/api/arena/points/${walletAddress}?scope=${scope}`, { cache: "no-store" }, ), - fetch(`${GAME_API_URL}/api/arena/points/rank/${walletAddress}`, { + fetch(`${GAME_API_URL}/api/arena/points/rank/${walletAddress}?scope=${scope}`, { cache: "no-store", }).catch(() => null), - fetch(`${GAME_API_URL}/api/arena/points/multiplier/${walletAddress}`, { + fetch(`${GAME_API_URL}/api/arena/points/multiplier/${walletAddress}?scope=${scope}`, { cache: "no-store", }).catch(() => null), ]); @@ -278,7 +280,7 @@ export function PointsDisplay({ } finally { setLoading(false); } - }, [resolvedLocale, walletAddress]); + }, [resolvedLocale, scope, walletAddress]); useEffect(() => { void fetchPoints(); diff --git a/packages/hyperbet-ui/src/components/PointsHistory.tsx b/packages/hyperbet-ui/src/components/PointsHistory.tsx index a1e3eb57..397b24a6 100644 --- a/packages/hyperbet-ui/src/components/PointsHistory.tsx +++ b/packages/hyperbet-ui/src/components/PointsHistory.tsx @@ -139,9 +139,11 @@ function truncateWallet(wallet: string): string { export function PointsHistory({ walletAddress, locale, + scope = "linked", }: { walletAddress: string | null; locale?: UiLocale; + scope?: "wallet" | "linked"; }) { const resolvedLocale = resolveUiLocale(locale); const copy = getHistoryCopy(resolvedLocale); @@ -169,6 +171,7 @@ export function PointsHistory({ const params = new URLSearchParams({ limit: String(pageSize), offset: String(page * pageSize), + scope, }); if (eventFilter) params.set("eventType", eventFilter); @@ -193,7 +196,7 @@ export function PointsHistory({ } finally { setLoading(false); } - }, [copy, eventFilter, page, walletAddress]); + }, [copy, eventFilter, page, scope, walletAddress]); useEffect(() => { void fetchHistory(); diff --git a/packages/hyperbet-ui/src/components/PointsLeaderboard.tsx b/packages/hyperbet-ui/src/components/PointsLeaderboard.tsx index 99baeed9..a71980fe 100644 --- a/packages/hyperbet-ui/src/components/PointsLeaderboard.tsx +++ b/packages/hyperbet-ui/src/components/PointsLeaderboard.tsx @@ -15,6 +15,11 @@ interface LeaderboardEntry { type TimeWindow = "alltime" | "daily" | "weekly" | "monthly"; type Scope = "linked" | "wallet"; +type PointsLeaderboardProps = { + defaultScope?: Scope; + locale?: UiLocale; +}; + function getLeaderboardCopy(locale: UiLocale) { if (locale === "zh") { return { @@ -70,7 +75,10 @@ function truncateWallet(wallet: string): string { return `${wallet.slice(0, 4)}...${wallet.slice(-4)}`; } -export function PointsLeaderboard({ locale }: { locale?: UiLocale } = {}) { +export function PointsLeaderboard({ + defaultScope = "linked", + locale, +}: PointsLeaderboardProps = {}) { const resolvedLocale = resolveUiLocale(locale); const copy = getLeaderboardCopy(resolvedLocale); const [leaderboard, setLeaderboard] = useState([]); @@ -78,7 +86,7 @@ export function PointsLeaderboard({ locale }: { locale?: UiLocale } = {}) { const [error, setError] = useState(null); const [page, setPage] = useState(0); const [timeWindow, setTimeWindow] = useState("alltime"); - const [scope, setScope] = useState("linked"); + const [scope, setScope] = useState(defaultScope); const pageSize = 20; const fetchLeaderboard = useCallback(async () => { @@ -119,6 +127,10 @@ export function PointsLeaderboard({ locale }: { locale?: UiLocale } = {}) { setPage(0); }, [timeWindow, scope]); + useEffect(() => { + setScope(defaultScope); + }, [defaultScope]); + const filterBtnStyle = (isActive: boolean): React.CSSProperties => ({ padding: "4px 10px", borderRadius: 6, diff --git a/packages/hyperbet-ui/src/components/PredictionMarketPanel.tsx b/packages/hyperbet-ui/src/components/PredictionMarketPanel.tsx index ce3e215c..ce670665 100644 --- a/packages/hyperbet-ui/src/components/PredictionMarketPanel.tsx +++ b/packages/hyperbet-ui/src/components/PredictionMarketPanel.tsx @@ -1,4 +1,4 @@ -import { useState, ReactNode } from "react"; +import { useRef, useState, ReactNode } from "react"; import { getUiCopy, resolveUiLocale, type UiLocale } from "@hyperbet/ui/i18n"; import { LineChart, @@ -6,9 +6,9 @@ import { XAxis, YAxis, Tooltip, - ResponsiveContainer, ReferenceLine, } from "recharts"; +import { useMeasuredContentBox } from "../lib/useMeasuredContentBox"; import { OrderBook, type OrderLevel } from "./OrderBook"; import { RecentTrades, type Trade } from "./RecentTrades"; @@ -88,6 +88,8 @@ export function PredictionMarketPanel({ const resolvedLocale = resolveUiLocale(locale); const copy = getUiCopy(resolvedLocale); const [activeTab, setActiveTab] = useState<"buy" | "sell">("buy"); + const chartContainerRef = useRef(null); + const chartSize = useMeasuredContentBox(chartContainerRef, true, 2); const yesSelected = side === "YES"; const noSelected = side === "NO"; @@ -118,6 +120,7 @@ export function PredictionMarketPanel({ const C_YES_BAR = compact ? "linear-gradient(90deg, var(--hm-buy-soft, rgba(34,197,94,0.2)), var(--hm-buy), var(--hm-buy-soft, rgba(34,197,94,0.2)))" : "linear-gradient(90deg, var(--hm-buy-soft, rgba(34,197,94,0.2)), var(--hm-buy), var(--hm-buy-soft, rgba(34,197,94,0.2)))"; + const C_YES_BAR_SHADOW = compact ? "0 0 8px var(--hm-buy-glow-strong, rgba(34,197,94,0.5))" : "0 0 8px var(--hm-buy-glow-strong, rgba(34,197,94,0.5))"; @@ -893,6 +896,7 @@ export function PredictionMarketPanel({ />
- - + {chartSize ? ( + - + ) : null}
diff --git a/packages/hyperbet-ui/src/components/ReferralPanel.tsx b/packages/hyperbet-ui/src/components/ReferralPanel.tsx index e5649846..73fc6fe1 100644 --- a/packages/hyperbet-ui/src/components/ReferralPanel.tsx +++ b/packages/hyperbet-ui/src/components/ReferralPanel.tsx @@ -193,14 +193,13 @@ export function ReferralPanel(props: { ) { return evmWallet; } - return solanaWallet ?? evmWallet ?? null; + return null; }, [activeChain, solanaWallet, evmWallet]); const platformQuery = useMemo(() => { - if (primaryWallet && primaryWallet === solanaWallet) return "solana"; - if (primaryWallet && primaryWallet === evmWallet) return "evm"; return activeChain === "solana" ? "solana" : "evm"; - }, [activeChain, evmWallet, primaryWallet, solanaWallet]); + }, [activeChain]); + const pointsScope = activeChain === "solana" ? "wallet" : "linked"; const [points, setPoints] = useState(null); const [invite, setInvite] = useState(null); const [loadingStats, setLoadingStats] = useState(false); @@ -223,7 +222,7 @@ export function ReferralPanel(props: { setStatsError(""); const [pointsRes, inviteRes] = await Promise.all([ fetch( - `${GAME_API_URL}/api/arena/points/${primaryWallet}?scope=linked`, + `${GAME_API_URL}/api/arena/points/${primaryWallet}?scope=${pointsScope}`, { cache: "no-store" }, ), fetch( @@ -256,7 +255,7 @@ export function ReferralPanel(props: { } finally { setLoadingStats(false); } - }, [copy, platformQuery, primaryWallet]); + }, [copy, platformQuery, pointsScope, primaryWallet]); useEffect(() => { void refreshStats(); diff --git a/packages/hyperbet-ui/src/components/SolanaClobPanel.tsx b/packages/hyperbet-ui/src/components/SolanaClobPanel.tsx index 90629f6d..38c198e4 100644 --- a/packages/hyperbet-ui/src/components/SolanaClobPanel.tsx +++ b/packages/hyperbet-ui/src/components/SolanaClobPanel.tsx @@ -49,9 +49,11 @@ import { } from "../lib/solanaRpc"; import { CONFIG } from "../lib/config"; import { + type PredictionMarketsDuelSnapshot, normalizePredictionMarketDuelKeyHex, usePredictionMarketLifecycle, } from "../lib/predictionMarkets"; +import type { PredictionMarketLifecycleRecord } from "@hyperbet/chain-registry"; import { derivePredictionMarketUiState, EMPTY_PREDICTION_MARKET_WALLET_SNAPSHOT, @@ -59,6 +61,7 @@ import { } from "../lib/predictionMarketUiState"; import { derivePredictionMarketClaimUi } from "../lib/predictionMarketClaimUi"; import { recordPredictionMarketTrade } from "../lib/predictionMarketTracking"; +import type { TradeGate } from "../lib/viewerAlignment"; import { useStreamingState } from "../spectator/useStreamingState"; import { PredictionMarketPanel, @@ -135,6 +138,12 @@ const SIDE_BID = 1; const SIDE_ASK = 2; const ORDER_BEHAVIOR_GTC = 0; const MAX_MATCH_ACCOUNTS = 100; +const PRICE_LEVEL_MARKET_STATE_OFFSET = 8; +const ORDER_MARKET_STATE_OFFSET = 8; +const ORDER_MAKER_OFFSET = 52; +const USER_BALANCE_MARKET_STATE_OFFSET = 40; +const SOLANA_REFRESH_INTERVAL_MS = 30_000; +const SOLANA_RATE_LIMIT_COOLDOWN_MS = 60_000; function walletReady(wallet: SigningWalletLike): boolean { return Boolean( @@ -199,9 +208,12 @@ function sleep(ms: number): Promise { function isRetryableRefreshError(error: unknown): boolean { const message = error instanceof Error ? error.message : String(error); - return /failed to fetch|fetch failed|networkerror|load failed|429/i.test( - message, - ); + return /failed to fetch|fetch failed|networkerror|load failed/i.test(message); +} + +function isRateLimitRefreshError(error: unknown): boolean { + const message = error instanceof Error ? error.message : String(error); + return /429|rate limit|too many requests/i.test(message); } function getFallbackLifecycleStatus(status: string | null | undefined) { @@ -295,6 +307,24 @@ function parsePublicKeyOrNull( } } +function describeSolanaViewerAlignmentTradeGate(params: { + tradeGate: TradeGate | null | undefined; + syncingCopy: string; + verifyingCopy: string; +}): string | null { + switch (params.tradeGate?.reason) { + case "clock-confidence-low": + case "clock-frozen": + return params.syncingCopy; + case "market-stale": + case "market-missing": + case "session-missing": + return params.verifyingCopy; + default: + return null; + } +} + function readSolanaE2eRuntimeOverride(): { duelKey: string | null; duelId: string | null; @@ -341,6 +371,18 @@ interface SolanaClobPanelProps { locale?: UiLocale; connectionOverride: Connection; walletOverride: SigningWalletLike; + /** + * Optional lifecycle overrides. When the app shell is running the + * viewer-aligned-bet-state path (`VITE_ENABLE_VIEWER_ALIGNED_BET_STATE`), + * the App passes in the selected aligned duel/market records so the + * panel's display-gating reflects the viewer's video frame rather + * than the freshest-by-wall-clock keeper response. The overrides are + * always optional — when omitted, the panel's internal + * `usePredictionMarketLifecycle` hook wins. + */ + lifecycleDuelOverride?: PredictionMarketsDuelSnapshot | null; + lifecycleMarketOverride?: PredictionMarketLifecycleRecord | null; + viewerAlignmentTradeGate?: TradeGate | null; } export interface SolanaClobMarketSnapshot { @@ -362,6 +404,9 @@ export function SolanaClobPanel({ locale, connectionOverride, walletOverride, + lifecycleDuelOverride = null, + lifecycleMarketOverride = null, + viewerAlignmentTradeGate = null, }: SolanaClobPanelProps) { const resolvedLocale = resolveUiLocale(locale); const isE2eMode = import.meta.env.MODE === "e2e" || import.meta.env.DEV; @@ -407,7 +452,9 @@ export function SolanaClobPanel({ yes: 0n, no: 0n, }); + const marketConfigCheckedRef = useRef(false); const refreshPromiseRef = useRef | null>(null); + const refreshCooldownUntilRef = useRef(0); const useHeliusSender = CONFIG.cluster === "mainnet-beta"; @@ -430,8 +477,20 @@ export function SolanaClobPanel({ const streamedDuelKeyHex = typeof cycle?.duelKeyHex === "string" ? cycle.duelKeyHex : null; const streamedDuelId = typeof cycle?.duelId === "string" ? cycle.duelId : null; - const { duel: lifecycleDuel, market: lifecycleMarket } = - usePredictionMarketLifecycle("solana"); + const { + duel: fetchedLifecycleDuel, + market: fetchedLifecycleMarket, + } = usePredictionMarketLifecycle("solana", { + pollIntervalMs: SOLANA_REFRESH_INTERVAL_MS, + }); + // App shell supplies aligned overrides when + // `VITE_ENABLE_VIEWER_ALIGNED_BET_STATE` is on — the override wins so + // that the panel's display-gating copy reflects the viewer's video + // frame rather than the freshest keeper response. When the flag is + // off, both overrides are null and the panel falls through to the + // direct hook values exactly as before (preserves current behavior). + const lifecycleDuel = lifecycleDuelOverride ?? fetchedLifecycleDuel; + const lifecycleMarket = lifecycleMarketOverride ?? fetchedLifecycleMarket; const runtimeE2eOverride = useMemo( () => isE2eMode @@ -491,6 +550,8 @@ export function SolanaClobPanel({ resolutionChallenged: "结果已被挑战,结算已暂停", marketOpen: "市场开放中", refreshFailed: (message: string) => `刷新失败:${message}`, + streamSyncing: "正在同步直播画面", + streamVerifying: "正在校验市场状态", connectWalletToTrade: "连接钱包后即可交易", amountTooLow: "数量必须大于 0", orderPlaced: "订单已提交", @@ -538,6 +599,8 @@ export function SolanaClobPanel({ resolutionChallenged: "Result challenged; settlement paused", marketOpen: "Market open", refreshFailed: (message: string) => `Refresh failed: ${message}`, + streamSyncing: "Syncing stream...", + streamVerifying: "Verifying market state...", connectWalletToTrade: "Connect wallet to trade", amountTooLow: "Amount must be greater than zero", orderPlaced: "Order placed", @@ -595,6 +658,21 @@ export function SolanaClobPanel({ ), [activeLifecycleMarket, activeMarket, walletSnapshot], ); + const viewerAlignmentStatusLabel = useMemo( + () => + describeSolanaViewerAlignmentTradeGate({ + tradeGate: viewerAlignmentTradeGate, + syncingCopy: copy.streamSyncing, + verifyingCopy: copy.streamVerifying, + }), + [copy.streamSyncing, copy.streamVerifying, viewerAlignmentTradeGate], + ); + const viewerAlignmentBlocksDisplay = + viewerAlignmentTradeGate?.canDisplayOpen === false && + uiState.lifecycleStatus !== "RESOLVED" && + uiState.lifecycleStatus !== "CANCELLED"; + const viewerAlignmentBlocksSubmit = + viewerAlignmentTradeGate?.canSubmitTrade === false; const lifecycleStatusLabel = useMemo(() => { switch (uiState.lifecycleStatus) { case "RESOLVED": @@ -624,6 +702,8 @@ export function SolanaClobPanel({ uiState.lifecycleStatus, uiState.winner, ]); + const liveStatusOverride = + viewerAlignmentBlocksDisplay ? viewerAlignmentStatusLabel : null; const updateChartAndTrades = useCallback( (nextYes: bigint, nextNo: bigint) => { @@ -786,12 +866,15 @@ export function SolanaClobPanel({ const oracleProgram: any = readonlyPrograms.fightOracle; const runtimeConfigPda = findClobConfigPda(clobProgram.programId); - const config = - await clobProgram.account.marketConfig.fetchNullable(runtimeConfigPda); - if (!config) { - setStatus(copy.marketConfigNotDeployed); - setActiveMarket(null); - return; + if (!marketConfigCheckedRef.current) { + const config = + await clobProgram.account.marketConfig.fetchNullable(runtimeConfigPda); + if (!config) { + setStatus(copy.marketConfigNotDeployed); + setActiveMarket(null); + return; + } + marketConfigCheckedRef.current = true; } if (!duelKeyHex) { @@ -807,7 +890,8 @@ export function SolanaClobPanel({ bLockedLamports: 0n, }); setStatus( - lifecycleStatusLabel ?? + liveStatusOverride ?? + lifecycleStatusLabel ?? getCycleDuelStatusLabel(cycle?.phase, duelKeyHex, resolvedLocale), ); return; @@ -824,23 +908,55 @@ export function SolanaClobPanel({ ); const vault = findClobVaultPda(clobProgram.programId, marketState); - const [duelAccount, marketAccount, allLevels, allOrders, allBalances] = + const marketStateBase58 = marketState.toBase58(); + const walletPublicKeyBase58 = wallet.publicKey?.toBase58() ?? null; + + const [duelAccount, marketAccount, levels, userOrders, balances] = await Promise.all([ oracleProgram.account.duelState.fetchNullable(duelState), clobProgram.account.marketState.fetchNullable(marketState), - clobProgram.account.priceLevel.all(), - clobProgram.account.order.all(), - clobProgram.account.userBalance.all(), + clobProgram.account.priceLevel.all([ + { + memcmp: { + offset: PRICE_LEVEL_MARKET_STATE_OFFSET, + bytes: marketStateBase58, + }, + }, + ]), + walletPublicKeyBase58 + ? clobProgram.account.order.all([ + { + memcmp: { + offset: ORDER_MARKET_STATE_OFFSET, + bytes: marketStateBase58, + }, + }, + { + memcmp: { + offset: ORDER_MAKER_OFFSET, + bytes: walletPublicKeyBase58, + }, + }, + ]) + : Promise.resolve([]), + clobProgram.account.userBalance.all([ + { + memcmp: { + offset: USER_BALANCE_MARKET_STATE_OFFSET, + bytes: marketStateBase58, + }, + }, + ]), ]); if (!duelAccount) { - setStatus(lifecycleStatusLabel ?? copy.waitingOracleReporter); + setStatus(liveStatusOverride ?? lifecycleStatusLabel ?? copy.waitingOracleReporter); setActiveMarket(null); return; } if (!marketAccount) { - setStatus(lifecycleStatusLabel ?? copy.waitingMarketOperator); + setStatus(liveStatusOverride ?? lifecycleStatusLabel ?? copy.waitingMarketOperator); setActiveMarket(null); return; } @@ -848,17 +964,11 @@ export function SolanaClobPanel({ const marketStatus = enumName(marketAccount.status); const winner = enumName(marketAccount.winner); - const levels = (allLevels as PriceLevelAccount[]).filter((entry) => - (entry.account.marketState as PublicKey).equals(marketState), - ); - const orders = (allOrders as OrderAccount[]).filter((entry) => - (entry.account.marketState as PublicKey).equals(marketState), - ); - const balances = (allBalances as BalanceAccount[]).filter((entry) => - (entry.account.marketState as PublicKey).equals(marketState), - ); + const marketLevels = levels as PriceLevelAccount[]; + const marketBalances = balances as BalanceAccount[]; + const walletOrders = userOrders as OrderAccount[]; - const bidRows = levels + const bidRows = marketLevels .filter( (entry) => Number(entry.account.side) === SIDE_BID && @@ -871,7 +981,7 @@ export function SolanaClobPanel({ total: 0, })); - const askRows = levels + const askRows = marketLevels .filter( (entry) => Number(entry.account.side) === SIDE_ASK && @@ -903,7 +1013,7 @@ export function SolanaClobPanel({ aLockedLamports: 0n, bLockedLamports: 0n, }; - for (const balance of balances) { + for (const balance of marketBalances) { const aShares = asBigInt(balance.account.aShares); const bShares = asBigInt(balance.account.bShares); const aLockedLamports = asBigInt(balance.account.aLockedLamports); @@ -918,7 +1028,7 @@ export function SolanaClobPanel({ } } - const userOpenOrders = orders + const userOpenOrders = walletOrders .filter( (entry) => wallet.publicKey && @@ -988,7 +1098,7 @@ export function SolanaClobPanel({ resolvedLocale, marketStatus, ); - setStatus(nextStatusLabel); + setStatus(liveStatusOverride ?? nextStatusLabel); }, [ cycle?.betCloseTime, cycle?.phase, @@ -999,6 +1109,7 @@ export function SolanaClobPanel({ effectiveAgent2, activeLifecycleDuel?.betCloseTime, activeLifecycleDuel?.phase, + liveStatusOverride, lifecycleMarketRef, lifecycleStatusLabel, readonlyPrograms.fightOracle, @@ -1009,6 +1120,9 @@ export function SolanaClobPanel({ ]); const refreshData = useCallback(async () => { + if (Date.now() < refreshCooldownUntilRef.current) { + return Promise.resolve(); + } if (refreshPromiseRef.current) { return refreshPromiseRef.current; } @@ -1019,6 +1133,7 @@ export function SolanaClobPanel({ for (let attempt = 0; attempt < 3; attempt += 1) { try { await runRefreshData(); + refreshCooldownUntilRef.current = 0; return; } catch (error) { if (!isRetryableRefreshError(error) || attempt === 2) { @@ -1028,6 +1143,10 @@ export function SolanaClobPanel({ } } } catch (error) { + if (isRateLimitRefreshError(error)) { + refreshCooldownUntilRef.current = + Date.now() + SOLANA_RATE_LIMIT_COOLDOWN_MS; + } setStatus(copy.refreshFailed((error as Error).message)); } finally { setIsRefreshing(false); @@ -1041,7 +1160,10 @@ export function SolanaClobPanel({ useEffect(() => { void refreshData(); - const id = window.setInterval(() => void refreshData(), 5000); + const id = window.setInterval( + () => void refreshData(), + SOLANA_REFRESH_INTERVAL_MS, + ); return () => window.clearInterval(id); }, [refreshData]); @@ -1184,6 +1306,12 @@ export function SolanaClobPanel({ const handlePlaceOrder = useCallback(async () => { + if (viewerAlignmentBlocksSubmit) { + setLastPlaceOrderDebug("blocked viewer-alignment trade gate"); + setLastPlaceOrderError(viewerAlignmentStatusLabel ?? copy.streamVerifying); + setStatus(viewerAlignmentStatusLabel ?? copy.streamVerifying); + return; + } const clobProgram: any = writablePrograms?.goldClobMarket; const marketRef = activeMarket?.marketState.toBase58() ?? "-"; const duelRef = duelId ?? "-"; @@ -1331,6 +1459,8 @@ export function SolanaClobPanel({ refreshData, side, submitTransaction, + viewerAlignmentBlocksSubmit, + viewerAlignmentStatusLabel, wallet.publicKey, writablePrograms, ]); @@ -1437,6 +1567,24 @@ export function SolanaClobPanel({ `claimableAmount=${uiState.claimableAmount.toString()}`, `canClaim=${uiState.canClaim ? "true" : "false"}`, ].join("\n"); + const solanaMarketEvidence = { + duelKey: + activeLifecycleMarket?.duelKey ?? activeLifecycleDuel?.duelKey ?? duelKeyHex ?? null, + duelId: + activeLifecycleMarket?.duelId ?? activeLifecycleDuel?.duelId ?? duelId ?? null, + marketRef: lifecycleMarketRef ?? activeMarket?.marketState.toBase58() ?? null, + lifecycleStatus: uiState.lifecycleStatus, + winner: uiState.winner, + marketStatus: activeMarket?.marketStatus ?? null, + marketWinner: activeMarket?.winner ?? null, + bestBid: activeMarket?.bestBid ?? null, + bestAsk: activeMarket?.bestAsk ?? null, + bidLevels: bids.length, + askLevels: asks.length, + yesPool: yesPool.toString(), + noPool: noPool.toString(), + status, + }; const walletDebugText = [ `wallet=${walletAddress ?? "-"}`, `aShares=${position.aShares.toString()}`, @@ -1454,6 +1602,24 @@ export function SolanaClobPanel({ `${copy.adminLastOrder} ${lastOrderId?.toString() ?? "-"}`, ].join("\n"); + useEffect(() => { + if (typeof window === "undefined") { + return; + } + ( + window as typeof window & { + __HYPERBET_SOLANA_MARKET_EVIDENCE__?: Record | null; + } + ).__HYPERBET_SOLANA_MARKET_EVIDENCE__ = solanaMarketEvidence; + return () => { + ( + window as typeof window & { + __HYPERBET_SOLANA_MARKET_EVIDENCE__?: Record | null; + } + ).__HYPERBET_SOLANA_MARKET_EVIDENCE__ = null; + }; + }, [solanaMarketEvidence]); + return (
void handlePlaceOrder()} isWalletReady={walletReady(wallet)} - programsReady={Boolean(activeMarket) && uiState.canTrade} + programsReady={ + Boolean(activeMarket) && + uiState.canTrade && + !viewerAlignmentBlocksSubmit + } agent1Name={effectiveAgent1} agent2Name={effectiveAgent2} isEvm={false} @@ -1483,6 +1653,7 @@ export function SolanaClobPanel({ walletAddress={walletAddress} compact={compact} locale={resolvedLocale} + scope="wallet" /> } locale={resolvedLocale} diff --git a/packages/hyperbet-ui/src/components/SolanaModelsMarketView.tsx b/packages/hyperbet-ui/src/components/SolanaModelsMarketView.tsx new file mode 100644 index 00000000..1dc7e2fd --- /dev/null +++ b/packages/hyperbet-ui/src/components/SolanaModelsMarketView.tsx @@ -0,0 +1,24 @@ +import { useAppConnection, useAppWallet, useAppWalletModal } from "../lib/solanaRuntime"; +import { + ModelsMarketViewRuntime, + type ModelsMarketViewProps, +} from "./ModelsMarketView"; + +type SolanaModelsMarketViewProps = Pick; + +export function SolanaModelsMarketView({ + activeMatchup, +}: SolanaModelsMarketViewProps) { + const { connection } = useAppConnection(); + const wallet = useAppWallet(); + const walletModal = useAppWalletModal(); + + return ( + + ); +} diff --git a/packages/hyperbet-ui/src/components/StreamPlayer.tsx b/packages/hyperbet-ui/src/components/StreamPlayer.tsx index 0d202acc..a31e8f64 100644 --- a/packages/hyperbet-ui/src/components/StreamPlayer.tsx +++ b/packages/hyperbet-ui/src/components/StreamPlayer.tsx @@ -1,5 +1,13 @@ -import React, { useCallback, useEffect, useMemo, useRef } from "react"; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import Hls from "hls.js"; +import { describeCanonicalRendererDegradedReason } from "../lib/streamSession"; +import type { ViewerBootPhase } from "../player/viewerBootPhases"; interface StreamPlayerProps { streamUrl: string; @@ -8,10 +16,297 @@ interface StreamPlayerProps { muted?: boolean; className?: string; style?: React.CSSProperties; + deliveryMode?: string | null; + presentationDelayMs?: number | null; + syncToleranceMs?: number; + showDiagnostics?: boolean; onStreamUnavailable?: () => void; onStreamReady?: () => void; + onStatusChange?: (status: StreamPlayerStatus) => void; } +export type ViewerSyncState = + | "starting" + | "aligned" + | "buffering" + | "out_of_sync" + | "error"; + +export type StreamPlayerStatus = { + ready: boolean; + status: string | null; + liveEdgeLatencyMs: number | null; + stallCount: number; + rebuildCount: number; + lastBufferedFragmentAt: number | null; + lastPlaybackProgressAt: number | null; + playbackUrl: string | null; + deliveryMode: string | null; + firstFrameAt: number | null; + startupDurationMs: number | null; + playbackStarted: boolean; + presentationDelayMs: number | null; + syncDeltaMs: number | null; + syncState: ViewerSyncState; + bootPhase: ViewerBootPhase; + loaderVisible: boolean; +}; + +type EmbedStatusPayload = { + type?: string; + ready?: boolean; + status?: string | null; + liveEdgeLatencyMs?: number | null; + stallCount?: number | null; + rebuildCount?: number | null; + lastBufferedFragmentAt?: number | null; + lastPlaybackProgressAt?: number | null; + playbackUrl?: string | null; + deliveryMode?: string | null; + firstFrameAt?: number | null; + startupDurationMs?: number | null; + playbackStarted?: boolean | null; + presentationDelayMs?: number | null; + syncDeltaMs?: number | null; + syncState?: ViewerSyncState | null; + bootPhase?: ViewerBootPhase | null; + loaderVisible?: boolean | null; + rendererHealth?: { + ready?: boolean; + degradedReason?: string | null; + } | null; +}; + +type HlsPlaybackProfile = { + config: Record; + driftThresholdMs: number; + syncDriftThresholdMs: number; + waitingGraceMs: number; + reloadOnBufferStall: boolean; + rebuildOnVideoError: boolean; + minVideoErrorTailMs: number; + startupGraceMs: number; +}; + +const LOW_LATENCY_HLS_CONFIG = { + enableWorker: true, + lowLatencyMode: true, + capLevelToPlayerSize: true, + testBandwidth: false, + liveSyncDurationCount: 3, + liveMaxLatencyDurationCount: 6, + liveBackBufferLength: 10, + maxBufferLength: 12, + maxMaxBufferLength: 12, + maxLiveSyncPlaybackRate: 1.5, + startFragPrefetch: true, + manifestLoadingMaxRetry: 6, + manifestLoadingRetryDelay: 800, + levelLoadingMaxRetry: 6, + levelLoadingRetryDelay: 800, + fragLoadingMaxRetry: 6, + fragLoadingRetryDelay: 800, +} as const; + +export const LIVE_EDGE_HLS_CONFIG = LOW_LATENCY_HLS_CONFIG; +export const RECENT_PLAYER_SIGNAL_WINDOW_MS = 30_000; +export const RECENT_PLAYER_SIGNAL_THRESHOLD = 3; +export const DEFAULT_SYNC_TOLERANCE_MS = 2_500; + +type HlsVariantDescriptor = { + index: number; + width: number; + height: number; + bitrate: number; +}; + +export function resolvePlaybackTargetSize(video: HTMLVideoElement | null) { + const devicePixelRatio = + typeof window !== "undefined" && Number.isFinite(window.devicePixelRatio) + ? Math.max(1, window.devicePixelRatio) + : 1; + const viewportWidth = + typeof window !== "undefined" && Number.isFinite(window.innerWidth) + ? Math.max(0, window.innerWidth) + : 0; + const viewportHeight = + typeof window !== "undefined" && Number.isFinite(window.innerHeight) + ? Math.max(0, window.innerHeight) + : 0; + return { + targetWidth: + Math.max(viewportWidth, video?.clientWidth ?? 0, video?.videoWidth ?? 0, 1) * + devicePixelRatio, + targetHeight: + Math.max( + viewportHeight, + video?.clientHeight ?? 0, + video?.videoHeight ?? 0, + 1, + ) * + devicePixelRatio, + }; +} + +function selectPreferredHlsVariantIndex( + variants: HlsVariantDescriptor[], + targetWidth: number, + targetHeight: number, +): number { + const sizeBudgetWidth = targetWidth * 1.25; + const sizeBudgetHeight = targetHeight * 1.25; + const rankedLevels = variants + .map((variant) => { + const hasWidth = variant.width > 0; + const hasHeight = variant.height > 0; + const fitsWidth = !hasWidth || variant.width <= sizeBudgetWidth; + const fitsHeight = !hasHeight || variant.height <= sizeBudgetHeight; + const fitsPlayerSize = (hasWidth || hasHeight) && fitsWidth && fitsHeight; + return { + ...variant, + fitsPlayerSize, + pixelCount: variant.width * variant.height, + }; + }) + .sort((left, right) => { + if (left.fitsPlayerSize !== right.fitsPlayerSize) { + return left.fitsPlayerSize ? -1 : 1; + } + if (left.pixelCount !== right.pixelCount) { + return right.pixelCount - left.pixelCount; + } + if (left.bitrate !== right.bitrate) { + return right.bitrate - left.bitrate; + } + return left.index - right.index; + }); + return rankedLevels[0]?.index ?? 0; +} + +export function selectPreferredHlsStartLevelFromManifest( + manifestText: string, + targetWidth: number, + targetHeight: number, +): number | null { + const variants: HlsVariantDescriptor[] = []; + let pendingStreamInf: string | null = null; + + for (const rawLine of manifestText.split(/\r?\n/)) { + const line = rawLine.trim(); + if (!line) { + continue; + } + if (line.startsWith("#EXT-X-STREAM-INF:")) { + pendingStreamInf = line.slice("#EXT-X-STREAM-INF:".length); + continue; + } + if (pendingStreamInf == null || line.startsWith("#")) { + continue; + } + + const resolutionMatch = pendingStreamInf.match(/RESOLUTION=(\d+)x(\d+)/i); + const bandwidthMatch = pendingStreamInf.match(/BANDWIDTH=(\d+)/i); + variants.push({ + index: variants.length, + width: resolutionMatch ? Number.parseInt(resolutionMatch[1] ?? "0", 10) : 0, + height: resolutionMatch + ? Number.parseInt(resolutionMatch[2] ?? "0", 10) + : 0, + bitrate: bandwidthMatch ? Number.parseInt(bandwidthMatch[1] ?? "0", 10) : 0, + }); + pendingStreamInf = null; + } + + if (variants.length === 0) { + return null; + } + + const preferredManifestIndex = selectPreferredHlsVariantIndex( + variants, + targetWidth, + targetHeight, + ); + const sortedVariants = [...variants].sort((left, right) => { + if (left.width !== right.width) { + return left.width - right.width; + } + if (left.height !== right.height) { + return left.height - right.height; + } + if (left.bitrate !== right.bitrate) { + return left.bitrate - right.bitrate; + } + return left.index - right.index; + }); + const preferredSortedIndex = sortedVariants.findIndex( + (variant) => variant.index === preferredManifestIndex, + ); + return preferredSortedIndex >= 0 ? preferredSortedIndex : null; +} + +export function preferHighestViableHlsLevel( + hls: Hls, + video: HTMLVideoElement | null, +): void { + const levels = hls.levels; + if (!Array.isArray(levels) || levels.length === 0) { + return; + } + + hls.capLevelToPlayerSize = true; + + const { targetWidth, targetHeight } = resolvePlaybackTargetSize(video); + const preferredLevel = selectPreferredHlsVariantIndex( + levels.map((level, index) => ({ + index, + width: level?.width ?? 0, + height: level?.height ?? 0, + bitrate: level?.bitrate ?? 0, + })), + targetWidth, + targetHeight, + ); + + hls.autoLevelCapping = preferredLevel; + hls.nextLoadLevel = preferredLevel; + hls.nextAutoLevel = preferredLevel; + hls.startLevel = preferredLevel; + hls.nextLevel = preferredLevel; + hls.loadLevel = preferredLevel; + if ("manualLevel" in hls) { + try { + (hls as Hls & { manualLevel: number }).manualLevel = preferredLevel; + } catch { + // Ignore manualLevel assignment failures on older hls.js shims. + } + } + try { + hls.currentLevel = preferredLevel; + } catch { + // Ignore early currentLevel assignment failures before the media controller is ready. + } +} + +const STABLE_LIVE_HLS_CONFIG = { + enableWorker: true, + lowLatencyMode: false, + capLevelToPlayerSize: true, + testBandwidth: false, + liveSyncDurationCount: 10, + liveMaxLatencyDurationCount: 16, + liveBackBufferLength: 45, + maxBufferLength: 45, + maxMaxBufferLength: 90, + maxLiveSyncPlaybackRate: 1.1, + startFragPrefetch: false, + manifestLoadingMaxRetry: 6, + manifestLoadingRetryDelay: 800, + levelLoadingMaxRetry: 6, + levelLoadingRetryDelay: 800, + fragLoadingMaxRetry: 6, + fragLoadingRetryDelay: 800, +} as const; + export const StreamPlayer: React.FC = ({ streamUrl, poster, @@ -19,31 +314,329 @@ export const StreamPlayer: React.FC = ({ muted = true, className, style, + deliveryMode = null, + presentationDelayMs = null, + syncToleranceMs = DEFAULT_SYNC_TOLERANCE_MS, + showDiagnostics = false, onStreamUnavailable, onStreamReady, + onStatusChange, }) => { const videoRef = useRef(null); + const syncTrackerRef = useRef<{ + consecutiveOutOfSyncPolls: number; + consecutiveAlignedPolls: number; + syncState: ViewerSyncState; + }>({ + consecutiveOutOfSyncPolls: 0, + consecutiveAlignedPolls: 0, + syncState: "starting", + }); const embedUrl = useMemo( - () => resolveEmbedUrl(streamUrl, autoPlay, muted), - [autoPlay, muted, streamUrl], + () => + resolveEmbedUrl(streamUrl, autoPlay, muted, { + deliveryMode, + presentationDelayMs, + showDiagnostics, + syncToleranceMs, + }), + [ + autoPlay, + deliveryMode, + muted, + presentationDelayMs, + showDiagnostics, + streamUrl, + syncToleranceMs, + ], ); + const embedKind = useMemo(() => classifyEmbedKind(embedUrl), [embedUrl]); const unavailableNotifiedRef = useRef(false); + const readyNotifiedRef = useRef(false); + const [embedFailure, setEmbedFailure] = useState(null); + const [diagnosticMessage, setDiagnosticMessage] = useState(null); + const [playerStatus, setPlayerStatus] = useState(() => + createInitialPlayerStatus({ + streamUrl, + deliveryMode, + presentationDelayMs, + }), + ); - const markUnavailable = useCallback(() => { - if (unavailableNotifiedRef.current) return; - unavailableNotifiedRef.current = true; - onStreamUnavailable?.(); - }, [onStreamUnavailable]); + const markUnavailable = useCallback( + ( + reason = "Live stream unavailable.", + status: string = "error:unavailable", + ) => { + setDiagnosticMessage(reason); + setEmbedFailure((current) => current ?? reason); + setPlayerStatus((current) => ({ + ...current, + ready: false, + status, + loaderVisible: true, + bootPhase: "error", + })); + if (unavailableNotifiedRef.current) return; + unavailableNotifiedRef.current = true; + onStreamUnavailable?.(); + }, + [onStreamUnavailable], + ); + + const markReady = useCallback(() => { + setEmbedFailure(null); + setDiagnosticMessage(null); + setPlayerStatus((current) => ({ + ...current, + ready: true, + status: "playing", + bootPhase: "finalizing", + loaderVisible: false, + })); + if (readyNotifiedRef.current) return; + readyNotifiedRef.current = true; + onStreamReady?.(); + }, [onStreamReady]); + + const markDegraded = useCallback((reason: string | null, status?: string | null) => { + setDiagnosticMessage(reason); + if (status) { + setPlayerStatus((current) => ({ + ...current, + ready: false, + status, + loaderVisible: true, + })); + } + }, []); useEffect(() => { unavailableNotifiedRef.current = false; - }, [streamUrl]); + readyNotifiedRef.current = false; + syncTrackerRef.current = { + consecutiveOutOfSyncPolls: 0, + consecutiveAlignedPolls: 0, + syncState: "starting", + }; + setEmbedFailure(null); + setDiagnosticMessage(null); + setPlayerStatus( + createInitialPlayerStatus({ + streamUrl, + deliveryMode, + presentationDelayMs, + }), + ); + }, [deliveryMode, presentationDelayMs, streamUrl]); + + useEffect(() => { + const nextSync = advanceViewerSyncState({ + previousState: syncTrackerRef.current.syncState, + consecutiveAlignedPolls: syncTrackerRef.current.consecutiveAlignedPolls, + consecutiveOutOfSyncPolls: syncTrackerRef.current.consecutiveOutOfSyncPolls, + liveEdgeLatencyMs: playerStatus.liveEdgeLatencyMs, + playbackStarted: playerStatus.playbackStarted, + presentationDelayMs: playerStatus.presentationDelayMs, + ready: playerStatus.ready, + status: playerStatus.status, + syncToleranceMs, + }); + syncTrackerRef.current = { + consecutiveOutOfSyncPolls: nextSync.consecutiveOutOfSyncPolls, + consecutiveAlignedPolls: nextSync.consecutiveAlignedPolls, + syncState: nextSync.syncState, + }; + if ( + playerStatus.syncDeltaMs !== nextSync.syncDeltaMs || + playerStatus.syncState !== nextSync.syncState + ) { + setPlayerStatus((current) => ({ + ...current, + syncDeltaMs: nextSync.syncDeltaMs, + syncState: nextSync.syncState, + })); + } + }, [ + playerStatus.liveEdgeLatencyMs, + playerStatus.playbackStarted, + playerStatus.presentationDelayMs, + playerStatus.ready, + playerStatus.status, + playerStatus.syncDeltaMs, + playerStatus.syncState, + syncToleranceMs, + ]); + + useEffect(() => { + onStatusChange?.(playerStatus); + }, [onStatusChange, playerStatus]); useEffect(() => { if (embedUrl) return; markUnavailable(); }, [embedUrl, markUnavailable]); + useEffect(() => { + if (embedKind !== "hyperscape-public") return; + markUnavailable( + "Invalid stream configuration. Embedded Hyperscapes streams must use a tokenized /stream URL.", + ); + }, [embedKind, markUnavailable]); + + useEffect(() => { + if ( + !embedUrl || + (embedKind !== "hyperscape" && embedKind !== "hls-player") || + typeof window === "undefined" + ) { + return; + } + + const embedOrigin = getEmbedOrigin(embedUrl); + if (!embedOrigin) { + return; + } + + const expectedMessageType = + embedKind === "hyperscape" + ? "HYPERSCAPE_STREAM_STATUS" + : "HLS_PLAYER_STATUS"; + let seenStatusMessage = false; + const bootstrapTimeout = window.setTimeout(() => { + if (!seenStatusMessage) { + markUnavailable( + embedKind === "hyperscape" + ? "Failed to initialize the embedded Hyperscapes stream." + : "Failed to initialize the embedded HLS stream.", + ); + } + }, 10_000); + + const handleMessage = (event: MessageEvent) => { + if (event.origin !== embedOrigin) return; + if (!event.data || typeof event.data !== "object") return; + + const payload = event.data as EmbedStatusPayload; + if (payload.type !== expectedMessageType) { + return; + } + + seenStatusMessage = true; + window.clearTimeout(bootstrapTimeout); + setPlayerStatus((current) => ({ + ...current, + ready: payload.ready === true ? true : current.ready, + status: + typeof payload.status === "string" && payload.status.trim().length > 0 + ? payload.status.trim() + : current.status, + liveEdgeLatencyMs: + typeof payload.liveEdgeLatencyMs === "number" && + Number.isFinite(payload.liveEdgeLatencyMs) + ? payload.liveEdgeLatencyMs + : current.liveEdgeLatencyMs, + stallCount: + typeof payload.stallCount === "number" && + Number.isFinite(payload.stallCount) + ? payload.stallCount + : current.stallCount, + rebuildCount: + typeof payload.rebuildCount === "number" && + Number.isFinite(payload.rebuildCount) + ? payload.rebuildCount + : current.rebuildCount, + lastBufferedFragmentAt: + typeof payload.lastBufferedFragmentAt === "number" && + Number.isFinite(payload.lastBufferedFragmentAt) + ? payload.lastBufferedFragmentAt + : current.lastBufferedFragmentAt, + lastPlaybackProgressAt: + typeof payload.lastPlaybackProgressAt === "number" && + Number.isFinite(payload.lastPlaybackProgressAt) + ? payload.lastPlaybackProgressAt + : current.lastPlaybackProgressAt, + playbackUrl: + typeof payload.playbackUrl === "string" && payload.playbackUrl.trim().length > 0 + ? payload.playbackUrl.trim() + : current.playbackUrl, + deliveryMode: + typeof payload.deliveryMode === "string" && payload.deliveryMode.trim().length > 0 + ? payload.deliveryMode.trim() + : current.deliveryMode, + firstFrameAt: + typeof payload.firstFrameAt === "number" && + Number.isFinite(payload.firstFrameAt) + ? payload.firstFrameAt + : current.firstFrameAt, + startupDurationMs: + typeof payload.startupDurationMs === "number" && + Number.isFinite(payload.startupDurationMs) + ? payload.startupDurationMs + : current.startupDurationMs, + playbackStarted: + payload.playbackStarted === true ? true : current.playbackStarted, + presentationDelayMs: + typeof payload.presentationDelayMs === "number" && + Number.isFinite(payload.presentationDelayMs) + ? Math.max(0, payload.presentationDelayMs) + : current.presentationDelayMs, + syncDeltaMs: + typeof payload.syncDeltaMs === "number" && + Number.isFinite(payload.syncDeltaMs) + ? payload.syncDeltaMs + : current.syncDeltaMs, + syncState: + payload.syncState && payload.syncState.trim().length > 0 + ? payload.syncState + : current.syncState, + bootPhase: + payload.bootPhase && payload.bootPhase.trim().length > 0 + ? payload.bootPhase + : current.bootPhase, + loaderVisible: + typeof payload.loaderVisible === "boolean" + ? payload.loaderVisible + : current.loaderVisible, + })); + + if (payload.ready === true) { + markReady(); + return; + } + + const degradedStatus = + typeof payload.status === "string" && payload.status.trim().length > 0 + ? payload.status.trim() + : typeof payload.rendererHealth?.degradedReason === "string" && + payload.rendererHealth.degradedReason.trim().length > 0 + ? payload.rendererHealth.degradedReason.trim() + : null; + + if (degradedStatus && degradedStatus.startsWith("error:")) { + markUnavailable( + embedKind === "hyperscape" + ? describeHyperscapeEmbedError(degradedStatus) + : describeHlsEmbedError(degradedStatus), + ); + return; + } + + if (!isTransientPlayerStatus(degradedStatus)) { + markDegraded( + describePlayerStatus(degradedStatus, embedKind), + degradedStatus ?? undefined, + ); + } + }; + + window.addEventListener("message", handleMessage); + return () => { + window.clearTimeout(bootstrapTimeout); + window.removeEventListener("message", handleMessage); + }; + }, [embedKind, embedUrl, markDegraded, markReady, markUnavailable]); + useEffect(() => { // External embeddable URLs render through iframe mode below. if (embedUrl) return; @@ -53,250 +646,540 @@ export const StreamPlayer: React.FC = ({ let hls: Hls | null = null; let retryTimeout: ReturnType | null = null; - let healthWatchdog: ReturnType | null = null; - let lastPlaybackTime = 0; - let lastPlaylistUpdateAt = Date.now(); - let stallCount = 0; + let latencyInterval: ReturnType | null = null; + let waitingTimeout: ReturnType | null = null; + let recoveryCooldownUntil = 0; + let fatalErrorCount = 0; let disposed = false; + let lastProgressAt = Date.now(); + let lastCurrentTime = 0; + let recentVideoErrorRecoveries = 0; + let playbackStarted = false; + let startupStartedAt = Date.now(); + let playerReady = false; + let waitingSignalTimestamps: number[] = []; + const sourceUrl = streamUrl.trim(); + const playbackProfile = resolveHlsPlaybackProfile(sourceUrl, deliveryMode); - const clearTimers = () => { - if (retryTimeout) { - clearTimeout(retryTimeout); - retryTimeout = null; - } - if (healthWatchdog) { - clearInterval(healthWatchdog); - healthWatchdog = null; - } + const updateTelemetry = ( + next: + | Partial + | ((current: StreamPlayerStatus) => StreamPlayerStatus), + ) => { + setPlayerStatus((current) => + typeof next === "function" + ? next(current) + : { + ...current, + ...next, + }, + ); }; - const sourceUrl = () => - `${streamUrl}${streamUrl.includes("?") ? "&" : "?"}t=${Date.now()}`; - - const probeManifest = async () => { - try { - const response = await fetch(sourceUrl(), { cache: "no-store" }); - if (!response.ok) return false; - const text = await response.text(); - // A valid live playlist should include media segments. - return /#EXTINF/i.test(text) && /\.(ts|m4s|mp4)\b/i.test(text); - } catch { - return false; - } + const clearRetry = () => { + if (!retryTimeout) return; + clearTimeout(retryTimeout); + retryTimeout = null; }; - const nudgeToLiveEdge = () => { - if (!video) return; + const clearLatencyInterval = () => { + if (!latencyInterval) return; + clearInterval(latencyInterval); + latencyInterval = null; + }; - const syncPosition = hls?.liveSyncPosition; - if (typeof syncPosition === "number" && Number.isFinite(syncPosition)) { - if (syncPosition - video.currentTime > 1) { - video.currentTime = Math.max(0, syncPosition - 0.5); - } - } else if (video.buffered.length > 0) { - const liveEdge = video.buffered.end(video.buffered.length - 1); - if (liveEdge - video.currentTime > 1) { - video.currentTime = Math.max(0, liveEdge - 0.5); - } + const clearWaitingTimeout = () => { + if (!waitingTimeout) return; + clearTimeout(waitingTimeout); + waitingTimeout = null; + }; + + const syncLatencyTelemetry = () => { + const latencyMs = readLiveEdgeLatencyMs(hls, video); + const normalizedPresentationDelayMs = + typeof presentationDelayMs === "number" && + Number.isFinite(presentationDelayMs) + ? Math.max(0, presentationDelayMs) + : null; + updateTelemetry({ + liveEdgeLatencyMs: latencyMs, + playbackUrl: sourceUrl, + deliveryMode: resolvePlayerDeliveryModeHint(sourceUrl, deliveryMode), + presentationDelayMs: normalizedPresentationDelayMs, + }); + if ( + playbackStarted && + !video.paused && + isPlaybackLatencyWithinBudget({ + driftThresholdMs: playbackProfile.driftThresholdMs, + syncDriftThresholdMs: playbackProfile.syncDriftThresholdMs, + latencyMs, + presentationDelayMs: normalizedPresentationDelayMs, + }) + ) { + playerReady = true; + markDegraded(null); + markReady(); + return; + } + if ( + shouldTreatPlaybackLatencyAsDrifted({ + driftThresholdMs: playbackProfile.driftThresholdMs, + syncDriftThresholdMs: playbackProfile.syncDriftThresholdMs, + latencyMs, + presentationDelayMs: normalizedPresentationDelayMs, + playbackStarted, + ready: playerReady, + }) + ) { + playerReady = false; + markDegraded( + describeCanonicalRendererDegradedReason("player_drifted"), + ); } + }; - void video.play().catch(() => {}); + const notePlaybackProgress = () => { + if (video.currentTime > lastCurrentTime + 0.05) { + playbackStarted = true; + lastCurrentTime = video.currentTime; + lastProgressAt = Date.now(); + recentVideoErrorRecoveries = 0; + waitingSignalTimestamps = []; + playerReady = true; + const firstFrameAt = Date.now(); + updateTelemetry((current) => ({ + ...current, + playbackStarted: true, + firstFrameAt: current.firstFrameAt ?? firstFrameAt, + startupDurationMs: + current.startupDurationMs ?? firstFrameAt - startupStartedAt, + lastPlaybackProgressAt: lastProgressAt, + })); + markDegraded(null); + markReady(); + } }; - const scheduleRebuild = (reason: string, delayMs = 1500) => { - console.warn(`[StreamPlayer] Rebuilding stream: ${reason}`); - if (retryTimeout) clearTimeout(retryTimeout); - retryTimeout = setTimeout(() => { - void initPlayer(); - }, delayMs); + const readBufferedTailMs = () => { + if (video.buffered.length === 0) { + return null; + } + const bufferedEnd = video.buffered.end(video.buffered.length - 1); + const remaining = bufferedEnd - video.currentTime; + if (!Number.isFinite(remaining) || remaining < 0) { + return null; + } + return Math.round(remaining * 1000); }; - const startHealthWatchdog = () => { - if (healthWatchdog) clearInterval(healthWatchdog); + const startLatencyPolling = () => { + clearLatencyInterval(); + latencyInterval = setInterval(syncLatencyTelemetry, 1000); + }; - lastPlaybackTime = 0; - stallCount = 0; + const isStartupPending = (now = Date.now()) => + shouldTreatPlaybackStartupAsPending({ + currentTime: video.currentTime, + now, + playbackStarted, + startupGraceMs: playbackProfile.startupGraceMs, + startupStartedAt, + }); - // Recovery loop for tiny stalls and stale playlist updates. - healthWatchdog = setInterval(() => { - if (!video) return; + const recoverPlayback = ( + reason: string, + { + reloadSource = false, + recoverMedia = false, + delayMs = 0, + }: { + reloadSource?: boolean; + recoverMedia?: boolean; + delayMs?: number; + } = {}, + ) => { + const run = () => { + if (disposed) return; const now = Date.now(); - const playbackDelta = Math.abs(video.currentTime - lastPlaybackTime); - const stalled = - video.currentTime > 0 && - playbackDelta < 0.01 && - !video.paused && - !video.ended; - - if (stalled) { - stallCount += 1; - console.warn( - `[StreamPlayer] Playback stalled (count: ${stallCount})`, - ); - - if (stallCount >= 3) { - scheduleRebuild("playback stalled repeatedly"); - return; - } - - if (stallCount === 1) { - nudgeToLiveEdge(); - } else { - hls?.recoverMediaError(); - nudgeToLiveEdge(); - } - } else { - stallCount = 0; + if (now < recoveryCooldownUntil) { + return; } + recoveryCooldownUntil = now + 2500; - if (hls && now - lastPlaylistUpdateAt > 8000) { - console.warn( - "[StreamPlayer] Playlist stalled; forcing manifest/fragment reload", - ); - hls.startLoad(); - nudgeToLiveEdge(); - lastPlaylistUpdateAt = now; + console.warn(`[StreamPlayer] Recovering playback: ${reason}`); + if (recoverMedia) { + hls?.recoverMediaError(); } + if (reloadSource) { + hls?.startLoad(-1); + } + syncLatencyTelemetry(); + void video.play().catch(() => {}); + }; - lastPlaybackTime = video.currentTime; - }, 2000); + clearRetry(); + if (delayMs > 0) { + retryTimeout = setTimeout(run, delayMs); + return; + } + + run(); + }; + + const rebuildPlayer = (reason: string, delayMs = 1500) => { + console.warn(`[StreamPlayer] Rebuilding stream: ${reason}`); + updateTelemetry((current) => ({ + ...current, + rebuildCount: current.rebuildCount + 1, + })); + markDegraded("Rebuilding live stream..."); + clearRetry(); + retryTimeout = setTimeout(() => { + if (disposed) return; + void initPlayer(); + }, delayMs); }; const initPlayer = async () => { if (disposed) return; - clearTimers(); + + clearRetry(); + fatalErrorCount = 0; + recoveryCooldownUntil = 0; + if (hls) { hls.destroy(); hls = null; } - lastPlaylistUpdateAt = Date.now(); + clearLatencyInterval(); + clearWaitingTimeout(); + playbackStarted = false; + playerReady = false; + startupStartedAt = Date.now(); + lastProgressAt = startupStartedAt; + lastCurrentTime = 0; + recentVideoErrorRecoveries = 0; + updateTelemetry((current) => ({ + ...current, + liveEdgeLatencyMs: null, + lastBufferedFragmentAt: null, + lastPlaybackProgressAt: null, + playbackUrl: sourceUrl, + deliveryMode: resolvePlayerDeliveryModeHint(sourceUrl, deliveryMode), + firstFrameAt: null, + startupDurationMs: null, + playbackStarted: false, + presentationDelayMs: + typeof presentationDelayMs === "number" && Number.isFinite(presentationDelayMs) + ? Math.max(0, presentationDelayMs) + : null, + })); - const manifestReady = await probeManifest(); - if (!manifestReady) { - scheduleRebuild("manifest not ready", 1000); - return; + video.preload = "auto"; + if (autoPlay) { + video.autoplay = true; } + video.muted = muted; - // Check if browser supports HLS natively (Safari) if (video.canPlayType("application/vnd.apple.mpegurl")) { - video.src = sourceUrl(); + video.src = sourceUrl; + startLatencyPolling(); + syncLatencyTelemetry(); void video.play().catch(() => {}); - startHealthWatchdog(); - } else if (Hls.isSupported()) { - hls = new Hls({ - enableWorker: true, - // FFmpeg emits standard live HLS, not LL-HLS parts. - lowLatencyMode: false, - // Keep a wider live window to absorb network jitter. - liveSyncDurationCount: 4, - liveMaxLatencyDurationCount: 12, - liveBackBufferLength: 30, - maxBufferLength: 30, - maxMaxBufferLength: 60, - // Aggressive retries when manifests/fragments fail. - manifestLoadingMaxRetry: 10, - manifestLoadingRetryDelay: 800, - levelLoadingMaxRetry: 10, - levelLoadingRetryDelay: 800, - fragLoadingMaxRetry: 10, - fragLoadingRetryDelay: 800, - }); + return; + } - hls.loadSource(sourceUrl()); - hls.attachMedia(video); + if (!Hls.isSupported()) { + console.error("[StreamPlayer] HLS is not supported in this browser"); + markUnavailable("HLS is not supported in this browser."); + return; + } - hls.on(Hls.Events.MANIFEST_LOADED, () => { - lastPlaylistUpdateAt = Date.now(); - }); + hls = new Hls({ ...playbackProfile.config }); - hls.on(Hls.Events.LEVEL_LOADED, () => { - lastPlaylistUpdateAt = Date.now(); - }); + hls.loadSource(sourceUrl); + hls.attachMedia(video); + startLatencyPolling(); - hls.on(Hls.Events.FRAG_LOADED, () => { - lastPlaylistUpdateAt = Date.now(); - }); + hls.on(Hls.Events.MANIFEST_PARSED, () => { + preferHighestViableHlsLevel(hls!, video); + console.log("[StreamPlayer] Manifest parsed, starting playback"); + markDegraded(null); + syncLatencyTelemetry(); + void video.play().catch(() => {}); + }); - hls.on(Hls.Events.MANIFEST_PARSED, () => { - console.log("[StreamPlayer] Manifest parsed, starting playback"); - onStreamReady?.(); - void video.play().catch(() => {}); + hls.on(Hls.Events.FRAG_BUFFERED, () => { + clearWaitingTimeout(); + lastProgressAt = Date.now(); + recentVideoErrorRecoveries = 0; + updateTelemetry({ + lastBufferedFragmentAt: Date.now(), }); + syncLatencyTelemetry(); + markDegraded(null); + markReady(); + }); - hls.on(Hls.Events.ERROR, (_event, data) => { - console.warn( - "[StreamPlayer] HLS error:", - data.type, - data.details, - data.fatal, - ); - - if (data.fatal) { - switch (data.type) { - case Hls.ErrorTypes.NETWORK_ERROR: - console.log("[StreamPlayer] Network error, retrying load..."); - hls?.startLoad(-1); - break; - case Hls.ErrorTypes.MEDIA_ERROR: - console.log("[StreamPlayer] Media error, recovering..."); - hls?.recoverMediaError(); - nudgeToLiveEdge(); - break; - default: - scheduleRebuild("fatal HLS error", 2000); - break; + hls.on(Hls.Events.LEVEL_UPDATED, () => { + syncLatencyTelemetry(); + }); + + hls.on(Hls.Events.ERROR, (_event, data) => { + console.warn( + "[StreamPlayer] HLS error:", + data.type, + data.details, + data.fatal, + ); + + if (!data.fatal) { + if (data.details === Hls.ErrorDetails.BUFFER_STALLED_ERROR) { + if (isStartupPending()) { + recoverPlayback("startup buffer stall", { + reloadSource: true, + recoverMedia: true, + delayMs: 750, + }); + return; + } + updateTelemetry((current) => ({ + ...current, + stallCount: current.stallCount + 1, + })); + markDegraded( + describeCanonicalRendererDegradedReason("player_drifted"), + ); + if (playbackProfile.reloadOnBufferStall) { + recoverPlayback("buffer stalled near live edge", { + reloadSource: true, + }); + } else { + syncLatencyTelemetry(); + void video.play().catch(() => {}); } } else if ( - data.details === Hls.ErrorDetails.BUFFER_STALLED_ERROR || data.details === Hls.ErrorDetails.FRAG_LOAD_TIMEOUT || - data.details === Hls.ErrorDetails.LEVEL_LOAD_TIMEOUT || - data.details === Hls.ErrorDetails.BUFFER_APPEND_ERROR + data.details === Hls.ErrorDetails.LEVEL_LOAD_TIMEOUT ) { - console.warn( - "[StreamPlayer] Non-fatal buffering/loading issue; forcing recovery", - ); - hls?.startLoad(); - nudgeToLiveEdge(); + updateTelemetry((current) => ({ + ...current, + stallCount: current.stallCount + 1, + })); + markDegraded("Reconnecting to the live stream."); + recoverPlayback("fragment/level load timeout", { + reloadSource: true, + }); + } else if (data.details === Hls.ErrorDetails.BUFFER_APPEND_ERROR) { + updateTelemetry((current) => ({ + ...current, + stallCount: current.stallCount + 1, + })); + markDegraded("Recovering live stream..."); + recoverPlayback("buffer append issue", { + recoverMedia: true, + reloadSource: true, + }); } - }); + return; + } - startHealthWatchdog(); - } else { - console.error("[StreamPlayer] HLS is not supported in this browser"); - } + fatalErrorCount += 1; + + switch (data.type) { + case Hls.ErrorTypes.NETWORK_ERROR: + if (fatalErrorCount < 3) { + markDegraded("Reconnecting to the live edge..."); + recoverPlayback("fatal network error", { + reloadSource: true, + delayMs: 1000, + }); + } else { + rebuildPlayer("repeated fatal network error", 2000); + } + break; + case Hls.ErrorTypes.MEDIA_ERROR: + if (fatalErrorCount < 3) { + markDegraded("Recovering live playback..."); + recoverPlayback("fatal media error", { + recoverMedia: true, + reloadSource: true, + delayMs: 500, + }); + } else { + rebuildPlayer("repeated fatal media error", 2000); + } + break; + default: + if (fatalErrorCount < 2) { + rebuildPlayer("fatal HLS error", 2000); + } else { + markUnavailable("Live stream unavailable."); + } + break; + } + }); }; - const onWaiting = () => nudgeToLiveEdge(); - const onStalled = () => nudgeToLiveEdge(); - const onLoadedMetadata = () => onStreamReady?.(); - const onVideoError = () => scheduleRebuild("video element error", 1000); + const onLoadedMetadata = () => markReady(); + const onLoadedData = () => markReady(); + const onCanPlay = () => markReady(); + const onPlaying = () => { + clearWaitingTimeout(); + notePlaybackProgress(); + markDegraded(null); + syncLatencyTelemetry(); + markReady(); + }; + const onWaiting = () => { + clearWaitingTimeout(); + waitingTimeout = setTimeout(() => { + const now = Date.now(); + if (isStartupPending(now)) { + syncLatencyTelemetry(); + return; + } + if (!playbackStarted && video.currentTime <= 0.05) { + markDegraded("Reconnecting to the live stream."); + recoverPlayback("startup waiting timeout", { + reloadSource: true, + recoverMedia: true, + delayMs: 750, + }); + return; + } + const bufferedTailMs = readBufferedTailMs(); + const idleForMs = now - lastProgressAt; + if ( + bufferedTailMs != null && + bufferedTailMs > playbackProfile.minVideoErrorTailMs && + idleForMs < playbackProfile.waitingGraceMs * 2 + ) { + syncLatencyTelemetry(); + return; + } + updateTelemetry((current) => ({ + ...current, + stallCount: current.stallCount + 1, + })); + waitingSignalTimestamps = recordRecentPlaybackSignal( + waitingSignalTimestamps, + now, + ); + const repeatedWaitingSignals = + waitingSignalTimestamps.length >= RECENT_PLAYER_SIGNAL_THRESHOLD; + const sustainedIdleWithoutTail = + bufferedTailMs == null && + idleForMs > playbackProfile.waitingGraceMs * 2; + if (!repeatedWaitingSignals && !sustainedIdleWithoutTail) { + syncLatencyTelemetry(); + return; + } + markDegraded("Your playback is catching up to the live edge."); + }, playbackProfile.waitingGraceMs); + }; + const onStalled = () => { + clearWaitingTimeout(); + if (isStartupPending()) { + syncLatencyTelemetry(); + return; + } + if (!playbackStarted && video.currentTime <= 0.05) { + markDegraded("Reconnecting to the live stream."); + recoverPlayback("startup video stalled", { + reloadSource: true, + recoverMedia: true, + delayMs: 750, + }); + return; + } + updateTelemetry((current) => ({ + ...current, + stallCount: current.stallCount + 1, + })); + markDegraded(describeCanonicalRendererDegradedReason("player_drifted")); + recoverPlayback("video stalled", { + reloadSource: true, + recoverMedia: true, + delayMs: 750, + }); + }; + const onTimeUpdate = () => { + notePlaybackProgress(); + syncLatencyTelemetry(); + }; + const onVideoError = () => { + const bufferedTailMs = readBufferedTailMs(); + const idleForMs = Date.now() - lastProgressAt; + if ( + !playbackProfile.rebuildOnVideoError && + recentVideoErrorRecoveries < 2 + ) { + recentVideoErrorRecoveries += 1; + updateTelemetry((current) => ({ + ...current, + stallCount: current.stallCount + 1, + })); + markDegraded("Recovering live playback..."); + recoverPlayback("video element error", { + reloadSource: true, + recoverMedia: true, + delayMs: + bufferedTailMs != null && + bufferedTailMs > playbackProfile.minVideoErrorTailMs + ? 1250 + : 750, + }); + return; + } + rebuildPlayer("video element error", 1000); + }; + video.addEventListener("loadedmetadata", onLoadedMetadata); + video.addEventListener("loadeddata", onLoadedData); + video.addEventListener("canplay", onCanPlay); + video.addEventListener("playing", onPlaying); video.addEventListener("waiting", onWaiting); video.addEventListener("stalled", onStalled); - video.addEventListener("loadedmetadata", onLoadedMetadata); + video.addEventListener("timeupdate", onTimeUpdate); video.addEventListener("error", onVideoError); - if (autoPlay) { - video.autoplay = true; - } - video.muted = muted; - void initPlayer(); return () => { + video.removeEventListener("loadedmetadata", onLoadedMetadata); + video.removeEventListener("loadeddata", onLoadedData); + video.removeEventListener("canplay", onCanPlay); + video.removeEventListener("playing", onPlaying); video.removeEventListener("waiting", onWaiting); video.removeEventListener("stalled", onStalled); - video.removeEventListener("loadedmetadata", onLoadedMetadata); + video.removeEventListener("timeupdate", onTimeUpdate); video.removeEventListener("error", onVideoError); - clearTimers(); + clearRetry(); + clearLatencyInterval(); + clearWaitingTimeout(); disposed = true; if (hls) { hls.destroy(); hls = null; } + video.removeAttribute("src"); + video.load(); }; - }, [embedUrl, streamUrl, autoPlay, muted]); + }, [ + embedUrl, + streamUrl, + autoPlay, + deliveryMode, + muted, + markDegraded, + markReady, + markUnavailable, + presentationDelayMs, + ]); + const overlayMessage = embedFailure ?? diagnosticMessage; if (!embedUrl) { return ( @@ -323,6 +1206,42 @@ export const StreamPlayer: React.FC = ({ backgroundColor: "#000", }} /> + {overlayMessage ? ( +
+
+ {overlayMessage} +
+
+ ) : null} + {showDiagnostics ? : null}
= ({ className={className} style={{ position: "relative", width: "100%", height: "100%", ...style }} > -