#!/usr/bin/env bash # CCIP Send Script # Sends tokens via CCIP bridge to a destination chain # # Usage: # ./ccip-send.sh --selector --receiver
--amount [--dry-run] # # Example: # export BRIDGE_ADDRESS=$CCIPWETH9_BRIDGE_CHAIN138 # ./ccip-send.sh --selector 5009297550715157269 --receiver 0xYourMainnetAddress --amount 100000000000000000 set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)" # Load environment variables ENV_FILE="${ENV_FILE:-$PROJECT_ROOT/.env}" if [ -f "$ENV_FILE" ]; then export $(grep -v '^#' "$ENV_FILE" | grep -E "^(RPC_URL|PRIVATE_KEY|CHAIN_ID|CCIP_ROUTER|LINK_TOKEN|WETH9_ADDRESS|WETH10_ADDRESS|FEE_TOKEN)" | xargs) fi # Default values DRY_RUN=false SELECTOR="" RECEIVER="" AMOUNT="" # Parse arguments while [[ $# -gt 0 ]]; do case "$1" in --selector) SELECTOR="$2" shift 2 ;; --receiver) RECEIVER="$2" shift 2 ;; --amount) AMOUNT="$2" shift 2 ;; --dry-run) DRY_RUN=true shift ;; *) echo "Unknown argument: $1" echo "" echo "Usage: $0 --selector --receiver
--amount [--dry-run]" echo "" echo "Arguments:" echo " --selector CCIP chain selector (required)" echo " --receiver
Receiver address on destination chain (required)" echo " --amount Amount to bridge in wei (required)" echo " --dry-run Simulate transaction without sending (optional)" echo "" echo "Environment variables:" echo " BRIDGE_ADDRESS Bridge contract address (required)" echo " RPC_URL RPC endpoint URL (required)" echo " PRIVATE_KEY Private key for signing (required)" echo " FEE_TOKEN Fee token address (0x0 for native, or LINK)" echo "" echo "Examples:" echo " # Dry run: estimate without sending" echo " export BRIDGE_ADDRESS=\$CCIPWETH9_BRIDGE_CHAIN138" echo " $0 --selector 5009297550715157269 --receiver 0xYourAddress --amount 100000000000000000 --dry-run" echo "" echo " # Actual send" echo " $0 --selector 5009297550715157269 --receiver 0xYourAddress --amount 100000000000000000" exit 1 ;; esac done # Validate required arguments if [ -z "$SELECTOR" ] || [ -z "$RECEIVER" ] || [ -z "$AMOUNT" ]; then echo "Error: Missing required arguments" echo "Usage: $0 --selector --receiver
--amount [--dry-run]" exit 1 fi # Validate required environment variables if [ -z "${BRIDGE_ADDRESS:-}" ]; then echo "Error: BRIDGE_ADDRESS not set" echo "Set it to the bridge contract address:" echo " export BRIDGE_ADDRESS=\$CCIPWETH9_BRIDGE_CHAIN138" exit 1 fi if [ -z "${RPC_URL:-}" ]; then echo "Error: RPC_URL not set" exit 1 fi if [ "$DRY_RUN" = false ] && [ -z "${PRIVATE_KEY:-}" ]; then echo "Error: PRIVATE_KEY not set (required for actual send)" exit 1 fi # Default to LINK token if FEE_TOKEN not set FEE_TOKEN="${FEE_TOKEN:-${LINK_TOKEN:-0x0000000000000000000000000000000000000000}}" # Validate cast is available if ! cast --version &>/dev/null; then echo "Error: cast (Foundry) not found. Please install Foundry:" echo " curl -L https://foundry.paradigm.xyz | bash" exit 1 fi BRIDGE_ADDRESS=$(cast --to-checksum-address "$BRIDGE_ADDRESS") RECEIVER=$(cast --to-checksum-address "$RECEIVER") echo "==========================================" if [ "$DRY_RUN" = true ]; then echo "CCIP Send (DRY RUN)" else echo "CCIP Send" fi echo "==========================================" echo "Bridge Address: $BRIDGE_ADDRESS" echo "Chain Selector: $SELECTOR" echo "Receiver: $RECEIVER" echo "Amount: $AMOUNT wei" echo "Fee Token: $FEE_TOKEN" echo "" # Estimate fee first echo "Estimating fee..." FEE=$(cast call "$BRIDGE_ADDRESS" \ "estimateCcipFee(uint64,address,uint256)(uint256)" \ "$SELECTOR" \ "$RECEIVER" \ "$AMOUNT" \ --rpc-url "$RPC_URL" 2>/dev/null || echo "0") if [ "$FEE" = "0" ] || [ -z "$FEE" ]; then echo "⚠ Warning: Could not estimate fee. Proceeding anyway..." FEE="0" else FEE_DECIMAL=$(cast --to-dec "$FEE" 2>/dev/null || echo "$FEE") echo "Estimated fee: $FEE wei ($FEE_DECIMAL)" fi # Check sender balance and approval if [ "$DRY_RUN" = false ]; then SENDER_ADDRESS=$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || echo "") if [ -z "$SENDER_ADDRESS" ]; then echo "Error: Could not derive address from private key" exit 1 fi echo "" echo "Checking sender balance and approvals..." echo "Sender: $SENDER_ADDRESS" # Determine which token (WETH9 or WETH10) based on bridge address # This is a heuristic - you may need to adjust based on your setup if [[ "$BRIDGE_ADDRESS" == *"WETH9"* ]] || [[ "${BRIDGE_ADDRESS:-}" == "${CCIPWETH9_BRIDGE_CHAIN138:-}" ]] || [[ "${BRIDGE_ADDRESS:-}" == "${CCIPWETH9_BRIDGE_MAINNET:-}" ]]; then TOKEN_ADDRESS="${WETH9_ADDRESS:-0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2}" TOKEN_NAME="WETH9" else TOKEN_ADDRESS="${WETH10_ADDRESS:-0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f}" TOKEN_NAME="WETH10" fi # Check token balance BALANCE=$(cast call "$TOKEN_ADDRESS" \ "balanceOf(address)(uint256)" \ "$SENDER_ADDRESS" \ --rpc-url "$RPC_URL" 2>/dev/null || echo "0") if [ "$(cast --to-dec "$BALANCE" 2>/dev/null || echo "0")" -lt "$(cast --to-dec "$AMOUNT" 2>/dev/null || echo "0")" ]; then echo "⚠ Warning: Insufficient $TOKEN_NAME balance" echo " Required: $AMOUNT wei" echo " Available: $BALANCE wei" else echo "✓ Sufficient $TOKEN_NAME balance" fi # Check approval ALLOWANCE=$(cast call "$TOKEN_ADDRESS" \ "allowance(address,address)(uint256)" \ "$SENDER_ADDRESS" \ "$BRIDGE_ADDRESS" \ --rpc-url "$RPC_URL" 2>/dev/null || echo "0") if [ "$(cast --to-dec "$ALLOWANCE" 2>/dev/null || echo "0")" -lt "$(cast --to-dec "$AMOUNT" 2>/dev/null || echo "0")" ]; then echo "⚠ Warning: Insufficient approval" echo " Required: $AMOUNT wei" echo " Approved: $ALLOWANCE wei" echo "" echo "Approve the bridge to spend tokens:" echo " cast send $TOKEN_ADDRESS \\" echo " \"approve(address,uint256)(bool)\" \\" echo " $BRIDGE_ADDRESS $AMOUNT \\" echo " --private-key \$PRIVATE_KEY \\" echo " --rpc-url \$RPC_URL" if [ "$DRY_RUN" = false ]; then read -p "Continue anyway? [y/N]: " confirm if [[ ! "$confirm" =~ ^[Yy]$ ]]; then echo "Aborted. Please approve first." exit 1 fi fi else echo "✓ Sufficient approval" fi fi echo "" if [ "$DRY_RUN" = true ]; then echo "DRY RUN: Simulating bridgeOut call..." cast call "$BRIDGE_ADDRESS" \ "bridgeOut(uint64,address,uint256,address)(bytes32)" \ "$SELECTOR" \ "$RECEIVER" \ "$AMOUNT" \ "$FEE_TOKEN" \ --rpc-url "$RPC_URL" || { echo "Error: Dry run failed" exit 1 } echo "✓ Dry run successful (transaction would succeed)" else echo "Sending transaction..." # Determine value to send (if using native fees) VALUE="0" if [ "$FEE_TOKEN" = "0x0000000000000000000000000000000000000000" ]; then VALUE="$FEE" fi TX_HASH=$(cast send "$BRIDGE_ADDRESS" \ "bridgeOut(uint64,address,uint256,address)(bytes32)" \ "$SELECTOR" \ "$RECEIVER" \ "$AMOUNT" \ "$FEE_TOKEN" \ --private-key "$PRIVATE_KEY" \ --rpc-url "$RPC_URL" \ --value "$VALUE" \ --gas-limit 500000 2>&1 | grep -oP '0x[a-fA-F0-9]{64}' || echo "") if [ -z "$TX_HASH" ]; then echo "Error: Transaction failed or hash not found" exit 1 fi echo "✓ Transaction sent!" echo " Transaction Hash: $TX_HASH" echo "" echo "Waiting for confirmation..." sleep 5 # Try to get receipt RECEIPT=$(cast receipt "$TX_HASH" --rpc-url "$RPC_URL" 2>/dev/null || echo "") if [ -n "$RECEIPT" ]; then echo "✓ Transaction confirmed" else echo "⚠ Transaction may still be pending. Check with:" echo " cast receipt $TX_HASH --rpc-url $RPC_URL" fi fi echo "" echo "==========================================" echo "Complete" echo "=========================================="