Files
proxmox/scripts/bridge/bridge-cstar-to-cw.sh
defiQUG dbd517b279 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
2026-04-12 06:12:20 -07:00

340 lines
12 KiB
Bash
Executable File

#!/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