參考: Universal Resolver https://docs.ens.domains/resolvers/universal/
Offchain / L2 Resolvers https://docs.ens.domains/resolvers/ccip-read#offchain-vs-l2-resolvers
註:不使用純 Bash/Zsh 原因
- 本質是 文字導向
- 對 \x00(NULL byte)處理極差
- 變數裡 不能安全保存 binary data
- 換一台機器 → encoding 就歪了
> code ~/.zshrc
# Encode DNS/ENS name to DNS wire-format hex (RFC 1035)
function dnsEncode() {
if [[ -z "$1" ]]; then
echo "Usage: dnsencode <name>"
return 1
fi
python3 - <<EOF "$1"
import sys
name = sys.argv[1]
out = b''
for label in name.split('.'):
out += bytes([len(label)]) + label.encode()
out += b'\x00'
print('0x' + out.hex())
EOF
}
### 開新視窗讓 ~/.zshrc 生效
> FORWARD_NAME="aruun.eth" \
&& COIN_TYPE="$(( 0x80000000 + 10 ))" \
&& REVERSE_ADDRESS_LOWERCASE="0x2625b4da309819cb8b5eb8aee13c86ad0e097ef6" \
&& ALCHEMY_KEY="XXX" \
&& NAME_NODE_HASH=$(cast namehash ${FORWARD_NAME}) \
&& NAME_DNS_ENCODE=$(dnsEncode ${FORWARD_NAME}) \
&& RPC_URL="https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}" \
&& UNIVERSAL_RESOLVER="0xeEeEEEeE14D718C2B47D9923Deab1335E144EeEe"
### 常用函數
> RESOLVER_ADDR_FN="addr(bytes32 node, uint256 coinType) returns (bytes addressBytes)" \
&& UNIVERSAL_RESOLVER_RESOLVE_FN="resolve(bytes name, bytes data) returns (bytes result, address resolver)" \
&& UNIVERSAL_RESOLVER_REVERSE_FN="reverse(bytes lookupAddress, uint256 coinType) returns (string primary, address resolver, address reverseResolver)" \
&& UNIVERSAL_RESOLVER_CCIP_READ_CALLBACK_FN="ccipReadCallback(bytes response, bytes extraData)" \
&& RESOLVER_OFFCHAIN_LOOKUP_ERR="OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData)"
### 建立查詢 addr() 的 calldata
> ADDR_DATA=$(cast calldata ${RESOLVER_ADDR_FN} ${NAME_NODE_HASH} ${COIN_TYPE}) && echo ${ADDR_DATA}
0xf1cb7e061641f824d4ccacc9bb1b01927b7be79f0d6a054d87a265b4e0dd5a27060ee2ab000000000000000000000000000000000000000000000000000000008000000a
### 查詢 addr()
> CALL_RESOLVE_RESULT=$(cast call --rpc-url ${RPC_URL} ${UNIVERSAL_RESOLVER} ${UNIVERSAL_RESOLVER_RESOLVE_FN} ${NAME_DNS_ENCODE} ${ADDR_DATA}) \
&& ADDR_RESULT_RAW=$(echo "$CALL_RESOLVE_RESULT" | sed -n '1p') \
&& ADDR_RESOLVER=$(echo "$CALL_RESOLVE_RESULT" | sed -n '2p') \
&& echo ${ADDR_RESULT_RAW} \
&& echo ${ADDR_RESOLVER}
0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000142625b4da309819cb8b5eb8aee13c86ad0e097ef6000000000000000000000000
0x231b0Ee14048e9dCcD1d247744d114a4EB5E8E63
### 解析 addressBytes
### 註:預設解析成 returns 參數,若要解析成 methodId + params 參數,則需加 --input flag
> cast decode-abi ${RESOLVER_ADDR_FN} ${ADDR_RESULT_RAW}
0x2625b4da309819cb8b5eb8aee13c86ad0e097ef6
### 反向查詢
> CALL_REVERSE_RESULT=$(cast call --rpc-url ${RPC_URL} ${UNIVERSAL_RESOLVER} ${UNIVERSAL_RESOLVER_REVERSE_FN} ${REVERSE_ADDRESS_LOWERCASE} ${COIN_TYPE}) \
&& NAME_PRIMARY=$(echo "$CALL_REVERSE_RESULT" | sed -n '1p') \
&& NAME_RESOLVER=$(echo "$CALL_REVERSE_RESULT" | sed -n '2p') \
&& NAME_REVERSE_RESOLVER=$(echo "$CALL_REVERSE_RESULT" | sed -n '3p') \
&& echo ${NAME_PRIMARY} \
&& echo ${NAME_RESOLVER} \
&& echo ${NAME_REVERSE_RESOLVER}
"aruun.eth"
0x231b0Ee14048e9dCcD1d247744d114a4EB5E8E63
0xF29100983E058B709F3D539b0c765937B804AC15
### 反向查詢並得到 OffchainLookup() 錯誤(這是已知錯誤,Gateway 資訊會被夾帶在錯誤中)
> OFFCHAIN_LOOKUP_ERR_RAW=$(cast call --rpc-url ${RPC_URL} ${UNIVERSAL_RESOLVER} ${UNIVERSAL_RESOLVER_REVERSE_FN} ${REVERSE_ADDRESS_LOWERCASE} ${COIN_TYPE} 2>&1 | sed -n 's/.*data: "\(0x[0-9a-fA-F]*\)".*/\1/p')
### 解析 OffchainLookup() 錯誤
> OFFCHAIN_LOOKUP_ERR=$(cast decode-calldata ${RESOLVER_OFFCHAIN_LOOKUP_ERR} ${OFFCHAIN_LOOKUP_ERR_RAW}) \
&& LOOKUP_SENDER=$(echo "$OFFCHAIN_LOOKUP_ERR" | sed -n '1p') \
&& LOOKUP_URLS=$(echo "$OFFCHAIN_LOOKUP_ERR" | sed -n '2p') \
&& LOOKUP_CALL_DATA=$(echo "$OFFCHAIN_LOOKUP_ERR" | sed -n '3p') \
&& LOOKUP_CALLBACK_FUNCTION=$(echo "$OFFCHAIN_LOOKUP_ERR" | sed -n '4p') \
&& LOOKUP_EXTRA_DATA=$(echo "$OFFCHAIN_LOOKUP_ERR" | sed -n '5p') \
&& echo ${LOOKUP_SENDER} \
&& echo ${LOOKUP_URLS} \
&& echo ${LOOKUP_CALLBACK_FUNCTION}
### 解析 urls
> CCIP_READ_GATEWAYS=($(echo $LOOKUP_URLS | sed 's/[][]//g; s/"//g' | tr ',' '\n' | sed 's/^[[:space:]]*//')) \
&& printf '%s\n' "${CCIP_READ_GATEWAYS[@]}" \
&& echo "${#CCIP_READ_GATEWAYS[@]}" # 陣列長度
https://ccip-v3.ens.xyz
x-batch-gateway:true
2
### 選定一組 CCIP-Read Gateway
> CCIP_READ_GATEWAY=${CCIP_READ_GATEWAYS[1]}
### 若 URL 含 {data} 則呼叫 Offchain Resolver CCIP-Read Gateway
### 否則呼叫 L2 Resolver CCIP-Read Gateway
if [[ "$CCIP_READ_GATEWAY" == *"{data}"* ]]; then
# Offchain Resolver CCIP-Read (GET)
gateway_url="${CCIP_READ_GATEWAY//\{sender\}/${LOOKUP_SENDER}}"
gateway_url="${gateway_url//\{data\}/${LOOKUP_CALL_DATA}}"
GATEWAY_RESPONSE_DATA=$(curl -sS "$gateway_url" | jq -r '.data')
else
# L2 Resolver CCIP-Read (POST)
GATEWAY_RESPONSE_DATA=$(curl -sS -X POST "$CCIP_READ_GATEWAY" -H "Content-Type: application/json" -d "{\"data\":\"${LOOKUP_CALL_DATA}\",\"sender\":\"${LOOKUP_SENDER}\"}" | jq -r '.data')
fi
### 驗證及取得結果
> CCIP_READ_REVERSE_RESULT_RAW=$(cast call --rpc-url ${RPC_URL} ${UNIVERSAL_RESOLVER} ${UNIVERSAL_RESOLVER_CCIP_READ_CALLBACK_FN} ${GATEWAY_RESPONSE_DATA} ${LOOKUP_EXTRA_DATA}) && echo ${CCIP_READ_REVERSE_RESULT_RAW}
0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000231b0ee14048e9dccd1d247744d114a4eb5e8e63000000000000000000000000f9edb1a21867ac11b023ce34abad916d29abf1070000000000000000000000000000000000000000000000000000000000000009617275756e2e6574680000000000000000000000000000000000000000000000
### 註:若想看驗證失敗的呼叫(不會回傳 data)
> cast call --rpc-url ${RPC_URL} ${UNIVERSAL_RESOLVER} ${UNIVERSAL_RESOLVER_CCIP_READ_CALLBACK_FN} "0xdead${GATEWAY_RESPONSE_DATA:6}" ${LOOKUP_EXTRA_DATA} --trace
Error: server returned an error response: error code 3: execution reverted
### 最後解析出 Name 的內容
> CCIP_READ_REVERSE_RESULT=$(cast decode-abi ${UNIVERSAL_RESOLVER_REVERSE_FN} ${CCIP_READ_REVERSE_RESULT_RAW}) \
&& CCIP_READ_NAME_PRIMARY=$(echo "$CCIP_READ_REVERSE_RESULT" | sed -n '1p') \
&& CCIP_READ_NAME_RESOLVER=$(echo "$CCIP_READ_REVERSE_RESULT" | sed -n '2p') \
&& CCIP_READ_NAME_REVERSE_RESOLVER=$(echo "$CCIP_READ_REVERSE_RESULT" | sed -n '3p') \
&& echo ${CCIP_READ_NAME_PRIMARY} \
&& echo ${CCIP_READ_NAME_RESOLVER} \
&& echo ${CCIP_READ_NAME_REVERSE_RESOLVER}
"aruun.eth"
0x231b0Ee14048e9dCcD1d247744d114a4EB5E8E63
0xF9Edb1A21867aC11b023CE34Abad916D29aBF107