Skip to content
Merged
54 changes: 52 additions & 2 deletions .github/workflows/dev-test-on-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
strategy:
fail-fast: false
matrix:
suite: [core, workflows, executions, triggers, nodes, templates]
suite: [workflows, triggers] # Other suites (core, executions, nodes, templates) disabled — bundler not reachable from CI, run locally

env:
# Secrets
Expand All @@ -86,6 +86,10 @@ jobs:
CONTEXT_MEMORY_API_KEY: ${{ secrets.CONTEXT_MEMORY_API_KEY }}
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
OPERATOR_ECDSA_KEYSTORE: ${{ secrets.OPERATOR_ECDSA_KEYSTORE }}
OPERATOR_ECDSA_KEY_PASSWORD: ${{ secrets.OPERATOR_ECDSA_KEY_PASSWORD }}
OPERATOR_BLS_KEYSTORE: ${{ secrets.OPERATOR_BLS_KEYSTORE }}
OPERATOR_BLS_KEY_PASSWORD: ${{ secrets.OPERATOR_BLS_KEY_PASSWORD }}
# Variables
TENDERLY_ACCOUNT: ${{ vars.TENDERLY_ACCOUNT }}
TENDERLY_PROJECT: ${{ vars.TENDERLY_PROJECT }}
Expand Down Expand Up @@ -120,9 +124,26 @@ jobs:
echo "All variables substituted correctly."
fi

- name: Inject secrets into operator config
run: |
# Write operator key files from secrets
echo "$OPERATOR_ECDSA_KEYSTORE" > config/operator-ecdsa-key.json
echo "$OPERATOR_BLS_KEYSTORE" > config/operator-bls-key.json

# Generate operator runtime config
export ETH_RPC_URL ETH_WS_URL
envsubst < config/operator.yaml > config/operator.runtime.yaml
echo "Generated config: $(wc -l < config/operator.runtime.yaml) lines"
if grep -q "\${.*}" config/operator.runtime.yaml; then
echo "WARNING: Found unsubstituted variables:"
grep "\${.*}" config/operator.runtime.yaml
else
echo "All variables substituted correctly."
fi

- name: Start aggregator
run: |
docker compose up -d
docker compose up -d aggregator
echo "Waiting for aggregator to start..."
sleep 10
docker compose ps
Expand All @@ -149,6 +170,35 @@ jobs:
exit 1
fi

- name: Start operator
run: |
docker compose up -d operator
echo "Waiting for operator to start..."
sleep 5
docker compose ps
docker compose logs operator --tail=20

- name: Wait for operator health
run: |
max_attempts=24
attempt=0
while [ $attempt -lt $max_attempts ]; do
if curl --output /dev/null --silent --fail --max-time 5 http://localhost:9010/eigen/node/health; then
echo "Operator is ready!"
break
fi
echo "Attempt $((attempt + 1))/$max_attempts: operator not ready yet..."
docker compose logs operator --tail=5
sleep 5
attempt=$((attempt + 1))
done
if [ $attempt -eq $max_attempts ]; then
echo "Operator failed to start"
docker compose ps
docker compose logs operator
exit 1
fi

- name: Generate AVS API key
run: |
API_KEY=$(docker compose exec aggregator /ava create-api-key --config /app/config/aggregator.runtime.yaml --role=admin --subject=apikey 2>/dev/null)
Expand Down
25 changes: 25 additions & 0 deletions GA_E2E_TESTS_STATUS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Github Actions E2E Test Status

## Current State

**Passing suites:** workflows, triggers
**Disabled suites:** core, executions, nodes, templates (run locally only)

## Infrastructure Setup

The CI runs an aggregator and operator via Docker Compose against Sepolia.

### Operator Address (hardcoded)

`0x997E5D40a32c44a3D93E59fC55C4Fd20b7d2d49D` — set in the workflow, not a secret.

## Known Limitation: Bundler Not Reachable from CI

The ERC-4337 bundler (`localhost:4437`) is only accessible locally via SSH tunnel (`ssh -L 4437:localhost:4437 ap-staging1`). GitHub Actions runners cannot reach it, causing all on-chain write operations (withdrawals, contract writes, ETH transfers, triggered workflows with execution) to timeout.

This affects the disabled test suites: core, nodes, templates, executions. These should be run locally where the bundler tunnel is available.

## Test Wallet

Address: `0x689337FdB82Da60FD0D5803b41a1614a2fDd6113` (Smart Wallet, salt:2)
Must be funded with Sepolia ETH for withdraw and transfer tests.
38 changes: 38 additions & 0 deletions config/operator.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
environment: development

operator_address: 0x997E5D40a32c44a3D93E59fC55C4Fd20b7d2d49D

# Sepolia AVS contract addresses (same as aggregator)
avs_registry_coordinator_address: 0xcA95381802FD1398d5BF2D01243210cFb3a2b3BD
operator_state_retriever_address: 0x070E0ABE8407eb4727fC7C5284bdc1e5E5CBf605

# Main RPC endpoints for AVS operations (Sepolia)
eth_rpc_url: ${ETH_RPC_URL}
eth_ws_url: ${ETH_WS_URL}

# Operator key files (written by CI from secrets)
ecdsa_private_key_store_path: /app/config/operator-ecdsa-key.json
bls_private_key_store_path: /app/config/operator-bls-key.json

# Aggregator connection (docker service name)
aggregator_server_ip_port_address: "aggregator:2206"

# AVS node spec compliance
eigen_metrics_ip_port_address: 0.0.0.0:9090
enable_metrics: true
node_api_ip_port_address: 0.0.0.0:9010
enable_node_api: true

# Storage
db_path: /tmp/ap-avs-operator

# Optional monitoring
server_name: "operator-dev-ci"

# Destination chain - Sepolia (same as main chain for dev)
target_chain:
eth_rpc_url: ${ETH_RPC_URL}
eth_ws_url: ${ETH_WS_URL}

enabled_features:
event_trigger: true
14 changes: 14 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,17 @@ services:
- "1323:1323"
volumes:
- ./config:/app/config

operator:
image: avaprotocol/avs-dev:${DOCKER_IMAGE_TAG:-latest}
command:
- "operator"
- "--config"
- "/app/config/operator.runtime.yaml"
ports:
- "9010:9010"
volumes:
- ./config:/app/config
environment:
- OPERATOR_ECDSA_KEY_PASSWORD=${OPERATOR_ECDSA_KEY_PASSWORD}
- OPERATOR_BLS_KEY_PASSWORD=${OPERATOR_BLS_KEY_PASSWORD}
1 change: 1 addition & 0 deletions tests/nodes/balanceNode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,7 @@ describeIfSepolia("BalanceNode Tests", () => {
const expectedAddresses = [
tokens.USDC.address.toLowerCase(),
tokens.LINK.address.toLowerCase(),
tokens.ETH.address.toLowerCase(),
];
expect(expectedAddresses).toContain(token.tokenAddress.toLowerCase());
}
Expand Down
4 changes: 2 additions & 2 deletions tests/templates/batch-recurring-payment-with-email.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ import {
import { getConfig } from "../utils/envalid";

const config = getConfig();
const { sendgridKey } = config;
const { sendgridKey, tokens } = config;

// Use a second EOA-derived address as the transfer recipient for testing
const TEST_RECIPIENT = "0x2e8bdb63d09ef989a0018eeb1c47ef84e3e61f7b";

// Native ETH address (matches studio template's default token)
const NATIVE_ETH_ADDRESS = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
const NATIVE_ETH_ADDRESS = tokens.ETH.address;

// Transfer amount: 0.01 ETH in wei (matches studio template's default)
const TRANSFER_AMOUNT_WEI = "10000000000000000"; // 0.01 ETH
Expand Down
4 changes: 2 additions & 2 deletions tests/templates/recurring-payment-with-report.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ import {
import { getConfig } from "../utils/envalid";

const config = getConfig();
const { telegramBotToken, telegramChatId } = config;
const { telegramBotToken, telegramChatId, tokens } = config;

// Use a second EOA-derived address as the transfer recipient for testing
const TEST_RECIPIENT = "0x2e8bdb63d09ef989a0018eeb1c47ef84e3e61f7b";

// Native ETH address (used in the template for token identification)
const NATIVE_ETH_ADDRESS = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
const NATIVE_ETH_ADDRESS = tokens.ETH.address;

// Transfer amount: 0.0001 ETH in wei (small amount for testing)
const TRANSFER_AMOUNT_WEI = "100000000000000"; // 0.0001 ETH
Expand Down
6 changes: 3 additions & 3 deletions tests/templates/test-single-approve-with-simulation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
getExpiredAt,
} from "../utils/utils";
import { getConfig } from "../utils/envalid";
const { chainId } = getConfig();
const { chainId, tokens } = getConfig();

// Set timeout to 240 seconds for this test suite
jest.setTimeout(TIMEOUT_DURATION * 4);
Expand Down Expand Up @@ -279,7 +279,7 @@ describe("Templates - Test Single Approve with Simulation Parameter", () => {
address: getWorkflowConfig().settings.runner,
chain: getWorkflowConfig().settings.chain,
tokenAddresses: [
"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", // ETH
tokens.ETH.address, // ETH
getWorkflowConfig().settings.uniswapv3_pool.token0.id,
getWorkflowConfig().settings.uniswapv3_pool.token1.id,
],
Expand Down Expand Up @@ -535,7 +535,7 @@ describe("Templates - Test Single Approve with Simulation Parameter", () => {
address: getWorkflowConfig().settings.runner,
chain: getWorkflowConfig().settings.chain,
tokenAddresses: [
"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", // ETH
tokens.ETH.address, // ETH
getWorkflowConfig().settings.uniswapv3_pool.token1.id, // USDC
],
};
Expand Down
Loading