HoodFi 与 Robinhood Chain:读取 coin type 2147488311 名称记录

用 viem 读取 HoodFi 名称的 Robinhood 地址,选择 coin type 2147488311,并在同一规范区块哈希比较 coin type 60。

选择 Robinhood 记录

读取 Robinhood Chain(4663)上的 HoodFi 名称时,用 addr(bytes32,uint256) 读取 coin type 2147488311,再在同一区块哈希比较 coin type 60。选择 Robinhood 记录;如果为空、零地址或不是恰好 20 bytes,立即停止,不回退到 coin type 60 或默认记录。两个地址不同时,使用前请用户确认 Robinhood 目标地址。

这遵循维护者的记录选择策略。ENSIP-11 用 0x80000000 | chainId 推导 EVM coin type:4663 对应 2147488311。单参数的 addr(bytes32) 读取 coin type 60,不能用它选择 Robinhood 记录。

本配方调用固定的 HoodFi 注册合约 0xf2bABA012244bdD7445129597350054E1B3aEe5C,以 namehash(NAME) 传入节点;这不是通用 ENS 解析器发现流程。

运行读取脚本

准备 Node.js 20+ 和 viem 2.x(在自己的项目执行 npm install viem)。从控制台获取 key,或使用程序化注册指南;初次配置见快速开始。在本机将 BLOCKVECTRA_API_KEY 设为真实 key,把下面的 JavaScript 保存为 hoodfi-name-records.mjs,执行 node hoodfi-name-records.mjs。可选设置 NAME,默认值为 mike.hoodfi.eth。不要把 key 写进源码、URL 或日志。

脚本请求 https://api.blockvectra.com/v1/robinhood_mainnet,key 只放在 x-api-key 请求头。最多串行发出四次请求,间隔至少 1.1 秒,每次超时 15 秒,不跟随重定向、不重试:

  1. 核对 eth_chainId 为 0x1237(4663)。
  2. 只读取一次 latest,保留区块哈希。
  3. 用 {blockHash, requireCanonical: true} 读取 coin type 2147488311;Robinhood 地址无效即停止。
  4. 用完全相同的区块选择器读取 coin type 60,仅用于比较。空值或零地址表示没有比较记录,其他畸形字节使脚本停止。
import { decodeFunctionResult, encodeFunctionData, namehash, parseAbi } from "viem";

const ENDPOINT = "https://api.blockvectra.com/v1/robinhood_mainnet";
const REGISTRY = "0xf2bABA012244bdD7445129597350054E1B3aEe5C";
const NAME = process.env.NAME ?? "mike.hoodfi.eth";
const RH_COIN_TYPE = 2147488311n; // ENSIP-11: 0x80000000 | 4663
const GAP_MS = 1_100;
const TIMEOUT_MS = 15_000;
const apiKey = process.env.BLOCKVECTRA_API_KEY;
if (!apiKey || /\s|replace|placeholder|your[_-]?(api[_-]?)?key|[<>]/i.test(apiKey)) {
  throw new Error("Set a real BLOCKVECTRA_API_KEY in your local environment");
}
const abi = parseAbi(["function addr(bytes32 node, uint256 coinType) view returns (bytes)"]);
const node = namehash(NAME); // A node for this fixed HoodFi registry, not general ENS resolution.

// Raw JSON-RPC preserves the EIP-1898 object without client-side block tag rewriting.
let sent = 0;
let lastSentAt;
async function rpc(method, params) {
  if (sent >= 4) throw new Error("Four-request diagnostic budget exceeded");
  if (lastSentAt !== undefined) {
    const wait = lastSentAt + GAP_MS - Date.now();
    if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));
  }
  lastSentAt = Date.now();
  const id = ++sent;
  let response;
  try {
    response = await fetch(ENDPOINT, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "x-api-key": apiKey,
        "user-agent": "hoodfi-name-records-diagnostic BlockVectraQA/1",
      },
      body: JSON.stringify({ jsonrpc: "2.0", id, method, params }),
      redirect: "error",
      signal: AbortSignal.timeout(TIMEOUT_MS),
    });
  } catch {
    throw new Error(`${method}: transport failure (including timeout or redirect)`);
  }
  if (response.status !== 200) throw new Error(`${method}: HTTP ${response.status}`);
  let body;
  try {
    body = await response.json();
  } catch {
    throw new Error(`${method}: invalid JSON`);
  }
  if (!body || body.jsonrpc !== "2.0" || body.id !== id) {
    throw new Error(`${method}: invalid JSON-RPC response id or version`);
  }
  if (Object.hasOwn(body, "error")) {
    // Do not echo server messages or arbitrary response data: they can contain secrets.
    const code = Number.isSafeInteger(body.error?.code) ? body.error.code : "unknown";
    const reason = body.error?.data?.reason === "state_window" ? " state_window" : "";
    throw new Error(`${method}: JSON-RPC error ${code}${reason}`);
  }
  if (!Object.hasOwn(body, "result")) throw new Error(`${method}: missing result`);
  return body.result;
}

const chainId = await rpc("eth_chainId", []);
if (typeof chainId !== "string" || !/^0x(?:0|[1-9a-f][0-9a-f]*)$/i.test(chainId)
    || BigInt(chainId) !== 4663n) throw new Error("Expected chain 4663 (0x1237)");

const head = await rpc("eth_getBlockByNumber", ["latest", false]);
const blockHash = head?.hash;
if (typeof blockHash !== "string" || !/^0x[0-9a-f]{64}$/i.test(blockHash)) {
  throw new Error("Invalid block hash");
}
const at = { blockHash, requireCanonical: true };

async function readAddress(coinType, label, optional) {
  const result = await rpc("eth_call", [{
    to: REGISTRY,
    data: encodeFunctionData({ abi, functionName: "addr", args: [node, coinType] }),
  }, at]);
  let bytes;
  try {
    if (typeof result !== "string" || !/^0x(?:[0-9a-f]{2})*$/i.test(result)) throw new Error();
    bytes = decodeFunctionResult({ abi, functionName: "addr", data: result });
  } catch {
    throw new Error(`${label}: invalid ABI result`);
  }
  if (bytes === "0x" || /^0x0{40}$/i.test(bytes)) {
    if (optional) return null;
    throw new Error(`${label}: missing or zero address`);
  }
  if (!/^0x[0-9a-f]{40}$/i.test(bytes)) throw new Error(`${label}: expected exactly 20 bytes`);
  return bytes.toLowerCase();
}

// Request three must succeed before coin type 60 is even read.
const robinhoodAddress = await readAddress(RH_COIN_TYPE, "Robinhood record", false);
const coin60 = await readAddress(60n, "Coin type 60 record", true);
const warnings = [];
if (coin60 !== null && coin60 !== robinhoodAddress) {
  warnings.push("Records differ: confirm the Robinhood destination before use; coin type 60 is comparison only.");
}
console.log(JSON.stringify({ blockHash, robinhoodAddress, coin60, warnings }, null, 2));

输出仅含 blockHash、robinhoodAddress、coin60(或 null)与 warnings。使用地址前先检查警告。脚本不生成、签名或发送交易,User-Agent 标记本次为诊断运行。

HTTP 非 200、JSON-RPC 错误、响应 id 非法、缺少 result、哈希非法或 ABI 解码异常都会停止脚本,不输出部分结果。哈希读取遭拒时,不得换成 latest 或区块号继续读取。需要新快照时,从核对链 ID 开始重新运行。

访问方式与快照语义

EIP-1898 用哈希指定区块,requireCanonical: true 使非规范区块被拒绝。在同一哈希接受的两次读取,也不保证该区块将来不会重组。

本配方使用带 key 的入口。免 key 的 /v1/robinhood_mainnet/public 路由此前对这类固定哈希的 eth_call 返回 -32011 / state_window;仅能读取链头,不代表支持名称记录读取。当前能力与额度请查 GET /v1/chains 和定价页。

mike.hoodfi.eth 的一份两条记录匹配的快照见公开读取说明。使用地址前,请自行读取当前记录。

最后更新:

本页目录