Démarrage RPC Robinhood Chain Testnet : lectures sans clé, logs WebSocket, puis mainnet

Démarrez avec le RPC Robinhood Chain Testnet : URL RPC publique, lectures sans clé, logs WebSocket avec une API key, accès au faucet et basculement de la même clé sur le mainnet.

BlockVectra prend en charge Robinhood Chain Testnet (chain ID 46630) via JSON-RPC et WebSocket, et la même API key fonctionne également sur le mainnet Robinhood Chain. Ce guide suit le modèle Robinhood Chain Testnet starter en trois étapes : lire le testnet sans clé, ouvrir un compte par programmation et diffuser des logs en streaming, puis utiliser la même clé sur le mainnet. Pour connaître les paramètres des points de terminaison, la politique de méthodes et les données sur les actions tokenisées, consultez le guide Robinhood Chain.

Exécuter le modèle

git clone https://github.com/blockvectra/robinhood-testnet-starter
cd robinhood-testnet-starter
npm install
npm run typecheck

Node.js 18 ou version ultérieure. Les trois scripts sont npm run step1, npm run step2 et npm run step3. Les variables optionnelles (BLOCKVECTRA_API_KEY, WALLET_PRIVATE_KEY) sont lues depuis le shell ; rien ne charge .env automatiquement.

Étape 1 : lire le testnet sans clé

Le point de terminaison public https://api.blockvectra.com/v1/robinhood_testnet/public dessert les méthodes JSON-RPC de portefeuille sans compte et sans API key. Interrogez directement le chain ID EIP-155 :

curl -s "https://api.blockvectra.com/v1/robinhood_testnet/public" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}'

La réponse est {"jsonrpc":"2.0","id":1,"result":"0xb626"} ; 0xb626 est le chain ID hexadécimal, correspondant à 46630 en décimal.

Le modèle lit l'entrée de la chaîne depuis GET /v1/chains et construit un client viem à partir de celle-ci, de sorte que le nom et l'ID de la chaîne ne soient pas codés en dur. Extrait de src/shared.ts :

import { createPublicClient, defineChain, http, webSocket } from "viem";

export const API_BASE = "https://api.blockvectra.com/v1";

export interface ChainEntry {
  chain: string;
  name: string;
  chain_id: number;
  data: boolean;
  ws: boolean;
  subscriptions: string[];
  public?: { url: string };
}

export async function getChain(slug: string): Promise<ChainEntry> {
  const res = await fetch(`${API_BASE}/chains`);
  if (!res.ok) throw new Error(`GET /v1/chains failed: ${res.status}`);
  const { chains } = (await res.json()) as { chains: ChainEntry[] };
  const entry = chains.find((c) => c.chain === slug);
  if (!entry) throw new Error(`${slug} not found in /v1/chains`);
  return entry;
}

export function toViemChain(entry: ChainEntry, rpcUrl: string) {
  return defineChain({
    id: entry.chain_id,
    name: entry.name,
    nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
    rpcUrls: { default: { http: [rpcUrl] } },
  });
}

export function httpClient(entry: ChainEntry, url: string, apiKey?: string) {
  return createPublicClient({
    chain: toViemChain(entry, url),
    transport: http(url, apiKey ? { fetchOptions: { headers: { "x-api-key": apiKey } } } : undefined),
  });
}

export function wsClient(entry: ChainEntry, apiKey: string) {
  const base = "wss://api.blockvectra.com/v1";
  const url = `${base}/${entry.chain}/${apiKey}`;
  return createPublicClient({ chain: toViemChain(entry, url), transport: webSocket(url) });
}

Avec ce module, src/step1-public-read.ts lit le chain ID, le dernier bloc et le solde d'une adresse :

import { formatEther, type Address } from "viem";
import { API_BASE, getChain, httpClient } from "./shared.js";

const SLUG = "robinhood_testnet";
// Any address works; override with the first CLI argument.
const address = (process.argv[2] ?? "0x0000000000000000000000000000000000000000") as Address;

const chain = await getChain(SLUG);
const client = httpClient(chain, `${API_BASE}/${SLUG}/public`);

const [chainId, blockNumber, balance] = await Promise.all([
  client.getChainId(),
  client.getBlockNumber(),
  client.getBalance({ address }),
]);

console.log(`chain       : ${chain.name} (${SLUG})`);
console.log(`eth_chainId : ${chainId} (0x${chainId.toString(16)})`);
console.log(`block       : ${blockNumber}`);
console.log(`balance     : ${formatEther(balance)} ETH  (${address})`);

Étape 2 : ouvrir un compte par programmation et s'abonner aux logs

src/step2-key-and-logs.ts se connecte avec un portefeuille (SIWE, EIP-191) sur https://console-api.blockvectra.com/v1. Une première connexion crée le compte ; le client crée ensuite une API key. Les nouveaux comptes reçoivent 30,000,000 CU à l'inscription — aucune carte bancaire requise.

import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import type { Hex } from "viem";

const CONSOLE_API = "https://console-api.blockvectra.com/v1";
const REF = "gh-robinhood-testnet-starter";

async function post<T>(path: string, body: unknown, token?: string): Promise<T> {
  const res = await fetch(`${CONSOLE_API}${path}`, {
    method: "POST",
    headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}) },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`${path} failed: ${res.status} ${await res.text()}`);
  return (await res.json()) as T;
}

async function provisionKey(): Promise<string> {
  const pk = (process.env.WALLET_PRIVATE_KEY as Hex | undefined) ?? generatePrivateKey();
  const account = privateKeyToAccount(pk);
  console.log(`wallet: ${account.address}`);

  // SIWE: ask for a challenge, sign it verbatim (EIP-191), log in. A first sign-in creates the account.
  const { message } = await post<{ message: string }>("/auth/siwe/challenge", {
    address: account.address,
    purpose: "login",
  });
  const signature = await account.signMessage({ message });
  const login = await post<{ session: { token: string }; account_created: boolean }>("/auth/siwe/login", {
    message,
    signature,
    ref: REF,
  });
  console.log(`account_created: ${login.account_created}`);

  const created = await post<{ api_key: string }>("/keys", { label: "robinhood-testnet-starter" }, login.session.token);
  return created.api_key;
}

const apiKey = process.env.BLOCKVECTRA_API_KEY?.trim() || (await provisionKey());

Si vous possédez déjà une clé, export BLOCKVECTRA_API_KEY=... permet d'ignorer l'inscription. Sans WALLET_PRIVATE_KEY, le script génère un portefeuille temporaire en mémoire et le supprime à la fin de l'exécution ; définissez WALLET_PRIVATE_KEY dans votre environnement local si vous souhaitez vous reconnecter au même compte.

L'URL WebSocket inclut la clé dans le chemin : wss://api.blockvectra.com/v1/robinhood_testnet/{api_key}. Le modèle vérifie l'indicateur ws actif et la liste subscriptions à partir de GET /v1/chains avant de s'abonner :

const chain = await getChain("robinhood_testnet");
if (!chain.ws || !chain.subscriptions.includes("logs")) {
  throw new Error(`robinhood_testnet does not offer a logs subscription in /v1/chains`);
}
console.log(`subscriptions (from /v1/chains): ${chain.subscriptions.join(", ")}`);

const client = wsClient(chain, apiKey);
const stop = client.watchEvent({
  address: "0x1234567890123456789012345678901234567890",
  onLogs: (logs) => {
    for (const l of logs) console.log(`block ${l.blockNumber} ${l.address} topic0=${l.topics[0]}`);
  },
  onError: (e) => console.error("subscription error:", e.message),
});

Chaque filtre d'abonnement logs doit inclure une address ou un topic0 ; remplacez l'adresse d'exemple par le contrat que vous souhaitez surveiller. Consultez le guide des abonnements WebSocket pour connaître les règles de filtrage, les codes de fermeture et la reconnexion.

Étape 3 : basculer la même clé sur le mainnet

Le passage au mainnet ne nécessite de modifier qu'un seul mot : utilisez le slug robinhood_mainnet et la même API key. src/step3-mainnet.ts lit le numéro de bloc du mainnet et appelle la Data API pour les tokens d'actions Robinhood :

const apiKey = process.env.BLOCKVECTRA_API_KEY?.trim();
if (!apiKey) throw new Error("Set BLOCKVECTRA_API_KEY (run step 2 first, or create a key in the console).");

const mainnet = await getChain("robinhood_mainnet");

// JSON-RPC with the key: the chain slug changes.
const client = httpClient(mainnet, `${API_BASE}/${mainnet.chain}`, apiKey);
console.log(`mainnet block: ${await client.getBlockNumber()}`);

// Data API: Robinhood stock-token leaderboard (see the Robinhood Chain guide).
const res = await fetch(`${API_BASE}/data/${mainnet.chain}/stocks`, { headers: { "x-api-key": apiKey } });
if (!res.ok) throw new Error(`Data API failed: ${res.status} ${await res.text()}`);
console.log(JSON.stringify(await res.json(), null, 2).slice(0, 2000));

Le même point de terminaison de classement avec curl :

curl -s -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  "https://api.blockvectra.com/v1/data/robinhood_mainnet/stocks"

La Data API est réservée au mainnet : GET /v1/chains indique data: false pour robinhood_testnet et data: true pour robinhood_mainnet. Pour les paramètres de requête et les enveloppes de réponse, consultez le guide des actions tokenisées.

Découvrir les limites et capacités via l'API

Lisez GET /v1/chains pour obtenir les indicateurs jsonrpc, data, ws et subscriptions en direct, les méthodes JSON-RPC autorisées, les plages de blocs de logs, la rétention d'état et le point de terminaison public de chaque chaîne. Lisez GET /v1/plans pour connaître les tarifs actuels et les limites des forfaits.

curl -s "https://api.blockvectra.com/v1/chains"

Guides associés

Prochaines étapes

Dernière mise à jour :

Sur cette page