Sync workspace: config, docs, scripts, CI, operator rules, and submodule pointers.
- Update dbis_core, cross-chain-pmm-lps, explorer-monorepo, metamask-integration, pr-workspace/chains - Omit embedded publish git dirs and empty placeholders from index Made-with: Cursor
This commit is contained in:
339
scripts/bridge/bridge-cstar-to-cw.sh
Executable file
339
scripts/bridge/bridge-cstar-to-cw.sh
Executable file
@@ -0,0 +1,339 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Send any configured canonical Chain 138 c* token through CWMultiTokenBridgeL1
|
||||
# to a destination cW* bridge lane.
|
||||
#
|
||||
# Dry-run by default. Pass --execute to submit approvals and the bridge send.
|
||||
#
|
||||
# Examples:
|
||||
# ./scripts/bridge/bridge-cstar-to-cw.sh --asset cUSDT --chain MAINNET --amount 1 --recipient 0xabc...
|
||||
# ./scripts/bridge/bridge-cstar-to-cw.sh --asset cEURC --chain POLYGON --amount 250.5 --approve --execute
|
||||
# ./scripts/bridge/bridge-cstar-to-cw.sh --asset cXAUC --chain AVALANCHE --raw-amount 1000000 --approve --execute
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
SMOM_ROOT="${PROJECT_ROOT}/smom-dbis-138"
|
||||
CONFIG_FILE="${PROJECT_ROOT}/config/token-mapping-multichain.json"
|
||||
|
||||
CHAIN=""
|
||||
ASSET=""
|
||||
AMOUNT=""
|
||||
RAW_AMOUNT=""
|
||||
RECIPIENT=""
|
||||
AUTO_APPROVE=false
|
||||
EXECUTE=false
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./scripts/bridge/bridge-cstar-to-cw.sh --asset <c*> --chain <CHAIN> [--amount <decimal> | --raw-amount <int>] [options]
|
||||
|
||||
Required:
|
||||
--asset <c*> cUSDT | cUSDC | cAUSDT | cUSDW | cEURC | cEURT | cGBPC | cGBPT | cAUDC | cJPYC | cCHFC | cCADC | cXAUC | cXAUT
|
||||
--chain <CHAIN> MAINNET | CRONOS | BSC | POLYGON | GNOSIS | AVALANCHE | BASE | ARBITRUM | OPTIMISM | CELO
|
||||
|
||||
Amount:
|
||||
--amount <decimal> Human-readable amount; converted using token decimals()
|
||||
--raw-amount <int> Raw integer amount in token base units
|
||||
|
||||
Options:
|
||||
--recipient <address> Destination recipient; defaults to the sender derived from PRIVATE_KEY
|
||||
--approve Auto-approve source token and ERC-20 fee token if needed
|
||||
--execute Broadcast approvals and lockAndSend; default is dry-run
|
||||
--help Show this message
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--chain) CHAIN="${2:-}"; shift 2 ;;
|
||||
--asset) ASSET="${2:-}"; shift 2 ;;
|
||||
--amount) AMOUNT="${2:-}"; shift 2 ;;
|
||||
--raw-amount) RAW_AMOUNT="${2:-}"; shift 2 ;;
|
||||
--recipient) RECIPIENT="${2:-}"; shift 2 ;;
|
||||
--approve) AUTO_APPROVE=true; shift ;;
|
||||
--execute) EXECUTE=true; shift ;;
|
||||
--help|-h) usage; exit 0 ;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$CHAIN" && -n "$ASSET" ]] || { usage >&2; exit 2; }
|
||||
if [[ -z "$AMOUNT" && -z "$RAW_AMOUNT" ]]; then
|
||||
echo "Provide --amount or --raw-amount" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ -n "$AMOUNT" && -n "$RAW_AMOUNT" ]]; then
|
||||
echo "Use only one of --amount or --raw-amount" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ -f "${SMOM_ROOT}/scripts/lib/deployment/dotenv.sh" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "${SMOM_ROOT}/scripts/lib/deployment/dotenv.sh"
|
||||
load_deployment_env --repo-root "$SMOM_ROOT"
|
||||
else
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "${SMOM_ROOT}/.env"
|
||||
set +a
|
||||
fi
|
||||
|
||||
command -v cast >/dev/null 2>&1 || { echo "cast (foundry) required" >&2; exit 1; }
|
||||
command -v node >/dev/null 2>&1 || { echo "node required" >&2; exit 1; }
|
||||
command -v python3 >/dev/null 2>&1 || { echo "python3 required" >&2; exit 1; }
|
||||
|
||||
[[ -n "${PRIVATE_KEY:-}" ]] || { echo "PRIVATE_KEY required in ${SMOM_ROOT}/.env" >&2; exit 1; }
|
||||
[[ -n "${RPC_URL_138:-}" ]] || { echo "RPC_URL_138 required in ${SMOM_ROOT}/.env" >&2; exit 1; }
|
||||
|
||||
declare -A CHAIN_NAME_TO_ID=(
|
||||
[MAINNET]=1
|
||||
[CRONOS]=25
|
||||
[BSC]=56
|
||||
[POLYGON]=137
|
||||
[GNOSIS]=100
|
||||
[AVALANCHE]=43114
|
||||
[BASE]=8453
|
||||
[ARBITRUM]=42161
|
||||
[OPTIMISM]=10
|
||||
[CELO]=42220
|
||||
)
|
||||
|
||||
declare -A CHAIN_NAME_TO_SELECTOR_VAR=(
|
||||
[MAINNET]=ETH_MAINNET_SELECTOR
|
||||
[CRONOS]=CRONOS_SELECTOR
|
||||
[BSC]=BSC_SELECTOR
|
||||
[POLYGON]=POLYGON_SELECTOR
|
||||
[GNOSIS]=GNOSIS_SELECTOR
|
||||
[AVALANCHE]=AVALANCHE_SELECTOR
|
||||
[BASE]=BASE_SELECTOR
|
||||
[ARBITRUM]=ARBITRUM_SELECTOR
|
||||
[OPTIMISM]=OPTIMISM_SELECTOR
|
||||
[CELO]=CELO_SELECTOR
|
||||
)
|
||||
|
||||
declare -A ASSET_TO_MAPPING_KEY=(
|
||||
[cUSDT]=Compliant_USDT_cW
|
||||
[cUSDC]=Compliant_USDC_cW
|
||||
[cAUSDT]=Compliant_AUSDT_cW
|
||||
[cUSDW]=Compliant_USDW_cW
|
||||
[cEURC]=Compliant_EURC_cW
|
||||
[cEURT]=Compliant_EURT_cW
|
||||
[cGBPC]=Compliant_GBPC_cW
|
||||
[cGBPT]=Compliant_GBPT_cW
|
||||
[cAUDC]=Compliant_AUDC_cW
|
||||
[cJPYC]=Compliant_JPYC_cW
|
||||
[cCHFC]=Compliant_CHFC_cW
|
||||
[cCADC]=Compliant_CADC_cW
|
||||
[cXAUC]=Compliant_XAUC_cW
|
||||
[cXAUT]=Compliant_XAUT_cW
|
||||
)
|
||||
|
||||
chain_id="${CHAIN_NAME_TO_ID[$CHAIN]:-}"
|
||||
selector_var="${CHAIN_NAME_TO_SELECTOR_VAR[$CHAIN]:-}"
|
||||
mapping_key="${ASSET_TO_MAPPING_KEY[$ASSET]:-}"
|
||||
[[ -n "$chain_id" && -n "$selector_var" && -n "$mapping_key" ]] || {
|
||||
echo "Unsupported chain or asset: chain=$CHAIN asset=$ASSET" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
selector="${!selector_var:-}"
|
||||
[[ -n "$selector" ]] || { echo "Missing selector env for $CHAIN ($selector_var)" >&2; exit 1; }
|
||||
|
||||
CHAIN138_L1_BRIDGE="${CW_L1_BRIDGE_CHAIN138:-${CHAIN138_L1_BRIDGE:-}}"
|
||||
[[ -n "$CHAIN138_L1_BRIDGE" ]] || { echo "Set CW_L1_BRIDGE_CHAIN138 (or CHAIN138_L1_BRIDGE) in .env" >&2; exit 1; }
|
||||
|
||||
SENDER="$(cast wallet address "$PRIVATE_KEY" 2>/dev/null)" || {
|
||||
echo "Failed to derive sender from PRIVATE_KEY" >&2
|
||||
exit 1
|
||||
}
|
||||
if [[ -z "$RECIPIENT" ]]; then
|
||||
RECIPIENT="$SENDER"
|
||||
fi
|
||||
|
||||
map_info="$(
|
||||
node - <<'NODE' "$CONFIG_FILE" "$chain_id" "$mapping_key"
|
||||
const fs = require('fs');
|
||||
const [configFile, chainIdRaw, mappingKey] = process.argv.slice(2);
|
||||
const chainId = Number(chainIdRaw);
|
||||
const json = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
||||
const pair = (json.pairs || []).find((entry) => entry.fromChainId === 138 && entry.toChainId === chainId);
|
||||
if (!pair) process.exit(10);
|
||||
const token = (pair.tokens || []).find((entry) => entry.key === mappingKey);
|
||||
if (!token) process.exit(11);
|
||||
process.stdout.write([token.addressFrom || '', token.addressTo || '', token.name || ''].join('\n'));
|
||||
NODE
|
||||
)" || {
|
||||
echo "Failed to resolve $ASSET -> $CHAIN from $CONFIG_FILE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
canonical_address="$(sed -n '1p' <<<"$map_info")"
|
||||
mirrored_address="$(sed -n '2p' <<<"$map_info")"
|
||||
lane_name="$(sed -n '3p' <<<"$map_info")"
|
||||
|
||||
[[ -n "$canonical_address" && "$canonical_address" != "0x0000000000000000000000000000000000000000" ]] || {
|
||||
echo "Canonical address missing for $ASSET -> $CHAIN in $CONFIG_FILE" >&2
|
||||
exit 1
|
||||
}
|
||||
[[ -n "$mirrored_address" && "$mirrored_address" != "0x0000000000000000000000000000000000000000" ]] || {
|
||||
echo "Mirrored cW address for $ASSET -> $CHAIN is missing in $CONFIG_FILE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
DECIMALS="$(cast call "$canonical_address" "decimals()(uint8)" --rpc-url "$RPC_URL_138" 2>/dev/null | tr -d '[:space:]')" || {
|
||||
echo "Failed to read decimals() from $canonical_address" >&2
|
||||
exit 1
|
||||
}
|
||||
[[ "$DECIMALS" =~ ^[0-9]+$ ]] || { echo "Unexpected decimals() result: $DECIMALS" >&2; exit 1; }
|
||||
|
||||
if [[ -z "$RAW_AMOUNT" ]]; then
|
||||
RAW_AMOUNT="$(
|
||||
python3 - <<'PY' "$AMOUNT" "$DECIMALS"
|
||||
from decimal import Decimal, ROUND_DOWN, getcontext
|
||||
import sys
|
||||
getcontext().prec = 80
|
||||
amount = Decimal(sys.argv[1])
|
||||
decimals = int(sys.argv[2])
|
||||
scale = Decimal(10) ** decimals
|
||||
raw = (amount * scale).quantize(Decimal('1'), rounding=ROUND_DOWN)
|
||||
print(int(raw))
|
||||
PY
|
||||
)"
|
||||
fi
|
||||
|
||||
[[ "$RAW_AMOUNT" =~ ^[0-9]+$ && "$RAW_AMOUNT" != "0" ]] || {
|
||||
echo "Invalid raw amount: $RAW_AMOUNT" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
human_amount="$(
|
||||
python3 - <<'PY' "$RAW_AMOUNT" "$DECIMALS"
|
||||
from decimal import Decimal, getcontext
|
||||
import sys
|
||||
getcontext().prec = 80
|
||||
raw = Decimal(sys.argv[1])
|
||||
decimals = int(sys.argv[2])
|
||||
scale = Decimal(10) ** decimals
|
||||
value = raw / scale
|
||||
text = format(value.normalize(), 'f')
|
||||
print(text.rstrip('0').rstrip('.') if '.' in text else text)
|
||||
PY
|
||||
)"
|
||||
|
||||
clean_scalar() {
|
||||
local value="$1"
|
||||
value="${value%%[*}"
|
||||
value="${value//$'\n'/}"
|
||||
value="${value//$'\r'/}"
|
||||
value="${value//[[:space:]]/}"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
fee_token="$(clean_scalar "$(cast call "$CHAIN138_L1_BRIDGE" "feeToken()(address)" --rpc-url "$RPC_URL_138" 2>/dev/null || true)")"
|
||||
fee_wei="$(clean_scalar "$(cast call "$CHAIN138_L1_BRIDGE" "calculateFee(address,uint64,address,uint256)(uint256)" "$canonical_address" "$selector" "$RECIPIENT" "$RAW_AMOUNT" --rpc-url "$RPC_URL_138" 2>/dev/null || true)")"
|
||||
source_allowance="$(clean_scalar "$(cast call "$canonical_address" "allowance(address,address)(uint256)" "$SENDER" "$CHAIN138_L1_BRIDGE" --rpc-url "$RPC_URL_138" 2>/dev/null || true)")"
|
||||
[[ -n "$source_allowance" ]] || source_allowance="0"
|
||||
|
||||
fee_allowance="0"
|
||||
if [[ -n "$fee_token" && "${fee_token,,}" != "0x0000000000000000000000000000000000000000" && -n "$fee_wei" ]]; then
|
||||
fee_allowance="$(clean_scalar "$(cast call "$fee_token" "allowance(address,address)(uint256)" "$SENDER" "$CHAIN138_L1_BRIDGE" --rpc-url "$RPC_URL_138" 2>/dev/null || true)")"
|
||||
[[ -n "$fee_allowance" ]] || fee_allowance="0"
|
||||
fi
|
||||
|
||||
dest_config="$(cast call "$CHAIN138_L1_BRIDGE" "destinations(address,uint64)((address,bool))" "$canonical_address" "$selector" --rpc-url "$RPC_URL_138" 2>/dev/null || true)"
|
||||
token_supported="$(cast call "$CHAIN138_L1_BRIDGE" "supportedCanonicalToken(address)(bool)" "$canonical_address" --rpc-url "$RPC_URL_138" 2>/dev/null || true)"
|
||||
|
||||
send_tx() {
|
||||
cast send "$CHAIN138_L1_BRIDGE" "lockAndSend(address,uint64,address,uint256)" "$canonical_address" "$selector" "$RECIPIENT" "$RAW_AMOUNT" \
|
||||
--rpc-url "$RPC_URL_138" --private-key "$PRIVATE_KEY" --legacy "$@"
|
||||
}
|
||||
|
||||
approve_tx() {
|
||||
local token="$1"
|
||||
local spender="$2"
|
||||
local amount="$3"
|
||||
cast send "$token" "approve(address,uint256)" "$spender" "$amount" \
|
||||
--rpc-url "$RPC_URL_138" --private-key "$PRIVATE_KEY" --legacy
|
||||
}
|
||||
|
||||
echo "=== Generic Chain 138 c* -> cW* send ==="
|
||||
echo "Chain: $CHAIN (chainId $chain_id)"
|
||||
echo "Lane: $lane_name"
|
||||
echo "Sender: $SENDER"
|
||||
echo "Recipient: $RECIPIENT"
|
||||
echo "Canonical token: $canonical_address"
|
||||
echo "Mirrored token: $mirrored_address"
|
||||
echo "L1 bridge: $CHAIN138_L1_BRIDGE"
|
||||
echo "Chain selector: $selector"
|
||||
echo "Amount (human): $human_amount"
|
||||
echo "Amount (raw): $RAW_AMOUNT"
|
||||
echo "Supported on L1: ${token_supported:-unavailable}"
|
||||
echo "Destination config: $(tr '\n' ' ' <<<"$dest_config" | xargs 2>/dev/null || true)"
|
||||
echo "Source allowance: $source_allowance"
|
||||
if [[ -n "$fee_wei" ]]; then
|
||||
echo "Bridge fee: $fee_wei"
|
||||
fi
|
||||
if [[ -n "$fee_token" ]]; then
|
||||
echo "Fee token: $fee_token"
|
||||
if [[ "${fee_token,,}" != "0x0000000000000000000000000000000000000000" ]]; then
|
||||
echo "Fee allowance: $fee_allowance"
|
||||
else
|
||||
echo "Fee mode: native"
|
||||
fi
|
||||
fi
|
||||
echo "Mode: $([[ "$EXECUTE" == "true" ]] && echo "execute" || echo "dry-run")"
|
||||
echo
|
||||
|
||||
if ! $EXECUTE; then
|
||||
echo "Dry-run commands:"
|
||||
if [[ "$source_allowance" -lt "$RAW_AMOUNT" ]]; then
|
||||
echo "1. Approve source token"
|
||||
printf 'cast send %q %q %q %q --rpc-url %q --private-key "$PRIVATE_KEY" --legacy\n' \
|
||||
"$canonical_address" "approve(address,uint256)" "$CHAIN138_L1_BRIDGE" "$RAW_AMOUNT" "$RPC_URL_138"
|
||||
fi
|
||||
if [[ -n "$fee_token" && "${fee_token,,}" != "0x0000000000000000000000000000000000000000" && -n "$fee_wei" && "$fee_allowance" -lt "$fee_wei" ]]; then
|
||||
echo "2. Approve ERC-20 fee token"
|
||||
printf 'cast send %q %q %q %q --rpc-url %q --private-key "$PRIVATE_KEY" --legacy\n' \
|
||||
"$fee_token" "approve(address,uint256)" "$CHAIN138_L1_BRIDGE" "$fee_wei" "$RPC_URL_138"
|
||||
fi
|
||||
echo "3. lockAndSend"
|
||||
if [[ -n "$fee_token" && "${fee_token,,}" == "0x0000000000000000000000000000000000000000" && -n "$fee_wei" ]]; then
|
||||
printf 'cast send %q %q %q %q %q %q --rpc-url %q --private-key "$PRIVATE_KEY" --legacy --value %q\n' \
|
||||
"$CHAIN138_L1_BRIDGE" "lockAndSend(address,uint64,address,uint256)" "$canonical_address" "$selector" "$RECIPIENT" "$RAW_AMOUNT" "$RPC_URL_138" "$fee_wei"
|
||||
else
|
||||
printf 'cast send %q %q %q %q %q %q --rpc-url %q --private-key "$PRIVATE_KEY" --legacy\n' \
|
||||
"$CHAIN138_L1_BRIDGE" "lockAndSend(address,uint64,address,uint256)" "$canonical_address" "$selector" "$RECIPIENT" "$RAW_AMOUNT" "$RPC_URL_138"
|
||||
fi
|
||||
echo
|
||||
echo "Re-run with --execute to broadcast. Add --approve to auto-submit approvals first."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$source_allowance" -lt "$RAW_AMOUNT" ]]; then
|
||||
if ! $AUTO_APPROVE; then
|
||||
echo "Source allowance is too low; re-run with --approve or approve manually first." >&2
|
||||
exit 1
|
||||
fi
|
||||
approve_tx "$canonical_address" "$CHAIN138_L1_BRIDGE" "$RAW_AMOUNT"
|
||||
fi
|
||||
|
||||
if [[ -n "$fee_token" && "${fee_token,,}" != "0x0000000000000000000000000000000000000000" && -n "$fee_wei" && "$fee_allowance" -lt "$fee_wei" ]]; then
|
||||
if ! $AUTO_APPROVE; then
|
||||
echo "Fee-token allowance is too low; re-run with --approve or approve manually first." >&2
|
||||
exit 1
|
||||
fi
|
||||
approve_tx "$fee_token" "$CHAIN138_L1_BRIDGE" "$fee_wei"
|
||||
fi
|
||||
|
||||
if [[ -n "$fee_token" && "${fee_token,,}" == "0x0000000000000000000000000000000000000000" && -n "$fee_wei" ]]; then
|
||||
send_tx --value "$fee_wei"
|
||||
else
|
||||
send_tx
|
||||
fi
|
||||
@@ -1,43 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
# Fund the mainnet CCIPRelayBridge with WETH from the deployer wallet.
|
||||
# Usage: ./scripts/bridge/fund-mainnet-relay-bridge.sh [amount_wei]
|
||||
# If amount_wei is omitted, transfers the deployer's full WETH balance.
|
||||
# Usage:
|
||||
# ./scripts/bridge/fund-mainnet-relay-bridge.sh [amount_wei] [--dry-run]
|
||||
# ./scripts/bridge/fund-mainnet-relay-bridge.sh --target-bridge-balance-wei 10000000000000000 [--dry-run]
|
||||
# If amount_wei is omitted, transfers the deployer's full WETH balance.
|
||||
# Env: PRIVATE_KEY, ETHEREUM_MAINNET_RPC or RPC_URL_MAINNET (use Infura/Alchemy if llamarpc rate-limits)
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
[[ -f "${PROJECT_ROOT}/smom-dbis-138/.env" ]] && source "${PROJECT_ROOT}/smom-dbis-138/.env" 2>/dev/null || true
|
||||
# shellcheck source=/dev/null
|
||||
source "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" >/dev/null 2>&1 || true
|
||||
|
||||
DEPLOYER="${DEPLOYER_ADDRESS:-0x4A666F96fC8764181194447A7dFdb7d471b301C8}"
|
||||
WETH="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
||||
BRIDGE="0xF9A32F37099c582D28b4dE7Fca6eaC1e5259f939"
|
||||
# Prefer RPC_URL_MAINNET so override works (e.g. RPC_URL_MAINNET=https://ethereum.publicnode.com if Infura requires secret)
|
||||
WETH="${WETH9_MAINNET:-0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2}"
|
||||
BRIDGE="${CCIP_RELAY_BRIDGE_MAINNET:-0xF9A32F37099c582D28b4dE7Fca6eaC1e5259f939}"
|
||||
RPC="${RPC_URL_MAINNET:-${ETHEREUM_MAINNET_RPC:-https://ethereum.publicnode.com}}"
|
||||
|
||||
[[ -n "${PRIVATE_KEY:-}" ]] || { echo "PRIVATE_KEY required"; exit 1; }
|
||||
DRY_RUN=0
|
||||
TARGET_BRIDGE_BALANCE_WEI=""
|
||||
AMOUNT_WEI=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dry-run)
|
||||
DRY_RUN=1
|
||||
shift
|
||||
;;
|
||||
--target-bridge-balance-wei)
|
||||
TARGET_BRIDGE_BALANCE_WEI="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$AMOUNT_WEI" ]]; then
|
||||
AMOUNT_WEI="$1"
|
||||
shift
|
||||
else
|
||||
echo "Usage: $0 [amount_wei] [--target-bridge-balance-wei WEI] [--dry-run]" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
command -v cast &>/dev/null || { echo "cast not found (install Foundry)"; exit 1; }
|
||||
|
||||
BALANCE=$(cast call $WETH "balanceOf(address)(uint256)" $DEPLOYER --rpc-url "$RPC" 2>/dev/null || echo "0")
|
||||
echo "Deployer WETH balance (mainnet): $BALANCE wei"
|
||||
echo "Bridge WETH balance (mainnet): $(cast call $WETH "balanceOf(address)(uint256)" $BRIDGE --rpc-url "$RPC" 2>/dev/null || echo "0") wei"
|
||||
normalize_uint() {
|
||||
awk '{print $1}'
|
||||
}
|
||||
|
||||
if [[ "${1:-}" = "" ]]; then
|
||||
AMT="$BALANCE"
|
||||
echo "Transferring full balance: $AMT wei"
|
||||
else
|
||||
AMT="$1"
|
||||
echo "Transferring: $AMT wei"
|
||||
DEPLOYER_BALANCE="$( (cast call "$WETH" "balanceOf(address)(uint256)" "$DEPLOYER" --rpc-url "$RPC" 2>/dev/null || echo "0") | normalize_uint )"
|
||||
BRIDGE_BALANCE="$( (cast call "$WETH" "balanceOf(address)(uint256)" "$BRIDGE" --rpc-url "$RPC" 2>/dev/null || echo "0") | normalize_uint )"
|
||||
|
||||
echo "Deployer WETH balance (mainnet): $DEPLOYER_BALANCE wei"
|
||||
echo "Bridge WETH balance (mainnet): $BRIDGE_BALANCE wei"
|
||||
|
||||
if [[ -n "$TARGET_BRIDGE_BALANCE_WEI" ]]; then
|
||||
AMOUNT_WEI="$(node -e 'const target=BigInt(process.argv[1]); const current=BigInt(process.argv[2]); const delta=target>current?target-current:0n; process.stdout.write(delta.toString())' "$TARGET_BRIDGE_BALANCE_WEI" "$BRIDGE_BALANCE")"
|
||||
echo "Target bridge balance: $TARGET_BRIDGE_BALANCE_WEI wei"
|
||||
echo "Calculated transfer: $AMOUNT_WEI wei"
|
||||
fi
|
||||
|
||||
if [[ -z "$AMT" ]] || [[ "$AMT" = "0" ]]; then
|
||||
if [[ -z "$AMOUNT_WEI" ]]; then
|
||||
AMOUNT_WEI="$DEPLOYER_BALANCE"
|
||||
echo "Transferring full deployer balance: $AMOUNT_WEI wei"
|
||||
else
|
||||
echo "Transferring: $AMOUNT_WEI wei"
|
||||
fi
|
||||
|
||||
if [[ -z "$AMOUNT_WEI" ]] || [[ "$AMOUNT_WEI" = "0" ]]; then
|
||||
echo "Nothing to transfer."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cast send $WETH "transfer(address,uint256)" $BRIDGE "$AMT" \
|
||||
if [[ "$DRY_RUN" = "1" ]]; then
|
||||
if node -e 'const amount=BigInt(process.argv[1]); const deployer=BigInt(process.argv[2]); process.exit(amount > deployer ? 1 : 0)' "$AMOUNT_WEI" "$DEPLOYER_BALANCE"; then
|
||||
:
|
||||
else
|
||||
echo "[dry-run] Warning: requested transfer exceeds current deployer balance."
|
||||
fi
|
||||
echo "[dry-run] Would execute:"
|
||||
echo "cast send $WETH \"transfer(address,uint256)\" $BRIDGE \"$AMOUNT_WEI\" --rpc-url \"$RPC\" --private-key \"\$PRIVATE_KEY\" --legacy"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[[ -n "${PRIVATE_KEY:-}" ]] || { echo "PRIVATE_KEY required unless --dry-run is used"; exit 1; }
|
||||
node -e 'const amount=BigInt(process.argv[1]); const deployer=BigInt(process.argv[2]); if (amount > deployer) { console.error(`Requested transfer ${amount} exceeds deployer balance ${deployer}.`); process.exit(2); }' "$AMOUNT_WEI" "$DEPLOYER_BALANCE"
|
||||
|
||||
cast send "$WETH" "transfer(address,uint256)" "$BRIDGE" "$AMOUNT_WEI" \
|
||||
--rpc-url "$RPC" \
|
||||
--private-key "$PRIVATE_KEY" \
|
||||
--legacy
|
||||
|
||||
echo "Done. Bridge WETH balance: $(cast call $WETH "balanceOf(address)(uint256)" $BRIDGE --rpc-url "$RPC") wei"
|
||||
FINAL_BRIDGE_BALANCE="$( (cast call "$WETH" "balanceOf(address)(uint256)" "$BRIDGE" --rpc-url "$RPC" 2>/dev/null || echo "0") | normalize_uint )"
|
||||
echo "Done. Bridge WETH balance: $FINAL_BRIDGE_BALANCE wei"
|
||||
|
||||
@@ -27,6 +27,7 @@ done
|
||||
|
||||
AMOUNT_ETH="${ARGS[0]:?Usage: $0 amount_eth [recipient] [--dry-run]}"
|
||||
RECIPIENT="${ARGS[1]:-$(cast wallet address "$PRIVATE_KEY" 2>/dev/null)}"
|
||||
SENDER_ADDR="$(cast wallet address "$PRIVATE_KEY" 2>/dev/null)"
|
||||
|
||||
DEST_SELECTOR="${CCIP_DEST_CHAIN_SELECTOR:-5009297550715157269}"
|
||||
GAS_PRICE="${GAS_PRICE:-1000000000}"
|
||||
@@ -39,13 +40,51 @@ extract_first_address() {
|
||||
echo "$1" | grep -oE '0x[a-fA-F0-9]{40}' | sed -n '1p'
|
||||
}
|
||||
|
||||
raw_uint() {
|
||||
echo "$1" | awk '{print $1}'
|
||||
}
|
||||
|
||||
lower() {
|
||||
echo "$1" | tr '[:upper:]' '[:lower:]'
|
||||
}
|
||||
|
||||
MAINNET_SELECTOR_VALUE="${MAINNET_SELECTOR:-5009297550715157269}"
|
||||
BSC_SELECTOR_VALUE="${BSC_SELECTOR:-11344663589394136015}"
|
||||
AVALANCHE_SELECTOR_VALUE="${AVALANCHE_SELECTOR:-6433500567565415381}"
|
||||
|
||||
assert_supported_direct_first_hop() {
|
||||
case "$DEST_SELECTOR" in
|
||||
"$MAINNET_SELECTOR_VALUE"|"${BSC_SELECTOR_VALUE}"|"${AVALANCHE_SELECTOR_VALUE}")
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "${ALLOW_UNSUPPORTED_DIRECT_FIRST_HOP:-0}" = "1" ]]; then
|
||||
echo "WARNING: proceeding with unsupported direct first hop for selector $DEST_SELECTOR because ALLOW_UNSUPPORTED_DIRECT_FIRST_HOP=1"
|
||||
return 0
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
ERROR: selector $DEST_SELECTOR is not a proven direct first hop from the current Chain 138 router.
|
||||
|
||||
The supported direct first-hop lanes today are:
|
||||
- Mainnet ($MAINNET_SELECTOR_VALUE)
|
||||
- BSC ($BSC_SELECTOR_VALUE)
|
||||
- Avalanche ($AVALANCHE_SELECTOR_VALUE)
|
||||
|
||||
For Gnosis, Cronos, Celo, Polygon, Arbitrum, Optimism, and Base, use the Mainnet hub:
|
||||
1. bridge 138 -> Mainnet
|
||||
2. then bridge Mainnet -> destination
|
||||
|
||||
Set ALLOW_UNSUPPORTED_DIRECT_FIRST_HOP=1 only if you are intentionally testing an unsupported path
|
||||
and accept that the source send may succeed without destination delivery.
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
DEST_RAW="$(cast call "$BRIDGE" 'destinations(uint64)((uint64,address,bool))' "$DEST_SELECTOR" --rpc-url "$RPC" 2>/dev/null || echo "")"
|
||||
DEST_ADDR="$(extract_first_address "$DEST_RAW")"
|
||||
AVALANCHE_SELECTOR_VALUE="${AVALANCHE_SELECTOR:-6433500567565415381}"
|
||||
assert_supported_direct_first_hop
|
||||
|
||||
if [[ "$DEST_SELECTOR" == "$AVALANCHE_SELECTOR_VALUE" ]]; then
|
||||
AVALANCHE_NATIVE_BRIDGE="${CCIPWETH9_BRIDGE_AVALANCHE:-}"
|
||||
@@ -76,8 +115,82 @@ if [[ -n "$CONFIRM_ABOVE" ]] && awk -v a="$AMOUNT_ETH" -v b="$CONFIRM_ABOVE" 'BE
|
||||
fi
|
||||
|
||||
AMOUNT_WEI=$(cast --to-wei "$AMOUNT_ETH" ether)
|
||||
ALLOWANCE_WETH_RAW="$(cast call "$WETH9" "allowance(address,address)(uint256)" "$SENDER_ADDR" "$BRIDGE" --rpc-url "$RPC" 2>/dev/null || echo "0")"
|
||||
ALLOWANCE_WETH="$(raw_uint "$ALLOWANCE_WETH_RAW")"
|
||||
if [[ "$ALLOWANCE_WETH" =~ ^[0-9]+$ ]] && (( ALLOWANCE_WETH < AMOUNT_WEI )); then
|
||||
cat <<EOF
|
||||
ERROR: insufficient WETH allowance for source bridge pull.
|
||||
|
||||
Current allowance:
|
||||
token: $WETH9
|
||||
owner: $SENDER_ADDR
|
||||
spender: $BRIDGE
|
||||
allowance_raw: $ALLOWANCE_WETH
|
||||
amount_raw: $AMOUNT_WEI
|
||||
|
||||
Approve WETH to the Chain 138 bridge first, for example:
|
||||
cast send "$WETH9" "approve(address,uint256)" "$BRIDGE" "$AMOUNT_WEI" --rpc-url "$RPC" --private-key "\$PRIVATE_KEY" --gas-price "$GAS_PRICE" --legacy
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FEE_WEI=$(cast call "$BRIDGE" "calculateFee(uint64,uint256)" "$DEST_SELECTOR" "$AMOUNT_WEI" --rpc-url "$RPC" 2>/dev/null | cast --to-dec || echo "0")
|
||||
FEE_TOKEN=$(cast call "$BRIDGE" "feeToken()(address)" --rpc-url "$RPC" 2>/dev/null || echo "0x0")
|
||||
if [[ "$FEE_TOKEN" != "0x0000000000000000000000000000000000000000" ]] && [[ -n "$FEE_WEI" ]] && [[ "$FEE_WEI" != "0" ]]; then
|
||||
ALLOWANCE_FEE_RAW="$(cast call "$FEE_TOKEN" "allowance(address,address)(uint256)" "$SENDER_ADDR" "$BRIDGE" --rpc-url "$RPC" 2>/dev/null || echo "0")"
|
||||
ALLOWANCE_FEE="$(raw_uint "$ALLOWANCE_FEE_RAW")"
|
||||
if [[ "$ALLOWANCE_FEE" =~ ^[0-9]+$ ]] && (( ALLOWANCE_FEE < FEE_WEI )); then
|
||||
cat <<EOF
|
||||
ERROR: insufficient fee-token allowance for bridge fee pull.
|
||||
|
||||
Current allowance:
|
||||
token: $FEE_TOKEN
|
||||
owner: $SENDER_ADDR
|
||||
spender: $BRIDGE
|
||||
allowance_raw: $ALLOWANCE_FEE
|
||||
required_fee_raw: $FEE_WEI
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
DEST_RPC=""
|
||||
DEST_WETH=""
|
||||
case "$DEST_SELECTOR" in
|
||||
"$MAINNET_SELECTOR_VALUE")
|
||||
DEST_RPC="${ETHEREUM_MAINNET_RPC:-}"
|
||||
DEST_WETH="${WETH9_MAINNET:-}"
|
||||
;;
|
||||
"$BSC_SELECTOR_VALUE")
|
||||
DEST_RPC="${BSC_RPC_URL:-${BSC_MAINNET_RPC:-}}"
|
||||
DEST_WETH="${WETH9_BSC:-}"
|
||||
;;
|
||||
"$AVALANCHE_SELECTOR_VALUE")
|
||||
DEST_RPC="${AVALANCHE_RPC_URL:-${AVALANCHE_MAINNET_RPC:-}}"
|
||||
DEST_WETH="${WETH9_AVALANCHE:-}"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -n "$DEST_ADDR" && -n "$DEST_RPC" && -n "$DEST_WETH" ]]; then
|
||||
DEST_BALANCE_RAW="$(cast call "$DEST_WETH" "balanceOf(address)(uint256)" "$DEST_ADDR" --rpc-url "$DEST_RPC" 2>/dev/null || echo "0")"
|
||||
DEST_BALANCE="$(raw_uint "$DEST_BALANCE_RAW")"
|
||||
if [[ "$DEST_BALANCE" =~ ^[0-9]+$ ]] && (( DEST_BALANCE < AMOUNT_WEI )); then
|
||||
SHORTFALL=$(( AMOUNT_WEI - DEST_BALANCE ))
|
||||
cat <<EOF
|
||||
ERROR: insufficient destination relay inventory for this direct first hop.
|
||||
|
||||
Destination receiver: $DEST_ADDR
|
||||
Destination token: $DEST_WETH
|
||||
Destination balance: $DEST_BALANCE
|
||||
Requested amount: $AMOUNT_WEI
|
||||
Shortfall: $SHORTFALL
|
||||
|
||||
Top up the destination relay inventory first, or reduce the send amount.
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
V=""; [[ "$FEE_TOKEN" = "0x0000000000000000000000000000000000000000" ]] && [[ -n "$FEE_WEI" ]] && [[ "$FEE_WEI" != "0" ]] && V="--value $FEE_WEI"
|
||||
|
||||
GAS_OPT=""; [[ -n "$GAS_LIMIT" ]] && GAS_OPT="--gas-limit $GAS_LIMIT"
|
||||
|
||||
Reference in New Issue
Block a user