HoodFi on Robinhood Chain: read coin type 2147488311 name records

Read a HoodFi name's Robinhood address with viem, select coin type 2147488311, and compare coin type 60 at the same canonical block hash.

Choose the Robinhood record

For a HoodFi name on Robinhood Chain (4663), read addr(bytes32,uint256) with coin type 2147488311, then compare coin type 60 at the same block hash. Select the Robinhood record. If it is empty, zero or not exactly 20 bytes, stop; do not fall back to coin type 60 or a default record. If the two addresses differ, ask the user to confirm the Robinhood destination before using it.

This follows the maintainer's record-selection policy. ENSIP-11 derives an EVM coin type as 0x80000000 | chainId: for 4663, that is 2147488311. The one-argument addr(bytes32) reads coin type 60; it does not select a Robinhood record.

The recipe calls the fixed HoodFi registry 0xf2bABA012244bdD7445129597350054E1B3aEe5C. namehash(NAME) supplies its node; this is not a general ENS resolver-discovery flow.

Run the read

Use Node.js 20+ and viem 2.x (npm install viem in your own project). Get a key from the console, or follow programmatic sign-up; see Quickstart for initial setup. Set BLOCKVECTRA_API_KEY locally to your real key, then save the JavaScript below as hoodfi-name-records.mjs and run node hoodfi-name-records.mjs. Optionally set NAME; its default is mike.hoodfi.eth. Keep the key out of source files, URLs and logs.

The script uses https://api.blockvectra.com/v1/robinhood_mainnet with the key only in x-api-key. It makes at most four serial requests, at least 1.1 seconds apart, with a 15 second timeout per request, no redirects and no retries:

  1. Verify eth_chainId is 0x1237 (4663).
  2. Read latest once and retain the block hash.
  3. Read coin type 2147488311 with {blockHash, requireCanonical: true}; reject an invalid Robinhood address immediately.
  4. Read coin type 60 with exactly the same selector, for comparison only. Empty or zero means no comparison record; other malformed bytes stop the run.
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));

Output contains only blockHash, robinhoodAddress, coin60 (or null) and warnings. Read warnings before using the address. The script neither creates nor signs nor sends transactions. Its User-Agent marks a diagnostic run.

HTTP responses other than 200, JSON-RPC errors, invalid response ids, missing results, invalid hashes and ABI decoding failures stop the script without partial output. A rejected hash read must not be replaced with latest or a block number. To take a new snapshot, start a new run from the chain check.

Access and snapshot semantics

EIP-1898 identifies the block by hash and makes requireCanonical: true reject a noncanonical block. Two reads accepted at the same hash do not guarantee that block cannot be reorganized later.

Use the keyed endpoint for this recipe. The keyless /v1/robinhood_mainnet/public route previously rejected this kind of hash-pinned eth_call with -32011 / state_window; a head read alone does not establish support for the record reads. For current capabilities and allowances, check GET /v1/chains and pricing.

For a concrete matching-record snapshot of mike.hoodfi.eth, see the public read explanation. Read the current records yourself before using an address.

Last updated:

On this page