Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/20260910_cardano_rpc_quickstart.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
project: cardano-rpc
pr: 1337
kind:
- documentation
description: |
Add a verified quickstart to the cardano-rpc README, showing how to start a local single-node cluster with gRPC enabled in one command, make first calls with buf and grpcurl, and configure the server, plus a new quickstart directory with runnable example projects in multiple languages demonstrating first calls and building and submitting a transaction, each with its own dedicated nix development shell.
137 changes: 137 additions & 0 deletions cardano-rpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,143 @@
The `cardano-rpc` package provides client and server haskell modules for gRPC interface of `cardano-node`.
It implements [UTxO RPC](https://utxorpc.org/introduction) protobuf communication protocol specification.

## Quickstart

cardano-rpc is cardano-node's built-in gRPC interface, implementing the [UTxO RPC](https://utxorpc.org) spec.
It is part of cardano-node itself: you enable it with a configuration flag, there is no extra service to run, and it is built, tested, and released together with cardano-node.
Because UTxO RPC is a standard, the same client code also works against other servers that implement it, such as Dolos.
It serves live chain data: the tip, UTxO queries, protocol parameters, and transaction evaluation and submission.
It is not an indexer: there are no address-history queries; see [UTxO RPC v1beta spec coverage](#utxo-rpc-v1beta-spec-coverage) below for the full method status.
The same configuration works against any network, from the local cluster used here to mainnet.

### Prerequisites

1. [Nix](https://nixos.org/download/) with flakes enabled; every command below fetches its tools through it.
The first invocations download the toolchain, which can take several minutes and a few gigabytes.
2. A checkout of this repository, for the vendored proto files the CLI example and the TypeScript quickstart load:

```bash
git clone https://github.com/IntersectMBO/cardano-api
cd cardano-api
```

Work through "Start a local cluster" and "Make your first call" below, then follow whichever quickstart in "Language examples" matches your stack.

### Start a local cluster

The cardano-testnet command below bundles the matching cardano-node and cardano-cli binaries, so one command is enough:

```bash
nix run github:IntersectMBO/cardano-node#cardano-testnet -- \
cardano --num-pool-nodes 1 --enable-grpc --output-dir /tmp/demo-cluster
```

This starts a testnet with a single block-producing cardano-node and its gRPC server enabled, and keeps running in the foreground until you press Ctrl+C.
The cluster is ready once it logs `Testnet started`; open a second terminal for everything below.
The cluster comes with funded test wallets, created at startup under `/tmp/demo-cluster/utxo-keys/utxo1` to `utxo3` (`utxo.skey`, `utxo.vkey`, `utxo.addr`); the transaction example below spends from `utxo1`.

The RPC endpoint is a Unix socket at `/tmp/demo-cluster/socket/node1/rpc.sock`, next to cardano-node's IPC socket.
Most gRPC tooling expects a TCP endpoint, so bridge the socket to `localhost:50051` with socat and leave it running (a second background process, alongside the cluster):

```bash
socat TCP-LISTEN:50051,fork,reuseaddr UNIX-CONNECT:/tmp/demo-cluster/socket/node1/rpc.sock
```

Every example below talks to `localhost:50051`.
Connecting to a cardano-node that listens on TCP directly (`--grpc-listen-port`, see the configuration reference) works the same way, without the bridge; cardano-testnet gets the same ability with `--enable-grpc-http` in [#6685](https://github.com/IntersectMBO/cardano-node/pull/6685).

> [!TIP]
> To use your own binaries instead of the bundled ones, export `CARDANO_NODE` and `CARDANO_CLI` with their paths before running.

> [!NOTE]
> Flag names differ between the two CLIs: `cardano-testnet` takes `--enable-grpc`, `cardano-node` itself takes `--grpc-enable`.

> [!WARNING]
> The output directory must not exist from a previous run; genesis creation fails on leftovers (`Genesis output directory already exists`).
> Run `rm -rf /tmp/demo-cluster` first when retrying, and make sure no cardano-node processes from an earlier attempt are still alive.
>
> Unix socket paths are capped at 108 bytes on Linux, and cardano-testnet fails at startup (`pokeSockAddr: path is too long`) when the output directory is nested too deep.
> Keep `--output-dir` shallow, e.g. under `/tmp`.

### Make your first call

The CLI examples use the proto files vendored in this repository, so run them from the repository root.
First enter a subshell that puts the tools on PATH (your prompt changes; run the commands below inside it).
`.#quickstart` bundles every tool the whole quickstart needs, including both language examples below; the per-language shells (`.#quickstart-rust`, `.#quickstart-typescript`) are minimal alternatives if you only want one:

```bash
nix develop .#quickstart
```

or with `nix-shell` (using `cardano-rpc/quickstart/shell.nix`):

```bash
nix-shell cardano-rpc/quickstart/shell.nix
```

or fetch them ad hoc:

```bash
nix shell nixpkgs#buf nixpkgs#grpcurl nixpkgs#socat
```

Read the chain tip (the most recently adopted block) with [buf](https://buf.build/docs/installation):

```bash
buf curl \
--schema cardano-rpc/proto --protocol grpc --http2-prior-knowledge \
http://localhost:50051/utxorpc.v1beta.sync.SyncService/ReadTip
```

It prints the tip as JSON: slot, hash, height, and timestamp.
Your values differ per run, and the tip advances between calls.

Then read the protocol parameters (the chain's current fee, size, and cost limits):

```bash
buf curl \
--schema cardano-rpc/proto --protocol grpc --http2-prior-knowledge -d '{}' \
http://localhost:50051/utxorpc.v1beta.query.QueryService/ReadParams
```

With grpcurl instead:

```bash
grpcurl -plaintext \
-import-path cardano-rpc/proto -proto utxorpc/v1beta/sync/sync.proto \
localhost:50051 \
utxorpc.v1beta.sync.SyncService/ReadTip
```

### Language examples

- **Rust**: first calls via the `utxorpc-spec` crate. See [quickstart/rust/README.md](quickstart/rust/README.md).
- **TypeScript**: build and submit a transaction with MeshJS. See [quickstart/typescript/README.md](quickstart/typescript/README.md).

### Clean up

Stop the cluster and the socat bridge with Ctrl+C in their terminals, then remove the cluster directory:

```bash
rm -rf /tmp/demo-cluster
```

### Configuration reference

The gRPC server is off by default.
Enable it with `--grpc-enable` or `EnableRpc: true` in the cardano-node configuration; a node socket path must also be configured.

Exactly one transport is active at a time:

1. Unix socket (default): `rpc.sock` next to the node socket, or `--grpc-socket-path` / `RpcSocketPath`.
2. HTTP/2 cleartext: `--grpc-listen-port` / `RpcListenPort`, optionally `--grpc-listen-address` / `RpcListenAddress` (default `127.0.0.1`).
3. HTTP/2 with TLS: add `--grpc-tls-certificate` and `--grpc-tls-private-key` (`RpcTlsCertificateFile`, `RpcTlsPrivateKeyFile`), optionally repeatable `--grpc-tls-chain-certificate` (`RpcTlsChainCertificateFiles`).

The three transports are mutually exclusive.
TLS additionally requires a listen port, and its certificate and key must be set together; see the [Security](#security) section below before exposing an endpoint beyond localhost.

Clients connect over TCP the same way as in the examples above: replace the Unix-socket connector or `unix://` target with the node's address and port.

## UTxO RPC v1beta spec coverage

Methods marked ⬜ or ❌ are exposed by the server but respond with the `UNIMPLEMENTED` gRPC status.
Expand Down
14 changes: 14 additions & 0 deletions cardano-rpc/quickstart/go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module cardano-rpc-quickstart

go 1.25.0

require (
connectrpc.com/connect v1.20.0
github.com/utxorpc/go-codegen v0.19.2
golang.org/x/net v0.58.0
)

require (
golang.org/x/text v0.41.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
12 changes: 12 additions & 0 deletions cardano-rpc/quickstart/go/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ=
connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/utxorpc/go-codegen v0.19.2 h1:IG8OhSc0GILy6emTeUM1/+t/PXbzJTmpJuRAhoWEkbM=
github.com/utxorpc/go-codegen v0.19.2/go.mod h1:QG/UEOXM8HVrm6H7LhuYAeMSA1OFgL2kTjzIeNUWFNg=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
52 changes: 52 additions & 0 deletions cardano-rpc/quickstart/go/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

import (
"context"
"crypto/tls"
"encoding/hex"
"fmt"
"net"
"net/http"

"connectrpc.com/connect"
"golang.org/x/net/http2"

"github.com/utxorpc/go-codegen/utxorpc/v1beta/query"
"github.com/utxorpc/go-codegen/utxorpc/v1beta/query/queryconnect"
"github.com/utxorpc/go-codegen/utxorpc/v1beta/sync"
"github.com/utxorpc/go-codegen/utxorpc/v1beta/sync/syncconnect"
)

const rpcURL = "http://localhost:50051"

func main() {
// cardano-rpc speaks plain gRPC over cleartext HTTP/2 (h2c), so the
// transport must dial TCP directly instead of negotiating TLS.
httpClient := &http.Client{
Transport: &http2.Transport{
AllowHTTP: true,
DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
return net.Dial(network, addr)
},
},
}

syncClient := syncconnect.NewSyncServiceClient(httpClient, rpcURL, connect.WithGRPC())
queryClient := queryconnect.NewQueryServiceClient(httpClient, rpcURL, connect.WithGRPC())

ctx := context.Background()

tipResp, err := syncClient.ReadTip(ctx, connect.NewRequest(&sync.ReadTipRequest{}))
if err != nil {
panic(err)
}
tip := tipResp.Msg.GetTip()
fmt.Printf("Tip: slot %d height %d hash %s\n", tip.GetSlot(), tip.GetHeight(), hex.EncodeToString(tip.GetHash()))

paramsResp, err := queryClient.ReadParams(ctx, connect.NewRequest(&query.ReadParamsRequest{}))
if err != nil {
panic(err)
}
pparams := paramsResp.Msg.GetValues().GetCardano()
fmt.Printf("Protocol parameters: max_tx_size %d max_block_body_size %d\n", pparams.GetMaxTxSize(), pparams.GetMaxBlockBodySize())
}
1 change: 1 addition & 0 deletions cardano-rpc/quickstart/go/shell.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ pkgs ? import <nixpkgs> {} }: pkgs.mkShell { packages = with pkgs; [ go ]; }
13 changes: 13 additions & 0 deletions cardano-rpc/quickstart/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "cardano-rpc-quickstart"
version = "0.1.0"
edition = "2021"

[dependencies]
utxorpc-spec = { version = "0.19.2", default-features = false, features = [
"utxorpc-v1beta-sync",
"utxorpc-v1beta-query",
] }
tonic = "0.12.3"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
hex = "0.4"
47 changes: 47 additions & 0 deletions cardano-rpc/quickstart/rust/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Rust quickstart

The Rust path is the simplest one: the published [`utxorpc-spec`](https://crates.io/crates/utxorpc-spec) crate already ships tonic-generated v1beta bindings, so no proto files and no protoc are needed.
The files below are already in this directory; no `cargo new` needed.

> [!IMPORTANT]
> Use `utxorpc-spec` directly, not the higher-level `utxorpc` SDK crate: that wrapper is hardwired to the older `utxorpc.v1alpha` services and cannot talk to cardano-rpc, which serves `v1beta` (v1beta packaging for the SDKs is tracked in utxorpc/spec#209).

## Prerequisites

Start a local cluster and the socat bridge as described in the main [Quickstart](../../README.md#quickstart).
`Cargo.toml` and `src/main.rs` here connect to `localhost:50051`, the socat bridge address; no repository checkout is required otherwise, the project can live anywhere.

## Run it

Get `cargo`, `rustc` and `gcc` from the repository's flake (run from the repository root; it only provides the toolchain, so `cd` into this directory before running `cargo run` below):

```bash
nix develop .#quickstart-rust
```

or with `nix-shell` (using the `shell.nix` in this directory):

```bash
nix-shell
```

or fetch them ad hoc:

```bash
nix shell nixpkgs#cargo nixpkgs#rustc nixpkgs#gcc
```

Then, from this directory:

```bash
cargo run
```

Sample output (your values will differ):

```
Tip: slot 834 height 37 hash d76df679ffa93ca9d1224cff29b3479c7535a84ed3eb8c17ce1d34aaeb4e774d
Protocol parameters: max_tx_size 16384 max_block_body_size 65536
```

tonic can also connect straight to the Unix socket through a custom connector (`Endpoint::connect_with_connector` with a `tokio::net::UnixStream`), skipping the socat bridge; the TCP form used here is the simpler one.
1 change: 1 addition & 0 deletions cardano-rpc/quickstart/rust/shell.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ pkgs ? import <nixpkgs> {} }: pkgs.mkShell { packages = with pkgs; [ cargo rustc gcc ]; }
46 changes: 46 additions & 0 deletions cardano-rpc/quickstart/rust/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use tonic::transport::Endpoint;

use utxorpc_spec::utxorpc::v1beta::query::{
any_chain_params::Params as AnyChainParamsVariant, query_service_client::QueryServiceClient,
ReadParamsRequest,
};
use utxorpc_spec::utxorpc::v1beta::sync::{sync_service_client::SyncServiceClient, ReadTipRequest};

const RPC_URL: &str = "http://localhost:50051";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let channel = Endpoint::try_from(RPC_URL)?.connect().await?;

let mut sync_client = SyncServiceClient::new(channel.clone());
let mut query_client = QueryServiceClient::new(channel);

let tip = sync_client
.read_tip(ReadTipRequest {})
.await?
.into_inner()
.tip
.expect("ReadTip response always carries a tip");
println!(
"Tip: slot {} height {} hash {}",
tip.slot,
tip.height,
hex::encode(&tip.hash)
);

let params = query_client
.read_params(ReadParamsRequest { field_mask: None })
.await?
.into_inner()
.values
.and_then(|v| v.params)
.expect("cardano-rpc always returns Cardano parameters");
match params {
AnyChainParamsVariant::Cardano(pparams) => println!(
"Protocol parameters: max_tx_size {} max_block_body_size {}",
pparams.max_tx_size, pparams.max_block_body_size
),
}

Ok(())
}
2 changes: 2 additions & 0 deletions cardano-rpc/quickstart/shell.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Covers every tool the cardano-rpc quickstart uses: the CLI examples plus both language examples.
{ pkgs ? import <nixpkgs> {} }: pkgs.mkShell { packages = with pkgs; [ buf grpcurl socat cargo rustc gcc nodejs ]; }
Loading
Loading