Compare commits
No commits in common. "main" and "mainnet" have entirely different histories.
127
AGENTS.md
127
AGENTS.md
@ -1,127 +0,0 @@
|
|||||||
# Agents: read this before touching laconicd-backed state
|
|
||||||
|
|
||||||
Written for AI coding agents (Claude, Codex, Copilot, etc.) working
|
|
||||||
on laconicd itself or on applications that persist data through
|
|
||||||
it. Humans can read it too — the content is the same — but the
|
|
||||||
tone addresses a specific failure mode that agents exhibit more
|
|
||||||
consistently than humans.
|
|
||||||
|
|
||||||
## The failure mode this document exists to prevent
|
|
||||||
|
|
||||||
Agents extending an existing laconicd-backed codebase pattern-match
|
|
||||||
the visible API calls and treat them as canonical. If a codebase
|
|
||||||
uses `queryRecords` with attribute filtering for everything —
|
|
||||||
including where it's wrong — an agent will extend that usage
|
|
||||||
rather than discover the Naming API.
|
|
||||||
|
|
||||||
When the wrong pattern eventually scales badly (latency grows with
|
|
||||||
accumulated record versions), agents reach for **workarounds
|
|
||||||
within the chosen pattern** — pagination, caches, compaction —
|
|
||||||
rather than re-evaluating the primitive choice. That drift
|
|
||||||
compounds: each workaround entrenches the wrong design.
|
|
||||||
|
|
||||||
This has happened repeatedly on real projects. The architectural
|
|
||||||
cost is high, because laconicd is append-only: wrong versions
|
|
||||||
persist on chain as permanent garbage that you can't silently
|
|
||||||
delete.
|
|
||||||
|
|
||||||
## Concrete rules for agents
|
|
||||||
|
|
||||||
### 1. Before designing any laconicd-backed state, read `docs/PATTERNS.md`
|
|
||||||
|
|
||||||
Not after a first implementation. Not when you hit a scaling
|
|
||||||
wall. **Before.** The decision tree there is the only reliable
|
|
||||||
way to pick the right primitive.
|
|
||||||
|
|
||||||
### 2. Do not pattern-match from existing client code as if it were canon
|
|
||||||
|
|
||||||
A gateway, SDK, or service that already uses laconicd is an
|
|
||||||
existing code sample. It is not evidence that the patterns it
|
|
||||||
uses are correct. Check:
|
|
||||||
|
|
||||||
- Does the client use `setName` / `lookupNames` / `resolveNames`
|
|
||||||
at all? If not, the chances it's modelling mutable identity
|
|
||||||
with `queryRecords`+attribute scan are high — that's almost
|
|
||||||
always a latent latency bug.
|
|
||||||
- Does `save_*` in the client write only a Record, never a name?
|
|
||||||
If your new feature has the same shape, you're about to extend
|
|
||||||
the bug.
|
|
||||||
- Does the client have "pick the latest" / "sort desc and take
|
|
||||||
first" logic after a `queryRecords` call? That is exactly what
|
|
||||||
`resolveName` does at the chain level — the client is
|
|
||||||
re-implementing it badly.
|
|
||||||
|
|
||||||
### 3. Questions an agent should ask itself before writing `queryRecords`
|
|
||||||
|
|
||||||
- "Am I looking up the current state of a logical entity?" If yes,
|
|
||||||
you want the Naming API, not `queryRecords`.
|
|
||||||
- "Will this query return more records as the same entity gets
|
|
||||||
modified over time?" If yes, you want the Naming API.
|
|
||||||
- "Am I about to write client-side code that picks the latest of
|
|
||||||
the returned list?" If yes, you want the Naming API.
|
|
||||||
|
|
||||||
If any answer is yes and you still want `queryRecords`, stop and
|
|
||||||
write a comment explaining why the Naming API doesn't fit. If you
|
|
||||||
can't, the Naming API does fit.
|
|
||||||
|
|
||||||
### 4. Do not propose caches, pagination, or compaction as the first fix for slow reads
|
|
||||||
|
|
||||||
Slow `queryRecords` on a mutable-identity use case is a design
|
|
||||||
smell, not a performance problem. The fixes agents reach for
|
|
||||||
first are all wrong:
|
|
||||||
|
|
||||||
- **TTL caches** hide the latency rather than fix it. First read
|
|
||||||
per window still pays the full cost.
|
|
||||||
- **`limit` / `offset` pagination** caps per-request fetch cost
|
|
||||||
but doesn't reduce the version-per-entity multiplier.
|
|
||||||
- **`delete_records` compaction** destroys the audit trail that
|
|
||||||
the immutable-Record design was supposed to provide.
|
|
||||||
|
|
||||||
If you're reaching for these, you are almost certainly on the
|
|
||||||
wrong side of the decision tree in `docs/PATTERNS.md`. Go back
|
|
||||||
and re-pick the primitive.
|
|
||||||
|
|
||||||
### 5. If you find a codebase already on the wrong side
|
|
||||||
|
|
||||||
Do not compound the mistake. Options in order of preference:
|
|
||||||
|
|
||||||
1. Migrate the specific feature you're touching to the Naming
|
|
||||||
API. You can usually do this incrementally — start writing
|
|
||||||
names on new writes, keep reading via attribute-scan until
|
|
||||||
the name index is populated, then flip the read path.
|
|
||||||
2. Write an ADR documenting the debt and which class of
|
|
||||||
regression will appear (latency grows with history; first
|
|
||||||
timeout hit around ~N records).
|
|
||||||
3. Ship the feature on the Naming API even if the rest of the
|
|
||||||
codebase doesn't — a single correct example in the codebase
|
|
||||||
is worth more than consistency with a wrong pattern.
|
|
||||||
|
|
||||||
### 6. Read laconicd's schema and keeper source, not just the client
|
|
||||||
|
|
||||||
Before proposing a fix that involves "laconicd doesn't support
|
|
||||||
X":
|
|
||||||
|
|
||||||
- `gql/cerc-io/laconicd/schema.graphql` — the actual read API.
|
|
||||||
- `x/registry/keeper/` — the write and index path.
|
|
||||||
- `proto/cerc/registry/v1/tx.proto` — the mutation messages.
|
|
||||||
|
|
||||||
Many "laconicd limitations" agents assume are actually client
|
|
||||||
limitations. `queryRecords` supports `limit` and `offset`;
|
|
||||||
`setName` and `lookupNames` exist and work; `NameRecord.history`
|
|
||||||
returns block-height-tagged prior bindings. Verify capabilities
|
|
||||||
against source before recommending workarounds or upstream
|
|
||||||
changes.
|
|
||||||
|
|
||||||
## Why this is in its own file
|
|
||||||
|
|
||||||
A `PATTERNS.md` on its own is a reference document humans browse.
|
|
||||||
An `AGENTS.md` is loaded into context and read top-to-bottom at
|
|
||||||
the start of agent sessions. Calling out the failure mode
|
|
||||||
explicitly — "agents tend to overlook the Naming API" — is load-
|
|
||||||
bearing because the people most likely to hit the failure are
|
|
||||||
the ones who wouldn't naturally pattern-match to a document
|
|
||||||
titled "Patterns."
|
|
||||||
|
|
||||||
If you are reading this and you are about to write your first
|
|
||||||
`queryRecords` call: stop. Read `docs/PATTERNS.md`. Then come
|
|
||||||
back.
|
|
||||||
20
README.md
20
README.md
@ -29,26 +29,6 @@ Run with a single node fixture:
|
|||||||
|
|
||||||
See [lockup.md](./lockup.md) for lockup account usage.
|
See [lockup.md](./lockup.md) for lockup account usage.
|
||||||
|
|
||||||
## Designing state that lives on laconicd
|
|
||||||
|
|
||||||
**Before** writing a client library, service, or migration that
|
|
||||||
persists data through laconicd, read:
|
|
||||||
|
|
||||||
- [`docs/PATTERNS.md`](./docs/PATTERNS.md) — the primitive
|
|
||||||
decision tree. Explains when to use the Naming API
|
|
||||||
(`setName` / `lookupNames` / `resolveNames`) vs. the Record
|
|
||||||
API (`setRecord` / `queryRecords` / `getRecordsByIds`), with
|
|
||||||
worked examples and the common anti-patterns.
|
|
||||||
- [`AGENTS.md`](./AGENTS.md) — written for AI coding agents, but
|
|
||||||
the failure mode it describes (extending the wrong primitive
|
|
||||||
rather than re-evaluating it) applies to human contributors
|
|
||||||
under deadline pressure too.
|
|
||||||
|
|
||||||
Picking the wrong primitive is expensive to unwind: laconicd is
|
|
||||||
append-only, so mistakes persist as on-chain data you cannot
|
|
||||||
silently remove. Spend the 10 minutes to read the pattern guide
|
|
||||||
before the first commit.
|
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
Run tests:
|
Run tests:
|
||||||
|
|||||||
@ -39,7 +39,7 @@ func AddGenesisLockupAccountCmd() *cobra.Command {
|
|||||||
moduleName := args[0]
|
moduleName := args[0]
|
||||||
distributionFilePath := args[1]
|
distributionFilePath := args[1]
|
||||||
|
|
||||||
var distribution []interface{}
|
var distribution map[string]interface{}
|
||||||
distributionBytes, err := os.ReadFile(distributionFilePath)
|
distributionBytes, err := os.ReadFile(distributionFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read %s file: %w", distributionFilePath, err)
|
return fmt.Errorf("failed to read %s file: %w", distributionFilePath, err)
|
||||||
|
|||||||
220
docs/PATTERNS.md
220
docs/PATTERNS.md
@ -1,220 +0,0 @@
|
|||||||
# Laconicd design patterns
|
|
||||||
|
|
||||||
**Read this before designing any state that lives on laconicd.**
|
|
||||||
Not after. Writing a client library, a service that persists user
|
|
||||||
data, or a migration from another store — start here.
|
|
||||||
|
|
||||||
This document exists because the wrong primitive choice in an
|
|
||||||
application is expensive to unwind after production traffic
|
|
||||||
accumulates. The chain is append-only; mistakes persist as
|
|
||||||
on-chain garbage you cannot delete retroactively. Most of the
|
|
||||||
architectural regret in the laconicd ecosystem so far has come
|
|
||||||
from picking `queryRecords` as the default read path when a
|
|
||||||
different primitive was the right tool.
|
|
||||||
|
|
||||||
## The primitives, honestly
|
|
||||||
|
|
||||||
Laconicd exposes three distinct storage and lookup primitives. They
|
|
||||||
look similar on first glance and have VERY different semantics.
|
|
||||||
|
|
||||||
| Primitive | Identity | Mutability | Lookup cost | Audit trail |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| **Record** (`setRecord` / `queryRecords` / `getRecordsByIds`) | Content hash (CID) | Immutable. Every change is a NEW record with a new CID. | Attribute-indexed, but returns EVERY version matching | Implicit — all versions persist forever |
|
|
||||||
| **Name** (`setName` / `lookupNames` / `resolveNames`) | Caller-chosen string (`"mtm/lootboxes/<wallet>/<id>"`) | Pointer. Re-setting updates which CID the name resolves to. | Direct name → latest CID lookup (single record) | Explicit — `NameRecord.history` returns prior bindings with block heights |
|
|
||||||
| **Authority / Bond / Auction** | Purpose-specific | Varies | Purpose-specific | Varies |
|
|
||||||
|
|
||||||
**The trap:** `Record` looks like the universal "put this thing on
|
|
||||||
chain" primitive because its API surface is biggest and most
|
|
||||||
obvious. If you treat it as such, every mutable-state use case
|
|
||||||
(game state, user profiles, inventories, counters) ends up re-
|
|
||||||
implementing the Naming API badly — appending version after
|
|
||||||
version, then filtering client-side for "the latest."
|
|
||||||
|
|
||||||
## Decision tree
|
|
||||||
|
|
||||||
```
|
|
||||||
Does the thing you're storing have a mutable logical identity?
|
|
||||||
(i.e., "the current state of thing X" is a meaningful question)
|
|
||||||
|
|
||||||
├── YES → Use the Naming API.
|
|
||||||
│ setName("mtm/lootboxes/<wallet>/<box_id>", cid) on every write.
|
|
||||||
│ lookupNames(...) or resolveNames(...) returns the one
|
|
||||||
│ current record. Audit trail via NameRecord.history.
|
|
||||||
│
|
|
||||||
└── NO → Is it a point-lookup by CID?
|
|
||||||
│
|
|
||||||
├── YES → getRecordsByIds([cid])
|
|
||||||
│
|
|
||||||
└── NO → It's an append-only event stream with
|
|
||||||
queryable attributes. Use setRecord +
|
|
||||||
queryRecords. Expect every historical record
|
|
||||||
to come back; plan pagination accordingly.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Anti-pattern: "queryRecords-as-key-value-store"
|
|
||||||
|
|
||||||
This is the mistake real laconicd codebases keep making. It looks
|
|
||||||
like this:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# BAD — reimplementing names via attribute scan
|
|
||||||
|
|
||||||
async def save_state(user_id: str, state: dict):
|
|
||||||
await set_record(
|
|
||||||
record_type="UserState",
|
|
||||||
attributes={"user_id": user_id}, # logical ID as attribute
|
|
||||||
data=state,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def get_state(user_id: str) -> dict:
|
|
||||||
records = await query_records(
|
|
||||||
record_type="UserState",
|
|
||||||
attributes={"user_id": user_id},
|
|
||||||
)
|
|
||||||
# Client-side "pick the latest" — smell
|
|
||||||
records.sort(key=lambda r: r["created_at"], reverse=True)
|
|
||||||
return records[0]
|
|
||||||
```
|
|
||||||
|
|
||||||
Why it's wrong:
|
|
||||||
|
|
||||||
1. Every call to `save_state` writes a NEW content-addressed record.
|
|
||||||
Old records stay on chain forever.
|
|
||||||
2. `query_records` returns all of them every time. Per-record fetch
|
|
||||||
cost scales linearly with save count.
|
|
||||||
3. The client-side `sort + [0]` is "I wanted the latest" — exactly
|
|
||||||
what `lookupNames` does at the index level.
|
|
||||||
4. Latency degrades silently as users accumulate history. The
|
|
||||||
query eventually breaches whatever timeout the caller has.
|
|
||||||
|
|
||||||
The correct pattern:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# GOOD — names for mutable identity
|
|
||||||
|
|
||||||
async def save_state(user_id: str, state: dict):
|
|
||||||
cid = await set_record(
|
|
||||||
record_type="UserState",
|
|
||||||
attributes={"user_id": user_id},
|
|
||||||
data=state,
|
|
||||||
)
|
|
||||||
await set_name(
|
|
||||||
name=f"my-app/user-state/{user_id}",
|
|
||||||
cid=cid,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def get_state(user_id: str) -> dict:
|
|
||||||
record = await resolve_name(f"my-app/user-state/{user_id}")
|
|
||||||
return record
|
|
||||||
```
|
|
||||||
|
|
||||||
Properties of the correct pattern:
|
|
||||||
|
|
||||||
- One name → one current CID. Latency doesn't scale with save count.
|
|
||||||
- Historical versions still exist on chain (immutable Record CIDs
|
|
||||||
aren't deleted), and are reachable via `NameRecord.history`.
|
|
||||||
- Audit trail is explicit rather than implicit — you can ask "what
|
|
||||||
was the state at block N?" rather than scanning every version.
|
|
||||||
- Client code has no "pick the latest" step; the chain already
|
|
||||||
answered that.
|
|
||||||
|
|
||||||
## Anti-pattern: "compaction via `delete_records`"
|
|
||||||
|
|
||||||
The `delete_records` primitive exists and is tempting to use for
|
|
||||||
"prune old versions of logical entity X to keep queries fast." Do
|
|
||||||
not use it for that unless you have signed off on dropping the
|
|
||||||
audit trail.
|
|
||||||
|
|
||||||
- `delete_records` physically removes a CID from the index.
|
|
||||||
- If your design relies on every version being recoverable (audit
|
|
||||||
trails, chain-of-custody, dispute resolution), compaction
|
|
||||||
destroys exactly that property.
|
|
||||||
- If your design doesn't need every version, you were never in
|
|
||||||
the Record + attribute-scan pattern to begin with — you wanted
|
|
||||||
the Naming API from the start.
|
|
||||||
|
|
||||||
`delete_records` is appropriate for **user-intent-driven
|
|
||||||
permanent deletion** (unregistering a device, unfollowing,
|
|
||||||
revoking a credential). Not for database-compaction-style
|
|
||||||
version pruning.
|
|
||||||
|
|
||||||
## Anti-pattern: "cache the slow query"
|
|
||||||
|
|
||||||
Caching a `queryRecords` result (TTL, LRU, whatever) at the client
|
|
||||||
layer is attractive because it appears to fix latency without
|
|
||||||
touching storage design. It doesn't — it hides the fact that you
|
|
||||||
chose the wrong primitive. The first read per cache window still
|
|
||||||
pays the growing-with-history cost, and the design drifts further
|
|
||||||
from correct as the caching surface calcifies.
|
|
||||||
|
|
||||||
Cache is acceptable for genuinely external-service latency (price
|
|
||||||
oracles, Solana RPC balance checks). It is not acceptable as a
|
|
||||||
substitute for using the Naming API.
|
|
||||||
|
|
||||||
## Pagination (`limit` / `offset`)
|
|
||||||
|
|
||||||
`queryRecords` accepts `limit` and `offset`. Use them when you
|
|
||||||
genuinely want an append-only event stream with cursor-style
|
|
||||||
paging (e.g., "the last 100 events matching filter X"). Do NOT
|
|
||||||
use pagination to patch over "my mutable-identity query is slow" —
|
|
||||||
pagination doesn't reduce the per-entity version multiplier,
|
|
||||||
only the per-request fetch budget.
|
|
||||||
|
|
||||||
## Mutations go through Cosmos tx, not GraphQL
|
|
||||||
|
|
||||||
Writes (`setRecord`, `setName`, `deleteRecord`, `reserveAuthority`,
|
|
||||||
etc.) are Cosmos SDK `Msg`s, submitted via Tendermint RPC. The
|
|
||||||
GraphQL endpoint is read-only. Client libraries usually wrap this
|
|
||||||
behind a "registry writer" sidecar or equivalent — check how your
|
|
||||||
SDK / service is structured before wiring writes.
|
|
||||||
|
|
||||||
## Worked example: mutable game state
|
|
||||||
|
|
||||||
Game records a user's loot box that transitions
|
|
||||||
`PENDING → ACTIVE → RESOLVED`.
|
|
||||||
|
|
||||||
Correct shape:
|
|
||||||
|
|
||||||
- **Record writes** (one per state transition; each gets a new CID):
|
|
||||||
- `setRecord(type="LootBox", attributes={wallet, id, status}, data={...})`
|
|
||||||
- Preserves complete history as immutable records. Needed for
|
|
||||||
audit: "what did this box look like when the user picked?"
|
|
||||||
- **Name write** (on every transition, pointing at the new CID):
|
|
||||||
- `setName(name=f"mygame/lootboxes/{wallet}/{box_id}", cid=<new_cid>)`
|
|
||||||
- **Read: current state of one box**:
|
|
||||||
- `resolveName(f"mygame/lootboxes/{wallet}/{box_id}")` → one record.
|
|
||||||
- **Read: all current boxes for a wallet**:
|
|
||||||
- Enumerate names by prefix, resolve each. Or maintain an
|
|
||||||
index name `mygame/lootboxes/{wallet}/_index` whose pointed
|
|
||||||
record lists active box ids.
|
|
||||||
- **Read: audit trail of one box**:
|
|
||||||
- `lookupNames([f"mygame/lootboxes/{wallet}/{box_id}"])` returns
|
|
||||||
`NameRecord.history`, one entry per state transition, each with
|
|
||||||
its block height. Resolve each entry's CID to get the full
|
|
||||||
historical state.
|
|
||||||
|
|
||||||
This shape works with any number of boxes per wallet. Per-wallet
|
|
||||||
latency is bounded by active-box count, not lifetime
|
|
||||||
transition-count.
|
|
||||||
|
|
||||||
## When you find an existing codebase doing it wrong
|
|
||||||
|
|
||||||
Appending a fix on top of the wrong primitive is usually the
|
|
||||||
wrong move. Options in order of preference:
|
|
||||||
|
|
||||||
1. **Migrate to Naming API.** The data already on chain as Records
|
|
||||||
is still valid — you just need to start writing names and
|
|
||||||
reading via names. Existing consumers of the attribute-scan
|
|
||||||
path can keep working until they're moved.
|
|
||||||
2. **Document the debt.** If the migration is deferred, write an
|
|
||||||
ADR saying so and naming the specific class of regression
|
|
||||||
this creates (latency as history accumulates).
|
|
||||||
3. **Do NOT reach for caches or compaction first.** Those hide
|
|
||||||
the design mistake and make the eventual migration harder.
|
|
||||||
|
|
||||||
## See also
|
|
||||||
|
|
||||||
- `AGENTS.md` (repo root) — why AI agents particularly mis-pick
|
|
||||||
primitives here, and what context they need.
|
|
||||||
- `gql/cerc-io/laconicd/schema.graphql` — inline anti-pattern
|
|
||||||
warnings on `queryRecords` and `getRecordsByIds`.
|
|
||||||
@ -240,30 +240,10 @@ type Query {
|
|||||||
# GraphDB API.
|
# GraphDB API.
|
||||||
#
|
#
|
||||||
|
|
||||||
# Point-lookup of records by content-hash CID.
|
# Get records by IDs.
|
||||||
#
|
|
||||||
# Returns one record per id. CIDs are immutable — a record's
|
|
||||||
# content never changes, so this API answers "what was at this
|
|
||||||
# CID?" If you want "what is the current state of logical
|
|
||||||
# thing X?", use resolveNames / lookupNames instead (below).
|
|
||||||
getRecordsByIds(ids: [String!]): [Record]
|
getRecordsByIds(ids: [String!]): [Record]
|
||||||
|
|
||||||
# Attribute-filtered query over records.
|
# Query records.
|
||||||
#
|
|
||||||
# WARNING — pick the right primitive before using this.
|
|
||||||
# Records are content-addressed and immutable; every state
|
|
||||||
# change writes a NEW record with a new CID. queryRecords
|
|
||||||
# returns EVERY version matching the filter, not "the latest."
|
|
||||||
# If you find yourself filtering by a logical id attribute
|
|
||||||
# (wallet, user_id, entity_id, etc.) and then picking the
|
|
||||||
# "latest" version client-side, you are reimplementing the
|
|
||||||
# Naming API (resolveNames / lookupNames) at the client layer.
|
|
||||||
# That is the correct primitive for mutable logical identity.
|
|
||||||
#
|
|
||||||
# Use queryRecords for: append-only event streams, list-all-
|
|
||||||
# events-matching-filter, cursor-paged audit scans.
|
|
||||||
# DO NOT use queryRecords for: "get the current state of X".
|
|
||||||
# See docs/PATTERNS.md and AGENTS.md for the decision tree.
|
|
||||||
queryRecords(
|
queryRecords(
|
||||||
# Multiple attribute conditions are in a logical AND.
|
# Multiple attribute conditions are in a logical AND.
|
||||||
attributes: [KeyValueInput!]
|
attributes: [KeyValueInput!]
|
||||||
@ -281,17 +261,6 @@ type Query {
|
|||||||
#
|
#
|
||||||
# Naming API.
|
# Naming API.
|
||||||
#
|
#
|
||||||
# USE THIS for any "current state of logical thing X" lookup.
|
|
||||||
# setName binds a caller-chosen name to a content-addressed
|
|
||||||
# record CID. Re-calling setName with a new CID updates which
|
|
||||||
# CID the name resolves to, without losing the prior binding
|
|
||||||
# (NameRecord.history preserves it with block heights).
|
|
||||||
#
|
|
||||||
# Names eliminate the "scan all versions, pick latest" client-
|
|
||||||
# side pattern that queryRecords invites. They are the
|
|
||||||
# correct primitive for mutable logical identity.
|
|
||||||
# See docs/PATTERNS.md for the decision tree and worked examples.
|
|
||||||
#
|
|
||||||
|
|
||||||
# Get authorities list.
|
# Get authorities list.
|
||||||
getAuthorities(owner: String): [Authority]!
|
getAuthorities(owner: String): [Authority]!
|
||||||
@ -300,12 +269,6 @@ type Query {
|
|||||||
lookupAuthorities(names: [String!]): [AuthorityRecord]!
|
lookupAuthorities(names: [String!]): [AuthorityRecord]!
|
||||||
|
|
||||||
# Lookup name to record mapping information.
|
# Lookup name to record mapping information.
|
||||||
#
|
|
||||||
# Returns a NameRecord per queried name, each containing the
|
|
||||||
# `latest` binding (the current CID) and `history` (all prior
|
|
||||||
# bindings with their block heights). Use this when you want
|
|
||||||
# both the current state and its audit trail. If you only want
|
|
||||||
# the current Record, resolveNames is more direct.
|
|
||||||
lookupNames(names: [String!]): [NameRecord]!
|
lookupNames(names: [String!]): [NameRecord]!
|
||||||
|
|
||||||
# Resolve names to records.
|
# Resolve names to records.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user