Import sibling archive WIP: keeper stack, CCIP scripts, and CCIP docs

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-06-02 05:59:06 -07:00
parent d3ccbb2145
commit 1734d88989
41 changed files with 8147 additions and 0 deletions

View 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

View 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"

View 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!"

View 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

View 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
View 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"

View 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
View 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;