A minimal, end-to-end TypeScript example demonstrating how an autonomous AI agent or automated script can onboard and interact with BlockVectra without a browser or human intervention.
This example covers:
- Wallet identity: Reads an Ethereum EOA private key from
AGENT_PRIVATE_KEYor from a local.agent-wallet.jsonfile. If neither is configured, generates a new wallet locally and saves it to.agent-wallet.json(permission0600) so subsequent runs reuse the same identity instead of creating new accounts. - Programmatic account sign-up: Authenticates via Sign-In with Ethereum (SIWE / EIP-191) against
https://console-api.blockvectra.com/v1in programmatic mode (omitting theOriginheader). First-time wallet sign-in automatically provisions an account. - API key provisioning: Creates an API key (
rgw_...) using the authenticated session token (rgs_...). - JSON-RPC execution: Reads the active chain directory from
GET https://api.blockvectra.com/v1/chains, selects the uniquerobinhood_mainnetentry with numeric chain ID4663, verifieseth_chainIdon that endpoint, then executeseth_blockNumberusing the API key. - Data API query: Calls the read-only dataset freshness endpoint (
GET /v1/data/{chain}/status/freshness) defined in the OpenAPI specification.
+-------------------------------------------------------------+
| Local Agent Runtime |
| |
| 1. Generate / load Ethereum EOA wallet (viem) |
| 2. Sign SIWE challenge locally with EIP-191 personal_sign |
+------------------------------+------------------------------+
|
| (HTTP POST, no Origin header)
v
+-------------------------------------------------------------+
| BlockVectra Control Plane (console-api) |
| |
| - POST /auth/siwe/challenge -> Issues nonce & SIWE message |
| - POST /auth/siwe/login -> Verifies signature & issues |
| session token (rgs_...) |
| - POST /keys -> Generates API key (rgw_...) |
+------------------------------+------------------------------+
|
| (Authenticated with x-api-key)
v
+-------------------------------------------------------------+
| BlockVectra Data Plane (Gateway) |
| |
| - GET /v1/chains -> Public chain directory |
| - POST /v1/{chain} -> Chain-scoped JSON-RPC |
| - GET /v1/data/{chain}/... -> REST Data API endpoints |
+-------------------------------------------------------------+
- Local private keys: Private keys never leave the local environment and are never transmitted to any API endpoint or conversational context.
- Header-based key authentication: API keys are passed exclusively via request headers (
x-api-keyorAuthorization: Bearer). - No secret leakage in logs: Private keys, session tokens, and API keys are never printed in plaintext (only truncated prefixes such as
rgw_54dd...appear in diagnostic output). - Wallet-bound account recovery: An agent's account identity is bound to its Ethereum wallet address. If a session token or API key is lost, signing in again with the same wallet restores access to manage or revoke existing keys.
- Node.js >= 18.0.0
- npm or another Node.js package manager
-
Clone or navigate to the directory:
cd agent-quickstart -
Install dependencies (only
viemandtsx):npm install
-
(Optional) Configure an existing wallet by injecting
AGENT_PRIVATE_KEYinto the process environment through your runtime's secret manager..env.exampledocuments its format;npm startdoes not load a.envfile automatically.Without this override, the script reuses
.agent-wallet.json. It creates a wallet only when that file is absent, using an exclusive write and permission0600on POSIX filesystems. An unreadable, damaged or inconsistent wallet stops execution before sign-in and is never overwritten. Preserve the file and restore a trusted backup before retrying; deleting it creates a different identity on the next run. The private key is never printed to the console.
Execute the TypeScript entrypoint directly using tsx:
npx tsx src/index.tsOr run via npm script:
npm startThe block-number check requires HTTP success, a matching JSON-RPC response and a valid Ethereum block quantity. Rejected responses stop before the Data API step; valid explicitly retryable errors have at most five attempts per RPC method (up to ten across chain-ID and block-number verification). See JSON-RPC responses and Ethereum quantities.
Whole-request errors with id: null may be retried when explicitly marked retryable; a successful response must still match request ID 1.
This mainnet example does not select a network by display-name similarity or fall back to testnet. Missing, duplicate or inconsistent mainnet directory entries stop before RPC. A valid eth_chainId result must equal 4663 before the script requests a block number or Data API freshness. This adds one read-only RPC call on a successful run; chain verification may fail after wallet/account/key provisioning has already occurred.
The Data API freshness request uses a 15-second timeout with no retry. A response is consumed only when HTTP succeeds, the JSON envelope is an object, meta.chain equals the selected robinhood_mainnet, data is an array, and every row has a string dataset with max_block_number and seconds_behind each an integer or null. Unexpected rows stop the run with a local error that preserves the HTTP status but not the upstream body. Empty lists, zero, negative, null and extra fields are accepted. This check only proves the request flow and the consumed fields; it does not establish that every dataset is fresh, stale-free or complete, and a null value is logged as unknown rather than zero.
Run npm test and npm run typecheck for offline regression checks. Tests replace network calls and do not provision accounts.
Pull requests and pushes to main run these checks on Node.js 24.21.0 through Offline checks, after installing the lockfile dependencies with npm ci --ignore-scripts.
src/wallet-balances.ts is an opt-in, read-only helper for GET /v1/data/{chain}/addresses/{address}/balances. collectBalancePages(readPage, expectedChain, maxPages = 20) follows next_cursor until it is omitted and rejects on malformed envelopes, chain mismatch, HTTP/read failures, repeated cursors, the page cap, duplicate tokens, or a changed known meta.refreshed_at. Balances stay exact decimal-integer strings; symbol/decimals stay null when unknown (decimals: 0 is a real value). readPage must reject HTTP errors before returning JSON. Run it server-side only; the API key must not reach browsers or logs.
import { collectBalancePages } from "./src/wallet-balances.js";
const chain = "robinhood_mainnet"; // check chain capability/coverage first
const address = "0x..."; // wallet to read
const result = await collectBalancePages(async (cursor) => {
const url = new URL(`https://api.blockvectra.com/v1/data/${chain}/addresses/${address}/balances`);
if (cursor !== undefined) url.searchParams.set("cursor", cursor); // opaque, unchanged
const res = await fetch(url, { headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! } });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}, chain);Before real use, confirm the selected chain's capability and coverage for this endpoint; this repository makes no claim that Robinhood balances are currently available and has not run this against a live API. Balances come from a snapshot refreshed every 6 hours (plus up to 30 minutes of delay and the refresh run), so rows may be stale. meta.as_of_block is the indexed head, not the snapshot height; read meta.refreshed_at (null means unknown, treat as stale). Pages are not a transactionally consistent portfolio; pages keeps each page's original meta. The aggregate refreshedAt is the shared known time only when every page reported the same one; if any page has refreshed_at: null it is null (unknown), regardless of page order, and differing known times are still rejected even with a null page between them.
Illustrative output (addresses, keys, block heights and dataset counts vary; this revision was validated offline):
=== BlockVectra AI Agent Quickstart ===
[1/5] Generated agent wallet and saved to .agent-wallet.json (0600).
Tip: Set AGENT_PRIVATE_KEY in your environment to override.
Wallet Address: 0x...
[2/5] Requesting SIWE challenge...
Signing verbatim challenge message with EIP-191...
Submitting SIWE login...
Account Created: true
Session Token: rgs_4f5b...
[3/5] Provisioning API key...
Key ID: k_xxxxxxxxxxxx
API Key: rgw_9b70...
[4/5] Reading chain directory from GET /v1/chains...
Selected: Robinhood Chain (robinhood_mainnet, Chain ID 4663)
Verifying eth_chainId via JSON-RPC...
Verified Chain ID: 4663
Calling eth_blockNumber via JSON-RPC...
Latest Block: 77213136 (0x49a2dd0)
[5/5] Querying Data API GET /{chain}/status/freshness...
Data Datasets Tracked: 15
Dataset 'blocks': block 77213095 (0s lag)
=== Quickstart Completed Successfully ===
Created Key ID: k_xxxxxxxxxxxx (Revoke anytime via POST https://console-api.blockvectra.com/v1/keys/k_xxxxxxxxxxxx/revoke)
BlockVectra exposes a Model Context Protocol (MCP) server over Streamable HTTP at:
https://docs.blockvectra.com/mcp
Provides read-only access to documentation, search, chain parameters, service status, and plan details without an API key:
claude mcp add --transport http blockvectra-docs https://docs.blockvectra.com/mcpTo enable tools that execute on-chain queries (rpc_call, data_api_get), pass the API key in the -H option:
claude mcp add --transport http -H "x-api-key: $BLOCKVECTRA_API_KEY" blockvectra-docs https://docs.blockvectra.com/mcpAdd the server to .cursor/mcp.json or through Cursor Settings:
{
"mcpServers": {
"blockvectra-docs": {
"url": "https://docs.blockvectra.com/mcp"
}
}
}Configure the headers object so the API key is passed securely in HTTP headers:
{
"mcpServers": {
"blockvectra": {
"url": "https://docs.blockvectra.com/mcp",
"headers": {
"x-api-key": "${BLOCKVECTRA_API_KEY}"
}
}
}
}- Headers only: API keys must be configured strictly in HTTP request headers (
x-api-keyorAuthorization: Bearer). - Never pass keys in chat: Do not pass API keys or wallet private keys as tool arguments or paste them into conversational prompts.
- Cache bypass: All keyed MCP requests carry
Cache-Control: no-storeto prevent caching of tenant data.
If an agent loses its API key or session token, it can regain access using the same Ethereum wallet:
- Re-authenticate: Submit a new SIWE challenge and signature to
POST /auth/siwe/login. The server returns a fresh session token (account_created: false). - List keys:
curl -s "https://console-api.blockvectra.com/v1/keys" \ -H "Authorization: Bearer <session_token>"
- Revoke a key:
curl -s -X POST "https://console-api.blockvectra.com/v1/keys/<key_id>/revoke" \ -H "Authorization: Bearer <session_token>"
- Provision a replacement key:
curl -s -X POST "https://console-api.blockvectra.com/v1/keys" \ -H "Authorization: Bearer <session_token>" \ -H "Content-Type: application/json" \ -d '{"label":"agent-replacement-key"}'
- Programmatic Sign-up Guide
- AI Agent Integration Guide
- Plans and Pricing
- Data API OpenAPI Specification
- JSON-RPC OpenAPI Specification