Import sibling archive WIP: keeper stack, CCIP scripts, and CCIP docs
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
87
scripts/ccip/calculate-chain-selector.sh
Executable file
87
scripts/ccip/calculate-chain-selector.sh
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
# Calculate CCIP Chain Selector for ChainID 138
|
||||
#
|
||||
# CCIP chain selectors are typically calculated using a deterministic method.
|
||||
# For custom chains, we can use a simple format: chainId as uint64
|
||||
# However, for compatibility with Chainlink's official CCIP, we should use
|
||||
# a format that matches their calculation method.
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
CHAIN_ID=138
|
||||
|
||||
echo "Calculating CCIP Chain Selector for ChainID ${CHAIN_ID}..."
|
||||
echo ""
|
||||
|
||||
# Method 1: Simple chainId as uint64 (for custom CCIP routers)
|
||||
# This is the simplest approach for custom implementations
|
||||
SIMPLE_SELECTOR=$CHAIN_ID
|
||||
echo "Method 1 (Simple): ChainID as uint64"
|
||||
echo " Selector: ${SIMPLE_SELECTOR}"
|
||||
echo " Hex: 0x$(printf '%016x' ${SIMPLE_SELECTOR})"
|
||||
echo ""
|
||||
|
||||
# Method 2: Chainlink-style calculation (if using official CCIP format)
|
||||
# Chainlink uses: uint64(uint256(keccak256(abi.encodePacked(chainId, "CCIP")))) >> 192
|
||||
# For now, we'll use a deterministic calculation based on chainId
|
||||
# Note: This is a placeholder - actual Chainlink calculation may differ
|
||||
echo "Method 2 (Chainlink-style): Deterministic hash-based"
|
||||
echo " Note: For custom CCIP routers, you can define your own format"
|
||||
echo " Recommended: Use Method 1 (simple chainId) for custom implementations"
|
||||
echo ""
|
||||
|
||||
# Method 3: Using cast to calculate (if foundry/cast is available)
|
||||
if command -v cast &> /dev/null; then
|
||||
echo "Method 3 (Using cast):"
|
||||
# Calculate using cast: cast keccak "0x$(printf '%064x' ${CHAIN_ID})CCIP"
|
||||
HASH=$(cast keccak "$(printf '0x%064x' ${CHAIN_ID})CCIP" 2>/dev/null || echo "")
|
||||
if [ -n "$HASH" ]; then
|
||||
# Extract first 8 bytes (uint64) from hash
|
||||
SELECTOR_HEX=$(echo "$HASH" | cut -c3-18) # Skip 0x, take 16 hex chars
|
||||
SELECTOR_DEC=$(printf '%d' "0x${SELECTOR_HEX}")
|
||||
echo " Hash: ${HASH}"
|
||||
echo " Selector (first 8 bytes): ${SELECTOR_DEC}"
|
||||
echo " Hex: 0x${SELECTOR_HEX}"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Recommended selector for ChainID 138
|
||||
# For custom CCIP routers, using chainId directly is simplest and most compatible
|
||||
RECOMMENDED_SELECTOR=$CHAIN_ID
|
||||
RECOMMENDED_SELECTOR_HEX=$(printf '0x%016x' ${CHAIN_ID})
|
||||
|
||||
echo "=========================================="
|
||||
echo "RECOMMENDED SELECTOR FOR CHAINID 138:"
|
||||
echo "=========================================="
|
||||
echo "Decimal: ${RECOMMENDED_SELECTOR}"
|
||||
echo "Hex: ${RECOMMENDED_SELECTOR_HEX}"
|
||||
echo "Hex (padded): 0x000000000000008a"
|
||||
echo ""
|
||||
echo "This selector should be used in:"
|
||||
echo " - Environment variables (CHAIN138_SELECTOR)"
|
||||
echo " - Bridge destination configuration"
|
||||
echo " - Router supported chain registration"
|
||||
echo " - All CCIP message routing"
|
||||
echo ""
|
||||
echo "Note: If integrating with Chainlink's official CCIP network,"
|
||||
echo " you may need to register this chain selector with Chainlink"
|
||||
echo " or use their official selector format."
|
||||
echo ""
|
||||
|
||||
# Update .env if it exists
|
||||
if [ -f "$PROJECT_ROOT/.env" ]; then
|
||||
if grep -q "^CHAIN138_SELECTOR=" "$PROJECT_ROOT/.env"; then
|
||||
echo "Updating CHAIN138_SELECTOR in .env..."
|
||||
sed -i.bak "s|^CHAIN138_SELECTOR=.*|CHAIN138_SELECTOR=${RECOMMENDED_SELECTOR_HEX}|" "$PROJECT_ROOT/.env"
|
||||
echo "✅ Updated .env with selector: ${RECOMMENDED_SELECTOR_HEX}"
|
||||
else
|
||||
echo "Adding CHAIN138_SELECTOR to .env..."
|
||||
echo "CHAIN138_SELECTOR=${RECOMMENDED_SELECTOR_HEX}" >> "$PROJECT_ROOT/.env"
|
||||
echo "✅ Added CHAIN138_SELECTOR to .env"
|
||||
fi
|
||||
fi
|
||||
|
||||
95
scripts/deployment/configure-ccip-router.sh
Executable file
95
scripts/deployment/configure-ccip-router.sh
Executable file
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
# Configure CCIP Router with supported chains and tokens
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/../lib/init.sh"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# Load environment
|
||||
if [ -f "$PROJECT_ROOT/.env" ]; then
|
||||
source "$PROJECT_ROOT/.env"
|
||||
fi
|
||||
|
||||
RPC_URL="${RPC_URL_138:-${CHAIN138_RPC_URL:-http://localhost:8545}}"
|
||||
PRIVATE_KEY="${PRIVATE_KEY:-}"
|
||||
|
||||
if [ -z "$PRIVATE_KEY" ]; then
|
||||
log_error "Error: PRIVATE_KEY environment variable not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CCIP_ROUTER="${CCIP_CHAIN138_ROUTER:-${CCIP_ROUTER:-}}"
|
||||
|
||||
if [ -z "$CCIP_ROUTER" ]; then
|
||||
log_error "Error: CCIP_CHAIN138_ROUTER not set. Deploy router first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Configuring CCIP Router: $CCIP_ROUTER"
|
||||
log_info "RPC URL: $RPC_URL"
|
||||
|
||||
# Chain selectors
|
||||
ETH_MAINNET_SELECTOR="${ETH_MAINNET_SELECTOR:-5009297550715157269}"
|
||||
CHAIN138_SELECTOR="${CHAIN138_SELECTOR:-138}"
|
||||
|
||||
# Token addresses (from genesis)
|
||||
WETH9_ADDRESS="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
||||
WETH10_ADDRESS="0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f"
|
||||
|
||||
log_info "Adding supported chains..."
|
||||
|
||||
# Add Ethereum Mainnet as supported chain
|
||||
log_info "Adding Ethereum Mainnet (selector: $ETH_MAINNET_SELECTOR)..."
|
||||
if cast send "$CCIP_ROUTER" "addSupportedChain(uint64)" "$ETH_MAINNET_SELECTOR" \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--private-key "$PRIVATE_KEY" \
|
||||
--legacy 2>&1 | grep -q "Success\|success\|transactionHash"; then
|
||||
log_success "✅ Ethereum Mainnet added as supported chain"
|
||||
else
|
||||
log_warn "⚠️ Failed to add Ethereum Mainnet (may already be added)"
|
||||
fi
|
||||
|
||||
# Add supported tokens for Ethereum Mainnet
|
||||
log_info "Adding supported tokens for Ethereum Mainnet..."
|
||||
|
||||
log_info "Adding WETH9 ($WETH9_ADDRESS)..."
|
||||
if cast send "$CCIP_ROUTER" "addSupportedToken(uint64,address)" "$ETH_MAINNET_SELECTOR" "$WETH9_ADDRESS" \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--private-key "$PRIVATE_KEY" \
|
||||
--legacy 2>&1 | grep -q "Success\|success\|transactionHash"; then
|
||||
log_success "✅ WETH9 added as supported token"
|
||||
else
|
||||
log_warn "⚠️ Failed to add WETH9 (may already be added)"
|
||||
fi
|
||||
|
||||
log_info "Adding WETH10 ($WETH10_ADDRESS)..."
|
||||
if cast send "$CCIP_ROUTER" "addSupportedToken(uint64,address)" "$ETH_MAINNET_SELECTOR" "$WETH10_ADDRESS" \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--private-key "$PRIVATE_KEY" \
|
||||
--legacy 2>&1 | grep -q "Success\|success\|transactionHash"; then
|
||||
log_success "✅ WETH10 added as supported token"
|
||||
else
|
||||
log_warn "⚠️ Failed to add WETH10 (may already be added)"
|
||||
fi
|
||||
|
||||
# Verify configuration
|
||||
log_info "Verifying configuration..."
|
||||
|
||||
ETH_CHAIN_SUPPORTED=$(cast call "$CCIP_ROUTER" "supportedChains(uint64)" "$ETH_MAINNET_SELECTOR" --rpc-url "$RPC_URL" 2>/dev/null || echo "false")
|
||||
if [ "$ETH_CHAIN_SUPPORTED" = "true" ]; then
|
||||
log_success "✅ Ethereum Mainnet is supported"
|
||||
else
|
||||
log_warn "⚠️ Ethereum Mainnet support verification failed"
|
||||
fi
|
||||
|
||||
log_success "✅ CCIP Router configuration complete!"
|
||||
echo ""
|
||||
echo "Router Address: $CCIP_ROUTER"
|
||||
echo "Supported Chains:"
|
||||
echo " - Ethereum Mainnet ($ETH_MAINNET_SELECTOR)"
|
||||
echo "Supported Tokens (Ethereum Mainnet):"
|
||||
echo " - WETH9: $WETH9_ADDRESS"
|
||||
echo " - WETH10: $WETH10_ADDRESS"
|
||||
|
||||
189
scripts/deployment/deploy-ccip-chain138-complete.sh
Executable file
189
scripts/deployment/deploy-ccip-chain138-complete.sh
Executable file
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env bash
|
||||
# Complete CCIP Deployment Script for ChainID 138
|
||||
# This script performs the complete CCIP deployment and configuration
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/../lib/init.sh"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# Load environment
|
||||
if [ -f "$PROJECT_ROOT/.env" ]; then
|
||||
source "$PROJECT_ROOT/.env"
|
||||
fi
|
||||
|
||||
RPC_URL="${RPC_URL_138:-${CHAIN138_RPC_URL:-http://localhost:8545}}"
|
||||
PRIVATE_KEY="${PRIVATE_KEY:-}"
|
||||
|
||||
if [ -z "$PRIVATE_KEY" ]; then
|
||||
log_error "Error: PRIVATE_KEY environment variable not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "=== CCIP Complete Deployment for ChainID 138 ==="
|
||||
echo "RPC URL: $RPC_URL"
|
||||
echo ""
|
||||
|
||||
# Verify RPC connectivity
|
||||
if ! cast block-number --rpc-url "$RPC_URL" &>/dev/null; then
|
||||
log_error "Error: Cannot connect to RPC at $RPC_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CHAIN_ID=$(cast chain-id --rpc-url "$RPC_URL" 2>/dev/null || echo "unknown")
|
||||
if [ "$CHAIN_ID" != "138" ]; then
|
||||
log_warn "⚠️ Warning: Chain ID is $CHAIN_ID, expected 138"
|
||||
read -p "Continue anyway? (y/N) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Chain ID: $CHAIN_ID"
|
||||
echo ""
|
||||
|
||||
# Step 1: Calculate Chain Selector
|
||||
log_info "Step 1: Calculating chain selector..."
|
||||
if [ -f "$PROJECT_ROOT/scripts/ccip/calculate-chain-selector.sh" ]; then
|
||||
bash "$PROJECT_ROOT/scripts/ccip/calculate-chain-selector.sh"
|
||||
log_success "✅ Chain selector calculated"
|
||||
else
|
||||
log_warn "⚠️ Chain selector script not found, using default: 138"
|
||||
CHAIN138_SELECTOR="${CHAIN138_SELECTOR:-138}"
|
||||
fi
|
||||
|
||||
# Step 2: Deploy CCIP Router
|
||||
log_info "Step 2: Deploying CCIP Router..."
|
||||
CCIP_ROUTER="${CCIP_CHAIN138_ROUTER:-}"
|
||||
|
||||
if [ -z "$CCIP_ROUTER" ] || [ "$CCIP_ROUTER" = "" ]; then
|
||||
log_info "Deploying CCIP Router..."
|
||||
|
||||
# Set default fee configuration if not set
|
||||
export CCIP_FEE_TOKEN="${CCIP_CHAIN138_LINK_TOKEN:-0x0000000000000000000000000000000000000000}"
|
||||
export CCIP_BASE_FEE="${CCIP_BASE_FEE:-1000000000000000}"
|
||||
export CCIP_DATA_FEE_PER_BYTE="${CCIP_DATA_FEE_PER_BYTE:-100000000}"
|
||||
|
||||
ROUTER_OUTPUT=$(forge script script/DeployCCIPRouter.s.sol \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--broadcast \
|
||||
--private-key "$PRIVATE_KEY" 2>&1 || true)
|
||||
|
||||
ROUTER_ADDRESS=$(echo "$ROUTER_OUTPUT" | grep -E "CCIP Router deployed at:|deployed at:" | tail -1 | sed 's/.*at: //' | awk '{print $1}' | tr -d '\n' || echo "")
|
||||
|
||||
if [ -n "$ROUTER_ADDRESS" ] && [ "$ROUTER_ADDRESS" != "" ]; then
|
||||
CCIP_ROUTER="$ROUTER_ADDRESS"
|
||||
log_success "✅ CCIP Router deployed at: $CCIP_ROUTER"
|
||||
|
||||
# Update .env
|
||||
if grep -q "^CCIP_CHAIN138_ROUTER=" "$PROJECT_ROOT/.env" 2>/dev/null; then
|
||||
sed -i.bak "s|^CCIP_CHAIN138_ROUTER=.*|CCIP_CHAIN138_ROUTER=$CCIP_ROUTER|" "$PROJECT_ROOT/.env"
|
||||
else
|
||||
echo "CCIP_CHAIN138_ROUTER=$CCIP_ROUTER" >> "$PROJECT_ROOT/.env"
|
||||
fi
|
||||
|
||||
# Configure router
|
||||
log_info "Configuring CCIP Router..."
|
||||
export CCIP_CHAIN138_ROUTER="$CCIP_ROUTER"
|
||||
bash "$PROJECT_ROOT/scripts/deployment/configure-ccip-router.sh" || log_warn "⚠️ Router configuration had issues"
|
||||
else
|
||||
log_error "❌ Failed to deploy CCIP Router"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_success "✅ CCIP Router already deployed: $CCIP_ROUTER"
|
||||
fi
|
||||
|
||||
# Step 3: Deploy CCIPWETH9Bridge
|
||||
log_info "Step 3: Deploying CCIPWETH9Bridge..."
|
||||
WETH9_BRIDGE="${CCIPWETH9_BRIDGE_CHAIN138:-}"
|
||||
|
||||
if [ -z "$WETH9_BRIDGE" ] || [ "$WETH9_BRIDGE" = "" ]; then
|
||||
log_info "Deploying CCIPWETH9Bridge..."
|
||||
|
||||
export CCIP_ROUTER="$CCIP_ROUTER"
|
||||
export CCIP_FEE_TOKEN="${CCIP_CHAIN138_LINK_TOKEN:-0x0000000000000000000000000000000000000000}"
|
||||
|
||||
BRIDGE9_OUTPUT=$(forge script script/DeployCCIPWETH9Bridge.s.sol \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--broadcast \
|
||||
--private-key "$PRIVATE_KEY" 2>&1 || true)
|
||||
|
||||
BRIDGE9_ADDRESS=$(echo "$BRIDGE9_OUTPUT" | grep -E "CCIPWETH9Bridge deployed at:|deployed at:" | tail -1 | sed 's/.*at: //' | awk '{print $1}' | tr -d '\n' || echo "")
|
||||
|
||||
if [ -n "$BRIDGE9_ADDRESS" ] && [ "$BRIDGE9_ADDRESS" != "" ]; then
|
||||
WETH9_BRIDGE="$BRIDGE9_ADDRESS"
|
||||
log_success "✅ CCIPWETH9Bridge deployed at: $WETH9_BRIDGE"
|
||||
|
||||
# Update .env
|
||||
if grep -q "^CCIPWETH9_BRIDGE_CHAIN138=" "$PROJECT_ROOT/.env" 2>/dev/null; then
|
||||
sed -i.bak "s|^CCIPWETH9_BRIDGE_CHAIN138=.*|CCIPWETH9_BRIDGE_CHAIN138=$WETH9_BRIDGE|" "$PROJECT_ROOT/.env"
|
||||
else
|
||||
echo "CCIPWETH9_BRIDGE_CHAIN138=$WETH9_BRIDGE" >> "$PROJECT_ROOT/.env"
|
||||
fi
|
||||
else
|
||||
log_error "❌ Failed to deploy CCIPWETH9Bridge"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_success "✅ CCIPWETH9Bridge already deployed: $WETH9_BRIDGE"
|
||||
fi
|
||||
|
||||
# Step 4: Deploy CCIPWETH10Bridge
|
||||
log_info "Step 4: Deploying CCIPWETH10Bridge..."
|
||||
WETH10_BRIDGE="${CCIPWETH10_BRIDGE_CHAIN138:-}"
|
||||
|
||||
if [ -z "$WETH10_BRIDGE" ] || [ "$WETH10_BRIDGE" = "" ]; then
|
||||
log_info "Deploying CCIPWETH10Bridge..."
|
||||
|
||||
export CCIP_ROUTER="$CCIP_ROUTER"
|
||||
export CCIP_FEE_TOKEN="${CCIP_CHAIN138_LINK_TOKEN:-0x0000000000000000000000000000000000000000}"
|
||||
|
||||
BRIDGE10_OUTPUT=$(forge script script/DeployCCIPWETH10Bridge.s.sol \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--broadcast \
|
||||
--private-key "$PRIVATE_KEY" 2>&1 || true)
|
||||
|
||||
BRIDGE10_ADDRESS=$(echo "$BRIDGE10_OUTPUT" | grep -E "CCIPWETH10Bridge deployed at:|deployed at:" | tail -1 | sed 's/.*at: //' | awk '{print $1}' | tr -d '\n' || echo "")
|
||||
|
||||
if [ -n "$BRIDGE10_ADDRESS" ] && [ "$BRIDGE10_ADDRESS" != "" ]; then
|
||||
WETH10_BRIDGE="$BRIDGE10_ADDRESS"
|
||||
log_success "✅ CCIPWETH10Bridge deployed at: $WETH10_BRIDGE"
|
||||
|
||||
# Update .env
|
||||
if grep -q "^CCIPWETH10_BRIDGE_CHAIN138=" "$PROJECT_ROOT/.env" 2>/dev/null; then
|
||||
sed -i.bak "s|^CCIPWETH10_BRIDGE_CHAIN138=.*|CCIPWETH10_BRIDGE_CHAIN138=$WETH10_BRIDGE|" "$PROJECT_ROOT/.env"
|
||||
else
|
||||
echo "CCIPWETH10_BRIDGE_CHAIN138=$WETH10_BRIDGE" >> "$PROJECT_ROOT/.env"
|
||||
fi
|
||||
else
|
||||
log_error "❌ Failed to deploy CCIPWETH10Bridge"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_success "✅ CCIPWETH10Bridge already deployed: $WETH10_BRIDGE"
|
||||
fi
|
||||
|
||||
# Step 5: Verify Deployment
|
||||
log_info "Step 5: Verifying deployment..."
|
||||
bash "$PROJECT_ROOT/scripts/deployment/verify-ccip-deployment.sh" || log_warn "⚠️ Verification had issues"
|
||||
|
||||
# Step 6: Summary
|
||||
echo ""
|
||||
log_success "=== Deployment Summary ==="
|
||||
echo "CCIP Router: $CCIP_ROUTER"
|
||||
echo "CCIPWETH9Bridge: $WETH9_BRIDGE"
|
||||
echo "CCIPWETH10Bridge: $WETH10_BRIDGE"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Configure bridge destinations:"
|
||||
echo " ./scripts/deployment/configure-weth9-bridge.sh"
|
||||
echo " ./scripts/deployment/configure-weth10-bridge.sh"
|
||||
echo "2. Verify on explorer"
|
||||
echo "3. Test cross-chain transfers"
|
||||
echo "4. Update documentation with deployed addresses"
|
||||
|
||||
log_success "✅ CCIP deployment complete!"
|
||||
|
||||
180
scripts/deployment/verify-ccip-deployment.sh
Executable file
180
scripts/deployment/verify-ccip-deployment.sh
Executable file
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verify CCIP deployment on ChainID 138
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/../lib/init.sh"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# Load environment
|
||||
if [ -f "$PROJECT_ROOT/.env" ]; then
|
||||
source "$PROJECT_ROOT/.env"
|
||||
fi
|
||||
|
||||
RPC_URL="${RPC_URL_138:-${CHAIN138_RPC_URL:-http://localhost:8545}}"
|
||||
|
||||
log_info "=== CCIP Deployment Verification for ChainID 138 ==="
|
||||
echo "RPC URL: $RPC_URL"
|
||||
echo ""
|
||||
|
||||
# Check if RPC is accessible
|
||||
if ! cast block-number --rpc-url "$RPC_URL" &>/dev/null; then
|
||||
log_error "Error: Cannot connect to RPC at $RPC_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CHAIN_ID=$(cast chain-id --rpc-url "$RPC_URL" 2>/dev/null || echo "unknown")
|
||||
if [ "$CHAIN_ID" != "138" ]; then
|
||||
log_warn "⚠️ Warning: Chain ID is $CHAIN_ID, expected 138"
|
||||
fi
|
||||
|
||||
echo "Chain ID: $CHAIN_ID"
|
||||
echo ""
|
||||
|
||||
# Required addresses
|
||||
CCIP_ROUTER="${CCIP_CHAIN138_ROUTER:-}"
|
||||
LINK_TOKEN="${CCIP_CHAIN138_LINK_TOKEN:-}"
|
||||
WETH9_BRIDGE="${CCIPWETH9_BRIDGE_CHAIN138:-}"
|
||||
WETH10_BRIDGE="${CCIPWETH10_BRIDGE_CHAIN138:-}"
|
||||
CHAIN_SELECTOR="${CHAIN138_SELECTOR:-}"
|
||||
|
||||
# Verification results
|
||||
VERIFICATION_PASSED=0
|
||||
VERIFICATION_FAILED=0
|
||||
VERIFICATION_WARNINGS=0
|
||||
|
||||
check_contract() {
|
||||
local name=$1
|
||||
local address=$2
|
||||
local required=${3:-true}
|
||||
|
||||
if [ -z "$address" ] || [ "$address" = "" ]; then
|
||||
if [ "$required" = "true" ]; then
|
||||
echo " ❌ $name: Not configured"
|
||||
((VERIFICATION_FAILED++))
|
||||
else
|
||||
echo " ⚠️ $name: Not configured (optional)"
|
||||
((VERIFICATION_WARNINGS++))
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if address has code
|
||||
CODE=$(cast code "$address" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
if [ -z "$CODE" ] || [ "$CODE" = "0x" ]; then
|
||||
echo " ❌ $name: No code at address $address"
|
||||
((VERIFICATION_FAILED++))
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo " ✅ $name: Deployed at $address"
|
||||
((VERIFICATION_PASSED++))
|
||||
return 0
|
||||
}
|
||||
|
||||
verify_router_config() {
|
||||
local router=$1
|
||||
if [ -z "$router" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Router Configuration:"
|
||||
|
||||
# Check supported chains
|
||||
ETH_SELECTOR="5009297550715157269"
|
||||
ETH_SUPPORTED=$(cast call "$router" "supportedChains(uint64)" "$ETH_SELECTOR" --rpc-url "$RPC_URL" 2>/dev/null || echo "false")
|
||||
if [ "$ETH_SUPPORTED" = "true" ]; then
|
||||
echo " ✅ Ethereum Mainnet is supported"
|
||||
else
|
||||
echo " ⚠️ Ethereum Mainnet not configured as supported chain"
|
||||
((VERIFICATION_WARNINGS++))
|
||||
fi
|
||||
|
||||
# Check fee token
|
||||
FEE_TOKEN=$(cast call "$router" "feeToken()" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
if [ -n "$FEE_TOKEN" ]; then
|
||||
if [ "$FEE_TOKEN" = "0x0000000000000000000000000000000000000000" ]; then
|
||||
echo " ✅ Fee token: Native ETH"
|
||||
else
|
||||
echo " ✅ Fee token: $FEE_TOKEN"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
verify_bridge_config() {
|
||||
local bridge=$1
|
||||
local name=$2
|
||||
if [ -z "$bridge" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "$name Configuration:"
|
||||
|
||||
# Check router
|
||||
BRIDGE_ROUTER=$(cast call "$bridge" "ccipRouter()" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
if [ -n "$BRIDGE_ROUTER" ] && [ "$BRIDGE_ROUTER" != "0x0000000000000000000000000000000000000000" ]; then
|
||||
echo " ✅ Router: $BRIDGE_ROUTER"
|
||||
else
|
||||
echo " ⚠️ Router not configured"
|
||||
((VERIFICATION_WARNINGS++))
|
||||
fi
|
||||
|
||||
# Check destinations
|
||||
ETH_SELECTOR="5009297550715157269"
|
||||
DEST=$(cast call "$bridge" "destinations(uint64)" "$ETH_SELECTOR" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
if [ -n "$DEST" ] && echo "$DEST" | grep -q "0x[0-9a-f]\{40\}"; then
|
||||
echo " ✅ Ethereum Mainnet destination configured"
|
||||
else
|
||||
echo " ⚠️ Ethereum Mainnet destination not configured"
|
||||
((VERIFICATION_WARNINGS++))
|
||||
fi
|
||||
}
|
||||
|
||||
echo "=== Contract Deployment Verification ==="
|
||||
check_contract "CCIP Router" "$CCIP_ROUTER" true
|
||||
check_contract "LINK Token" "$LINK_TOKEN" false
|
||||
check_contract "CCIPWETH9Bridge" "$WETH9_BRIDGE" true
|
||||
check_contract "CCIPWETH10Bridge" "$WETH10_BRIDGE" true
|
||||
|
||||
if [ -n "$CCIP_ROUTER" ]; then
|
||||
verify_router_config "$CCIP_ROUTER"
|
||||
fi
|
||||
|
||||
if [ -n "$WETH9_BRIDGE" ]; then
|
||||
verify_bridge_config "$WETH9_BRIDGE" "CCIPWETH9Bridge"
|
||||
fi
|
||||
|
||||
if [ -n "$WETH10_BRIDGE" ]; then
|
||||
verify_bridge_config "$WETH10_BRIDGE" "CCIPWETH10Bridge"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Chain Selector ==="
|
||||
if [ -n "$CHAIN_SELECTOR" ]; then
|
||||
echo " ✅ Chain Selector: $CHAIN_SELECTOR"
|
||||
else
|
||||
echo " ⚠️ Chain Selector: Not configured"
|
||||
((VERIFICATION_WARNINGS++))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Verification Summary ==="
|
||||
echo "✅ Passed: $VERIFICATION_PASSED"
|
||||
echo "❌ Failed: $VERIFICATION_FAILED"
|
||||
echo "⚠️ Warnings: $VERIFICATION_WARNINGS"
|
||||
echo ""
|
||||
|
||||
if [ $VERIFICATION_FAILED -eq 0 ]; then
|
||||
log_success "✅ All required contracts are deployed!"
|
||||
if [ $VERIFICATION_WARNINGS -gt 0 ]; then
|
||||
log_warn "⚠️ Some optional configurations are missing"
|
||||
fi
|
||||
exit 0
|
||||
else
|
||||
log_error "❌ Some required contracts are missing or invalid"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
141
scripts/reserve/chainlink-keeper-setup.js
Executable file
141
scripts/reserve/chainlink-keeper-setup.js
Executable file
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Chainlink Keepers Setup Script
|
||||
* Registers PriceFeedKeeper with Chainlink Automation
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/reserve/chainlink-keeper-setup.js
|
||||
*
|
||||
* Environment Variables:
|
||||
* RPC_URL_138 - ChainID 138 RPC endpoint
|
||||
* PRIVATE_KEY - Deployer private key
|
||||
* KEEPER_REGISTRY_ADDRESS - Chainlink KeeperRegistry address
|
||||
* PRICE_FEED_KEEPER_ADDRESS - PriceFeedKeeper contract address
|
||||
* LINK_TOKEN_ADDRESS - LINK token address
|
||||
* UPKEEP_INTERVAL - Upkeep interval in seconds (default: 30)
|
||||
* GAS_LIMIT - Gas limit for upkeep (default: 500000)
|
||||
* FUNDING_AMOUNT - LINK amount to fund (default: 10 LINK)
|
||||
*/
|
||||
|
||||
const { ethers } = require('ethers');
|
||||
require('dotenv').config();
|
||||
|
||||
const CONFIG = {
|
||||
rpcUrl: process.env.RPC_URL_138 || 'http://localhost:8545',
|
||||
privateKey: process.env.PRIVATE_KEY,
|
||||
keeperRegistry: process.env.KEEPER_REGISTRY_ADDRESS,
|
||||
keeperAddress: process.env.PRICE_FEED_KEEPER_ADDRESS,
|
||||
linkToken: process.env.LINK_TOKEN_ADDRESS,
|
||||
upkeepInterval: parseInt(process.env.UPKEEP_INTERVAL || '30'),
|
||||
gasLimit: parseInt(process.env.GAS_LIMIT || '500000'),
|
||||
fundingAmount: ethers.parseEther(process.env.FUNDING_AMOUNT || '10'),
|
||||
};
|
||||
|
||||
// Chainlink KeeperRegistry ABI (simplified)
|
||||
const KEEPER_REGISTRY_ABI = [
|
||||
"function registerUpkeep(address target, uint32 gasLimit, address admin, bytes calldata checkData, uint96 amount, address source, bytes memory encryptedEmail) external returns (uint256)",
|
||||
"function getUpkeep(uint256 id) external view returns (address target, uint32 executeGas, bytes memory checkData, uint96 balance, address lastKeeper, uint96 balance, address admin, uint64 maxValidBlocknumber, uint96 amountSpent)",
|
||||
"function addFunds(uint256 id, uint96 amount) external",
|
||||
"event UpkeepRegistered(uint256 indexed id, address indexed target, uint32 gasLimit, address admin)",
|
||||
];
|
||||
|
||||
// ChainlinkKeeperCompatible ABI
|
||||
const KEEPER_COMPATIBLE_ABI = [
|
||||
"function checkUpkeep(bytes calldata checkData) external view returns (bool upkeepNeeded, bytes memory performData)",
|
||||
"function performUpkeep(bytes calldata performData) external",
|
||||
];
|
||||
|
||||
// LINK Token ABI
|
||||
const LINK_TOKEN_ABI = [
|
||||
"function approve(address spender, uint256 amount) external returns (bool)",
|
||||
"function transfer(address to, uint256 amount) external returns (bool)",
|
||||
"function balanceOf(address account) external view returns (uint256)",
|
||||
];
|
||||
|
||||
async function main() {
|
||||
console.log('=== Chainlink Keepers Setup ===\n');
|
||||
|
||||
// Validate configuration
|
||||
if (!CONFIG.privateKey) throw new Error('PRIVATE_KEY is required');
|
||||
if (!CONFIG.keeperRegistry) throw new Error('KEEPER_REGISTRY_ADDRESS is required');
|
||||
if (!CONFIG.keeperAddress) throw new Error('PRICE_FEED_KEEPER_ADDRESS is required');
|
||||
if (!CONFIG.linkToken) throw new Error('LINK_TOKEN_ADDRESS is required');
|
||||
|
||||
const provider = new ethers.JsonRpcProvider(CONFIG.rpcUrl);
|
||||
const wallet = new ethers.Wallet(CONFIG.privateKey, provider);
|
||||
|
||||
console.log('Network:', CONFIG.rpcUrl);
|
||||
console.log('Deployer:', wallet.address);
|
||||
console.log('Keeper Registry:', CONFIG.keeperRegistry);
|
||||
console.log('Keeper Address:', CONFIG.keeperAddress);
|
||||
console.log('LINK Token:', CONFIG.linkToken);
|
||||
console.log('Upkeep Interval:', CONFIG.upkeepInterval, 'seconds');
|
||||
console.log('Gas Limit:', CONFIG.gasLimit);
|
||||
console.log('Funding Amount:', ethers.formatEther(CONFIG.fundingAmount), 'LINK');
|
||||
console.log('');
|
||||
|
||||
// Check LINK balance
|
||||
const linkToken = new ethers.Contract(CONFIG.linkToken, LINK_TOKEN_ABI, wallet);
|
||||
const linkBalance = await linkToken.balanceOf(wallet.address);
|
||||
console.log('LINK Balance:', ethers.formatEther(linkBalance), 'LINK');
|
||||
|
||||
if (linkBalance < CONFIG.fundingAmount) {
|
||||
throw new Error(`Insufficient LINK balance. Need ${ethers.formatEther(CONFIG.fundingAmount)} LINK`);
|
||||
}
|
||||
|
||||
// Approve LINK spending
|
||||
console.log('\nApproving LINK spending...');
|
||||
const approveTx = await linkToken.approve(CONFIG.keeperRegistry, CONFIG.fundingAmount);
|
||||
await approveTx.wait();
|
||||
console.log('✓ LINK approved');
|
||||
|
||||
// Register upkeep
|
||||
console.log('\nRegistering upkeep...');
|
||||
const keeperRegistry = new ethers.Contract(CONFIG.keeperRegistry, KEEPER_REGISTRY_ABI, wallet);
|
||||
|
||||
const checkData = ethers.toUtf8Bytes(''); // Empty check data
|
||||
const encryptedEmail = ethers.toUtf8Bytes(''); // Empty email
|
||||
|
||||
const registerTx = await keeperRegistry.registerUpkeep(
|
||||
CONFIG.keeperAddress,
|
||||
CONFIG.gasLimit,
|
||||
wallet.address, // Admin
|
||||
checkData,
|
||||
CONFIG.fundingAmount,
|
||||
wallet.address, // Source
|
||||
encryptedEmail
|
||||
);
|
||||
|
||||
console.log('Transaction sent:', registerTx.hash);
|
||||
const receipt = await registerTx.wait();
|
||||
|
||||
// Parse UpkeepRegistered event
|
||||
const event = receipt.logs.find(log => {
|
||||
try {
|
||||
const parsed = keeperRegistry.interface.parseLog(log);
|
||||
return parsed.name === 'UpkeepRegistered';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (event) {
|
||||
const parsed = keeperRegistry.interface.parseLog(event);
|
||||
const upkeepId = parsed.args.id;
|
||||
console.log('✓ Upkeep registered!');
|
||||
console.log('Upkeep ID:', upkeepId.toString());
|
||||
console.log('');
|
||||
console.log('=== Setup Complete ===');
|
||||
console.log('Upkeep ID:', upkeepId.toString());
|
||||
console.log('Monitor at:', `https://automation.chain.link/${upkeepId}`);
|
||||
} else {
|
||||
console.log('⚠ Could not parse UpkeepRegistered event');
|
||||
console.log('Check transaction receipt for upkeep ID');
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error('Error:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
103
scripts/reserve/deploy-all.sh
Executable file
103
scripts/reserve/deploy-all.sh
Executable file
@@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
# Complete Keeper Deployment Script
|
||||
# Deploys all keeper components and sets up monitoring
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Load environment variables
|
||||
if [ -f .env ]; then
|
||||
source .env
|
||||
fi
|
||||
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-https://rpc.d-bis.org}"
|
||||
PRIVATE_KEY="${PRIVATE_KEY}"
|
||||
KEEPER_ADDRESS="${PRICE_FEED_KEEPER_ADDRESS}"
|
||||
|
||||
echo -e "${GREEN}=== Complete Keeper Deployment ===${NC}\n"
|
||||
|
||||
# Step 1: Deploy PriceFeedKeeper
|
||||
echo -e "${YELLOW}Step 1: Deploying PriceFeedKeeper...${NC}"
|
||||
if [ -z "$KEEPER_ADDRESS" ]; then
|
||||
forge script script/reserve/DeployKeeper.s.sol:DeployKeeper \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--broadcast \
|
||||
--verify
|
||||
|
||||
# Extract keeper address from output
|
||||
KEEPER_ADDRESS=$(forge script script/reserve/DeployKeeper.s.sol:DeployKeeper \
|
||||
--rpc-url "$RPC_URL" 2>&1 | grep "PriceFeedKeeper deployed at:" | awk '{print $NF}')
|
||||
|
||||
echo "export PRICE_FEED_KEEPER_ADDRESS=$KEEPER_ADDRESS" >> .env
|
||||
echo -e "${GREEN}✓ PriceFeedKeeper deployed: $KEEPER_ADDRESS${NC}\n"
|
||||
else
|
||||
echo -e "${GREEN}✓ PriceFeedKeeper already deployed: $KEEPER_ADDRESS${NC}\n"
|
||||
fi
|
||||
|
||||
# Step 2: Deploy Chainlink Keeper Compatible (optional)
|
||||
if [ "$DEPLOY_CHAINLINK" = "true" ]; then
|
||||
echo -e "${YELLOW}Step 2: Deploying ChainlinkKeeperCompatible...${NC}"
|
||||
forge script script/reserve/DeployChainlinkKeeper.s.sol:DeployChainlinkKeeper \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--broadcast \
|
||||
--verify
|
||||
|
||||
CHAINLINK_KEEPER=$(forge script script/reserve/DeployChainlinkKeeper.s.sol:DeployChainlinkKeeper \
|
||||
--rpc-url "$RPC_URL" 2>&1 | grep "ChainlinkKeeperCompatible deployed at:" | awk '{print $NF}')
|
||||
|
||||
echo "export CHAINLINK_KEEPER_COMPATIBLE_ADDRESS=$CHAINLINK_KEEPER" >> .env
|
||||
echo -e "${GREEN}✓ ChainlinkKeeperCompatible deployed: $CHAINLINK_KEEPER${NC}\n"
|
||||
|
||||
echo -e "${YELLOW}Next: Register with Chainlink Keepers:${NC}"
|
||||
echo "node scripts/reserve/chainlink-keeper-setup.js"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Step 3: Deploy Gelato Keeper Compatible (optional)
|
||||
if [ "$DEPLOY_GELATO" = "true" ]; then
|
||||
echo -e "${YELLOW}Step 3: Deploying GelatoKeeperCompatible...${NC}"
|
||||
forge script script/reserve/DeployGelatoKeeper.s.sol:DeployGelatoKeeper \
|
||||
--rpc-url "$RPC_URL" \
|
||||
--broadcast \
|
||||
--verify
|
||||
|
||||
GELATO_KEEPER=$(forge script script/reserve/DeployGelatoKeeper.s.sol:DeployGelatoKeeper \
|
||||
--rpc-url "$RPC_URL" 2>&1 | grep "GelatoKeeperCompatible deployed at:" | awk '{print $NF}')
|
||||
|
||||
echo "export GELATO_KEEPER_COMPATIBLE_ADDRESS=$GELATO_KEEPER" >> .env
|
||||
echo -e "${GREEN}✓ GelatoKeeperCompatible deployed: $GELATO_KEEPER${NC}\n"
|
||||
|
||||
echo -e "${YELLOW}Next: Create task with Gelato:${NC}"
|
||||
echo "node scripts/reserve/gelato-keeper-setup.js"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Step 4: Verify deployment
|
||||
echo -e "${YELLOW}Step 4: Verifying deployment...${NC}"
|
||||
forge script script/reserve/CheckUpkeep.s.sol:CheckUpkeep \
|
||||
--rpc-url "$RPC_URL"
|
||||
|
||||
echo -e "\n${GREEN}=== Deployment Complete ===${NC}"
|
||||
echo ""
|
||||
echo "Keeper Address: $KEEPER_ADDRESS"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Next Steps:${NC}"
|
||||
echo "1. Start keeper service:"
|
||||
echo " node scripts/reserve/keeper-service.js"
|
||||
echo ""
|
||||
echo "2. Start monitor service:"
|
||||
echo " node scripts/reserve/monitor-keeper.js"
|
||||
echo ""
|
||||
echo "3. Or use Docker:"
|
||||
echo " docker-compose -f docker/docker-compose.keeper.yml up -d"
|
||||
echo ""
|
||||
echo "4. Or use systemd:"
|
||||
echo " sudo systemctl enable price-feed-keeper"
|
||||
echo " sudo systemctl start price-feed-keeper"
|
||||
|
||||
136
scripts/reserve/gelato-keeper-setup.js
Executable file
136
scripts/reserve/gelato-keeper-setup.js
Executable file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Gelato Network Setup Script
|
||||
* Registers PriceFeedKeeper with Gelato Network
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/reserve/gelato-keeper-setup.js
|
||||
*
|
||||
* Environment Variables:
|
||||
* RPC_URL_138 - ChainID 138 RPC endpoint
|
||||
* PRIVATE_KEY - Deployer private key
|
||||
* GELATO_OPS - Gelato Ops contract address
|
||||
* PRICE_FEED_KEEPER_ADDRESS - PriceFeedKeeper contract address
|
||||
* EXECUTION_INTERVAL - Execution interval in seconds (default: 30)
|
||||
* FUNDING_AMOUNT - Native token amount to fund (default: 0.1 ETH)
|
||||
*/
|
||||
|
||||
const { ethers } = require('ethers');
|
||||
require('dotenv').config();
|
||||
|
||||
const CONFIG = {
|
||||
rpcUrl: process.env.RPC_URL_138 || 'http://localhost:8545',
|
||||
privateKey: process.env.PRIVATE_KEY,
|
||||
gelatoOps: process.env.GELATO_OPS || '0x527a819db1eb0e34426297b03bae11F2f8B3A19E', // Default Gelato Ops
|
||||
keeperAddress: process.env.PRICE_FEED_KEEPER_ADDRESS,
|
||||
executionInterval: parseInt(process.env.EXECUTION_INTERVAL || '30'),
|
||||
fundingAmount: ethers.parseEther(process.env.FUNDING_AMOUNT || '0.1'),
|
||||
};
|
||||
|
||||
// Gelato Ops ABI (simplified)
|
||||
const GELATO_OPS_ABI = [
|
||||
"function createTask(address execAddress, bytes4 execSelector, address resolverAddress, bytes calldata resolverData) external returns (bytes32)",
|
||||
"function getTaskId(address execAddress, bytes4 execSelector, address resolverAddress, bytes calldata resolverData) external view returns (bytes32)",
|
||||
"function cancelTask(bytes32 taskId) external",
|
||||
"function depositFunds(address taskCreator, address token, uint256 amount) external payable",
|
||||
"function getTaskId(address execAddress, bytes4 execSelector, address resolverAddress, bytes calldata resolverData) external view returns (bytes32)",
|
||||
"event TaskCreated(bytes32 indexed taskId, address indexed execAddress, bytes4 execSelector, address indexed resolverAddress, bytes resolverData)",
|
||||
];
|
||||
|
||||
// GelatoKeeperCompatible ABI
|
||||
const GELATO_KEEPER_ABI = [
|
||||
"function executeTask() external",
|
||||
"function canExec() external view returns (bool canExec, bytes memory execData)",
|
||||
];
|
||||
|
||||
async function main() {
|
||||
console.log('=== Gelato Network Setup ===\n');
|
||||
|
||||
// Validate configuration
|
||||
if (!CONFIG.privateKey) throw new Error('PRIVATE_KEY is required');
|
||||
if (!CONFIG.keeperAddress) throw new Error('PRICE_FEED_KEEPER_ADDRESS is required');
|
||||
|
||||
const provider = new ethers.JsonRpcProvider(CONFIG.rpcUrl);
|
||||
const wallet = new ethers.Wallet(CONFIG.privateKey, provider);
|
||||
|
||||
console.log('Network:', CONFIG.rpcUrl);
|
||||
console.log('Deployer:', wallet.address);
|
||||
console.log('Gelato Ops:', CONFIG.gelatoOps);
|
||||
console.log('Keeper Address:', CONFIG.keeperAddress);
|
||||
console.log('Execution Interval:', CONFIG.executionInterval, 'seconds');
|
||||
console.log('Funding Amount:', ethers.formatEther(CONFIG.fundingAmount), 'ETH');
|
||||
console.log('');
|
||||
|
||||
// Check balance
|
||||
const balance = await provider.getBalance(wallet.address);
|
||||
console.log('Balance:', ethers.formatEther(balance), 'ETH');
|
||||
|
||||
if (balance < CONFIG.fundingAmount) {
|
||||
throw new Error(`Insufficient balance. Need ${ethers.formatEther(CONFIG.fundingAmount)} ETH`);
|
||||
}
|
||||
|
||||
// Deploy GelatoKeeperCompatible if not already deployed
|
||||
console.log('Checking GelatoKeeperCompatible contract...');
|
||||
// For now, assume it needs to be deployed via Foundry script
|
||||
// In production, deploy it first and set GELATO_KEEPER_COMPATIBLE_ADDRESS
|
||||
|
||||
const gelatoKeeperAddress = process.env.GELATO_KEEPER_COMPATIBLE_ADDRESS;
|
||||
if (!gelatoKeeperAddress) {
|
||||
console.log('⚠ GELATO_KEEPER_COMPATIBLE_ADDRESS not set');
|
||||
console.log('Please deploy GelatoKeeperCompatible contract first using:');
|
||||
console.log('forge script script/reserve/DeployGelatoKeeper.s.sol:DeployGelatoKeeper --rpc-url chain138 --broadcast');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('GelatoKeeperCompatible:', gelatoKeeperAddress);
|
||||
|
||||
// Create task
|
||||
console.log('\nCreating Gelato task...');
|
||||
const gelatoOps = new ethers.Contract(CONFIG.gelatoOps, GELATO_OPS_ABI, wallet);
|
||||
|
||||
const execAddress = gelatoKeeperAddress;
|
||||
const execSelector = '0x3c4b2e02'; // executeTask() selector
|
||||
const resolverAddress = gelatoKeeperAddress;
|
||||
const resolverData = ethers.AbiCoder.defaultAbiCoder().encode(['bytes4'], ['0x3c4b2e02']); // canExec() selector
|
||||
|
||||
const createTaskTx = await gelatoOps.createTask(
|
||||
execAddress,
|
||||
execSelector,
|
||||
resolverAddress,
|
||||
resolverData,
|
||||
{ value: CONFIG.fundingAmount }
|
||||
);
|
||||
|
||||
console.log('Transaction sent:', createTaskTx.hash);
|
||||
const receipt = await createTaskTx.wait();
|
||||
|
||||
// Parse TaskCreated event
|
||||
const event = receipt.logs.find(log => {
|
||||
try {
|
||||
const parsed = gelatoOps.interface.parseLog(log);
|
||||
return parsed.name === 'TaskCreated';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (event) {
|
||||
const parsed = gelatoOps.interface.parseLog(event);
|
||||
const taskId = parsed.args.taskId;
|
||||
console.log('✓ Task created!');
|
||||
console.log('Task ID:', taskId);
|
||||
console.log('');
|
||||
console.log('=== Setup Complete ===');
|
||||
console.log('Task ID:', taskId);
|
||||
console.log('Monitor at: https://app.gelato.network/task/' + taskId);
|
||||
} else {
|
||||
console.log('⚠ Could not parse TaskCreated event');
|
||||
console.log('Check transaction receipt for task ID');
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error('Error:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
249
scripts/reserve/monitor-keeper.js
Executable file
249
scripts/reserve/monitor-keeper.js
Executable file
@@ -0,0 +1,249 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Keeper Monitoring Service
|
||||
* Monitors keeper performance and sends alerts on failures
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/reserve/monitor-keeper.js
|
||||
*
|
||||
* Environment Variables:
|
||||
* RPC_URL_138 - ChainID 138 RPC endpoint
|
||||
* PRICE_FEED_KEEPER_ADDRESS - PriceFeedKeeper contract address
|
||||
* ALERT_WEBHOOK - Webhook URL for alerts (optional)
|
||||
* CHECK_INTERVAL - Check interval in seconds (default: 60)
|
||||
* ALERT_THRESHOLD - Number of consecutive failures before alert (default: 3)
|
||||
*/
|
||||
|
||||
const { ethers } = require('ethers');
|
||||
const http = require('http');
|
||||
require('dotenv').config();
|
||||
|
||||
const CONFIG = {
|
||||
rpcUrl: process.env.RPC_URL_138 || 'http://localhost:8545',
|
||||
keeperAddress: process.env.PRICE_FEED_KEEPER_ADDRESS,
|
||||
alertWebhook: process.env.ALERT_WEBHOOK,
|
||||
checkInterval: parseInt(process.env.CHECK_INTERVAL || '60') * 1000,
|
||||
alertThreshold: parseInt(process.env.ALERT_THRESHOLD || '3'),
|
||||
};
|
||||
|
||||
const KEEPER_ABI = [
|
||||
"function checkUpkeep() external view returns (bool needsUpdate, address[] memory assets)",
|
||||
"function getTrackedAssets() external view returns (address[] memory)",
|
||||
"function updateInterval() external view returns (uint256)",
|
||||
"function lastUpdateTime(address asset) external view returns (uint256)",
|
||||
"event PriceFeedsUpdated(address[] assets, uint256 timestamp)",
|
||||
];
|
||||
|
||||
class KeeperMonitor {
|
||||
constructor() {
|
||||
if (!CONFIG.keeperAddress) {
|
||||
throw new Error('PRICE_FEED_KEEPER_ADDRESS is required');
|
||||
}
|
||||
|
||||
this.provider = new ethers.JsonRpcProvider(CONFIG.rpcUrl);
|
||||
this.keeper = new ethers.Contract(CONFIG.keeperAddress, KEEPER_ABI, this.provider);
|
||||
this.failureCount = 0;
|
||||
this.lastCheck = null;
|
||||
this.stats = {
|
||||
checks: 0,
|
||||
failures: 0,
|
||||
alerts: 0,
|
||||
lastUpdate: null,
|
||||
};
|
||||
}
|
||||
|
||||
async start() {
|
||||
console.log('=== Keeper Monitor Service ===');
|
||||
console.log(`RPC URL: ${CONFIG.rpcUrl}`);
|
||||
console.log(`Keeper Address: ${CONFIG.keeperAddress}`);
|
||||
console.log(`Check Interval: ${CONFIG.checkInterval / 1000} seconds`);
|
||||
console.log(`Alert Threshold: ${CONFIG.alertThreshold} failures`);
|
||||
console.log('');
|
||||
|
||||
// Start monitoring loop
|
||||
this.monitorLoop();
|
||||
|
||||
// Start HTTP health endpoint
|
||||
this.startHealthServer();
|
||||
}
|
||||
|
||||
async monitorLoop() {
|
||||
while (true) {
|
||||
try {
|
||||
await this.checkKeeper();
|
||||
} catch (error) {
|
||||
console.error(`Monitor error: ${error.message}`);
|
||||
this.failureCount++;
|
||||
}
|
||||
|
||||
await this.sleep(CONFIG.checkInterval);
|
||||
}
|
||||
}
|
||||
|
||||
async checkKeeper() {
|
||||
this.stats.checks++;
|
||||
const checkTime = new Date();
|
||||
|
||||
try {
|
||||
// Check if upkeep is needed
|
||||
const [needsUpdate, assets] = await this.keeper.checkUpkeep();
|
||||
|
||||
// Get tracked assets
|
||||
const trackedAssets = await this.keeper.getTrackedAssets();
|
||||
const updateInterval = await this.keeper.updateInterval();
|
||||
|
||||
// Check last update times
|
||||
const staleAssets = [];
|
||||
for (const asset of trackedAssets) {
|
||||
const lastUpdate = await this.keeper.lastUpdateTime(asset);
|
||||
const timeSinceUpdate = checkTime.getTime() / 1000 - Number(lastUpdate);
|
||||
|
||||
if (lastUpdate > 0 && timeSinceUpdate > Number(updateInterval) * 2) {
|
||||
staleAssets.push({ asset, lastUpdate: Number(lastUpdate), stale: timeSinceUpdate });
|
||||
}
|
||||
}
|
||||
|
||||
// Log status
|
||||
const status = {
|
||||
timestamp: checkTime.toISOString(),
|
||||
needsUpdate,
|
||||
assetsNeedingUpdate: assets.length,
|
||||
trackedAssets: trackedAssets.length,
|
||||
staleAssets: staleAssets.length,
|
||||
updateInterval: Number(updateInterval),
|
||||
};
|
||||
|
||||
console.log(`[${status.timestamp}] Status:`, {
|
||||
needsUpdate: status.needsUpdate,
|
||||
assetsNeedingUpdate: status.assetsNeedingUpdate,
|
||||
trackedAssets: status.trackedAssets,
|
||||
staleAssets: status.staleAssets,
|
||||
});
|
||||
|
||||
// Check for issues
|
||||
if (needsUpdate && assets.length > 0) {
|
||||
const timeSinceLastCheck = this.lastCheck
|
||||
? (checkTime.getTime() - this.lastCheck.getTime()) / 1000
|
||||
: 0;
|
||||
|
||||
if (timeSinceLastCheck > Number(updateInterval) * 2 && this.lastCheck !== null) {
|
||||
// Keeper hasn't updated in time
|
||||
this.failureCount++;
|
||||
console.warn(`⚠ Keeper appears stalled. ${assets.length} assets need update.`);
|
||||
|
||||
if (this.failureCount >= CONFIG.alertThreshold) {
|
||||
await this.sendAlert({
|
||||
type: 'keeper_stalled',
|
||||
message: `Keeper has not updated for ${timeSinceLastCheck} seconds`,
|
||||
assetsNeedingUpdate: assets.length,
|
||||
staleAssets,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.failureCount = 0; // Reset on success
|
||||
}
|
||||
}
|
||||
|
||||
// Check for stale assets
|
||||
if (staleAssets.length > 0) {
|
||||
console.warn(`⚠ Found ${staleAssets.length} stale assets:`);
|
||||
staleAssets.forEach(({ asset, stale }) => {
|
||||
console.warn(` - ${asset}: ${(stale / 60).toFixed(1)} minutes stale`);
|
||||
});
|
||||
|
||||
await this.sendAlert({
|
||||
type: 'stale_assets',
|
||||
message: `Found ${staleAssets.length} stale assets`,
|
||||
staleAssets,
|
||||
});
|
||||
}
|
||||
|
||||
this.lastCheck = checkTime;
|
||||
this.stats.lastUpdate = checkTime;
|
||||
|
||||
} catch (error) {
|
||||
this.stats.failures++;
|
||||
this.failureCount++;
|
||||
console.error(`✗ Check failed: ${error.message}`);
|
||||
|
||||
if (this.failureCount >= CONFIG.alertThreshold) {
|
||||
await this.sendAlert({
|
||||
type: 'monitor_error',
|
||||
message: `Monitor check failed: ${error.message}`,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async sendAlert(alert) {
|
||||
this.stats.alerts++;
|
||||
const alertData = {
|
||||
...alert,
|
||||
timestamp: new Date().toISOString(),
|
||||
keeperAddress: CONFIG.keeperAddress,
|
||||
stats: this.stats,
|
||||
};
|
||||
|
||||
console.error('🚨 ALERT:', alertData);
|
||||
|
||||
if (CONFIG.alertWebhook) {
|
||||
try {
|
||||
await fetch(CONFIG.alertWebhook, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(alertData),
|
||||
});
|
||||
console.log('Alert sent to webhook');
|
||||
} catch (error) {
|
||||
console.error('Failed to send alert:', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startHealthServer() {
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.url === '/health') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
status: 'healthy',
|
||||
stats: this.stats,
|
||||
lastCheck: this.lastCheck?.toISOString(),
|
||||
}));
|
||||
} else if (req.url === '/stats') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(this.stats));
|
||||
} else {
|
||||
res.writeHead(404);
|
||||
res.end('Not found');
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(3000, () => {
|
||||
console.log('Health server listening on port 3000');
|
||||
});
|
||||
}
|
||||
|
||||
sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
|
||||
// Main execution
|
||||
if (require.main === module) {
|
||||
const monitor = new KeeperMonitor();
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\nStopping monitor...');
|
||||
console.log('Final stats:', monitor.stats);
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
monitor.start().catch(error => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = KeeperMonitor;
|
||||
|
||||
Reference in New Issue
Block a user