diff --git a/.gitignore b/.gitignore index 7cb5df7d..951f3e1c 100755 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ universalClient/coverage.out # TSS data directory tss-data/ local-native/data/ +e2e-tests/.pchain/ +e2e-tests/genesis_accounts.json diff --git a/Makefile b/Makefile index a5db6e09..84322ffa 100755 --- a/Makefile +++ b/Makefile @@ -155,6 +155,10 @@ draw-deps: clean: rm -rf snapcraft-local.yaml build/ +.PHONY: replace-addresses +replace-addresses: + bash scripts/replace_addresses.sh + distclean: clean rm -rf vendor/ diff --git a/config/testnet-donut/arb_sepolia/chain.json b/config/testnet-donut/arb_sepolia/chain.json index 64051629..9e15528d 100644 --- a/config/testnet-donut/arb_sepolia/chain.json +++ b/config/testnet-donut/arb_sepolia/chain.json @@ -1,6 +1,6 @@ { "chain": "eip155:421614", - "public_rpc_url": "https://endpoints.omniatech.io/v1/arbitrum/sepolia/public", + "public_rpc_url": "https://arbitrum-sepolia.gateway.tenderly.co", "vm_type": 1, "gateway_address": "0x2cd870e0166Ba458dEC615168Fd659AacD795f34", "block_confirmation": { diff --git a/config/testnet-donut/solana_devnet/tokens/sol.json b/config/testnet-donut/solana_devnet/tokens/sol.json index 2ec25016..05f65f7f 100644 --- a/config/testnet-donut/solana_devnet/tokens/sol.json +++ b/config/testnet-donut/solana_devnet/tokens/sol.json @@ -6,7 +6,7 @@ "decimals": 9, "enabled": true, "liquidity_cap": "1000000000000000000000000", - "token_type": 4, + "token_type": 4, "native_representation": { "denom": "", "contract_address": "0x5D525Df2bD99a6e7ec58b76aF2fd95F39874EBed" diff --git a/e2e-tests/.env.example b/e2e-tests/.env.example new file mode 100644 index 00000000..c41836bd --- /dev/null +++ b/e2e-tests/.env.example @@ -0,0 +1,71 @@ +# Copy this file to e2e-tests/.env and adjust values. + +# Path to push-chain workspace root. +# Keep this empty to use auto-detection (parent of e2e-tests). +# PUSH_CHAIN_DIR= + +# Local Push RPC +PUSH_RPC_URL=http://localhost:8545 + +# Local chain info +CHAIN_ID=localchain_9000-1 +KEYRING_BACKEND=test + +# Genesis key recovery/funding +GENESIS_KEY_NAME=genesis-acc-1 +GENESIS_KEY_HOME=./e2e-tests/.pchain +# Optional local fallback file. If missing, setup.sh reads accounts from docker core-validator-1 (/tmp/push-accounts/genesis_accounts.json) +GENESIS_ACCOUNTS_JSON=./e2e-tests/genesis_accounts.json + +# Optional: set to skip interactive mnemonic prompt +# GENESIS_MNEMONIC="word1 word2 ..." + +# Address to fund from genesis account +FUND_TO_ADDRESS=push1w7xnyp3hf79vyetj3cvw8l32u6unun8yr6zn60 +FUND_AMOUNT=1000000000000000000upc +POOL_CREATION_TOPUP_AMOUNT=50000000000000000000upc +GAS_PRICES=100000000000upc + +# EVM private key used by forge/hardhat scripts +PRIVATE_KEY=0xYOURPRIVATEKEY + +# External repositories +CORE_CONTRACTS_REPO=https://github.com/pushchain/push-chain-core-contracts.git +CORE_CONTRACTS_BRANCH=node-e2e + +SWAP_AMM_REPO=https://github.com/pushchain/push-chain-swap-internal-amm-contracts.git +SWAP_AMM_BRANCH=e2e-push-node + +GATEWAY_REPO=https://github.com/pushchain/push-chain-gateway-contracts.git +GATEWAY_BRANCH=e2e-push-node + +PUSH_CHAIN_SDK_REPO=https://github.com/pushchain/push-chain-sdk.git +PUSH_CHAIN_SDK_BRANCH=feb-11-2026-alpha-publish + +# push-chain-sdk core .env target path (relative to PUSH_CHAIN_SDK_DIR) +PUSH_CHAIN_SDK_CORE_ENV_PATH=packages/core/.env + +# Local clone layout (outside push-chain directory) +E2E_PARENT_DIR=../ +CORE_CONTRACTS_DIR=../push-chain-core-contracts +SWAP_AMM_DIR=../push-chain-swap-internal-amm-contracts +GATEWAY_DIR=../push-chain-gateway-contracts +PUSH_CHAIN_SDK_DIR=../push-chain-sdk +PUSH_CHAIN_SDK_E2E_DIR=packages/core/__e2e__ + +# push-chain-sdk required env vars (mirrored into PUSH_CHAIN_SDK_DIR/packages/core/.env by setup-sdk) +# Defaults used by setup-sdk when omitted: +# EVM_PRIVATE_KEY <= PRIVATE_KEY +# EVM_RPC <= PUSH_RPC_URL +# PUSH_PRIVATE_KEY<= PRIVATE_KEY +EVM_PRIVATE_KEY= +EVM_RPC= +SOLANA_RPC_URL=https://api.devnet.solana.com +SOLANA_PRIVATE_KEY= +PUSH_PRIVATE_KEY= + +# Tracking files +DEPLOY_ADDRESSES_FILE=./e2e-tests/deploy_addresses.json +TEST_ADDRESSES_PATH=../push-chain-swap-internal-amm-contracts/test-addresses.json +TOKEN_CONFIG_PATH=./config/testnet-donut/eth_sepolia/tokens/eth.json +CHAIN_CONFIG_PATH=./config/testnet-donut/eth_sepolia/chain.json diff --git a/e2e-tests/.gitignore b/e2e-tests/.gitignore new file mode 100644 index 00000000..4ea7f858 --- /dev/null +++ b/e2e-tests/.gitignore @@ -0,0 +1,2 @@ +.env +logs/ \ No newline at end of file diff --git a/e2e-tests/README.md b/e2e-tests/README.md new file mode 100644 index 00000000..80267847 --- /dev/null +++ b/e2e-tests/README.md @@ -0,0 +1,274 @@ +# e2e-tests setup + +This folder provides a full, automated local E2E bootstrap for Push Chain. + +It covers: + +1. local-multi-validator devnet (Docker, validators + universal validators) +2. genesis key recovery + account funding +3. core contracts deployment +4. swap AMM deployment (WPC + V3 core + V3 periphery) +5. pool creation for pETH/WPC +6. core `.env` generation from deployed addresses +7. token config update (`eth_sepolia_eth.json`) +8. gateway contracts deployment +9. push-chain-sdk setup + E2E test runners +10. uregistry chain/token config submission + +--- + +## What gets created + +- `e2e-tests/repos/` — cloned external repos + - push-chain-core-contracts + - push-chain-swap-internal-amm-contracts + - push-chain-gateway-contracts + - push-chain-sdk +- `e2e-tests/logs/` — logs for each major deployment step +- `e2e-tests/deploy_addresses.json` — contract/token address source-of-truth + +--- + +## Prerequisites + +Required tools: + +- `git` +- `jq` +- `node`, `npm`, `npx` +- `forge` (Foundry) +- `make` + +Also ensure the Push Chain repo builds/runs locally. + +Before running any e2e setup command, run: + +```bash +make replace-addresses +``` + +--- + +## Configuration + +Copy env template: + +```bash +cp e2e-tests/.env.example e2e-tests/.env +``` + +Important variables in `.env`: + +- `PUSH_RPC_URL` (default `http://localhost:8545`) +- `PRIVATE_KEY` +- `FUND_TO_ADDRESS` +- `POOL_CREATION_TOPUP_AMOUNT` (funding for deployer before pool creation) +- `CORE_CONTRACTS_BRANCH` +- `SWAP_AMM_BRANCH` +- `GATEWAY_BRANCH` (currently `e2e-push-node`) +- `PUSH_CHAIN_SDK_BRANCH` (default `feb-11-2026-alpha-publish`) + +Genesis account source: + +- `GENESIS_ACCOUNTS_JSON` can point to a local file, but if missing `setup.sh` automatically + reads `/tmp/push-accounts/genesis_accounts.json` from docker container `core-validator-1`. + +Path settings are repository-relative and portable. + +--- + +## One-command full run + +```bash +make replace-addresses +./e2e-tests/setup.sh all +``` + +This runs the full sequence in order: + +1. `devnet` +2. `recover-genesis-key` +3. `fund` +4. `setup-core` +5. `setup-swap` +6. `sync-addresses` +7. `create-pool` +8. `check-addresses` +9. `write-core-env` +10. `update-token-config` +11. `setup-gateway` +12. `add-uregistry-configs` + +--- + +## Command reference + +```bash +./e2e-tests/setup.sh devnet +./e2e-tests/setup.sh print-genesis +./e2e-tests/setup.sh recover-genesis-key +./e2e-tests/setup.sh fund +./e2e-tests/setup.sh setup-core +./e2e-tests/setup.sh setup-swap +./e2e-tests/setup.sh sync-addresses +./e2e-tests/setup.sh create-pool +./e2e-tests/setup.sh check-addresses +./e2e-tests/setup.sh write-core-env +./e2e-tests/setup.sh update-token-config +./e2e-tests/setup.sh setup-gateway +./e2e-tests/setup.sh setup-sdk +./e2e-tests/setup.sh sdk-test-all +./e2e-tests/setup.sh sdk-test-pctx-last-transaction +./e2e-tests/setup.sh sdk-test-send-to-self +./e2e-tests/setup.sh sdk-test-progress-hook +./e2e-tests/setup.sh sdk-test-bridge-multicall +./e2e-tests/setup.sh sdk-test-pushchain +./e2e-tests/setup.sh add-uregistry-configs +make replace-addresses +./e2e-tests/setup.sh all +``` + +### push-chain-sdk setup + tests + +Clone and install dependencies in one command: + +```bash +./e2e-tests/setup.sh setup-sdk +``` + +This executes: + +- `yarn install` +- `npm install` +- `npm i --save-dev @types/bs58` + +It also fetches `UEA_PROXY_IMPLEMENTATION` with: + +- `cast call 0x00000000000000000000000000000000000000ea "UEA_PROXY_IMPLEMENTATION()(address)"` + +Then it updates both: + +- `e2e-tests/deploy_addresses.json` as `contracts.UEA_PROXY_IMPLEMENTATION` +- `push-chain-sdk/packages/core/src/lib/constants/chain.ts` at `[PUSH_NETWORK.LOCALNET]` + +Run all configured SDK E2E files: + +```bash +./e2e-tests/setup.sh sdk-test-all +``` + +Run single files: + +```bash +./e2e-tests/setup.sh sdk-test-pctx-last-transaction +./e2e-tests/setup.sh sdk-test-send-to-self +./e2e-tests/setup.sh sdk-test-progress-hook +./e2e-tests/setup.sh sdk-test-bridge-multicall +./e2e-tests/setup.sh sdk-test-pushchain +``` + +Before each SDK test run, the script automatically rewrites these values in configured files: + +- `PUSH_NETWORK.TESTNET_DONUT` → `PUSH_NETWORK.LOCALNET` +- `PUSH_NETWORK.TESTNET` → `PUSH_NETWORK.LOCALNET` + +--- + +## Address tracking model + +`deploy_addresses.json` is the canonical address registry used by later steps. + +### Required contracts + +- `contracts.WPC` +- `contracts.Factory` +- `contracts.QuoterV2` +- `contracts.SwapRouter` + +### Token entries + +- `tokens[]` from core deployment logs (`name`, `symbol`, `address`, `source`) + +These addresses are used to: + +- sync swap repo `test-addresses.json` +- generate core contracts `.env` +- update `config/testnet-donut/tokens/eth_sepolia_eth.json` + +Manual helpers: + +```bash +./e2e-tests/setup.sh record-contract Factory 0x1234567890123456789012345678901234567890 +./e2e-tests/setup.sh record-token "Push ETH" pETH 0x1234567890123456789012345678901234567890 +``` + +--- + +## Auto-retry and resilience behavior + +### Core contracts + +- Runs `forge script scripts/localSetup/setup.s.sol ...` +- If receipt fetch fails, auto-retries with `--resume` in a loop until success +- Optional cap via: + +```bash +CORE_RESUME_MAX_ATTEMPTS=0 # 0 means unlimited (default) +``` + +### Gateway contracts + +- Runs gateway `forge script ... setup.s.sol` +- If initial execution fails, retries with `--resume` + +### uregistry tx submission + +- Submits chain config then token config +- Retries automatically on account sequence mismatch +- Validates tx result by checking returned `code` + +--- + +## Generated files of interest + +- `e2e-tests/deploy_addresses.json` +- `e2e-tests/repos/push-chain-swap-internal-amm-contracts/test-addresses.json` +- `e2e-tests/repos/push-chain-core-contracts/.env` +- `config/testnet-donut/tokens/eth_sepolia_eth.json` (updated contract address) + +--- + +## Clean re-run + +For a fresh run: + +```bash +rm -rf e2e-tests/repos +./local-multi-validator/devnet down || true +make replace-addresses +./e2e-tests/setup.sh all +``` + +--- + +## Troubleshooting + +### 1) Core script keeps stopping with receipt errors + +This is expected intermittently on local RPC. The script auto-runs `--resume` until completion. + +### 2) Missing branch in a dependency repo + +The script attempts to resolve/fallback to available remote branches. + +### 3) `account sequence mismatch` in uregistry tx + +The script retries automatically for this error. + +### 4) WPC deployment artifact not found + +`setup-swap` compiles before deployment. If interrupted mid-run, re-run: + +```bash +./e2e-tests/setup.sh setup-swap +``` diff --git a/e2e-tests/deploy_addresses.json b/e2e-tests/deploy_addresses.json new file mode 100644 index 00000000..e69de29b diff --git a/e2e-tests/setup.sh b/e2e-tests/setup.sh new file mode 100755 index 00000000..7541d63c --- /dev/null +++ b/e2e-tests/setup.sh @@ -0,0 +1,1522 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +SCRIPT_DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PUSH_CHAIN_DIR_DEFAULT="$(cd -P "$SCRIPT_DIR/.." && pwd)" +ENV_FILE="$SCRIPT_DIR/.env" + +if [[ -f "$ENV_FILE" ]]; then + set -a + source "$ENV_FILE" + set +a +fi + +: "${PUSH_CHAIN_DIR:=$PUSH_CHAIN_DIR_DEFAULT}" +: "${PUSH_RPC_URL:=http://localhost:8545}" +: "${CHAIN_ID:=localchain_9000-1}" +: "${KEYRING_BACKEND:=test}" +: "${GENESIS_KEY_NAME:=genesis-acc-1}" +: "${GENESIS_KEY_HOME:=./e2e-tests/.pchain}" +: "${GENESIS_ACCOUNTS_JSON:=./e2e-tests/genesis_accounts.json}" +: "${FUND_AMOUNT:=1000000000000000000upc}" +: "${POOL_CREATION_TOPUP_AMOUNT:=50000000000000000000upc}" +: "${GAS_PRICES:=100000000000upc}" +: "${LOCAL_DEVNET_DIR:=./local-multi-validator}" +: "${LEGACY_LOCAL_NATIVE_DIR:=./local-native}" + +: "${CORE_CONTRACTS_REPO:=https://github.com/pushchain/push-chain-core-contracts.git}" +: "${CORE_CONTRACTS_BRANCH:=e2e-push-node}" +: "${SWAP_AMM_REPO:=https://github.com/pushchain/push-chain-swap-internal-amm-contracts.git}" +: "${SWAP_AMM_BRANCH:=e2e-push-node}" +: "${GATEWAY_REPO:=https://github.com/pushchain/push-chain-gateway-contracts.git}" +: "${GATEWAY_BRANCH:=e2e-push-node}" +: "${PUSH_CHAIN_SDK_REPO:=https://github.com/pushchain/push-chain-sdk.git}" +: "${PUSH_CHAIN_SDK_BRANCH:=feb-11-2026-alpha-publish}" + +: "${E2E_PARENT_DIR:=../}" +: "${CORE_CONTRACTS_DIR:=$E2E_PARENT_DIR/push-chain-core-contracts}" +: "${SWAP_AMM_DIR:=$E2E_PARENT_DIR/push-chain-swap-internal-amm-contracts}" +: "${GATEWAY_DIR:=$E2E_PARENT_DIR/push-chain-gateway-contracts}" +: "${PUSH_CHAIN_SDK_DIR:=$E2E_PARENT_DIR/push-chain-sdk}" +: "${PUSH_CHAIN_SDK_E2E_DIR:=packages/core/__e2e__}" +: "${PUSH_CHAIN_SDK_CHAIN_CONSTANTS_PATH:=packages/core/src/lib/constants/chain.ts}" +: "${PUSH_CHAIN_SDK_ACCOUNT_TS_PATH:=packages/core/src/lib/universal/account/account.ts}" +: "${PUSH_CHAIN_SDK_CORE_ENV_PATH:=packages/core/.env}" +: "${DEPLOY_ADDRESSES_FILE:=$SCRIPT_DIR/deploy_addresses.json}" +: "${LOG_DIR:=$SCRIPT_DIR/logs}" +: "${TEST_ADDRESSES_PATH:=$SWAP_AMM_DIR/test-addresses.json}" +: "${TOKENS_CONFIG_DIR:=./config/testnet-donut}" +: "${TOKEN_CONFIG_PATH:=./config/testnet-donut/eth_sepolia/tokens/eth.json}" +: "${CHAIN_CONFIG_PATH:=./config/testnet-donut/eth_sepolia/chain.json}" + +abs_from_root() { + local path="$1" + if [[ "$path" = /* ]]; then + printf "%s" "$path" + else + printf "%s/%s" "$PUSH_CHAIN_DIR" "${path#./}" + fi +} + +GENESIS_KEY_HOME="$(abs_from_root "$GENESIS_KEY_HOME")" +GENESIS_ACCOUNTS_JSON="$(abs_from_root "$GENESIS_ACCOUNTS_JSON")" +LOCAL_DEVNET_DIR="$(abs_from_root "$LOCAL_DEVNET_DIR")" +LEGACY_LOCAL_NATIVE_DIR="$(abs_from_root "$LEGACY_LOCAL_NATIVE_DIR")" +E2E_PARENT_DIR="$(abs_from_root "$E2E_PARENT_DIR")" +CORE_CONTRACTS_DIR="$(abs_from_root "$CORE_CONTRACTS_DIR")" +SWAP_AMM_DIR="$(abs_from_root "$SWAP_AMM_DIR")" +GATEWAY_DIR="$(abs_from_root "$GATEWAY_DIR")" +PUSH_CHAIN_SDK_DIR="$(abs_from_root "$PUSH_CHAIN_SDK_DIR")" +DEPLOY_ADDRESSES_FILE="$(abs_from_root "$DEPLOY_ADDRESSES_FILE")" +TEST_ADDRESSES_PATH="$(abs_from_root "$TEST_ADDRESSES_PATH")" +LOG_DIR="$(abs_from_root "$LOG_DIR")" +TOKENS_CONFIG_DIR="$(abs_from_root "$TOKENS_CONFIG_DIR")" +TOKEN_CONFIG_PATH="$(abs_from_root "$TOKEN_CONFIG_PATH")" +CHAIN_CONFIG_PATH="$(abs_from_root "$CHAIN_CONFIG_PATH")" + +mkdir -p "$LOG_DIR" + +green='\033[0;32m' +yellow='\033[0;33m' +red='\033[0;31m' +cyan='\033[0;36m' +nc='\033[0m' + +log_info() { printf "%b\n" "${cyan}==>${nc} $*"; } +log_ok() { printf "%b\n" "${green}✓${nc} $*"; } +log_warn() { printf "%b\n" "${yellow}!${nc} $*"; } +log_err() { printf "%b\n" "${red}x${nc} $*"; } + +get_genesis_accounts_json() { + if [[ -f "$GENESIS_ACCOUNTS_JSON" ]]; then + cat "$GENESIS_ACCOUNTS_JSON" + return 0 + fi + + if command -v docker >/dev/null 2>&1; then + if docker ps --format '{{.Names}}' | grep -qx 'core-validator-1'; then + if docker exec core-validator-1 test -f /tmp/push-accounts/genesis_accounts.json >/dev/null 2>&1; then + docker exec core-validator-1 cat /tmp/push-accounts/genesis_accounts.json + return 0 + fi + fi + fi + + return 1 +} + +require_cmd() { + local c + for c in "$@"; do + command -v "$c" >/dev/null 2>&1 || { + log_err "Missing command: $c" + exit 1 + } + done +} + +list_remote_branches() { + local repo_url="$1" + git ls-remote --heads "$repo_url" | awk '{print $2}' | sed 's#refs/heads/##' +} + +select_best_matching_branch() { + local requested="$1" + shift + local branches=("$@") + local best="" + local best_score=0 + local branch token score + + # Tokenize requested branch by non-alphanumeric delimiters. + local tokens=() + while IFS= read -r token; do + [[ -n "$token" ]] && tokens+=("$token") + done < <(echo "$requested" | tr -cs '[:alnum:]' '\n' | tr '[:upper:]' '[:lower:]') + + for branch in "${branches[@]}"; do + score=0 + local b_lc + b_lc="$(echo "$branch" | tr '[:upper:]' '[:lower:]')" + for token in "${tokens[@]}"; do + if [[ "$b_lc" == *"$token"* ]]; then + score=$((score + 1)) + fi + done + if (( score > best_score )); then + best_score=$score + best="$branch" + fi + done + + if (( best_score >= 2 )); then + printf "%s" "$best" + fi +} + +resolve_branch() { + local repo_url="$1" + local requested="$2" + local branches=() + local b + + while IFS= read -r b; do + [[ -n "$b" ]] && branches+=("$b") + done < <(list_remote_branches "$repo_url") + + local branch + for branch in "${branches[@]}"; do + if [[ "$branch" == "$requested" ]]; then + printf "%s" "$requested" + return + fi + done + + local best + best="$(select_best_matching_branch "$requested" "${branches[@]}")" + if [[ -n "$best" ]]; then + printf "%b\n" "${yellow}!${nc} Branch '$requested' not found. Auto-selected '$best'." >&2 + printf "%s" "$best" + return + fi + + for branch in main master; do + for b in "${branches[@]}"; do + if [[ "$b" == "$branch" ]]; then + printf "%b\n" "${yellow}!${nc} Branch '$requested' not found. Falling back to '$branch'." >&2 + printf "%s" "$branch" + return + fi + done + done + + if [[ ${#branches[@]} -gt 0 ]]; then + printf "%b\n" "${yellow}!${nc} Branch '$requested' not found. Falling back to '${branches[0]}'." >&2 + printf "%s" "${branches[0]}" + return + fi + + log_err "No remote branches found for $repo_url" + exit 1 +} + +ensure_deploy_file() { + mkdir -p "$(dirname "$DEPLOY_ADDRESSES_FILE")" + + if [[ ! -s "$DEPLOY_ADDRESSES_FILE" ]]; then + cat >"$DEPLOY_ADDRESSES_FILE" <<'JSON' +{ + "generatedAt": "", + "contracts": {}, + "tokens": [] +} +JSON + return + fi + + if ! jq -e . "$DEPLOY_ADDRESSES_FILE" >/dev/null 2>&1; then + log_warn "Deploy file is empty/invalid JSON, reinitializing: $DEPLOY_ADDRESSES_FILE" + cat >"$DEPLOY_ADDRESSES_FILE" <<'JSON' +{ + "generatedAt": "", + "contracts": {}, + "tokens": [] +} +JSON + return + fi + + local tmp + tmp="$(mktemp)" + jq ' + .generatedAt = (.generatedAt // "") + | .contracts = (.contracts // {}) + | .tokens = (.tokens // []) + ' "$DEPLOY_ADDRESSES_FILE" >"$tmp" + mv "$tmp" "$DEPLOY_ADDRESSES_FILE" +} + +set_generated_at() { + local tmp + tmp="$(mktemp)" + jq --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '.generatedAt = $now' "$DEPLOY_ADDRESSES_FILE" >"$tmp" + mv "$tmp" "$DEPLOY_ADDRESSES_FILE" +} + +record_contract() { + local key="$1" + local address="$2" + local tmp + tmp="$(mktemp)" + jq --arg key "$key" --arg val "$address" '.contracts[$key] = $val' "$DEPLOY_ADDRESSES_FILE" >"$tmp" + mv "$tmp" "$DEPLOY_ADDRESSES_FILE" + set_generated_at + log_ok "Recorded contract $key=$address" +} + +record_token() { + local name="$1" + local symbol="$2" + local address="$3" + local source="$4" + local tmp + tmp="$(mktemp)" + jq \ + --arg name "$name" \ + --arg symbol "$symbol" \ + --arg address "$address" \ + --arg source "$source" \ + ' + .tokens = ( + ([.tokens[]? | select((.address | ascii_downcase) != ($address | ascii_downcase))]) + + [{name:$name, symbol:$symbol, address:$address, source:$source}] + ) + ' "$DEPLOY_ADDRESSES_FILE" >"$tmp" + mv "$tmp" "$DEPLOY_ADDRESSES_FILE" + set_generated_at + log_ok "Recorded token $symbol=$address ($name)" +} + +validate_eth_address() { + [[ "$1" =~ ^0x[a-fA-F0-9]{40}$ ]] +} + +clone_or_update_repo() { + local repo_url="$1" + local branch="$2" + local dest="$3" + local resolved_branch + + resolved_branch="$(resolve_branch "$repo_url" "$branch")" + + if [[ -d "$dest" && ! -d "$dest/.git" ]]; then + log_warn "Removing non-git directory at $dest" + rm -rf "$dest" + fi + + if [[ -d "$dest/.git" ]]; then + local current_branch has_changes + current_branch="$(git -C "$dest" rev-parse --abbrev-ref HEAD 2>/dev/null || true)" + has_changes="$(git -C "$dest" status --porcelain 2>/dev/null)" + + if [[ -n "$has_changes" && "$current_branch" == "$resolved_branch" ]]; then + log_warn "Repo $(basename "$dest") has local changes on branch '$current_branch'. Skipping update to preserve local changes." + return 0 + fi + + log_info "Updating repo $(basename "$dest")" + local current_origin + current_origin="$(git -C "$dest" remote get-url origin 2>/dev/null || true)" + if [[ -z "$current_origin" || "$current_origin" != "$repo_url" ]]; then + log_warn "Setting origin for $(basename "$dest") to $repo_url" + if git -C "$dest" remote get-url origin >/dev/null 2>&1; then + git -C "$dest" remote set-url origin "$repo_url" + else + git -C "$dest" remote add origin "$repo_url" + fi + fi + + git -C "$dest" fetch origin + git -C "$dest" checkout -B "$resolved_branch" "origin/$resolved_branch" + git -C "$dest" reset --hard "origin/$resolved_branch" + else + log_info "Cloning $(basename "$dest")" + git clone --branch "$resolved_branch" "$repo_url" "$dest" + fi +} + +sdk_test_files() { + local base_dir="$PUSH_CHAIN_SDK_DIR/$PUSH_CHAIN_SDK_E2E_DIR" + local file alt + local requested_files=( + "pctx-last-transaction.spec.ts" + "send-to-self.spec.ts" + "progress-hook-per-tx.spec.ts" + "bridge-multicall.spec.ts" + "pushchain.spec.ts" + "bridge-hooks.spec.ts" + ) + + for file in "${requested_files[@]}"; do + if [[ -f "$base_dir/$file" ]]; then + printf "%s\n" "$base_dir/$file" + continue + fi + + if [[ "$file" == *.tx ]]; then + alt="${file%.tx}.ts" + if [[ -f "$base_dir/$alt" ]]; then + printf "%b\n" "${yellow}!${nc} Test file '$file' not found. Using '$alt'." >&2 + printf "%s\n" "$base_dir/$alt" + continue + fi + fi + + log_err "SDK test file not found: $base_dir/$file" + exit 1 + done +} + +sdk_prepare_test_files_for_localnet() { + require_cmd perl + + if [[ ! -d "$PUSH_CHAIN_SDK_DIR/.git" && ! -d "$PUSH_CHAIN_SDK_DIR" ]]; then + log_err "SDK repo not found at $PUSH_CHAIN_SDK_DIR" + log_err "Run: $0 setup-sdk" + exit 1 + fi + + if [[ ! -d "$PUSH_CHAIN_SDK_DIR/$PUSH_CHAIN_SDK_E2E_DIR" ]]; then + log_err "SDK E2E directory not found: $PUSH_CHAIN_SDK_DIR/$PUSH_CHAIN_SDK_E2E_DIR" + exit 1 + fi + + while IFS= read -r test_file; do + [[ -n "$test_file" ]] || continue + perl -0pi -e 's/\bPUSH_NETWORK\.TESTNET_DONUT\b/PUSH_NETWORK.LOCALNET/g; s/\bPUSH_NETWORK\.TESTNET\b/PUSH_NETWORK.LOCALNET/g; s/\bCHAIN\.PUSH_TESTNET_DONUT\b/CHAIN.PUSH_LOCALNET/g' "$test_file" + log_ok "Prepared LOCALNET network replacement in $(basename "$test_file")" + done < <(sdk_test_files) +} + +step_setup_push_chain_sdk() { + require_cmd git yarn npm cast jq perl + + local chain_constants_file="$PUSH_CHAIN_SDK_DIR/$PUSH_CHAIN_SDK_CHAIN_CONSTANTS_PATH" + local sdk_account_file="$PUSH_CHAIN_SDK_DIR/$PUSH_CHAIN_SDK_ACCOUNT_TS_PATH" + local uea_impl_raw uea_impl synced_localnet_uea + + clone_or_update_repo "$PUSH_CHAIN_SDK_REPO" "$PUSH_CHAIN_SDK_BRANCH" "$PUSH_CHAIN_SDK_DIR" + + local sdk_env_path="$PUSH_CHAIN_SDK_DIR/$PUSH_CHAIN_SDK_CORE_ENV_PATH" + local sdk_evm_private_key sdk_evm_rpc sdk_solana_rpc sdk_solana_private_key sdk_push_private_key + + sdk_evm_private_key="${EVM_PRIVATE_KEY:-${PRIVATE_KEY:-}}" + sdk_evm_rpc="${EVM_RPC:-${PUSH_RPC_URL:-}}" + sdk_solana_rpc="${SOLANA_RPC_URL:-https://api.devnet.solana.com}" + sdk_solana_private_key="${SOLANA_PRIVATE_KEY:-${SVM_PRIVATE_KEY:-${SOL_PRIVATE_KEY:-}}}" + sdk_push_private_key="${PUSH_PRIVATE_KEY:-${PRIVATE_KEY:-}}" + + mkdir -p "$(dirname "$sdk_env_path")" + { + echo "# Auto-generated by e2e-tests/setup.sh setup-sdk" + echo "# Source: e2e-tests/.env" + echo "EVM_PRIVATE_KEY=$sdk_evm_private_key" + echo "EVM_RPC=$sdk_evm_rpc" + echo "SOLANA_RPC_URL=$sdk_solana_rpc" + echo "SOLANA_PRIVATE_KEY=$sdk_solana_private_key" + echo "PUSH_PRIVATE_KEY=$sdk_push_private_key" + } >"$sdk_env_path" + + [[ -n "$sdk_evm_private_key" ]] || log_warn "SDK env EVM_PRIVATE_KEY is empty (set EVM_PRIVATE_KEY or PRIVATE_KEY in e2e-tests/.env)" + [[ -n "$sdk_evm_rpc" ]] || log_warn "SDK env EVM_RPC is empty (set EVM_RPC or PUSH_RPC_URL in e2e-tests/.env)" + [[ -n "$sdk_solana_private_key" ]] || log_warn "SDK env SOLANA_PRIVATE_KEY is empty (set SOLANA_PRIVATE_KEY in e2e-tests/.env)" + [[ -n "$sdk_push_private_key" ]] || log_warn "SDK env PUSH_PRIVATE_KEY is empty (set PUSH_PRIVATE_KEY or PRIVATE_KEY in e2e-tests/.env)" + log_ok "Generated push-chain-sdk env file: $sdk_env_path" + + if [[ ! -f "$chain_constants_file" ]]; then + log_err "SDK chain constants file not found: $chain_constants_file" + exit 1 + fi + + log_info "Fetching UEA_PROXY_IMPLEMENTATION from local chain" + uea_impl_raw="$(cast call 0x00000000000000000000000000000000000000ea 'UEA_PROXY_IMPLEMENTATION()(address)' --rpc-url "$PUSH_RPC_URL" 2>/dev/null || true)" + uea_impl="$(echo "$uea_impl_raw" | grep -Eo '0x[a-fA-F0-9]{40}' | head -1 || true)" + + if ! validate_eth_address "$uea_impl"; then + log_err "Could not resolve valid UEA_PROXY_IMPLEMENTATION address from cast output: $uea_impl_raw" + exit 1 + fi + + ensure_deploy_file + record_contract "UEA_PROXY_IMPLEMENTATION" "$uea_impl" + + UEA_PROXY_IMPL="$uea_impl" perl -0pi -e 's#(\[PUSH_NETWORK\.LOCALNET\]:\s*)'\''[^'\'']*'\''#$1'\''$ENV{UEA_PROXY_IMPL}'\''#g' "$chain_constants_file" + + synced_localnet_uea="$(grep -E '\[PUSH_NETWORK\.LOCALNET\]:' "$chain_constants_file" | head -1 | sed -E "s/.*'([^']+)'.*/\1/")" + if [[ "$synced_localnet_uea" != "$uea_impl" ]]; then + log_err "Failed to update PUSH_NETWORK.LOCALNET UEA proxy in $chain_constants_file" + exit 1 + fi + + log_ok "Synced PUSH_NETWORK.LOCALNET UEA proxy to $uea_impl" + + if [[ ! -f "$sdk_account_file" ]]; then + log_err "SDK account file not found: $sdk_account_file" + exit 1 + fi + + perl -0pi -e ' + s{(function\s+convertExecutorToOriginAccount\b.*?\{)(.*?)(\n\})}{ + my ($head, $body, $tail) = ($1, $2, $3); + $body =~ s/\bCHAIN\.PUSH_TESTNET_DONUT\b/CHAIN.PUSH_LOCALNET/g; + "$head$body$tail"; + }gse; + ' "$sdk_account_file" + log_ok "Replaced CHAIN.PUSH_TESTNET_DONUT with CHAIN.PUSH_LOCALNET only in convertExecutorToOriginAccount() in $sdk_account_file" + + log_info "Installing push-chain-sdk dependencies" + ( + cd "$PUSH_CHAIN_SDK_DIR" + yarn install + npm install + npm i --save-dev @types/bs58 + ) + + log_ok "push-chain-sdk setup complete" +} + +step_run_sdk_test_file() { + local test_basename="$1" + local test_file="" + + sdk_prepare_test_files_for_localnet + + while IFS= read -r candidate; do + [[ -n "$candidate" ]] || continue + if [[ "$(basename "$candidate")" == "$test_basename" ]]; then + test_file="$candidate" + break + fi + done < <(sdk_test_files) + + if [[ -z "$test_file" ]]; then + log_err "Requested SDK test file not in configured list: $test_basename" + exit 1 + fi + + log_info "Running SDK test: $test_basename" + ( + cd "$PUSH_CHAIN_SDK_DIR" + npx nx test core --runInBand --testPathPattern="$(basename "$test_file")" + ) + + log_ok "Completed SDK test: $test_basename" +} + +step_run_sdk_tests_all() { + local test_file + + sdk_prepare_test_files_for_localnet + + while IFS= read -r test_file; do + [[ -n "$test_file" ]] || continue + log_info "Running SDK test: $(basename "$test_file")" + ( + cd "$PUSH_CHAIN_SDK_DIR" + npx nx test core --runInBand --testPathPattern="$(basename "$test_file")" + ) + done < <(sdk_test_files) + + log_ok "Completed all configured SDK E2E tests" +} + +step_devnet() { + require_cmd bash + log_info "Starting local-multi-validator devnet" + ( + cd "$LOCAL_DEVNET_DIR" + ./devnet start --build + ./devnet setup-uvalidators + ) + log_ok "Devnet is up" +} + +step_stop_running_nodes() { + log_info "Stopping running local nodes/validators" + + if [[ -x "$LOCAL_DEVNET_DIR/devnet" ]]; then + ( + cd "$LOCAL_DEVNET_DIR" + ./devnet down || true + ) + fi + + if [[ -x "$LEGACY_LOCAL_NATIVE_DIR/devnet" ]]; then + ( + cd "$LEGACY_LOCAL_NATIVE_DIR" + ./devnet down || true + ) + fi + + pkill -f "$PUSH_CHAIN_DIR/build/pchaind start" >/dev/null 2>&1 || true + pkill -f "$PUSH_CHAIN_DIR/build/puniversald" >/dev/null 2>&1 || true + + log_ok "Running nodes stopped" +} + +step_print_genesis() { + require_cmd jq + local accounts_json + if ! accounts_json="$(get_genesis_accounts_json)"; then + log_err "Could not resolve genesis accounts from $GENESIS_ACCOUNTS_JSON or docker container core-validator-1" + exit 1 + fi + + jq -r '.[0] | "Account: \(.name)\nAddress: \(.address)\nMnemonic: \(.mnemonic)"' <<<"$accounts_json" +} + +step_recover_genesis_key() { + require_cmd "$PUSH_CHAIN_DIR/build/pchaind" jq + + local mnemonic="${GENESIS_MNEMONIC:-}" + if [[ -z "$mnemonic" ]]; then + local accounts_json + accounts_json="$(get_genesis_accounts_json || true)" + if [[ -n "$accounts_json" ]]; then + mnemonic="$(jq -r --arg n "$GENESIS_KEY_NAME" ' + (first(.[] | select(.name == $n) | .mnemonic) // first(.[].mnemonic) // "") + ' <<<"$accounts_json")" + fi + fi + + if [[ -z "$mnemonic" ]]; then + log_err "Could not auto-resolve mnemonic from $GENESIS_ACCOUNTS_JSON or docker container core-validator-1" + log_err "Set GENESIS_MNEMONIC in e2e-tests/.env" + exit 1 + fi + + if "$PUSH_CHAIN_DIR/build/pchaind" keys show "$GENESIS_KEY_NAME" \ + --keyring-backend "$KEYRING_BACKEND" \ + --home "$GENESIS_KEY_HOME" >/dev/null 2>&1; then + log_warn "Key ${GENESIS_KEY_NAME} already exists. Deleting before recover." + "$PUSH_CHAIN_DIR/build/pchaind" keys delete "$GENESIS_KEY_NAME" \ + --keyring-backend "$KEYRING_BACKEND" \ + --home "$GENESIS_KEY_HOME" \ + -y >/dev/null + fi + + log_info "Recovering key ${GENESIS_KEY_NAME}" + printf "%s\n" "$mnemonic" | "$PUSH_CHAIN_DIR/build/pchaind" keys add "$GENESIS_KEY_NAME" \ + --recover \ + --keyring-backend "$KEYRING_BACKEND" \ + --algo eth_secp256k1 \ + --home "$GENESIS_KEY_HOME" >/dev/null + + log_ok "Recovered key ${GENESIS_KEY_NAME}" +} + +step_fund_account() { + require_cmd "$PUSH_CHAIN_DIR/build/pchaind" + + local to_addr="${FUND_TO_ADDRESS:-}" + if [[ -z "$to_addr" ]]; then + log_err "Set FUND_TO_ADDRESS in e2e-tests/.env" + exit 1 + fi + if ! validate_eth_address "$to_addr" && [[ ! "$to_addr" =~ ^push1[0-9a-z]+$ ]]; then + log_err "Invalid FUND_TO_ADDRESS: $to_addr" + exit 1 + fi + + log_info "Funding $to_addr with $FUND_AMOUNT" + "$PUSH_CHAIN_DIR/build/pchaind" tx bank send "$GENESIS_KEY_NAME" "$to_addr" "$FUND_AMOUNT" \ + --gas-prices "$GAS_PRICES" \ + --keyring-backend "$KEYRING_BACKEND" \ + --chain-id "$CHAIN_ID" \ + --home "$GENESIS_KEY_HOME" \ + -y + + log_ok "Funding transaction submitted" +} + +step_update_env_fund_to_address() { + require_cmd jq + ENV_FILE="$SCRIPT_DIR/.env" + if [[ ! -f "$ENV_FILE" ]]; then + log_err ".env file not found in e2e-tests folder" + exit 1 + fi + PRIVATE_KEY=$(grep '^PRIVATE_KEY=' "$ENV_FILE" | cut -d= -f2 | tr -d '"' | tr -d "'") + if [[ -z "$PRIVATE_KEY" ]]; then + log_err "PRIVATE_KEY not found in .env" + exit 1 + fi + if ! command -v $PUSH_CHAIN_DIR/build/pchaind >/dev/null 2>&1; then + log_err "pchaind binary not found in build/ (run make build)" + exit 1 + fi + EVM_ADDRESS=$(cast wallet address $PRIVATE_KEY) + COSMOS_ADDRESS=$($PUSH_CHAIN_DIR/build/pchaind debug addr $(echo $EVM_ADDRESS | tr '[:upper:]' '[:lower:]' | sed 's/^0x//') | awk -F': ' '/Bech32 Acc:/ {print $2; exit}') + if [[ -z "$COSMOS_ADDRESS" ]]; then + log_err "Could not derive cosmos address from $EVM_ADDRESS" + exit 1 + fi + if grep -q '^FUND_TO_ADDRESS=' "$ENV_FILE"; then + sed -i.bak "s|^FUND_TO_ADDRESS=.*$|FUND_TO_ADDRESS=$COSMOS_ADDRESS|" "$ENV_FILE" + else + echo "FUND_TO_ADDRESS=$COSMOS_ADDRESS" >> "$ENV_FILE" + fi + # Refresh .env after updating FUND_TO_ADDRESS + set -a + source "$SCRIPT_DIR/.env" + set +a + log_ok "Updated FUND_TO_ADDRESS in .env to $COSMOS_ADDRESS" +} + +parse_core_prc20_logs() { + local log_file="$1" + local current_addr="" + local line + + while IFS= read -r line; do + if [[ "$line" =~ PRC20[[:space:]]deployed[[:space:]]at:[[:space:]](0x[a-fA-F0-9]{40}) ]]; then + current_addr="${BASH_REMATCH[1]}" + continue + fi + + if [[ -n "$current_addr" && "$line" =~ Name:[[:space:]](.+)[[:space:]]Symbol:[[:space:]]([A-Za-z0-9._-]+)$ ]]; then + local token_name="${BASH_REMATCH[1]}" + local token_symbol="${BASH_REMATCH[2]}" + record_token "$token_name" "$token_symbol" "$current_addr" "core-contracts" + current_addr="" + fi + done <"$log_file" +} + +enrich_core_token_decimals() { + require_cmd jq cast + ensure_deploy_file + + local addr decimals tmp + while IFS= read -r addr; do + [[ -n "$addr" ]] || continue + decimals="$(cast call "$addr" "decimals()(uint8)" --rpc-url "$PUSH_RPC_URL" 2>/dev/null || true)" + decimals="$(echo "$decimals" | tr -d '[:space:]')" + + if [[ "$decimals" =~ ^[0-9]+$ ]]; then + tmp="$(mktemp)" + jq --arg addr "$addr" --argjson dec "$decimals" ' + .tokens |= map( + if ((.address | ascii_downcase) == ($addr | ascii_downcase)) + then . + {decimals: $dec} + else . + end + ) + ' "$DEPLOY_ADDRESSES_FILE" >"$tmp" + mv "$tmp" "$DEPLOY_ADDRESSES_FILE" + log_ok "Resolved token decimals: $addr => $decimals" + else + log_warn "Could not resolve decimals() for token $addr" + fi + done < <(jq -r '.tokens[]? | select(.decimals == null) | .address' "$DEPLOY_ADDRESSES_FILE") +} + +step_setup_core_contracts() { + require_cmd git forge jq + [[ -n "${PRIVATE_KEY:-}" ]] || { log_err "Set PRIVATE_KEY in e2e-tests/.env"; exit 1; } + + ensure_deploy_file + clone_or_update_repo "$CORE_CONTRACTS_REPO" "$CORE_CONTRACTS_BRANCH" "$CORE_CONTRACTS_DIR" + + log_info "Running forge build in core contracts" + (cd "$CORE_CONTRACTS_DIR" && forge build) + + local log_file="$LOG_DIR/core_setup_$(date +%Y%m%d_%H%M%S).log" + local failed=0 + local resume_attempt=1 + local resume_max_attempts="${CORE_RESUME_MAX_ATTEMPTS:-0}" # 0 = unlimited + + log_info "Running local core setup script" + ( + cd "$CORE_CONTRACTS_DIR" + forge script scripts/localSetup/setup.s.sol \ + --broadcast \ + --rpc-url "$PUSH_RPC_URL" \ + --private-key "$PRIVATE_KEY" \ + --slow + ) 2>&1 | tee "$log_file" || failed=1 + + if [[ "$failed" -ne 0 ]]; then + log_warn "Initial run failed. Retrying with --resume until success" + while true; do + log_info "Resume attempt: $resume_attempt" + if ( + cd "$CORE_CONTRACTS_DIR" + forge script scripts/localSetup/setup.s.sol \ + --broadcast \ + --rpc-url "$PUSH_RPC_URL" \ + --private-key "$PRIVATE_KEY" \ + --slow \ + --resume + ) 2>&1 | tee -a "$log_file"; then + break + fi + + if [[ "$resume_max_attempts" != "0" && "$resume_attempt" -ge "$resume_max_attempts" ]]; then + log_err "Reached CORE_RESUME_MAX_ATTEMPTS=$resume_max_attempts without success" + exit 1 + fi + + resume_attempt=$((resume_attempt + 1)) + sleep 2 + done + fi + + parse_core_prc20_logs "$log_file" + enrich_core_token_decimals + log_ok "Core contracts setup complete" +} + +find_first_address_with_keywords() { + local log_file="$1" + shift + local pattern + pattern="$(printf '%s|' "$@")" + pattern="${pattern%|}" + grep -Ei "$pattern" "$log_file" | grep -Eo '0x[a-fA-F0-9]{40}' | tail -1 || true +} + +address_from_deploy_contract() { + local key="$1" + jq -r --arg k "$key" '.contracts[$k] // ""' "$DEPLOY_ADDRESSES_FILE" +} + +address_from_deploy_token() { + local sym="$1" + jq -r --arg s "$sym" 'first(.tokens[]? | select((.symbol|ascii_downcase) == ($s|ascii_downcase)) | .address) // ""' "$DEPLOY_ADDRESSES_FILE" +} + +resolve_peth_token_address() { + local addr="" + addr="$(address_from_deploy_token "pETH")" + [[ -n "$addr" ]] || addr="$(address_from_deploy_token "WETH")" + if [[ -z "$addr" ]]; then + addr="$(jq -r 'first(.tokens[]? | select((.name|ascii_downcase) | test("eth")) | .address) // ""' "$DEPLOY_ADDRESSES_FILE")" + fi + printf "%s" "$addr" +} + +assert_required_addresses() { + ensure_deploy_file + local required=("WPC" "Factory" "QuoterV2" "SwapRouter") + local missing=0 + local key val + + for key in "${required[@]}"; do + val="$(address_from_deploy_contract "$key")" + if [[ -z "$val" ]]; then + log_warn "Missing address in deploy file: contracts.$key" + missing=1 + else + log_ok "contracts.$key=$val" + fi + done + + if [[ "$missing" -ne 0 ]]; then + log_warn "Some addresses are missing in $DEPLOY_ADDRESSES_FILE; continuing with available values" + fi +} + +step_write_core_env() { + require_cmd jq + ensure_deploy_file + assert_required_addresses + + local core_env="$CORE_CONTRACTS_DIR/.env" + local wpc factory quoter router + wpc="$(address_from_deploy_contract "WPC")" + factory="$(address_from_deploy_contract "Factory")" + quoter="$(address_from_deploy_contract "QuoterV2")" + router="$(address_from_deploy_contract "SwapRouter")" + + log_info "Writing core-contracts .env" + { + echo "PUSH_RPC_URL=$PUSH_RPC_URL" + echo "PRIVATE_KEY=$PRIVATE_KEY" + echo "WPC_ADDRESS=$wpc" + echo "FACTORY_ADDRESS=$factory" + echo "QUOTER_V2_ADDRESS=$quoter" + echo "SWAP_ROUTER_ADDRESS=$router" + echo "WPC=$wpc" + echo "UNISWAP_V3_FACTORY=$factory" + echo "UNISWAP_V3_QUOTER=$quoter" + echo "UNISWAP_V3_ROUTER=$router" + echo "" + echo "# Tokens deployed from core setup" + jq -r '.tokens | to_entries[]? | "TOKEN" + ((.key + 1)|tostring) + "=" + .value.address' "$DEPLOY_ADDRESSES_FILE" + } >"$core_env" + + log_ok "Generated $core_env" +} + +step_update_eth_token_config() { + step_update_deployed_token_configs +} + +norm_token_key() { + local s="$1" + s="$(echo "$s" | tr '[:upper:]' '[:lower:]')" + s="$(echo "$s" | sed -E 's/[^a-z0-9]+//g')" + printf "%s" "$s" +} + +norm_token_key_without_leading_p() { + local s + s="$(norm_token_key "$1")" + if [[ "$s" == p* && ${#s} -gt 1 ]]; then + printf "%s" "${s#p}" + else + printf "%s" "$s" + fi +} + +find_matching_token_config_file() { + local deployed_symbol="$1" + local deployed_name="$2" + local best_file="" + local best_score=0 + + local d_sym d_name d_sym_np d_name_np + d_sym="$(norm_token_key "$deployed_symbol")" + d_name="$(norm_token_key "$deployed_name")" + d_sym_np="$(norm_token_key_without_leading_p "$deployed_symbol")" + d_name_np="$(norm_token_key_without_leading_p "$deployed_name")" + + local file f_sym f_name f_base f_sym_np f_name_np score + while IFS= read -r file; do + [[ -f "$file" ]] || continue + f_sym="$(jq -r '.symbol // ""' "$file")" + f_name="$(jq -r '.name // ""' "$file")" + f_base="$(basename "$file" .json)" + + f_sym="$(norm_token_key "$f_sym")" + f_name="$(norm_token_key "$f_name")" + f_base="$(norm_token_key "$f_base")" + f_sym_np="$(norm_token_key_without_leading_p "$f_sym")" + f_name_np="$(norm_token_key_without_leading_p "$f_name")" + + score=0 + [[ -n "$d_sym" && "$d_sym" == "$f_sym" ]] && score=$((score + 100)) + [[ -n "$d_name" && "$d_name" == "$f_name" ]] && score=$((score + 90)) + [[ -n "$d_sym_np" && "$d_sym_np" == "$f_sym" ]] && score=$((score + 80)) + [[ -n "$d_name_np" && "$d_name_np" == "$f_name" ]] && score=$((score + 70)) + [[ -n "$d_sym" && "$d_sym" == "$f_name" ]] && score=$((score + 60)) + [[ -n "$d_name" && "$d_name" == "$f_sym" ]] && score=$((score + 60)) + [[ -n "$d_sym_np" && "$f_base" == *"$d_sym_np"* ]] && score=$((score + 30)) + [[ -n "$d_name_np" && "$f_base" == *"$d_name_np"* ]] && score=$((score + 20)) + + if (( score > best_score )); then + best_score=$score + best_file="$file" + fi + done < <(find "$TOKENS_CONFIG_DIR" -type f -path '*/tokens/*.json' | sort) + + if (( best_score >= 60 )); then + printf "%s" "$best_file" + fi +} + +step_update_deployed_token_configs() { + require_cmd jq + ensure_deploy_file + + if [[ ! -d "$TOKENS_CONFIG_DIR" ]]; then + log_err "Tokens config directory missing: $TOKENS_CONFIG_DIR" + exit 1 + fi + + if ! find "$TOKENS_CONFIG_DIR" -type f -path '*/tokens/*.json' | grep -q .; then + log_err "No token config files found under: $TOKENS_CONFIG_DIR" + exit 1 + fi + + local used_files="" + local updated=0 + local token_json token_symbol token_name token_address match_file tmp + + while IFS= read -r token_json; do + token_symbol="$(echo "$token_json" | jq -r '.symbol // ""')" + token_name="$(echo "$token_json" | jq -r '.name // ""')" + token_address="$(echo "$token_json" | jq -r '.address // ""')" + + [[ -n "$token_address" ]] || continue + match_file="$(find_matching_token_config_file "$token_symbol" "$token_name")" + + if [[ -z "$match_file" ]]; then + log_warn "No token config match found for deployed token: $token_symbol ($token_name)" + continue + fi + + if echo "$used_files" | grep -Fxq "$match_file"; then + log_warn "Token config already matched by another token, skipping: $(basename "$match_file")" + continue + fi + + tmp="$(mktemp)" + jq --arg a "$token_address" '.native_representation.contract_address = $a' "$match_file" >"$tmp" + mv "$tmp" "$match_file" + used_files+="$match_file"$'\n' + updated=$((updated + 1)) + log_ok "Updated $(basename "$match_file") contract_address => $token_address" + done < <(jq -c '.tokens[]?' "$DEPLOY_ADDRESSES_FILE") + + if [[ "$updated" -eq 0 ]]; then + log_warn "No token config files were updated from deployed tokens" + else + log_ok "Updated $updated token config file(s) from deployed tokens" + fi +} + +step_setup_swap_amm() { + require_cmd git node npm npx jq + [[ -n "${PRIVATE_KEY:-}" ]] || { log_err "Set PRIVATE_KEY in e2e-tests/.env"; exit 1; } + + ensure_deploy_file + clone_or_update_repo "$SWAP_AMM_REPO" "$SWAP_AMM_BRANCH" "$SWAP_AMM_DIR" + + log_info "Installing swap-amm dependencies" + ( + cd "$SWAP_AMM_DIR" + npm install + (cd v3-core && npm install) + (cd v3-periphery && npm install) + ) + + log_info "Writing swap repo .env from main e2e .env" + cat >"$SWAP_AMM_DIR/.env" <&1 | tee "$wpc_log" + + local wpc_addr + wpc_addr="$(find_first_address_with_keywords "$wpc_log" wpc wpush wrapped)" + if [[ -n "$wpc_addr" ]]; then + record_contract "WPC" "$wpc_addr" + else + log_warn "Could not auto-detect WPC address from logs" + fi + + local core_log="$LOG_DIR/swap_core_$(date +%Y%m%d_%H%M%S).log" + log_info "Deploying v3-core" + ( + cd "$SWAP_AMM_DIR/v3-core" + npx hardhat compile + npx hardhat run scripts/deploy-core.js --network pushchain + ) 2>&1 | tee "$core_log" + + local factory_addr + factory_addr="$(grep -E 'Factory Address|FACTORY_ADDRESS=' "$core_log" | grep -Eo '0x[a-fA-F0-9]{40}' | tail -1 || true)" + if [[ -n "$factory_addr" ]]; then + record_contract "Factory" "$factory_addr" + else + log_warn "Could not auto-detect Factory address from logs" + fi + + local periphery_log="$LOG_DIR/swap_periphery_$(date +%Y%m%d_%H%M%S).log" + log_info "Deploying v3-periphery" + ( + cd "$SWAP_AMM_DIR/v3-periphery" + npx hardhat compile + npx hardhat run scripts/deploy-periphery.js --network pushchain + ) 2>&1 | tee "$periphery_log" + + local swap_router quoter_v2 position_manager + swap_router="$(grep -E 'SwapRouter' "$periphery_log" | grep -Eo '0x[a-fA-F0-9]{40}' | tail -1 || true)" + quoter_v2="$(grep -E 'QuoterV2' "$periphery_log" | grep -Eo '0x[a-fA-F0-9]{40}' | tail -1 || true)" + position_manager="$(grep -E 'PositionManager' "$periphery_log" | grep -Eo '0x[a-fA-F0-9]{40}' | tail -1 || true)" + wpc_addr="$(grep -E '^.*WPC:' "$periphery_log" | grep -Eo '0x[a-fA-F0-9]{40}' | tail -1 || true)" + + [[ -n "$swap_router" ]] && record_contract "SwapRouter" "$swap_router" + [[ -n "$quoter_v2" ]] && record_contract "QuoterV2" "$quoter_v2" + [[ -n "$position_manager" ]] && record_contract "PositionManager" "$position_manager" + [[ -n "$wpc_addr" ]] && record_contract "WPC" "$wpc_addr" + + assert_required_addresses + + log_ok "Swap AMM setup complete" +} + +step_setup_gateway() { + require_cmd git forge + [[ -n "${PRIVATE_KEY:-}" ]] || { log_err "Set PRIVATE_KEY in e2e-tests/.env"; exit 1; } + + clone_or_update_repo "$GATEWAY_REPO" "$GATEWAY_BRANCH" "$GATEWAY_DIR" + + log_info "Preparing gateway repo submodules" + ( + cd "$GATEWAY_DIR" + if [[ -d "contracts/svm-gateway/mock-pyth" ]]; then + git rm --cached contracts/svm-gateway/mock-pyth || true + rm -rf contracts/svm-gateway/mock-pyth + fi + git submodule update --init --recursive + ) + + local gw_dir="$GATEWAY_DIR/contracts/evm-gateway" + local gw_log="$LOG_DIR/gateway_setup_$(date +%Y%m%d_%H%M%S).log" + local failed=0 + local resume_attempt=1 + local resume_max_attempts="${GATEWAY_RESUME_MAX_ATTEMPTS:-0}" # 0 = unlimited + + log_info "Building gateway evm contracts" + (cd "$gw_dir" && forge build) + + log_info "Running gateway local setup script" + ( + cd "$gw_dir" + forge script scripts/localSetup/setup.s.sol \ + --broadcast \ + --rpc-url "$PUSH_RPC_URL" \ + --private-key "$PRIVATE_KEY" \ + --slow + ) 2>&1 | tee "$gw_log" || failed=1 + + if [[ "$failed" -ne 0 ]]; then + log_warn "Gateway script failed. Retrying with --resume until success" + while true; do + log_info "Gateway resume attempt: $resume_attempt" + if ( + cd "$gw_dir" + forge script scripts/localSetup/setup.s.sol \ + --broadcast \ + --rpc-url "$PUSH_RPC_URL" \ + --private-key "$PRIVATE_KEY" \ + --slow \ + --resume + ) 2>&1 | tee -a "$gw_log"; then + break + fi + + if [[ "$resume_max_attempts" != "0" && "$resume_attempt" -ge "$resume_max_attempts" ]]; then + log_err "Reached GATEWAY_RESUME_MAX_ATTEMPTS=$resume_max_attempts without success" + exit 1 + fi + + resume_attempt=$((resume_attempt + 1)) + sleep 2 + done + fi + + log_ok "Gateway setup complete" +} + +step_add_uregistry_configs() { + require_cmd "$PUSH_CHAIN_DIR/build/pchaind" jq + + [[ -d "$TOKENS_CONFIG_DIR" ]] || { log_err "Missing tokens config directory: $TOKENS_CONFIG_DIR"; exit 1; } + + local token_payload + + run_registry_tx() { + local kind="$1" + local payload="$2" + local max_attempts=10 + local attempt=1 + local out code raw + + while true; do + if [[ "$kind" == "chain" ]]; then + out="$("$PUSH_CHAIN_DIR/build/pchaind" tx uregistry add-chain-config \ + --chain-config "$payload" \ + --from "$GENESIS_KEY_NAME" \ + --keyring-backend "$KEYRING_BACKEND" \ + --home "$GENESIS_KEY_HOME" \ + --node tcp://127.0.0.1:26657 \ + --gas-prices "$GAS_PRICES" \ + -y)" + else + out="$("$PUSH_CHAIN_DIR/build/pchaind" tx uregistry add-token-config \ + --token-config "$payload" \ + --from "$GENESIS_KEY_NAME" \ + --keyring-backend "$KEYRING_BACKEND" \ + --home "$GENESIS_KEY_HOME" \ + --node tcp://127.0.0.1:26657 \ + --gas-prices "$GAS_PRICES" \ + -y)" + fi + echo "$out" + if [[ "$out" =~ ^\{ ]]; then + code="$(echo "$out" | jq -r '.code // 1')" + raw="$(echo "$out" | jq -r '.raw_log // ""')" + else + code="$(echo "$out" | awk -F': ' '/^code:/ {print $2; exit}')" + raw="$(echo "$out" | awk -F': ' '/^raw_log:/ {sub(/^\x27|\x27$/, "", $2); print $2; exit}')" + [[ -n "$code" ]] || code="1" + fi + + if [[ "$code" == "0" ]]; then + return 0 + fi + + if [[ "$raw" == *"account sequence mismatch"* && "$attempt" -lt "$max_attempts" ]]; then + log_warn "Sequence mismatch on attempt $attempt/$max_attempts. Retrying..." + attempt=$((attempt + 1)) + sleep 2 + continue + fi + + log_err "Registry tx failed: code=$code raw_log=$raw" + return 1 + done + } + + local chain_config_dir chain_file chain_payload chain_count + chain_config_dir="$TOKENS_CONFIG_DIR" + chain_count=0 + + while IFS= read -r chain_file; do + [[ -f "$chain_file" ]] || continue + chain_payload="$(jq -c . "$chain_file")" + log_info "Adding chain config to uregistry: $(basename "$chain_file")" + run_registry_tx "chain" "$chain_payload" + chain_count=$((chain_count + 1)) + done < <(find "$chain_config_dir" -type f \( -name 'chain.json' -o -name '*_chain_config.json' \) | sort) + + if [[ "$chain_count" -eq 0 ]]; then + log_err "No chain config files found in: $chain_config_dir" + exit 1 + fi + + log_ok "Registered $chain_count chain config(s) from $chain_config_dir" + + local token_json token_file token_addr token_symbol token_name matched_count submitted_files tmp + matched_count=0 + submitted_files="" + + while IFS= read -r token_json; do + token_symbol="$(echo "$token_json" | jq -r '.symbol // ""')" + token_name="$(echo "$token_json" | jq -r '.name // ""')" + token_addr="$(echo "$token_json" | jq -r '.address // ""')" + + [[ -n "$token_addr" ]] || continue + + token_file="$(find_matching_token_config_file "$token_symbol" "$token_name")" + if [[ -z "$token_file" ]]; then + log_warn "No token config match found for deployed token (uregistry): $token_symbol ($token_name)" + continue + fi + + if echo "$submitted_files" | grep -Fxq "$token_file"; then + log_warn "Token config already submitted by another deployed token, skipping: $(basename "$token_file")" + continue + fi + + tmp="$(mktemp)" + jq --arg a "$token_addr" '.native_representation.contract_address = $a' "$token_file" >"$tmp" + mv "$tmp" "$token_file" + + token_payload="$(jq -c . "$token_file")" + log_info "Adding token config to uregistry: $(basename "$token_file") (from $token_symbol)" + run_registry_tx "token" "$token_payload" + + submitted_files+="$token_file"$'\n' + matched_count=$((matched_count + 1)) + done < <(jq -c '.tokens[]?' "$DEPLOY_ADDRESSES_FILE") + + if [[ "$matched_count" -eq 0 ]]; then + log_warn "No token configs were registered from deploy_addresses.json tokens" + else + log_ok "Registered $matched_count token config(s) from deploy_addresses.json" + fi + + log_ok "uregistry chain/token configs added" +} + +step_sync_test_addresses() { + require_cmd jq + ensure_deploy_file + + if [[ ! -f "$TEST_ADDRESSES_PATH" ]]; then + log_err "test-addresses.json not found: $TEST_ADDRESSES_PATH" + exit 1 + fi + + log_info "Syncing deploy addresses into test-addresses.json" + local tmp + tmp="$(mktemp)" + + jq \ + --arg today "$(date +%F)" \ + --arg rpc "$PUSH_RPC_URL" \ + --slurpfile dep "$DEPLOY_ADDRESSES_FILE" \ + ' + ($dep[0]) as $d + | def token_addr($sym): first(($d.tokens[]? | select(.symbol == $sym) | .address), empty); + .lastUpdated = $today + | .network.rpcUrl = $rpc + | if ($d.contracts.Factory // "") != "" then .contracts.factory = $d.contracts.Factory else . end + | if ($d.contracts.WPC // "") != "" then .contracts.WPC = $d.contracts.WPC else . end + | if ($d.contracts.SwapRouter // "") != "" then .contracts.swapRouter = $d.contracts.SwapRouter else . end + | if ($d.contracts.PositionManager // "") != "" then .contracts.positionManager = $d.contracts.PositionManager else . end + | if ($d.contracts.QuoterV2 // "") != "" then .contracts.quoterV2 = $d.contracts.QuoterV2 else . end + | .testTokens |= with_entries( + .value.address = (token_addr(.key) // .value.address) + ) + | .testTokens = ( + .testTokens as $existing + | $existing + + ( + reduce ($d.tokens[]?) as $t ({}; + .[$t.symbol] = { + name: $t.name, + symbol: $t.symbol, + address: $t.address, + decimals: ($t.decimals // ($existing[$t.symbol].decimals // null)), + totalSupply: ($existing[$t.symbol].totalSupply // "") + } + ) + ) + ) + | .pools |= with_entries( + .value.token0 = (token_addr(.value.token0Symbol) // .value.token0) + | .value.token1 = (token_addr(.value.token1Symbol) // .value.token1) + ) + ' "$TEST_ADDRESSES_PATH" >"$tmp" + + mv "$tmp" "$TEST_ADDRESSES_PATH" + log_ok "Updated $TEST_ADDRESSES_PATH" +} + +step_create_all_wpc_pools() { + require_cmd node cast "$PUSH_CHAIN_DIR/build/pchaind" + ensure_deploy_file + + [[ -n "${PRIVATE_KEY:-}" ]] || { log_err "Set PRIVATE_KEY in e2e-tests/.env"; exit 1; } + + if [[ ! -f "$TEST_ADDRESSES_PATH" ]]; then + log_err "Missing test-addresses.json at $TEST_ADDRESSES_PATH" + exit 1 + fi + + local wpc_addr token_count token_addr token_symbol + wpc_addr="$(address_from_deploy_contract "WPC")" + if [[ -z "$wpc_addr" ]]; then + log_err "Missing WPC contract address in $DEPLOY_ADDRESSES_FILE" + exit 1 + fi + + token_count="$(jq -r '.tokens | length' "$DEPLOY_ADDRESSES_FILE")" + if [[ "$token_count" == "0" ]]; then + log_warn "No core tokens found in deploy addresses; skipping pool creation" + return 0 + fi + + local deployer_evm_addr + deployer_evm_addr="$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || true)" + if ! validate_eth_address "$deployer_evm_addr"; then + log_err "Could not resolve deployer EVM address from PRIVATE_KEY" + exit 1 + fi + + local deployer_hex deployer_push_addr + deployer_hex="$(echo "$deployer_evm_addr" | tr '[:upper:]' '[:lower:]' | sed 's/^0x//')" + deployer_push_addr="$("$PUSH_CHAIN_DIR/build/pchaind" debug addr "$deployer_hex" 2>/dev/null | awk -F': ' '/Bech32 Acc:/ {print $2; exit}')" + if [[ -z "$deployer_push_addr" ]]; then + log_err "Could not derive bech32 deployer address from $deployer_evm_addr" + exit 1 + fi + + log_info "Funding deployer $deployer_push_addr ($deployer_evm_addr) for pool creation ($POOL_CREATION_TOPUP_AMOUNT)" + local fund_attempt=1 + local fund_max_attempts=5 + local fund_out="" + while true; do + fund_out="$("$PUSH_CHAIN_DIR/build/pchaind" tx bank send "$GENESIS_KEY_NAME" "$deployer_push_addr" "$POOL_CREATION_TOPUP_AMOUNT" \ + --gas-prices "$GAS_PRICES" \ + --keyring-backend "$KEYRING_BACKEND" \ + --chain-id "$CHAIN_ID" \ + --home "$GENESIS_KEY_HOME" \ + -y 2>&1 || true)" + + if echo "$fund_out" | grep -q 'txhash:' || echo "$fund_out" | grep -q '"txhash"'; then + log_ok "Deployer funding transaction submitted" + break + fi + + if echo "$fund_out" | grep -qi 'account sequence mismatch' && [[ "$fund_attempt" -lt "$fund_max_attempts" ]]; then + log_warn "Funding sequence mismatch on attempt $fund_attempt/$fund_max_attempts. Retrying..." + fund_attempt=$((fund_attempt + 1)) + sleep 2 + continue + fi + + log_err "Failed to fund deployer for pool creation" + echo "$fund_out" + exit 1 + done + sleep 2 + + while IFS=$'\t' read -r token_symbol token_addr; do + [[ -n "$token_addr" ]] || continue + if [[ "$(echo "$token_addr" | tr '[:upper:]' '[:lower:]')" == "$(echo "$wpc_addr" | tr '[:upper:]' '[:lower:]')" ]]; then + continue + fi + + log_info "Creating ${token_symbol}/WPC pool with liquidity" + ( + cd "$SWAP_AMM_DIR" + node scripts/pool-manager.js create-pool "$token_addr" "$wpc_addr" 4 500 true 1 4 + ) + done < <(jq -r '.tokens[]? | [.symbol, .address] | @tsv' "$DEPLOY_ADDRESSES_FILE") + + log_ok "All token/WPC pool creation commands completed" +} + +step_configure_universal_core() { + require_cmd forge + [[ -n "${PRIVATE_KEY:-}" ]] || { log_err "Set PRIVATE_KEY in e2e-tests/.env"; exit 1; } + + # configureUniversalCore depends on values from core .env + step_write_core_env + + local script_path="scripts/localSetup/configureUniversalCore.s.sol" + local log_file="$LOG_DIR/core_configure_$(date +%Y%m%d_%H%M%S).log" + local resume_attempt=1 + local resume_max_attempts="${CORE_CONFIGURE_RESUME_MAX_ATTEMPTS:-0}" # 0 = unlimited + + if [[ ! -f "$CORE_CONTRACTS_DIR/$script_path" ]]; then + log_warn "configureUniversalCore script not found at $CORE_CONTRACTS_DIR/$script_path; skipping" + return 0 + fi + + log_info "Running configureUniversalCore script" + if ( + cd "$CORE_CONTRACTS_DIR" + forge script "$script_path" \ + --broadcast \ + --rpc-url "$PUSH_RPC_URL" \ + --private-key "$PRIVATE_KEY" \ + --slow + ) 2>&1 | tee "$log_file"; then + log_ok "configureUniversalCore completed" + return 0 + fi + + log_warn "configureUniversalCore failed. Retrying with --resume until success" + while true; do + log_info "configureUniversalCore resume attempt: $resume_attempt" + if ( + cd "$CORE_CONTRACTS_DIR" + forge script "$script_path" \ + --broadcast \ + --rpc-url "$PUSH_RPC_URL" \ + --private-key "$PRIVATE_KEY" \ + --slow \ + --resume + ) 2>&1 | tee -a "$log_file"; then + log_ok "configureUniversalCore resumed successfully" + return 0 + fi + + if [[ "$resume_max_attempts" != "0" && "$resume_attempt" -ge "$resume_max_attempts" ]]; then + log_err "Reached CORE_CONFIGURE_RESUME_MAX_ATTEMPTS=$resume_max_attempts without success" + exit 1 + fi + + resume_attempt=$((resume_attempt + 1)) + sleep 2 + done +} + +cmd_all() { + (cd "$PUSH_CHAIN_DIR" && make replace-addresses) + (cd "$PUSH_CHAIN_DIR" && make build) + step_update_env_fund_to_address + step_stop_running_nodes + step_devnet + step_recover_genesis_key + step_fund_account + step_setup_core_contracts + step_setup_swap_amm + step_sync_test_addresses + step_create_all_wpc_pools + assert_required_addresses + step_write_core_env + step_configure_universal_core + step_update_eth_token_config + step_setup_gateway + step_add_uregistry_configs +} + +cmd_show_help() { + cat < + +Commands: + devnet Build/start local-multi-validator devnet + uvalidators + print-genesis Print first genesis account + mnemonic + recover-genesis-key Recover genesis key into local keyring + fund Fund FUND_TO_ADDRESS from genesis key + setup-core Clone/build/setup core contracts (auto resume on failure) + setup-swap Clone/install/deploy swap AMM contracts + sync-addresses Apply deploy_addresses.json into test-addresses.json + create-pool Create WPC pools for all deployed core tokens + configure-core Run configureUniversalCore.s.sol (auto --resume retries) + check-addresses Check/report deploy addresses (WPC/Factory/QuoterV2/SwapRouter) + write-core-env Create core-contracts .env from deploy_addresses.json + update-token-config Update eth_sepolia_eth.json contract_address using deployed token + setup-gateway Clone/setup gateway repo and run forge localSetup (with --resume retry) + setup-sdk Clone/setup push-chain-sdk, generate SDK .env from e2e .env, and install dependencies + sdk-test-all Replace PUSH_NETWORK TESTNET variants with LOCALNET and run all configured SDK E2E tests + sdk-test-pctx-last-transaction Run pctx-last-transaction.spec.ts + sdk-test-send-to-self Run send-to-self.spec.ts + sdk-test-progress-hook Run progress-hook-per-tx.spec.ts + sdk-test-bridge-multicall Run bridge-multicall.spec.ts + sdk-test-pushchain Run pushchain.spec.ts + sdk-test-bridge-hooks Run bridge-hooks.spec.ts + add-uregistry-configs Submit chain + token config txs via local-multi-validator validator1 + record-contract K A Manually record contract key/address + record-token N S A Manually record token name/symbol/address + all Run full setup pipeline + help Show this help + +Primary files: + Env: $ENV_FILE + Address: $DEPLOY_ADDRESSES_FILE +EOF +} + +main() { + local cmd="${1:-help}" + case "$cmd" in + devnet) step_devnet ;; + print-genesis) step_print_genesis ;; + recover-genesis-key) step_recover_genesis_key ;; + fund) step_fund_account ;; + setup-core) step_setup_core_contracts ;; + setup-swap) step_setup_swap_amm ;; + sync-addresses) step_sync_test_addresses ;; + create-pool) step_create_all_wpc_pools ;; + configure-core) step_configure_universal_core ;; + check-addresses) assert_required_addresses ;; + write-core-env) step_write_core_env ;; + update-token-config) step_update_deployed_token_configs ;; + setup-gateway) step_setup_gateway ;; + setup-sdk) step_setup_push_chain_sdk ;; + sdk-test-all) step_run_sdk_tests_all ;; + sdk-test-pctx-last-transaction) step_run_sdk_test_file "pctx-last-transaction.spec.ts" ;; + sdk-test-send-to-self) step_run_sdk_test_file "send-to-self.spec.ts" ;; + sdk-test-progress-hook) step_run_sdk_test_file "progress-hook-per-tx.spec.ts" ;; + sdk-test-bridge-multicall) step_run_sdk_test_file "bridge-multicall.spec.ts" ;; + sdk-test-pushchain) step_run_sdk_test_file "pushchain.spec.ts" ;; + sdk-test-bridge-hooks) step_run_sdk_test_file "bridge-hooks.spec.ts" ;; + add-uregistry-configs) step_add_uregistry_configs ;; + record-contract) + ensure_deploy_file + [[ $# -eq 3 ]] || { log_err "Usage: $0 record-contract
"; exit 1; } + validate_eth_address "$3" || { log_err "Invalid address: $3"; exit 1; } + record_contract "$2" "$3" + ;; + record-token) + ensure_deploy_file + [[ $# -eq 4 ]] || { log_err "Usage: $0 record-token
"; exit 1; } + validate_eth_address "$4" || { log_err "Invalid address: $4"; exit 1; } + record_token "$2" "$3" "$4" "manual" + ;; + all) cmd_all ;; + help|--help|-h) cmd_show_help ;; + *) log_err "Unknown command: $cmd"; cmd_show_help; exit 1 ;; + esac +} + +main "$@" \ No newline at end of file diff --git a/local-multi-validator/README.md b/local-multi-validator/README.md index 818b72f6..bb8d6be2 100644 --- a/local-multi-validator/README.md +++ b/local-multi-validator/README.md @@ -37,6 +37,28 @@ docker compose up --build - Auto-builds base image if missing (~15-20 min first time) - Pulls core/universal from cache or builds locally - Starts all 8 validators +- Auto-sets `event_start_from` to latest height/slot for Sepolia, Base Sepolia, Arbitrum Sepolia, BSC testnet, and Solana devnet + +### Event Start Heights/Slots + +On `./devnet start`, the script fetches latest chain heights/slots and injects them into each universal validator config: + +- `chain_configs["eip155:11155111"].event_start_from = ` +- `chain_configs["eip155:84532"].event_start_from = ` +- `chain_configs["eip155:421614"].event_start_from = ` +- `chain_configs["eip155:97"].event_start_from = ` +- `chain_configs["solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"].event_start_from = ` + +You can override any of them manually at startup: + +```bash +SEPOLIA_EVENT_START_FROM=12345678 \ +BASE_EVENT_START_FROM=23456789 \ +ARBITRUM_EVENT_START_FROM=34567890 \ +BSC_EVENT_START_FROM=45678901 \ +SOLANA_EVENT_START_FROM=56789012 \ +./devnet start +``` ### I Changed Core Validator Code **Files:** `cmd/pchaind/`, `app/`, `x/` modules @@ -133,6 +155,16 @@ docker compose up -d # Start containers directly | `./devnet push-cache` | Push local images to GCR | | `./devnet refresh-cache` | Force rebuild and push to GCR | +The `start` command also supports: + +| Environment Variable | Description | +|----------------------|-------------| +| `SEPOLIA_EVENT_START_FROM` | Force universal validators to start monitoring Sepolia from a specific block | +| `BASE_EVENT_START_FROM` | Force universal validators to start monitoring Base Sepolia from a specific block | +| `ARBITRUM_EVENT_START_FROM` | Force universal validators to start monitoring Arbitrum Sepolia from a specific block | +| `BSC_EVENT_START_FROM` | Force universal validators to start monitoring BSC testnet from a specific block | +| `SOLANA_EVENT_START_FROM` | Force universal validators to start monitoring Solana devnet from a specific slot | + ## Endpoints | Service | Port | Description | diff --git a/local-multi-validator/devnet b/local-multi-validator/devnet index fd92da91..e8ff9e40 100755 --- a/local-multi-validator/devnet +++ b/local-multi-validator/devnet @@ -15,6 +15,17 @@ cd "$SCRIPT_DIR" # ═══════════════════════════════════════════════════════════════════════════════ GCR_REGISTRY="${GCR_REGISTRY:-gcr.io/push-chain-testnet}" CACHE_TAG="${CACHE_TAG:-latest}" +SEPOLIA_CHAIN_ID="eip155:11155111" +ARBITRUM_SEPOLIA_CHAIN_ID="eip155:421614" +BASE_SEPOLIA_CHAIN_ID="eip155:84532" +BSC_TESTNET_CHAIN_ID="eip155:97" +SOLANA_DEVNET_CHAIN_ID="solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" + +SEPOLIA_DEFAULT_RPC_URL="${SEPOLIA_DEFAULT_RPC_URL:-https://sepolia.drpc.org}" +ARBITRUM_SEPOLIA_DEFAULT_RPC_URL="${ARBITRUM_SEPOLIA_DEFAULT_RPC_URL:-https://arbitrum-sepolia.gateway.tenderly.co}" +BASE_SEPOLIA_DEFAULT_RPC_URL="${BASE_SEPOLIA_DEFAULT_RPC_URL:-https://sepolia.base.org}" +BSC_TESTNET_DEFAULT_RPC_URL="${BSC_TESTNET_DEFAULT_RPC_URL:-https://bsc-testnet-rpc.publicnode.com}" +SOLANA_DEVNET_DEFAULT_RPC_URL="${SOLANA_DEVNET_DEFAULT_RPC_URL:-https://api.devnet.solana.com}" # ═══════════════════════════════════════════════════════════════════════════════ # COLORS @@ -68,6 +79,40 @@ has_buildx() { docker buildx version >/dev/null 2>&1 } +fetch_evm_height() { + local rpc_url="$1" + local response + response=$(curl -sS -X POST "$rpc_url" \ + -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}') + + local hex_height + hex_height=$(echo "$response" | jq -r '.result // empty') + + if [ -z "$hex_height" ] || [ "$hex_height" = "null" ] || [[ ! "$hex_height" =~ ^0x[0-9a-fA-F]+$ ]]; then + return 1 + fi + + echo "$((16#${hex_height#0x}))" +} + +fetch_solana_slot() { + local rpc_url="$1" + local response + response=$(curl -sS -X POST "$rpc_url" \ + -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","id":1,"method":"getSlot","params":[{"commitment":"processed"}]}') + + local slot + slot=$(echo "$response" | jq -r '.result // empty') + + if [ -z "$slot" ] || [ "$slot" = "null" ] || [[ ! "$slot" =~ ^[0-9]+$ ]]; then + return 1 + fi + + echo "$slot" +} + # ═══════════════════════════════════════════════════════════════════════════════ # STATUS HELPERS # ═══════════════════════════════════════════════════════════════════════════════ @@ -413,6 +458,77 @@ cmd_up() { fi fi + local sepolia_start_height="${SEPOLIA_EVENT_START_FROM:-}" + local base_start_height="${BASE_EVENT_START_FROM:-}" + local arbitrum_start_height="${ARBITRUM_EVENT_START_FROM:-}" + local bsc_start_height="${BSC_EVENT_START_FROM:-}" + local solana_start_height="${SOLANA_EVENT_START_FROM:-}" + + if [ -z "$sepolia_start_height" ]; then + if sepolia_start_height=$(fetch_evm_height "$SEPOLIA_DEFAULT_RPC_URL"); then + print_status "Using Sepolia event_start_from: $sepolia_start_height" + else + print_warning "Could not fetch Sepolia latest block from $SEPOLIA_DEFAULT_RPC_URL" + print_warning "Sepolia will use default event_start_from from pushuv config" + sepolia_start_height="" + fi + else + print_status "Using provided SEPOLIA_EVENT_START_FROM: $sepolia_start_height" + fi + + if [ -z "$base_start_height" ]; then + if base_start_height=$(fetch_evm_height "$BASE_SEPOLIA_DEFAULT_RPC_URL"); then + print_status "Using Base Sepolia event_start_from: $base_start_height" + else + print_warning "Could not fetch Base Sepolia latest block from $BASE_SEPOLIA_DEFAULT_RPC_URL" + print_warning "Base Sepolia will use default event_start_from from pushuv config" + base_start_height="" + fi + else + print_status "Using provided BASE_EVENT_START_FROM: $base_start_height" + fi + + if [ -z "$arbitrum_start_height" ]; then + if arbitrum_start_height=$(fetch_evm_height "$ARBITRUM_SEPOLIA_DEFAULT_RPC_URL"); then + print_status "Using Arbitrum Sepolia event_start_from: $arbitrum_start_height" + else + print_warning "Could not fetch Arbitrum Sepolia latest block from $ARBITRUM_SEPOLIA_DEFAULT_RPC_URL" + print_warning "Arbitrum Sepolia will use default event_start_from from pushuv config" + arbitrum_start_height="" + fi + else + print_status "Using provided ARBITRUM_EVENT_START_FROM: $arbitrum_start_height" + fi + + if [ -z "$bsc_start_height" ]; then + if bsc_start_height=$(fetch_evm_height "$BSC_TESTNET_DEFAULT_RPC_URL"); then + print_status "Using BSC testnet event_start_from: $bsc_start_height" + else + print_warning "Could not fetch BSC testnet latest block from $BSC_TESTNET_DEFAULT_RPC_URL" + print_warning "BSC testnet will use default event_start_from from pushuv config" + bsc_start_height="" + fi + else + print_status "Using provided BSC_EVENT_START_FROM: $bsc_start_height" + fi + + if [ -z "$solana_start_height" ]; then + if solana_start_height=$(fetch_solana_slot "$SOLANA_DEVNET_DEFAULT_RPC_URL"); then + print_status "Using Solana devnet event_start_from: $solana_start_height" + else + print_warning "Could not fetch Solana devnet latest slot from $SOLANA_DEVNET_DEFAULT_RPC_URL" + print_warning "Solana devnet will use default event_start_from from pushuv config" + solana_start_height="" + fi + else + print_status "Using provided SOLANA_EVENT_START_FROM: $solana_start_height" + fi + + SEPOLIA_EVENT_START_FROM="$sepolia_start_height" \ + BASE_EVENT_START_FROM="$base_start_height" \ + ARBITRUM_EVENT_START_FROM="$arbitrum_start_height" \ + BSC_EVENT_START_FROM="$bsc_start_height" \ + SOLANA_EVENT_START_FROM="$solana_start_height" \ docker compose up -d # Auto-push to cache if we built locally (populate cache for team) diff --git a/local-multi-validator/docker-compose.yml b/local-multi-validator/docker-compose.yml index a9374086..2c1c39b5 100644 --- a/local-multi-validator/docker-compose.yml +++ b/local-multi-validator/docker-compose.yml @@ -231,6 +231,11 @@ services: - CORE_VALIDATOR_GRPC=core-validator-1:9090 - QUERY_PORT=8080 - TSS_ENABLED=true + - SEPOLIA_EVENT_START_FROM=${SEPOLIA_EVENT_START_FROM:-} + - BASE_EVENT_START_FROM=${BASE_EVENT_START_FROM:-} + - ARBITRUM_EVENT_START_FROM=${ARBITRUM_EVENT_START_FROM:-} + - BSC_EVENT_START_FROM=${BSC_EVENT_START_FROM:-} + - SOLANA_EVENT_START_FROM=${SOLANA_EVENT_START_FROM:-} command: ["/opt/scripts/setup-universal.sh"] depends_on: core-validator-1: @@ -264,6 +269,11 @@ services: - CORE_VALIDATOR_GRPC=core-validator-2:9090 - QUERY_PORT=8080 - TSS_ENABLED=true + - SEPOLIA_EVENT_START_FROM=${SEPOLIA_EVENT_START_FROM:-} + - BASE_EVENT_START_FROM=${BASE_EVENT_START_FROM:-} + - ARBITRUM_EVENT_START_FROM=${ARBITRUM_EVENT_START_FROM:-} + - BSC_EVENT_START_FROM=${BSC_EVENT_START_FROM:-} + - SOLANA_EVENT_START_FROM=${SOLANA_EVENT_START_FROM:-} command: ["/opt/scripts/setup-universal.sh"] depends_on: core-validator-2: @@ -297,6 +307,11 @@ services: - CORE_VALIDATOR_GRPC=core-validator-3:9090 - QUERY_PORT=8080 - TSS_ENABLED=true + - SEPOLIA_EVENT_START_FROM=${SEPOLIA_EVENT_START_FROM:-} + - BASE_EVENT_START_FROM=${BASE_EVENT_START_FROM:-} + - ARBITRUM_EVENT_START_FROM=${ARBITRUM_EVENT_START_FROM:-} + - BSC_EVENT_START_FROM=${BSC_EVENT_START_FROM:-} + - SOLANA_EVENT_START_FROM=${SOLANA_EVENT_START_FROM:-} command: ["/opt/scripts/setup-universal.sh"] depends_on: core-validator-3: @@ -330,6 +345,11 @@ services: - CORE_VALIDATOR_GRPC=core-validator-4:9090 - QUERY_PORT=8080 - TSS_ENABLED=true + - SEPOLIA_EVENT_START_FROM=${SEPOLIA_EVENT_START_FROM:-} + - BASE_EVENT_START_FROM=${BASE_EVENT_START_FROM:-} + - ARBITRUM_EVENT_START_FROM=${ARBITRUM_EVENT_START_FROM:-} + - BSC_EVENT_START_FROM=${BSC_EVENT_START_FROM:-} + - SOLANA_EVENT_START_FROM=${SOLANA_EVENT_START_FROM:-} command: ["/opt/scripts/setup-universal.sh"] depends_on: core-validator-4: diff --git a/local-multi-validator/scripts/setup-universal.sh b/local-multi-validator/scripts/setup-universal.sh index cfb4d3f3..437d739f 100755 --- a/local-multi-validator/scripts/setup-universal.sh +++ b/local-multi-validator/scripts/setup-universal.sh @@ -136,6 +136,44 @@ if [ "$QUERY_PORT" != "8080" ]; then mv "$HOME_DIR/config/pushuv_config.json.tmp" "$HOME_DIR/config/pushuv_config.json" fi +# After initialization and before event start from overrides +# Force Arbitrum Sepolia RPC URL to tenderly endpoint +ARBITRUM_CHAIN_ID="eip155:421614" +ARBITRUM_TENDERLY_URL="https://arbitrum-sepolia.gateway.tenderly.co" +BSC_TESTNET_CHAIN_ID="eip155:97" +BSC_TESTNET_RPC_URL="${BSC_TESTNET_RPC_URL:-https://bsc-testnet-rpc.publicnode.com}" + +jq --arg chain "$ARBITRUM_CHAIN_ID" --arg url "$ARBITRUM_TENDERLY_URL" \ + '.chain_configs[$chain].rpc_urls = [$url]' \ + "$HOME_DIR/config/pushuv_config.json" > "$HOME_DIR/config/pushuv_config.json.tmp" && \ + mv "$HOME_DIR/config/pushuv_config.json.tmp" "$HOME_DIR/config/pushuv_config.json" + +jq --arg chain "$BSC_TESTNET_CHAIN_ID" --arg url "$BSC_TESTNET_RPC_URL" \ + '.chain_configs[$chain].rpc_urls = [$url]' \ + "$HOME_DIR/config/pushuv_config.json" > "$HOME_DIR/config/pushuv_config.json.tmp" && \ + mv "$HOME_DIR/config/pushuv_config.json.tmp" "$HOME_DIR/config/pushuv_config.json" + +# Optionally override chain event start heights (set by ./devnet start) +set_chain_event_start_from() { + local chain_id="$1" + local chain_label="$2" + local start_height="$3" + + [ -n "$start_height" ] || return 0 + + echo "📍 Setting ${chain_label} event_start_from: $start_height" + jq --arg chain "$chain_id" --argjson height "$start_height" \ + '.chain_configs[$chain].event_start_from = $height' \ + "$HOME_DIR/config/pushuv_config.json" > "$HOME_DIR/config/pushuv_config.json.tmp" && \ + mv "$HOME_DIR/config/pushuv_config.json.tmp" "$HOME_DIR/config/pushuv_config.json" +} + +set_chain_event_start_from "eip155:11155111" "Sepolia" "${SEPOLIA_EVENT_START_FROM:-}" +set_chain_event_start_from "eip155:84532" "Base Sepolia" "${BASE_EVENT_START_FROM:-}" +set_chain_event_start_from "eip155:421614" "Arbitrum Sepolia" "${ARBITRUM_EVENT_START_FROM:-}" +set_chain_event_start_from "eip155:97" "BSC testnet" "${BSC_EVENT_START_FROM:-}" +set_chain_event_start_from "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" "Solana devnet" "${SOLANA_EVENT_START_FROM:-}" + # --------------------------- # === SET CORE VALOPER ADDRESS === # --------------------------- diff --git a/local-native/README.md b/local-native/README.md index 50f3cc4d..b911c8f5 100644 --- a/local-native/README.md +++ b/local-native/README.md @@ -49,7 +49,8 @@ cd local-native |---------|-------------| | `./devnet start [n]` | Start n core validators (default: 1) | | `./devnet setup-uvalidators` | Register UVs on-chain + create AuthZ grants | -| `./devnet start-uv [n]` | Start n universal validators (default: 4) | +| `./devnet start-uv [n]` | Start n universal validators (default: 4) and auto-set Sepolia `event_start_from` | +| `./devnet configure` | Manually refresh Sepolia `event_start_from` in existing UV configs | | `./devnet down` | Stop all validators | | `./devnet status` | Show network status | | `./devnet logs [service]` | View logs | diff --git a/local-native/devnet b/local-native/devnet index 081590fa..40f01540 100755 --- a/local-native/devnet +++ b/local-native/devnet @@ -236,6 +236,7 @@ start_validator() { start_universal() { local id=$1 + local sepolia_start_height=${2:-} local pid_file="$DATA_DIR/universal$id.pid" # Check if already running @@ -251,7 +252,7 @@ start_universal() { mkdir -p "$DATA_DIR/universal$id" print_status "Starting universal validator $id..." - UNIVERSAL_ID=$id "$SCRIPT_DIR/scripts/setup-universal.sh" > "$DATA_DIR/universal$id/universal.log" 2>&1 & + UNIVERSAL_ID=$id SEPOLIA_EVENT_START_FROM="$sepolia_start_height" "$SCRIPT_DIR/scripts/setup-universal.sh" > "$DATA_DIR/universal$id/universal.log" 2>&1 & echo $! > "$pid_file" print_success "Universal validator $id started (PID: $(cat $pid_file))" @@ -297,12 +298,19 @@ cmd_up() { cmd_start_uv() { require_binaries print_header "Starting Universal Validators..." + + local sepolia_start_height="" + if ! sepolia_start_height=$(bash "$SCRIPT_DIR/scripts/configure-pushuv.sh" --get-height); then + print_error "Failed to fetch latest Sepolia height" + exit 1 + fi + print_status "Using Sepolia event_start_from: $sepolia_start_height" local num_uv=${1:-4} for i in $(seq 1 $num_uv); do if [ $i -le 4 ]; then - start_universal $i + start_universal $i "$sepolia_start_height" sleep 3 fi done @@ -313,6 +321,14 @@ cmd_start_uv() { cmd_status } +# ═══════════════════════════════════════════════════════════════════════════════ +# CONFIGURE COMMANDS +# ═══════════════════════════════════════════════════════════════════════════════ +cmd_configure() { + print_header "Configuring local-native universal relayer configs..." + bash "$SCRIPT_DIR/scripts/configure-pushuv.sh" +} + # ═══════════════════════════════════════════════════════════════════════════════ # STOP/DOWN COMMANDS # ═══════════════════════════════════════════════════════════════════════════════ @@ -487,6 +503,7 @@ cmd_help() { echo -e "${BOLD}${CYAN}UNIVERSAL VALIDATORS${NC}" printf " ${BOLD}%-20s${NC}%s\n" "setup-uvalidators" "Register UVs and create AuthZ grants" printf " ${BOLD}%-20s${NC}%s\n" "start-uv [n]" "Start n universal validators (default: 4)" + printf " ${BOLD}%-20s${NC}%s\n" "configure" "Set Sepolia event_start_from to latest block" echo echo -e "${BOLD}${CYAN}TSS COMMANDS${NC}" printf " ${BOLD}%-20s${NC}%s\n" "tss-keygen" "Initiate TSS key generation" @@ -536,6 +553,7 @@ case "${1:-help}" in # Universal validators setup-uvalidators) "$SCRIPT_DIR/scripts/setup-uvalidators.sh" ;; start-uv) shift; cmd_start_uv "$@" ;; + configure) cmd_configure ;; # Maintenance clean) cmd_clean ;; diff --git a/local-native/scripts/configure-pushuv.sh b/local-native/scripts/configure-pushuv.sh new file mode 100644 index 00000000..8eea0098 --- /dev/null +++ b/local-native/scripts/configure-pushuv.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LOCAL_NATIVE_DIR="$(cd -P "$SCRIPT_DIR/.." && pwd)" +DATA_DIR="$LOCAL_NATIVE_DIR/data" + +require_bin() { + local bin="$1" + if ! command -v "$bin" >/dev/null 2>&1; then + echo "❌ Required binary not found: $bin" + exit 1 + fi +} + +require_bin curl +require_bin jq + +SEPOLIA_CHAIN_ID="eip155:11155111" +DEFAULT_RPC_URL="https://sepolia.drpc.org" + +# Prefer RPC URL from existing config, fallback to default. +detect_rpc_url() { + local cfg="$1" + jq -r --arg chain "$SEPOLIA_CHAIN_ID" '.chain_configs[$chain].rpc_url[0] // empty' "$cfg" 2>/dev/null || true +} + +fetch_sepolia_height() { + local rpc_url="$1" + local response + response=$(curl -sS -X POST "$rpc_url" \ + -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}') + + local hex_height + hex_height=$(echo "$response" | jq -r '.result // empty') + + if [[ -z "$hex_height" || "$hex_height" == "null" || ! "$hex_height" =~ ^0x[0-9a-fA-F]+$ ]]; then + return 1 + fi + + echo "$((16#${hex_height#0x}))" +} + +find_pushuv_configs() { + find "$DATA_DIR" -type f -path '*/.puniversal/config/pushuv_config.json' | sort +} + +print_only_height() { + local rpc_url="$DEFAULT_RPC_URL" + local height="" + + if ! height=$(fetch_sepolia_height "$rpc_url"); then + echo "❌ Failed to fetch Sepolia block height from $rpc_url" >&2 + exit 1 + fi + + echo "$height" +} + +main() { + if [ "${1:-}" = "--get-height" ]; then + print_only_height + return 0 + fi + + local configs=() + while IFS= read -r cfg; do + configs+=("$cfg") + done < <(find_pushuv_configs) + + if [ "${#configs[@]}" -eq 0 ]; then + echo "❌ No pushuv_config.json files found under $DATA_DIR" + echo " Start universal validators first with: ./devnet start-uv 4" + exit 1 + fi + + local rpc_url="" + rpc_url=$(detect_rpc_url "${configs[0]}") + if [ -z "$rpc_url" ]; then + rpc_url="$DEFAULT_RPC_URL" + fi + + local height="" + if ! height=$(fetch_sepolia_height "$rpc_url"); then + echo "⚠️ Failed using configured RPC ($rpc_url), retrying default RPC ($DEFAULT_RPC_URL)..." + if ! height=$(fetch_sepolia_height "$DEFAULT_RPC_URL"); then + echo "❌ Failed to fetch Sepolia block height from both RPC endpoints" + exit 1 + fi + rpc_url="$DEFAULT_RPC_URL" + fi + + echo "ℹ️ Sepolia latest block height: $height" + echo "ℹ️ RPC used: $rpc_url" + + local updated=0 + for cfg in "${configs[@]}"; do + local tmp + tmp=$(mktemp) + jq --arg chain "$SEPOLIA_CHAIN_ID" --argjson height "$height" \ + '.chain_configs[$chain].event_start_from = $height' \ + "$cfg" > "$tmp" + mv "$tmp" "$cfg" + updated=$((updated + 1)) + echo "✅ Updated: $cfg" + done + + echo "🎉 Updated event_start_from for $updated config file(s)." +} + +main "$@" diff --git a/local-native/scripts/setup-genesis-auto.sh b/local-native/scripts/setup-genesis-auto.sh index 1ab5e6d6..f4ed4c6d 100755 --- a/local-native/scripts/setup-genesis-auto.sh +++ b/local-native/scripts/setup-genesis-auto.sh @@ -109,6 +109,7 @@ update_genesis '.app_state["gov"]["params"]["max_deposit_period"]="300s"' update_genesis '.app_state["gov"]["params"]["voting_period"]="300s"' update_genesis ".app_state[\"evm\"][\"params\"][\"evm_denom\"]=\"$DENOM\"" update_genesis ".app_state[\"evm\"][\"params\"][\"chain_config\"][\"chain_id\"]=$EVM_CHAIN_ID" +update_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000CB","0x00000000000000000000000000000000000000ca","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805"]' update_genesis ".app_state[\"staking\"][\"params\"][\"bond_denom\"]=\"$DENOM\"" update_genesis ".app_state[\"mint\"][\"params\"][\"mint_denom\"]=\"$DENOM\"" update_genesis ".app_state[\"uregistry\"][\"params\"][\"admin\"]=\"$GENESIS_ADDR1\"" diff --git a/local-native/scripts/setup-universal.sh b/local-native/scripts/setup-universal.sh index e8023cde..e90919a0 100755 --- a/local-native/scripts/setup-universal.sh +++ b/local-native/scripts/setup-universal.sh @@ -86,6 +86,14 @@ jq --argjson port "$QUERY_PORT" '.query_server_port = $port' \ "$HOME_DIR/config/pushuv_config.json" > "$HOME_DIR/config/pushuv_config.json.tmp" && \ mv "$HOME_DIR/config/pushuv_config.json.tmp" "$HOME_DIR/config/pushuv_config.json" +# Optionally override Sepolia event start height (set by ./devnet start-uv) +if [ -n "${SEPOLIA_EVENT_START_FROM:-}" ]; then + jq --argjson height "$SEPOLIA_EVENT_START_FROM" \ + '.chain_configs["eip155:11155111"].event_start_from = $height' \ + "$HOME_DIR/config/pushuv_config.json" > "$HOME_DIR/config/pushuv_config.json.tmp" && \ + mv "$HOME_DIR/config/pushuv_config.json.tmp" "$HOME_DIR/config/pushuv_config.json" +fi + # Enable TSS TSS_PRIVATE_KEY=$(printf '%02x' $UNIVERSAL_ID | head -c 2) TSS_PRIVATE_KEY=$(yes $TSS_PRIVATE_KEY | head -32 | tr -d '\n') diff --git a/scripts/replace_addresses.sh b/scripts/replace_addresses.sh new file mode 100644 index 00000000..63ecc00e --- /dev/null +++ b/scripts/replace_addresses.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +ENV_FILE="e2e-tests/.env" +if [[ ! -f "$ENV_FILE" ]]; then + echo "e2e-tests/.env not found" >&2 + exit 1 +fi + +PRIVATE_KEY=$(grep '^PRIVATE_KEY=' "$ENV_FILE" | cut -d= -f2 | tr -d '"' | tr -d "'") +if [[ -z "$PRIVATE_KEY" ]]; then + echo "PRIVATE_KEY not found in $ENV_FILE" >&2 + exit 1 +fi + +# Derive EVM address +if ! command -v cast >/dev/null 2>&1; then + echo "cast command not found (install foundry/cast)" >&2 + exit 1 +fi +EVM_ADDRESS=$(cast wallet address $PRIVATE_KEY) + +# Derive push (cosmos) address +if ! command -v $PWD/build/pchaind >/dev/null 2>&1; then + echo "pchaind binary not found in build/ (run make build)" >&2 + exit 1 +fi +PUSH_ADDRESS=push1gjaw568e35hjc8udhat0xnsxxmkm2snrexxz20 + +echo "Replacing with PUSH_ADDRESS: $PUSH_ADDRESS" +echo "Replacing with EVM_ADDRESS: $EVM_ADDRESS" + +# Replace Admin in params.go files +for f in x/utss/types/params.go x/uregistry/types/params.go x/uvalidator/types/params.go; do + if [[ -f "$f" ]]; then + perl -pi -e "s/Admin: \"push1[0-9a-z]+\"/Admin: \"$PUSH_ADDRESS\"/g" "$f" + echo "Updated Admin in $f" + fi +done + +# Replace PROXY_ADMIN_OWNER_ADDRESS in constants.go files +for f in x/uexecutor/types/constants.go x/uregistry/types/constants.go; do + if [[ -f "$f" ]]; then + perl -pi -e "s/PROXY_ADMIN_OWNER_ADDRESS_HEX = \"0x[a-fA-F0-9]{40}\"/PROXY_ADMIN_OWNER_ADDRESS_HEX = \"$EVM_ADDRESS\"/g" "$f" + perl -pi -e "s/PROXY_ADMIN_OWNER_ADDRESS = \"0x[a-fA-F0-9]{40}\"/PROXY_ADMIN_OWNER_ADDRESS = \"$EVM_ADDRESS\"/g" "$f" + echo "Updated PROXY_ADMIN_OWNER_ADDRESS in $f" + fi +done + +echo "Address replacement completed." diff --git a/universalClient/config/default_config.json b/universalClient/config/default_config.json index 9f4542a3..71924b8b 100644 --- a/universalClient/config/default_config.json +++ b/universalClient/config/default_config.json @@ -49,7 +49,7 @@ }, "eip155:97": { "rpc_urls": [ - "https://binance.llamarpc.com" + "https://bsc-testnet-rpc.publicnode.com" ], "cleanup_interval_seconds": 1800, "retention_period_seconds": 43200,