gotchi-channeling
Channel Aavegotchis on Base via Bankr. Checks cooldown, builds calldata, and submits channel txs safely.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install openclaw-skills-gotchi-channeling
Repository
Skill path: skills/aaigotchi/gotchi-channeling
Channel Aavegotchis on Base via Bankr. Checks cooldown, builds calldata, and submits channel txs safely.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: openclaw.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install gotchi-channeling into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/openclaw/skills before adding gotchi-channeling to shared team environments
- Use gotchi-channeling for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: gotchi-channeling
description: Channel Aavegotchis on Base via Bankr. Checks cooldown, builds calldata, and submits channel txs safely.
homepage: https://github.com/aaigotchi/gotchi-channeling
metadata:
openclaw:
requires:
bins:
- cast
- jq
- curl
env:
- BANKR_API_KEY
primaryEnv: BANKR_API_KEY
---
# gotchi-channeling
Channel Alchemica for configured gotchi/parcel pairs.
## Scripts
- `./scripts/check-cooldown.sh <gotchi-id>`
- Outputs `ready:0` or `waiting:<seconds>`.
- Fails if RPC query fails.
- `./scripts/channel.sh <gotchi-id> <parcel-id>`
- Validates cooldown, submits tx via Bankr, prints tx hash.
- `./scripts/channel-all.sh`
- Iterates `config.json` pairs and channels only ready gotchis.
## Config
`config.json` keys:
- `realmDiamond`
- `rpcUrl`
- `chainId`
- `channeling[]` entries: `{ "parcelId": "...", "gotchiId": "...", "description": "..." }`
Optional env:
- `GOTCHI_CHANNELING_CONFIG_FILE` override config path.
- `BASE_MAINNET_RPC` overrides `rpcUrl`.
## Bankr API key resolution
1. `BANKR_API_KEY`
2. `systemctl --user show-environment`
3. `~/.openclaw/skills/bankr/config.json`
4. `~/.openclaw/workspace/skills/bankr/config.json`
## Quick use
```bash
./scripts/check-cooldown.sh 9638
./scripts/channel.sh 9638 867
./scripts/channel-all.sh
```
## Safety notes
- Cooldown enforced at 24h (`86400` seconds).
- Scripts fail closed on RPC/config/tool errors.
- Batch mode exits non-zero when any entry fails.
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### scripts/check-cooldown.sh
```bash
#!/usr/bin/env bash
# Check Gotchi channeling cooldown status
# Returns: ready:0 or waiting:SECONDS
set -euo pipefail
usage() {
cat <<USAGE
Usage: ./scripts/check-cooldown.sh <gotchi-id>
Prints one machine-readable line:
ready:0
waiting:<seconds>
USAGE
}
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
usage
exit 0
fi
if [ "$#" -ne 1 ]; then
usage
exit 1
fi
GOTCHI_ID="$1"
COOLDOWN_SECONDS=86400
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib.sh
source "$SCRIPT_DIR/lib.sh"
require_bin cast
require_bin jq
load_config
require_numeric "$GOTCHI_ID" "gotchi-id"
if ! LAST_CHANNELED_HEX="$(cast call "$REALM_DIAMOND" \
"s_gotchiChannelings(uint256)" \
"$GOTCHI_ID" \
--rpc-url "$RPC_URL" 2>/dev/null)"; then
err "Failed to query cooldown from RPC"
fi
LAST_CHANNELED_DEC="$(cast --to-dec "$LAST_CHANNELED_HEX" 2>/dev/null || true)"
[[ "$LAST_CHANNELED_DEC" =~ ^[0-9]+$ ]] || err "Unexpected cooldown value from contract"
CURRENT_TIME="$(date +%s)"
TIME_SINCE=$((CURRENT_TIME - LAST_CHANNELED_DEC))
TIME_REMAINING=$((COOLDOWN_SECONDS - TIME_SINCE))
if [ "$TIME_REMAINING" -le 0 ]; then
echo "ready:0"
else
echo "waiting:$TIME_REMAINING"
fi
```
### scripts/channel.sh
```bash
#!/usr/bin/env bash
# Channel Alchemica for a single Gotchi via Bankr
set -euo pipefail
usage() {
cat <<USAGE
Usage: ./scripts/channel.sh <gotchi-id> <parcel-id>
Examples:
./scripts/channel.sh 9638 867
USAGE
}
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
usage
exit 0
fi
if [ "$#" -ne 2 ]; then
usage
exit 1
fi
GOTCHI_ID="$1"
PARCEL_ID="$2"
TRANSFER_TOPIC="0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
FUD_ADDR="0x2028b4043e6722ea164946c82fe806c4a43a0ff4"
FOMO_ADDR="0xa32137bfb57d2b6a9fd2956ba4b54741a6d54b58"
ALPHA_ADDR="0x15e7cac885e3730ce6389447bc0f7ac032f31947"
KEK_ADDR="0xe52b9170ff4ece4c35e796ffd74b57dec68ca0e5"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib.sh
source "$SCRIPT_DIR/lib.sh"
require_bin cast
require_bin curl
require_bin jq
load_config
require_numeric "$GOTCHI_ID" "gotchi-id"
require_numeric "$PARCEL_ID" "parcel-id"
API_KEY="$(resolve_bankr_api_key)"
echo "🔮 Gotchi Channeling"
echo "===================="
echo "👻 Gotchi: #$GOTCHI_ID"
echo "🏰 Parcel: #$PARCEL_ID"
echo
echo "⏰ Checking cooldown..."
if ! COOLDOWN_RESULT="$($SCRIPT_DIR/check-cooldown.sh "$GOTCHI_ID")"; then
echo "❌ Cooldown check failed; aborting channel attempt"
exit 1
fi
if [[ "$COOLDOWN_RESULT" =~ ^ready: ]]; then
echo "✅ Cooldown ready!"
elif [[ "$COOLDOWN_RESULT" =~ ^waiting:([0-9]+)$ ]]; then
WAIT_TIME="${BASH_REMATCH[1]}"
echo "⏰ Not ready yet!"
echo " Wait: $(format_wait "$WAIT_TIME")"
exit 1
else
err "Unexpected cooldown response: $COOLDOWN_RESULT"
fi
echo
echo "📦 Building transaction..."
CALLDATA="$(cast calldata \
"channelAlchemica(uint256,uint256,uint256,bytes)" \
"$PARCEL_ID" \
"$GOTCHI_ID" \
0 \
"0x")"
echo " Function: channelAlchemica"
echo " Parcel: $PARCEL_ID"
echo " Gotchi: $GOTCHI_ID"
echo " Calldata: ${CALLDATA:0:66}..."
echo
echo "🦞 Submitting to Bankr..."
REQUEST_PAYLOAD="$(jq -n \
--arg to "$REALM_DIAMOND" \
--argjson chainId "$CHAIN_ID" \
--arg data "$CALLDATA" \
--arg description "Channel Alchemica: Gotchi #$GOTCHI_ID on Parcel #$PARCEL_ID" \
'{
transaction: {
to: $to,
chainId: $chainId,
value: "0",
data: $data
},
description: $description,
waitForConfirmation: true
}')"
RESPONSE="$(curl -sS -X POST "https://api.bankr.bot/agent/submit" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "$REQUEST_PAYLOAD")"
SUCCESS="$(echo "$RESPONSE" | jq -r '.success // false')"
if [ "$SUCCESS" != "true" ]; then
ERROR_MSG="$(echo "$RESPONSE" | jq -r '.error // .message // "Unknown error"')"
echo
echo "============================================"
echo "❌ CHANNELING FAILED"
echo "============================================"
echo "Error: $ERROR_MSG"
echo
echo "$RESPONSE" | jq '.'
exit 1
fi
TX_HASH="$(echo "$RESPONSE" | jq -r '.transactionHash // empty')"
BLOCK="$(echo "$RESPONSE" | jq -r '.blockNumber // "pending"')"
echo
echo "============================================"
echo "✅ CHANNELING SUCCESSFUL!"
echo "============================================"
echo
echo "👻 Gotchi #$GOTCHI_ID channeled on Parcel #$PARCEL_ID"
echo "📦 Block: $BLOCK"
if [ -n "$TX_HASH" ]; then
echo "🔗 Transaction: $TX_HASH"
echo "🌐 View: https://basescan.org/tx/$TX_HASH"
fi
echo
echo "💰 Fetching rewards..."
# Reward parsing is best-effort and should never flip a successful tx to failure.
if [ -n "$TX_HASH" ] && RECEIPT="$(cast receipt "$TX_HASH" --rpc-url "$RPC_URL" --json 2>/dev/null)"; then
extract_amount_hex() {
local token_addr="$1"
echo "$RECEIPT" | jq -r --arg topic "$TRANSFER_TOPIC" --arg token "$token_addr" '
.logs[]? |
select((.topics[0] // "" | ascii_downcase) == ($topic | ascii_downcase)) |
select((.address // "" | ascii_downcase) == ($token | ascii_downcase)) |
.data
' | head -1
}
to_token_dec() {
local hex="$1"
local dec
if [ -z "$hex" ] || [ "$hex" = "null" ]; then
printf '0.00'
return
fi
dec="$(cast --to-dec "$hex" 2>/dev/null || echo 0)"
awk -v n="$dec" 'BEGIN { printf "%.2f", n/1e18 }'
}
FUD_HEX="$(extract_amount_hex "$FUD_ADDR")"
FOMO_HEX="$(extract_amount_hex "$FOMO_ADDR")"
ALPHA_HEX="$(extract_amount_hex "$ALPHA_ADDR")"
KEK_HEX="$(extract_amount_hex "$KEK_ADDR")"
FUD_DEC="$(to_token_dec "$FUD_HEX")"
FOMO_DEC="$(to_token_dec "$FOMO_HEX")"
ALPHA_DEC="$(to_token_dec "$ALPHA_HEX")"
KEK_DEC="$(to_token_dec "$KEK_HEX")"
TOTAL_DEC="$(awk -v a="$FUD_DEC" -v b="$FOMO_DEC" -v c="$ALPHA_DEC" -v d="$KEK_DEC" 'BEGIN { printf "%.2f", a+b+c+d }')"
if [ "$TOTAL_DEC" != "0.00" ]; then
echo "💎 Alchemica Earned:"
echo " 🔥 FUD: $FUD_DEC"
echo " 😱 FOMO: $FOMO_DEC"
echo " 🧠 ALPHA: $ALPHA_DEC"
echo " 💚 KEK: $KEK_DEC"
echo " 💰 Total: $TOTAL_DEC Alchemica"
else
echo "💎 Alchemica minted! (reward amounts unavailable from receipt parsing)"
fi
else
echo "💎 Alchemica minted! (receipt unavailable for reward parsing)"
fi
echo
echo "⏰ Next channel: $(date -u -d '+24 hours' '+%Y-%m-%d %H:%M UTC' 2>/dev/null || echo '24 hours from now')"
echo
echo "LFGOTCHi!"
```
### scripts/channel-all.sh
```bash
#!/usr/bin/env bash
# Channel all configured gotchis
set -euo pipefail
usage() {
cat <<USAGE
Usage: ./scripts/channel-all.sh
Reads config.json channeling entries and channels all ready gotchis.
USAGE
}
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
usage
exit 0
fi
if [ "$#" -gt 0 ]; then
usage
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib.sh
source "$SCRIPT_DIR/lib.sh"
require_bin jq
load_config
if ! jq -e '.channeling | type == "array" and length > 0' "$CONFIG_FILE" >/dev/null 2>&1; then
err "No channeling entries in $CONFIG_FILE"
fi
ENTRIES="$(jq -r '.channeling[] | "\(.gotchiId // "")\t\(.parcelId // "")\t\(.description // "")"' "$CONFIG_FILE")"
echo "🔮 Channeling All Gotchis"
echo "========================="
echo
echo "📊 Checking gotchis..."
echo
TOTAL=0
READY=0
WAITING=0
CHANNELED=0
FAILED=0
while IFS=$'\t' read -r GOTCHI_ID PARCEL_ID DESCRIPTION; do
[ -n "$GOTCHI_ID" ] || continue
TOTAL=$((TOTAL + 1))
echo "👻 Gotchi #$GOTCHI_ID (Parcel #$PARCEL_ID)"
if [ -n "$DESCRIPTION" ]; then
echo " 📝 $DESCRIPTION"
fi
if [[ ! "$GOTCHI_ID" =~ ^[0-9]+$ ]] || [[ ! "$PARCEL_ID" =~ ^[0-9]+$ ]]; then
echo " ❌ Invalid gotchiId/parcelId in config"
FAILED=$((FAILED + 1))
echo
continue
fi
if ! COOLDOWN_RESULT="$($SCRIPT_DIR/check-cooldown.sh "$GOTCHI_ID" 2>&1)"; then
echo " ❌ Cooldown check failed: $COOLDOWN_RESULT"
FAILED=$((FAILED + 1))
echo
continue
fi
if [[ "$COOLDOWN_RESULT" =~ ^ready: ]]; then
READY=$((READY + 1))
echo " ✅ Ready to channel"
LOG_FILE="$(mktemp "/tmp/channel-${GOTCHI_ID}-XXXX.log")"
if "$SCRIPT_DIR/channel.sh" "$GOTCHI_ID" "$PARCEL_ID" >"$LOG_FILE" 2>&1; then
CHANNELED=$((CHANNELED + 1))
echo " ✅ Channeled successfully"
REWARDS_LINE="$(grep -E "Total:" "$LOG_FILE" | tail -1 || true)"
if [ -n "$REWARDS_LINE" ]; then
echo " 💎 $REWARDS_LINE"
fi
else
FAILED=$((FAILED + 1))
echo " ❌ Channeling failed (log: $LOG_FILE)"
fi
elif [[ "$COOLDOWN_RESULT" =~ ^waiting:([0-9]+)$ ]]; then
WAITING=$((WAITING + 1))
WAIT_TIME="${BASH_REMATCH[1]}"
echo " ⏰ Wait $(format_wait "$WAIT_TIME")"
else
FAILED=$((FAILED + 1))
echo " ❌ Unexpected cooldown output: $COOLDOWN_RESULT"
fi
echo
done <<< "$ENTRIES"
echo "============================================"
echo "📊 CHANNELING SUMMARY"
echo "============================================"
echo "Total gotchis: $TOTAL"
echo "Ready: $READY"
echo "Channeled: $CHANNELED"
echo "Failed: $FAILED"
echo "Still waiting: $WAITING"
echo
if [ "$FAILED" -gt 0 ]; then
echo "⚠️ Completed with failures"
exit 1
fi
if [ "$CHANNELED" -gt 0 ]; then
echo "✅ Successfully channeled $CHANNELED gotchi(s)!"
elif [ "$READY" -eq 0 ]; then
echo "⏰ No gotchis ready to channel yet"
else
echo "⚠️ No channels were submitted"
fi
echo
echo "LFGOTCHi!"
```
---
## Skill Companion Files
> Additional files collected from the skill directory layout.
### README.md
```markdown
# Gotchi Channeling
Automate Aavegotchi Alchemica channeling on Base via Bankr wallet submission.
## Scripts
- `./scripts/check-cooldown.sh <gotchi-id>`
- Returns `ready:0` or `waiting:<seconds>`
- `./scripts/channel.sh <gotchi-id> <parcel-id>`
- Channels one gotchi if cooldown is ready
- `./scripts/channel-all.sh`
- Reads `config.json` and channels all ready entries
## Requirements
- `cast` (Foundry)
- `jq`
- `curl`
- Bankr API key
Bankr API key resolution order:
1. `BANKR_API_KEY`
2. user systemd environment (`systemctl --user show-environment`)
3. `~/.openclaw/skills/bankr/config.json`
4. `~/.openclaw/workspace/skills/bankr/config.json`
## Config
Default config path: `./config.json`
Override path: `GOTCHI_CHANNELING_CONFIG_FILE=/path/to/config.json`
Example:
```json
{
"realmDiamond": "0x4B0040c3646D3c44B8a28Ad7055cfCF536c05372",
"rpcUrl": "https://mainnet.base.org",
"chainId": 8453,
"channeling": [
{
"parcelId": "867",
"gotchiId": "9638",
"description": "Primary pair"
}
]
}
```
## Notes
- Cooldown is 24h (`86400` seconds).
- `channel.sh` fails closed if cooldown/RPC checks fail.
- `channel-all.sh --help` is non-destructive.
```
### _meta.json
```json
{
"owner": "aaigotchi",
"slug": "gotchi-channeling",
"displayName": "Gotchi Channeling",
"latest": {
"version": "0.2.0",
"publishedAt": 1772826838966,
"commit": "https://github.com/openclaw/skills/commit/430d7525c4786ed04852a1ca4c56611280fc59bf"
},
"history": [
{
"version": "0.1.0",
"publishedAt": 1771648557060,
"commit": "https://github.com/openclaw/skills/commit/32270de1cdbe1e822a3a27c857c7a417c606a033"
}
]
}
```
### references/FUNCTION_SEARCH.md
```markdown
# Finding channelAlchemica Function
## Contract Info
- **Contract:** Aavegotchi Diamond (Base)
- **Address:** 0xA99c4B08201F2913Db8D28e71d020c4298F29dBF
- **Chain:** Base (8453)
## Possible Function Signatures
Based on Aavegotchi documentation, the function likely has one of these signatures:
### Option 1: Single Parameter (Gotchi ID only)
```solidity
function channelAlchemica(uint256 _gotchiId) external
```
- **Selector:** `0x38d0b418`
- **Use case:** Channel using gotchi on your default parcel
### Option 2: Two Parameters (Realm + Gotchi)
```solidity
function channelAlchemica(uint256 _realmId, uint256 _gotchiId) external
```
- **Selector:** `0x7e27b66f`
- **Use case:** Specify which parcel to channel on
### Option 3: Three Parameters (Realm + Gotchi + Installation)
```solidity
function channelAlchemica(uint256 _realmId, uint256 _gotchiId, uint256 _installationId) external
```
- **Selector:** `0x356dedfb`
- **Use case:** Specify exact Aaltar installation
## How to Find The Real One
### Method 1: Manual Channel Transaction
1. Go to https://verse.aavegotchi.com
2. Channel Gotchi #9638 manually
3. Check transaction on BaseScan
4. View "Input Data" to see actual function call
### Method 2: Contract ABI
- Check BaseScan verified contract
- Look for channelAlchemica in read/write functions
- Extract exact signature
### Method 3: GitHub Source
- Repository: aavegotchi/aavegotchi-realm-diamond
- File: contracts/RealmDiamond/facets/AlchemicaFacet.sol
- Search for: `function channelAlchemica`
## Next Steps
Once we find the correct function:
1. Update `scripts/channel.sh` with correct selector
2. Test with Gotchi #9638 on Parcel #867
3. Verify transaction succeeds
4. Add to automated daily channeling
## Resources
- [Aavegotchi Contracts](https://wiki.aavegotchi.com/en/contracts)
- [Gotchiverse Bible Chapter 3](https://blog.aavegotchi.com/gotchiverse-bible-chapter-3/)
- [BaseScan Contract](https://basescan.org/address/0xA99c4B08201F2913Db8D28e71d020c4298F29dBF)
---
**Status:** Searching for function signature...
**Date:** 2026-02-20
```
### references/FUNCTION_SIGNATURE.md
```markdown
# channelAlchemica Function Signature
## ✅ FOUND!
**Contract:** Aavegotchi REALM Diamond
**Source:** https://github.com/aavegotchi/aavegotchi-realm-diamond
**File:** contracts/RealmDiamond/facets/AlchemicaFacet.sol
## Function Signature
```solidity
function channelAlchemica(
uint256 _realmId,
uint256 _gotchiId,
uint256 _lastChanneled,
bytes memory _signature
) external diamondPaused gameActive
```
**Function Selector:** `cast sig "channelAlchemica(uint256,uint256,uint256,bytes)"`
## Parameters
1. **_realmId** (uint256) - Your REALM parcel token ID
- Example: 867 (your parcel)
2. **_gotchiId** (uint256) - Your Aavegotchi token ID
- Example: 9638
3. **_lastChanneled** (uint256) - Timestamp of last channeling for this gotchi
- Get from: `s.gotchiChannelings[_gotchiId]`
- First time: 0
4. **_signature** (bytes) - Backend signature for anti-bot protection
- **⚠️ CRITICAL:** Requires backend API call to get signature
- Signature validates: keccak256(abi.encodePacked(_realmId, _gotchiId, _lastChanneled))
## Key Requirements
### 1. **24-Hour Cooldown**
```solidity
if (s.lastChanneledDay[_gotchiId] == block.timestamp / (60 * 60 * 24))
revert("AlchemicaFacet: Gotchi can't channel yet");
```
### 2. **Aaltar Must Be Equipped**
```solidity
require(altarLevel > 0, "AlchemicaFacet: Must equip Altar");
```
### 3. **Parcel Cooldown (Aaltar Level Dependent)**
```solidity
require(
block.timestamp >= s.parcelChannelings[_realmId] + s.channelingLimits[altarLevel],
"AlchemicaFacet: Parcel can't channel yet"
);
```
### 4. **Backend Signature Validation**
```solidity
require(
LibSignature.isValid(
keccak256(abi.encodePacked(_realmId, _gotchiId, _lastChanneled)),
_signature,
s.backendPubKey
),
"AlchemicaFacet: Invalid signature"
);
```
## ⚠️ BLOCKER: Backend Signature Required
**The function REQUIRES a signature from Aavegotchi's backend server.**
This signature:
- Prevents botting/automation
- Validates the channeling request
- Cannot be generated without access to backend private key
## Solutions
### Option 1: Use Gotchiverse UI (Manual)
- Visit https://verse.aavegotchi.com
- Gotchiverse frontend calls backend API to get signature
- Click "Channel" button - signature included automatically
### Option 2: Reverse Engineer Backend API (Advanced)
- Monitor network traffic in Gotchiverse
- Find the backend API endpoint for signatures
- Call it programmatically before channeling
- **Risk:** API may have rate limits or require auth
### Option 3: Browser Automation (Hybrid)
- Use Selenium/Puppeteer to automate Gotchiverse UI
- Let frontend handle signature fetching
- Programmatically click "Channel" button
### Option 4: Contact Aavegotchi Team
- Request official API access for channeling
- Explain automation use case
- Get authorized signature endpoint
## Recommended Path Forward
**For now:** The skill **cannot** do fully autonomous channeling due to the signature requirement.
**Alternative:** Build a **channeling reminder** skill instead:
- Check if channeling is available (read-only)
- Notify you when ready to channel
- Provide direct link to Gotchiverse
- Track your channeling history
## Updated Skill Scope
Instead of autonomous channeling, we can build:
1. **Channeling Status Checker**
- Check 24h cooldown
- Verify Aaltar equipped
- Calculate next available time
2. **Channeling Reminder System**
- Daily notifications
- "Ready to channel!" alerts
- One-click link to Gotchiverse
3. **Channeling History Tracker**
- Log when you channel
- Track rewards earned
- Calculate total Alchemica farmed
Would you like me to pivot the skill to this approach?
---
**Date:** 2026-02-20
**Status:** Function found, signature blocker identified
**Next:** Redesign skill as reminder/tracker system
```
### scripts/lib.sh
```bash
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="${GOTCHI_CHANNELING_CONFIG_FILE:-$SCRIPT_DIR/../config.json}"
DEFAULT_REALM_DIAMOND="0x4B0040c3646D3c44B8a28Ad7055cfCF536c05372"
DEFAULT_RPC_URL="https://mainnet.base.org"
DEFAULT_CHAIN_ID="8453"
err() {
echo "ERROR: $*" >&2
exit 1
}
require_bin() {
local bin="$1"
command -v "$bin" >/dev/null 2>&1 || err "Missing required binary: $bin"
}
require_numeric() {
local value="$1"
local label="$2"
[[ "$value" =~ ^[0-9]+$ ]] || err "Invalid $label: $value"
}
load_config() {
[ -f "$CONFIG_FILE" ] || err "Config file not found: $CONFIG_FILE"
REALM_DIAMOND="$(jq -r '.realmDiamond // empty' "$CONFIG_FILE")"
RPC_URL="$(jq -r '.rpcUrl // empty' "$CONFIG_FILE")"
CHAIN_ID="$(jq -r '.chainId // empty' "$CONFIG_FILE")"
[ -n "$REALM_DIAMOND" ] || REALM_DIAMOND="$DEFAULT_REALM_DIAMOND"
[ -n "$RPC_URL" ] || RPC_URL="$DEFAULT_RPC_URL"
[ -n "$CHAIN_ID" ] || CHAIN_ID="$DEFAULT_CHAIN_ID"
if [ -n "${BASE_MAINNET_RPC:-}" ]; then
RPC_URL="$BASE_MAINNET_RPC"
fi
[[ "$REALM_DIAMOND" =~ ^0x[0-9a-fA-F]{40}$ ]] || err "Invalid realmDiamond in config"
require_numeric "$CHAIN_ID" "chainId"
}
resolve_bankr_api_key() {
local key="${BANKR_API_KEY:-}"
if [ -z "$key" ] && command -v systemctl >/dev/null 2>&1; then
key="$(systemctl --user show-environment 2>/dev/null | sed -n 's/^BANKR_API_KEY=//p' | head -n1 || true)"
fi
if [ -z "$key" ] && [ -f "$HOME/.openclaw/skills/bankr/config.json" ]; then
key="$(jq -r '.apiKey // empty' "$HOME/.openclaw/skills/bankr/config.json" 2>/dev/null || true)"
fi
if [ -z "$key" ] && [ -f "$HOME/.openclaw/workspace/skills/bankr/config.json" ]; then
key="$(jq -r '.apiKey // empty' "$HOME/.openclaw/workspace/skills/bankr/config.json" 2>/dev/null || true)"
fi
[ -n "$key" ] || err "BANKR_API_KEY missing (env/systemd/bankr config)"
printf '%s\n' "$key"
}
format_wait() {
local seconds="$1"
local hours mins
hours=$((seconds / 3600))
mins=$(((seconds % 3600) / 60))
printf '%sh %sm' "$hours" "$mins"
}
```