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

> 原文地址: https://docs.blockvectra.com/zh/guides/hoodfi-name-records/

## 选择 Robinhood 记录

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

这遵循[维护者的记录选择策略](https://github.com/blockhash-xyz/mancer-sdk/issues/2#issuecomment-6622496038)。[ENSIP-11](https://docs.ens.domains/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`）。从[控制台](https://console.blockvectra.com/login/?next=%2Fkeys%2F)获取 key，或使用[程序化注册指南](https://docs.blockvectra.com/zh/guides/programmatic-signup/)；初次配置见[快速开始](https://docs.blockvectra.com/zh/quickstart/)。在本机将 `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，仅用于比较。空值或零地址表示没有比较记录，其他畸形字节使脚本停止。

```js
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](https://eips.ethereum.org/EIPS/eip-1898) 用哈希指定区块，`requireCanonical: true` 使非规范区块被拒绝。在同一哈希接受的两次读取，也不保证该区块将来不会重组。

本配方使用带 key 的入口。免 key 的 `/v1/robinhood_mainnet/public` 路由此前对这类固定哈希的 `eth_call` 返回 `-32011` / `state_window`；仅能读取链头，不代表支持名称记录读取。当前能力与额度请查 [GET /v1/chains](https://api.blockvectra.com/v1/chains) 和[定价页](https://blockvectra.com/zh/pricing/)。

`mike.hoodfi.eth` 的一份两条记录匹配的快照见[公开读取说明](https://github.com/blockhash-xyz/mancer-sdk/issues/2#issuecomment-6625982298)。使用地址前，请自行读取当前记录。
