# Panduan awal RPC Robinhood Chain Testnet: baca tanpa API key, log WebSocket, lalu mainnet

> Source: https://docs.blockvectra.com/id/guides/robinhood-testnet-starter/

BlockVectra menyediakan Robinhood Chain Testnet (Chain ID 46630) melalui JSON-RPC dan WebSocket, dan API key yang sama juga berfungsi di mainnet Robinhood Chain. Panduan ini mengikuti template [Robinhood Chain Testnet starter](https://github.com/blockvectra/robinhood-testnet-starter?ref=docs-testnet-starter) melalui tiga langkah: membaca testnet tanpa API key, membuka akun secara terprogram dan melakukan streaming log, lalu menggunakan API key yang sama di mainnet. Untuk parameter endpoint, kebijakan metode, dan data saham tertokenisasi, lihat [panduan Robinhood Chain](https://docs.blockvectra.com/en/guides/robinhood-chain/).

## Jalankan template

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

Node.js 18 atau lebih baru. Ketiga skrip adalah `npm run step1`, `npm run step2`, dan `npm run step3`. Variabel opsional (`BLOCKVECTRA_API_KEY`, `WALLET_PRIVATE_KEY`) dibaca dari shell; tidak ada yang memuat `.env` secara otomatis.

## Langkah 1: baca testnet tanpa API key

Endpoint publik `https://api.blockvectra.com/v1/robinhood_testnet/public` menyediakan metode JSON-RPC dompet tanpa akun dan tanpa API key. Kueri Chain ID EIP-155 secara langsung:

```bash
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":[]}'
```

Responsnya adalah `{"jsonrpc":"2.0","id":1,"result":"0xb626"}`; `0xb626` adalah Chain ID heksadesimal, yaitu 46630 dalam desimal.

Template membaca entri chain dari `GET /v1/chains` dan membangun klien viem darinya, sehingga nama dan ID chain tidak ditulis secara tetap. Dari `src/shared.ts`:

```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) });
}
```

Dengan modul tersebut, `src/step1-public-read.ts` membaca Chain ID, blok terbaru, dan saldo alamat:

```ts
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})`);
```

## Langkah 2: buka akun secara terprogram dan berlangganan log

`src/step2-key-and-logs.ts` masuk dengan dompet (SIWE, EIP-191) di `https://console-api.blockvectra.com/v1`. Login pertama membuat akun; klien kemudian membuat API key. Akun baru mendapatkan 30,000,000 CU saat pendaftaran — tanpa kartu kredit.

```ts
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());
```

Jika sudah memiliki API key, `export BLOCKVECTRA_API_KEY=...` melewati pendaftaran. Tanpa `WALLET_PRIVATE_KEY`, skrip menghasilkan dompet sekali pakai dalam memori dan membuangnya saat eksekusi berakhir; atur `WALLET_PRIVATE_KEY` dari lingkungan lokal jika ingin masuk lagi ke akun yang sama.

URL WebSocket menempatkan API key di path: `wss://api.blockvectra.com/v1/robinhood_testnet/{api_key}`. Template memeriksa flag `ws` langsung dan daftar `subscriptions` dari `GET /v1/chains` sebelum berlangganan:

```ts
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),
});
```

Setiap filter langganan `logs` harus menyertakan `address` atau `topic0`; ganti alamat contoh dengan kontrak yang ingin Anda pantau. Lihat [panduan langganan WebSocket](https://docs.blockvectra.com/en/guides/websocket-subscriptions/) untuk aturan filter, kode penutupan, dan penyambungan kembali.

## Langkah 3: gunakan API key yang sama di mainnet

Beralih ke mainnet hanya memerlukan perubahan satu kata: gunakan slug `robinhood_mainnet` dan API key yang sama. `src/step3-mainnet.ts` membaca nomor blok mainnet dan memanggil Data API untuk token saham Robinhood:

```ts
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));
```

Endpoint peringkat yang sama dalam curl:

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

Data API hanya tersedia di mainnet: `GET /v1/chains` melaporkan `data: false` untuk `robinhood_testnet` dan `data: true` untuk `robinhood_mainnet`. Untuk parameter permintaan dan struktur respons, lihat [panduan Saham tertokenisasi](https://docs.blockvectra.com/en/guides/stocks/).

## Temukan batas dan kemampuan melalui API

Baca `GET /v1/chains` untuk flag langsung `jsonrpc`, `data`, `ws`, dan `subscriptions`, metode JSON-RPC yang diizinkan, rentang blok log, retensi state, dan endpoint publik setiap chain. Baca `GET /v1/plans` untuk harga dan batas paket saat ini.

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

## Panduan terkait

* [Temukan URL RPC testnet dan batasnya](https://blockvectra.com/en/chains/robinhood_testnet/).
* [Klaim ETH uji dari faucet testnet](https://docs.blockvectra.com/en/guides/robinhood-testnet-faucet/) sebelum mengirim transaksi.

## Langkah selanjutnya

* [Jelajahi direktori dataset](https://blockvectra.com/en/data/) untuk melihat setiap dataset yang diindeks BlockVectra.
* [Lihat paket gratis dan harga](https://blockvectra.com/en/pricing/#free) untuk memeriksa apa yang termasuk dalam akun Anda.
* [Masuk ke konsol](https://console.blockvectra.com/login/?next=%2Fkeys%2F) untuk membuat API key.
