Skip to content
Merged
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
623 changes: 0 additions & 623 deletions content/build/create-a-view/examples/index.md

This file was deleted.

95 changes: 94 additions & 1 deletion content/build/how-to/configure-attestation-thresholds/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,97 @@ title = "Configure attestation thresholds"
description = "How to require a minimum number of Generator attestations before query results are returned in your app."
+++

This page is coming soon.
Shinzo data is signed by the Generator clients that produced it, and Host clients keep attestation records that track how many independent Generator clients signed the same data. With the app-sdk you can set a bar: only return documents whose attestation count meets your threshold. The threshold is a query-time filter, not a system-wide setting, so one app can apply different bars to different queries.

{% admonition(type="warning") %}
The attestation helpers described here live on the `Feature/attestationFilter` branch of the app-sdk and are not merged into `main` yet. They also depend on pushed replication, which is currently blocked by the DefraDB version mismatch described in [Subscribe to Views with the app-sdk](/build/how-to/subscribe-to-views/). This page documents the API as implemented on that branch so you can build against it ahead of the merge.
{% end %}

## Add attestation records for a View

Attestation records are segmented per View, so your app only receives records for the data it cares about. Opt in per View with `AddAttestationRecordCollection`:

```go
import "github.com/shinzonetwork/shinzo-app-sdk/pkg/attestation"

err := attestation.AddAttestationRecordCollection(context.Background(), myNode, myView.Name)
if err != nil {
if strings.Contains(err.Error(), "collection already exists") {
// Records for this View were added before. Informational and safe to ignore.
} else {
panic(err)
}
}
```

This works like `SubscribeTo`: it adds an `AttestationRecord_<ViewName>` collection to your embedded DefraDB instance and registers it for passive replication, so Host clients push the View's attestation records alongside its documents. Call it once per View you want to filter, after subscribing to the View itself.

## Choose configured or per-query thresholds

Four helpers cover the two ways to set the bar. All four work like `defra.QuerySingle` and `defra.QueryArray`, except they drop results that fail the attestation check. Your result struct needs a `DocID` field, because the filter matches documents to their attestation records by DocID.

The configured pair reads the threshold from `shinzo.minimum_attestations` in your config:

```go
transfers, err := attestation.QueryArrayWithConfiguredAttestationFilter[Transfer](ctx, myNode, query)
transfer, err := attestation.QuerySingleWithConfiguredAttestationFilter[Transfer](ctx, myNode, query)
```

The per-call pair takes the threshold as an argument:

```go
transfers, err := attestation.QueryArrayWithAttestationFilter[Transfer](ctx, myNode, query, 3)
transfer, err := attestation.QuerySingleWithAttestationFilter[Transfer](ctx, myNode, query, 3)
```

Set the config default in `config.yaml`:

```yaml
shinzo:
minimum_attestations: 2
```

Which style to use depends on how uniform your trust requirements are:

| Situation | Approach |
| --- | --- |
| One threshold covers the whole app | Configured helpers with `minimum_attestations` |
| A wallet display where showing something fast beats certainty | Per-call threshold of 1 |
| A high-value flow like a settlement or payout | Per-call threshold of 3 or more |
| A mix of casual and critical reads in one app | Configured default, per-call overrides where it matters |

Make sure you added the attestation record collection for any View you query through these helpers. Without it there are no records to filter on, and every result fails the check.

## Debug an empty result set

If a filtered query returns nothing but the unfiltered equivalent has rows, the filter is doing its job and your data is under-attested. Inspect the records directly to see why. An attestation record ties one of your View's documents to the evidence behind it:

- `attested_doc` is the DocID of the View document being attested to.
- `source_doc` links back to the source document the attestation came from.
- `CIDs` are the signed commit CIDs backing the attestation.
- `doc_type` names the attested collection.
- `vote_count` is a CRDT counter that Host clients increment as they observe more Generator clients signing the same data.

Query the records for the document that went missing:

```graphql
{
<Chain>__<Network>__AttestationRecord(
filter: { attested_doc: { _eq: "<doc-id>" } }
) {
attested_doc
source_doc
CIDs
doc_type
vote_count
}
}
```

If `vote_count` (or the number of records) is below your threshold, the filter correctly excluded the document. Lower the threshold, or wait for more Generator clients to attest. Note that attestations only accumulate while Generator clients are actually signing the underlying data, so a quiet View on a testnet may legitimately sit at a low count.

To check signatures and CIDs by hand, see [Verify data with signatures and CIDs](/build/how-to/verify-data/). For the reasoning behind per-query trust, see [Attestation as a query filter](/build/explanation/attestation-as-a-query-filter/), and [Attestation](/understand/core-concepts/attestation/) for the platform-level picture.

## Need help

{{ need_help(client="app-sdk", repo_name="app-sdk", repo="https://github.com/shinzonetwork/app-sdk/issues") }}
85 changes: 84 additions & 1 deletion content/build/how-to/connect-to-a-host/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,87 @@ title = "Connect your app to a Host"
description = "How to connect an application to a Shinzo Host: P2P connection strings for embedded Go apps and GraphQL endpoints for direct-query TypeScript apps."
+++

This page is coming soon.
How you connect to a Host depends on your architecture. A local-first Go app embeds DefraDB and peers with Hosts over libp2p, so it needs connection strings. A direct-query TypeScript app talks to a Host's GraphQL endpoint over HTTP, so it needs an endpoint URL. Both start with the discovery step in [Find Views and Hosts](/build/how-to/find-views-and-hosts/), which explains where each value comes from.

A Host client exposes four interfaces:

| Port | Interface |
| --- | --- |
| 9181 | GraphQL API at `/api/v0/graphql` |
| 9182 | GraphQL playground, when enabled |
| 9171 | libp2p peering |
| 8080 | Health and self-description endpoints |

Public Hosts vary in how they publish these, so always read the actual `connection_string` and `endpoint_address` from the registry rather than assuming ports.

## Check a Host's health first

Before wiring a Host into your app, check that it is alive and processing data:

```shell
curl -s -H "Accept: application/json" http://<host>:8080/health | jq '{status, current_block}'
```

```json
{
"status": "healthy",
"current_block": 25903651
}
```

The `Accept: application/json` header asks for the JSON form; a browser gets an HTML status page instead. A `healthy` status with a recent `current_block` means the Host is peered and syncing. The same server answers `GET /registration` with the Host's DID, `connection_string`, and `endpoint_address`, which is handy for confirming a Host's identity before you trust its data.

## Embedded app (Go)

A local-first app receives pushed data over libp2p, so the connection happens in the app-sdk config. Take the Host's `connection_string` from the registry:

```shell
curl -s http://testnet.shinzo.network:1317/shinzonetwork/host/v1/hosts \
| jq -r '.hosts[].connection_string'
```

Add one or more of them to `defradb.p2p.bootstrap_peers` in your `config.yaml`:

```yaml
defradb:
p2p:
enabled: true
bootstrap_peers:
- "/ip4/34.66.172.230/tcp/9171/p2p/12D3KooWKVCMswzcXYe9kW2z7nSB9YUWjVVLsMbJnBjVPFUQkbQ7"
listen_addr: "/ip4/127.0.0.1/tcp/9171"
```

The full multiaddr form `/ip4/<ip>/tcp/9171/p2p/<peerID>` always works, but it is not required. Bare IPs (`34.66.172.230`) and `ip:port` pairs (`34.66.172.230:9171`) also work, because the client discovers the peer ID during the connection handshake and fills it in for you. Listing several bootstrap peers makes the first connection more reliable, since registered Hosts come and go on a testnet.

Once peered, subscribe to a View and data starts arriving. [Subscribe to Views with the app-sdk](/build/how-to/subscribe-to-views/) covers that flow.

## Direct-query app (TypeScript)

A direct-query app never peers with anything. It reads the Host's `endpoint_address` from the registry and POSTs signed GraphQL requests to it:

```shell
curl -s http://testnet.shinzo.network:1317/shinzonetwork/host/v1/hosts \
| jq -r '.hosts[].endpoint_address'
```

The endpoint already includes the API path, so you POST straight to it:

```shell
curl -s -X POST "http://34.66.172.230/api/v0/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "{ Erc20Event(limit: 1) { blockNumber } }", "extensions": { ... } }'
```

View queries carry a signature in the `extensions` envelope, and one billed query maps to one pool: a request may touch only one View collection, and its `pool_address` extension names the pool it bills to. When a Host enforces billing, rejections come back as plain errors: `403` if the request signature is missing, stale, or fails verification, and `402` if the signer's query balance is too low. [Query your first View](/build/tutorials/query-your-first-view/) builds the signing flow end to end.

{% admonition(type="note") %}
Billing enforcement is rolling out on the testnet, so some Hosts still answer unsigned queries. Signed requests are the supported interface either way; treat unsigned access as a convenience that will go away.
{% end %}

## Prefer your own Host

Public Hosts are shared infrastructure. If you want guaranteed availability, or you do not want a third party to see your queries at all, you can point everything above at a Host you run yourself. See [Use your own infrastructure](/build/how-to/use-your-own-infrastructure/).

## Need help

{{ need_help(client="Host", repo_name="shinzo-host-client", repo="https://github.com/shinzonetwork/shinzo-host-client/issues") }}
120 changes: 119 additions & 1 deletion content/build/how-to/find-views-and-hosts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,122 @@ title = "Find Views and Hosts"
description = "How to discover registered Views, serving Hosts, and pools on the Shinzo network using the Explorer, Studio, the REST API, or the TypeScript SDK."
+++

This page is coming soon.
Views, Hosts, and Generators are all registered on ShinzoHub, so discovery is a matter of reading the registry. There are four ways to do it: the Explorer in a browser, Shinzo Studio, the chain's REST API, or the TypeScript SDK. Pick whichever fits the job; they all read the same on-chain state.

Two fields come up constantly, so get familiar with them first:

- `connection_string` is a libp2p multiaddr, like `/ip4/34.66.172.230/tcp/9171/p2p/12D3KooW...`. It is how peers dial each other. Your local-first app needs it.
- `endpoint_address` is the URL of a Host's GraphQL API, like `http://34.66.172.230/api/v0/graphql`. It is what direct-query apps POST to.

## Browse the Explorer

The [Shinzo Explorer](https://explorer.shinzo.network/shinzohub) renders the registry as web pages. Open it and switch between the **Blocks**, **Transactions**, **Generators**, **Hosts**, and **Validators** tabs.

The **Hosts** tab lists every registered Host client with its connection details, and the **Generators** tab lists the registered Generator clients. Use the Explorer when you want to eyeball the network: who is online, what is registered, and whether anything changed recently.

## Browse the View catalog in Studio

[Shinzo Studio](https://studio.shinzo.network/) shows registered Views in its catalog, each with its definition, pool, and serving Hosts. Studio is the better browser experience when your question is "what Views exist and what do they return", because it shows the View's SDL alongside its network status. You can also deploy your own View from the same UI; see [Create and deploy Views in Shinzo Studio](/build/how-to/use-shinzo-studio/).

## Query the REST API

ShinzoHub exposes a REST API at `http://testnet.shinzo.network:1317`. This is the option for scripts and terminals, and the responses below are real testnet output.

List registered Views:

```shell
curl -s "http://testnet.shinzo.network:1317/shinzonetwork/view/v1/views?include_data=true" \
| jq -r '.views[] | .name + " " + .address'
```

```output
Studio_v1_Erc20TransferUSDC 0xD3084cAddCe8E1bab07C6eDd8afb835566904C6B
Erc20Event 0xEAc245f905e0aAcF3b9Fe27153F2AaF485dc1B48
FilteredAndDecodedLogs 0xa1226B03c54789e9Bf8876ac956aBbD1bDf5B654
...
```

List registered Host clients:

```shell
curl -s "http://testnet.shinzo.network:1317/shinzonetwork/host/v1/hosts" \
| jq -r '.hosts[] | .connection_string + " " + .endpoint_address'
```

```output
/ip4/34.66.172.230/tcp/9171/p2p/12D3KooWKVCMswzcXYe9kW2z7nSB9YUWjVVLsMbJnBjVPFUQkbQ7 http://34.66.172.230/api/v0/graphql
/ip4/34.63.186.249/tcp/9171/p2p/12D3KooWSqvLctTtcQLvqSVZU4sTCUWxCX9z4NeFpSHnmVWBiFMZ http://34.63.186.249/api/v0/graphql
...
```

List registered Generator clients:

```shell
curl -s "http://testnet.shinzo.network:1317/shinzonetwork/indexer/v1/indexers" \
| jq -r '.indexers[] | .operator_address + " " + .source_chain'
```

List the pools serving a View, with membership and activity:

```shell
curl -s "http://testnet.shinzo.network:1317/shinzonetwork/pool/v1/views/0xEAc245f905e0aAcF3b9Fe27153F2AaF485dc1B48/pools" \
| jq -c '.details[] | {pool: .pool.pool_address, hosts: (.hosts | length), is_active}'
```

```output
{"pool":"0xDbc3bE7CBd8Dc8901E3BbbeA1A740BE490dAe23B","hosts":3,"is_active":true}
```

A pool becomes active once at least 3 Hosts have joined it, so `is_active` is the quick check for whether a View is being served. The registry moves over time, so expect different names and addresses when you run these.

## Query with the TypeScript SDK

The same registry reads are available from `@shinzo/shinzohub` if your app needs them programmatically. The client extends a viem public client with ShinzoHub actions:

```ts
import { createPublicClient, http } from "viem";
import { shinzoHubActions } from "@shinzo/shinzohub";
import { shinzoHubTestnet } from "@shinzo/shinzohub/chains";

const client = createPublicClient({
chain: shinzoHubTestnet,
transport: http(),
}).extend(shinzoHubActions);

const { views } = await client.listViews({ limit: 25, includeMetadata: true });
const { hosts } = await client.listHosts({ limit: 100 });
const pools = await client.listViewPools({ viewAddress: views[0].viewAddress });
```

`listViews` returns registered Views (with the parsed query, SDL, and lens metadata when `includeMetadata` is set), `listHosts` returns Host clients with their `endpointAddress`, and `listViewPools` returns the pools for a View with member Hosts and an `isActive` flag. `getNetworkUnitPrice` reads the network-wide unit price for queries. The [Query your first View](/build/tutorials/query-your-first-view/) tutorial walks through combining these into a full query flow.

## Ask a Host about itself

Any running Host client describes itself over its health server, on port 8080 by default:

```shell
curl -s -H "Accept: application/json" http://<host>:8080/registration | jq .registration
```

```json
{
"did": "did:key:zQ3shQKyThhTw3M83ZcTobUQER5mxNbSrW3Yrds3if4mPPrje",
"connection_string": "/ip4/203.0.113.10/tcp/9171/p2p/12D3KooWB1K1k67DNEcxShBq3o15LQrKHcBkRxzv6AuFR8p8idqJ",
"endpoint_address": "http://203.0.113.10/api/v0/graphql"
}
```

This is the same information a Host publishes to the registry when it registers, so it is the quickest way to check what a specific Host claims to be. See [Connect your app to a Host](/build/how-to/connect-to-a-host/) for the health endpoint and the rest of the port layout.

## What does not exist

Viewkit has no list or discovery commands. It builds and deploys View bundles, and that is all. Discovery happens on-chain through the paths above, or through the Explorer and Studio UIs listed in [Tools](/reference/tools/).

## Where to next

- [Connect your app to a Host](/build/how-to/connect-to-a-host/) to put a `connection_string` or `endpoint_address` to work.
- [Query your first View](/build/tutorials/query-your-first-view/) for the full flow from discovery to a signed query.

## Need help

{{ need_help(client="Host", repo_name="shinzo-host-client", repo="https://github.com/shinzonetwork/shinzo-host-client/issues") }}
Loading
Loading