{
  "openrpc": "1.3.2",
  "info": {
    "title": "ZNS Indexer API",
    "description": "Read-only JSON-RPC API for the Zcash Name System (ZNS). Provides name resolution, marketplace listings, and indexer status for names registered on Zcash (mainnet or testnet) via Orchard shielded memos.\n\n## Signature Verification\n\nEvery signed assertion returned by this API — registrations, listings, and events — carries an Ed25519 signature. For admin-signed names the signer is the registry's admin key (returned as `status.admin_pubkey`). For sovereign-owned names the signer is the user's own Ed25519 key, which is returned in the `pubkey` field of the `Registration` or `Event` object; in this case `pubkey` must be used for verification instead of `admin_pubkey`. Clients can verify these signatures using only the data returned in responses.\n\n### Encoding\n\n- **Algorithm:** Ed25519 per RFC 8032.\n- **Public key (`admin_pubkey`):** 32 raw bytes, standard base64 with padding (44 characters).\n- **Signature fields:** 64 raw bytes, standard base64 with padding (88 characters).\n- **Pre-image:** the UTF-8 bytes of a literal ASCII string. All fields used in pre-image construction (`name`, `address`, `nonce`, `price`, pricing tiers) are ASCII-restricted, so UTF-8 encoding is byte-identical to the ASCII representation. No normalization, case folding, or whitespace handling is applied — reproduce each field byte-for-byte as returned in the response.\n- **Numeric fields (`nonce`, `price`, pricing tiers):** decimal ASCII with no leading zeros, no padding, and no sign (the output of a standard unsigned-integer-to-decimal conversion).\n- **Address fields:** the exact bech32m unified-address string as returned in the `address` or `ua` field, verbatim.\n- **Delimiter:** the single ASCII colon `:` (0x3A). All field values are constrained to exclude this byte.\n\n### Pre-image templates\n\nEach signed action has a fixed pre-image template. The signed bytes do NOT include the on-chain `ZNS:` memo prefix.\n\n- `CLAIM` — `CLAIM:{name}:{address}`\n- `UPDATE` — `UPDATE:{name}:{address}:{nonce}`\n- `BUY` — `BUY:{name}:{address}`\n- `DELIST` — `DELIST:{name}:{nonce}`\n- `LIST` — `LIST:{name}:{price}:{nonce}`\n- `RELEASE` — `RELEASE:{name}:{nonce}`\n- `SETPRICE` — `SETPRICE:{count}:{tier_1}:...:{tier_count}:{nonce}` where `count` is the number of tiers and each `tier_i` is a zatoshi amount in decimal ASCII.\n\n### Verifying a registration\n\nGiven a `Registration` with `name`, `address`, `nonce`, `last_action`, and `signature`:\n\n1. Select the pre-image template for `last_action` (one of `CLAIM`, `UPDATE`, `BUY`, `DELIST`).\n2. Build the pre-image string by substituting `name`, `address`, and `nonce` verbatim from the response. For `CLAIM` and `BUY`, `nonce` is not used.\n3. Encode the pre-image string as UTF-8 bytes.\n4. Base64-decode `signature` to 64 raw bytes.\n5. If `pubkey` is non-null, base64-decode it to 32 raw bytes and use it as the verification key. Otherwise base64-decode `status.admin_pubkey` to 32 raw bytes.\n6. Run Ed25519 verify. Accept the registration only on success.\n\n### Verifying a listing\n\nA `Listing` carries an independent `LIST` signature over `LIST:{name}:{price}:{nonce}`. To trust the assertion that a given name is listed at a given price, a client must verify the listing's `signature` in addition to verifying the enclosing `Registration`. The two signatures cover different facts and must be checked independently. For sovereign-owned names the listing signature is also produced by the user's key — use the enclosing `Registration.pubkey` (not `admin_pubkey`) as the verification key.\n\n### Verifying events\n\nEvents returned by the `events` method carry signatures for all action types, including `RELEASE` and `SETPRICE`. Use the pre-image template for the event's `action`, substituting fields from the event object (`name`, `ua` as the address, `nonce`, `price`). Full verification of `SETPRICE` events requires the pricing tier list, which is not individually exposed on the `Event` object; clients must obtain the tier list from the on-chain memo by scanning Orchard outputs with `status.uivk`. `status.pricing` reports the current tiers but is itself unsigned (see 'Verifying pricing' below), so it is not a trust-minimized source for SETPRICE verification.\n\n### Verifying pricing\n\nThe `status.pricing` object is NOT accompanied by a signature in this endpoint, so it cannot be verified directly against `admin_pubkey`. The admin does sign every SETPRICE action on-chain with pre-image `SETPRICE:{count}:{tier_1}:...:{tier_count}:{nonce}`, but this indexer only surfaces that signature via the `events` method for historical SETPRICE events. To obtain a trust-minimized view of current pricing, clients must scan Orchard outputs using `status.uivk` and re-parse the latest SETPRICE memo from the chain. Clients that accept `status.pricing` as-returned are trusting the indexer for pricing data even if they verify every other assertion cryptographically.\n\n### Worked construction example\n\nGiven a registration with `name = alice`, `address = u1example` (shortened for illustration), `nonce = 2`, and `last_action = UPDATE`, the pre-image string is `UPDATE:alice:u1example:2` and its UTF-8 bytes (24 bytes, hex) are `55 50 44 41 54 45 3a 61 6c 69 63 65 3a 75 31 65 78 61 6d 70 6c 65 3a 32`. Pass these bytes, the base64-decoded signature, and the base64-decoded `admin_pubkey` to any standards-compliant Ed25519 verifier. The reference implementation lives in `src/memo.rs` (`verify_signature`).",
    "version": "0.1.0",
    "license": {
      "name": "MIT"
    }
  },
  "servers": [
    {
      "name": "Local",
      "url": "http://127.0.0.1:3000"
    }
  ],
  "methods": [
    {
      "name": "resolve",
      "summary": "Look up a name or address registration",
      "description": "Resolves a ZNS registration by name or by Zcash unified address. If the query is a valid Zcash address, returns an array of all registrations bound to that address (one address may own multiple names). If the query is a name, returns a single registration object or null if not found.",
      "params": [
        {
          "name": "query",
          "required": true,
          "schema": {
            "type": "string",
            "description": "A ZNS name (e.g. \"alice\") or a Zcash unified address"
          }
        }
      ],
      "result": {
        "name": "registration",
        "description": "For name queries: a single ResolveResult or null. For address queries: an array of ResolveResult.",
        "schema": {
          "oneOf": [
            { "$ref": "#/components/schemas/ResolveResult" },
            {
              "type": "array",
              "items": { "$ref": "#/components/schemas/ResolveResult" }
            },
            { "type": "null" }
          ]
        }
      },
      "errors": [
        { "$ref": "#/components/errors/InvalidParams" }
      ],
      "examples": [
        {
          "name": "Resolve by name",
          "params": [
            { "name": "query", "value": "alice" }
          ],
          "result": {
            "name": "found",
            "value": {
              "name": "alice",
              "address": "utest1qqq...fff",
              "txid": "abc123def456...",
              "height": 3901200,
              "nonce": 2,
              "signature": "AQID...",
              "last_action": "UPDATE",
              "pubkey": null,
              "listing": null
            }
          }
        },
        {
          "name": "Resolve listed name",
          "params": [
            { "name": "query", "value": "bob" }
          ],
          "result": {
            "name": "found with listing",
            "value": {
              "name": "bob",
              "address": "utest1qqq...aaa",
              "txid": "def789...",
              "height": 3901180,
              "nonce": 1,
              "signature": "BQUF...",
              "last_action": "UPDATE",
              "pubkey": null,
              "listing": {
                "name": "bob",
                "price": 100000,
                "nonce": 3,
                "txid": "fed321...",
                "height": 3901250,
                "signature": "CQID..."
              }
            }
          }
        },
        {
          "name": "Resolve by address",
          "params": [
            { "name": "query", "value": "utest1qqq...fff" }
          ],
          "result": {
            "name": "address registrations",
            "value": [
              {
                "name": "alice",
                "address": "utest1qqq...fff",
                "txid": "abc123def456...",
                "height": 3901200,
                "nonce": 2,
                "signature": "AQID...",
                "last_action": "UPDATE",
                "pubkey": null,
                "listing": null
              },
              {
                "name": "myname",
                "address": "utest1qqq...fff",
                "txid": "ccc999...",
                "height": 3901400,
                "nonce": 0,
                "signature": "EQID...",
                "last_action": "CLAIM",
                "pubkey": "dGVzdHB1YmtleWZvcnNvdmVyZWlnblB0eQ==",
                "listing": null
              }
            ]
          }
        },
        {
          "name": "Name not found",
          "params": [
            { "name": "query", "value": "doesnotexist" }
          ],
          "result": {
            "name": "not found",
            "value": null
          }
        }
      ]
    },
    {
      "name": "list_for_sale",
      "summary": "Get all names currently listed for sale",
      "description": "Returns every name that has an active marketplace listing, ordered by block height descending (newest first). Prices are in zatoshis.",
      "params": [],
      "result": {
        "name": "listings",
        "schema": {
          "$ref": "#/components/schemas/ListForSaleResult"
        }
      },
      "examples": [
        {
          "name": "Some listings",
          "params": [],
          "result": {
            "name": "listings",
            "value": {
              "listings": [
                {
                  "name": "bob",
                  "price": 100000,
                  "nonce": 3,
                  "txid": "fed321...",
                  "height": 3901250,
                  "signature": "CQID..."
                },
                {
                  "name": "carol",
                  "price": 500000,
                  "nonce": 1,
                  "txid": "aaa111...",
                  "height": 3901200,
                  "signature": "DQID..."
                }
              ]
            }
          }
        },
        {
          "name": "No listings",
          "params": [],
          "result": {
            "name": "empty",
            "value": {
              "listings": []
            }
          }
        }
      ]
    },
    {
      "name": "status",
      "summary": "Get indexer synchronization status",
      "description": "Returns the current state of the ZNS indexer, including the latest synced block height, the admin public key used for signature verification, the UIVK being watched, counts of registered names and active listings, and the current pricing tiers.",
      "params": [],
      "result": {
        "name": "status",
        "schema": {
          "$ref": "#/components/schemas/StatusResult"
        }
      },
      "examples": [
        {
          "name": "Indexer status",
          "params": [],
          "result": {
            "name": "status",
            "value": {
              "synced_height": 3902500,
              "admin_pubkey": "abc123...",
              "uivk": "uivktest1qqq...fff",
              "address": "utest1qqq...indexeraddr",
              "registered": 42,
              "listed": 3,
              "pricing": {
                "nonce": 1,
                "height": 3901000,
                "tiers": [500000000, 100000000, 50000000, 10000000, 5000000]
              }
            }
          }
        }
      ]
    },
    {
      "name": "events",
      "summary": "Query the activity log with optional filters",
      "description": "Returns historical ZNS events (CLAIM, LIST, DELIST, RELEASE, UPDATE, BUY, SETPRICE) with optional filtering by name, action type(s), and block height. Results are ordered by block height descending. Supports pagination via limit and offset.",
      "params": [
        {
          "name": "name",
          "required": false,
          "schema": {
            "type": "string",
            "description": "Filter events for a specific name"
          }
        },
        {
          "name": "action",
          "required": false,
          "schema": {
            "type": "string",
            "enum": ["CLAIM", "LIST", "DELIST", "RELEASE", "UPDATE", "BUY", "SETPRICE"],
            "description": "Filter by a single action type"
          }
        },
        {
          "name": "since_height",
          "required": false,
          "schema": {
            "type": "integer",
            "description": "Only return events after this block height"
          }
        },
        {
          "name": "limit",
          "required": false,
          "schema": {
            "type": "integer",
            "description": "Maximum number of events to return (default 50, max 500)"
          }
        },
        {
          "name": "offset",
          "required": false,
          "schema": {
            "type": "integer",
            "description": "Number of events to skip for pagination (default 0)"
          }
        }
      ],
      "result": {
        "name": "events",
        "schema": {
          "$ref": "#/components/schemas/EventsResult"
        }
      },
      "examples": [
        {
          "name": "History for a name",
          "params": [
            { "name": "name", "value": "alice" }
          ],
          "result": {
            "name": "alice history",
            "value": {
              "events": [
                {
                  "id": 45,
                  "name": "alice",
                  "action": "LIST",
                  "txid": "def456...",
                  "height": 3901300,
                  "ua": "utest1qqq...fff",
                  "price": 50000000,
                  "nonce": 1,
                  "signature": "AQID...",
                  "pubkey": "dGVzdHNvdmVyZWlnbnB1YmtleWZvcmFsaWNl"
                },
                {
                  "id": 12,
                  "name": "alice",
                  "action": "CLAIM",
                  "txid": "abc123...",
                  "height": 3901200,
                  "ua": "utest1qqq...fff",
                  "price": null,
                  "nonce": null,
                  "signature": "AQID...",
                  "pubkey": "dGVzdHNvdmVyZWlnbnB1YmtleWZvcmFsaWNl"
                }
              ],
              "total": 2
            }
          }
        },
        {
          "name": "All claims",
          "params": [
            { "name": "action", "value": "CLAIM" },
            { "name": "limit", "value": 10 }
          ],
          "result": {
            "name": "claims",
            "value": {
              "events": [
                {
                  "id": 12,
                  "name": "alice",
                  "action": "CLAIM",
                  "txid": "abc123...",
                  "height": 3901200,
                  "ua": "utest1qqq...fff",
                  "price": null,
                  "nonce": null,
                  "signature": "AQID...",
                  "pubkey": null
                }
              ],
              "total": 1
            }
          }
        },
        {
          "name": "Recent sales",
          "params": [
            { "name": "action", "value": "BUY" },
            { "name": "since_height", "value": 3901000 }
          ],
          "result": {
            "name": "recent buys",
            "value": {
              "events": [],
              "total": 0
            }
          }
        }
      ]
    }
  ],
  "components": {
    "schemas": {
      "Registration": {
        "type": "object",
        "description": "A ZNS name registration",
        "required": ["name", "address", "txid", "height", "nonce", "last_action"],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 62,
            "pattern": "^[a-z0-9]{1,62}$",
            "description": "The registered name. Must be 1-62 characters of lowercase ASCII letters and digits only. No hyphens, no underscores, no unicode. Used verbatim in signature pre-images."
          },
          "address": {
            "type": "string",
            "description": "Zcash unified address bound to this name. Used verbatim (byte-for-byte) in signature pre-images."
          },
          "txid": {
            "type": "string",
            "description": "Transaction ID of the registration memo"
          },
          "height": {
            "type": "integer",
            "description": "Block height at which the registration was confirmed"
          },
          "nonce": {
            "type": "integer",
            "description": "Replay-protection counter, incremented on each UPDATE/DELIST. Reset to 0 on BUY. Used verbatim (decimal ASCII) in signature pre-images for UPDATE and DELIST."
          },
          "signature": {
            "type": ["string", "null"],
            "description": "Ed25519 admin signature over the most recent action that wrote this row, encoded as standard base64 with padding (88 characters). The pre-image template is determined by `last_action`. See the 'Signature Verification' section in the API description for encoding rules, all templates, and the full verification procedure."
          },
          "last_action": {
            "type": "string",
            "enum": ["CLAIM", "UPDATE", "DELIST", "BUY"],
            "description": "The action that produced the current `signature`. Selects the pre-image template used for signature verification. See the 'Signature Verification' section in the API description for all templates and the full verification procedure."
          },
          "pubkey": {
            "type": ["string", "null"],
            "description": "Ed25519 public key of the sovereign owner, encoded as standard base64 with padding (44 characters). Identifies the entity that performed the action recorded in `last_action`."
          }
        }
      },
      "Listing": {
        "type": "object",
        "description": "A marketplace listing for a registered name",
        "required": ["name", "price", "nonce", "txid", "height", "signature"],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 62,
            "pattern": "^[a-z0-9]{1,62}$",
            "description": "The listed name. Same constraints as `Registration.name`. Used verbatim in the LIST signature pre-image."
          },
          "price": {
            "type": "integer",
            "description": "Asking price in zatoshis (1 ZEC = 100,000,000 zatoshis). Used verbatim (decimal ASCII, no padding) in the LIST signature pre-image."
          },
          "nonce": {
            "type": "integer",
            "description": "Replay-protection nonce used in the LIST signature pre-image. Used verbatim (decimal ASCII) to reconstruct `LIST:{name}:{price}:{nonce}`."
          },
          "txid": {
            "type": "string",
            "description": "Transaction ID of the listing memo"
          },
          "height": {
            "type": "integer",
            "description": "Block height at which the listing was confirmed"
          },
          "signature": {
            "type": "string",
            "description": "Ed25519 admin signature over the pre-image `LIST:{name}:{price}:{nonce}`, encoded as standard base64 with padding (88 characters). See the 'Signature Verification' section in the API description for encoding rules and the verification procedure. This signature must be verified independently of any enclosing `Registration.signature`."
          }
        }
      },
      "ResolveResult": {
        "type": "object",
        "description": "A registration with optional listing info",
        "allOf": [
          { "$ref": "#/components/schemas/Registration" },
          {
            "type": "object",
            "properties": {
              "listing": {
                "oneOf": [
                  { "$ref": "#/components/schemas/Listing" },
                  { "type": "null" }
                ],
                "description": "Active listing if the name is for sale, otherwise null"
              }
            }
          }
        ]
      },
      "ListForSaleResult": {
        "type": "object",
        "required": ["listings"],
        "properties": {
          "listings": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Listing" },
            "description": "All active listings, newest first"
          }
        }
      },
      "Event": {
        "type": "object",
        "description": "A single ZNS activity event",
        "required": ["id", "name", "action", "txid", "height"],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Auto-incrementing event ID"
          },
          "name": {
            "type": "string",
            "description": "The name this event relates to. Follows the same pattern as `Registration.name` for name-scoped actions (CLAIM, UPDATE, BUY, LIST, DELIST, RELEASE). Empty string for SETPRICE events, which are not scoped to a specific name."
          },
          "action": {
            "type": "string",
            "enum": ["CLAIM", "LIST", "DELIST", "RELEASE", "UPDATE", "BUY", "SETPRICE"],
            "description": "The type of action that occurred. Selects the pre-image template for verifying `signature`. See the 'Signature Verification' section in the API description for all templates."
          },
          "txid": {
            "type": "string",
            "description": "Transaction ID that triggered this event"
          },
          "height": {
            "type": "integer",
            "description": "Block height at which this event was confirmed"
          },
          "ua": {
            "type": ["string", "null"],
            "description": "Relevant address: claimer (CLAIM), owner (LIST), new address (UPDATE), buyer (BUY). Null for DELIST, RELEASE, and SETPRICE. Used verbatim in the signature pre-image as the `{address}` field for CLAIM/UPDATE/BUY."
          },
          "price": {
            "type": ["integer", "null"],
            "description": "Price in zatoshis. Present for LIST and BUY; null otherwise. Used verbatim (decimal ASCII) in the LIST signature pre-image."
          },
          "nonce": {
            "type": ["integer", "null"],
            "description": "Replay-protection nonce used in the signed payload. Present for LIST, DELIST, RELEASE, UPDATE, and SETPRICE. Used verbatim (decimal ASCII) in the signature pre-image."
          },
          "signature": {
            "type": ["string", "null"],
            "description": "Ed25519 admin signature over the event's pre-image, encoded as standard base64 with padding (88 characters). The pre-image template is determined by `action`. Present for all action types. See the 'Signature Verification' section in the API description for all templates and the verification procedure."
          },
          "pubkey": {
            "type": ["string", "null"],
            "description": "Ed25519 public key of the sovereign owner who performed this action, encoded as standard base64 with padding (44 characters). Present only for name-scoped actions (CLAIM, LIST, DELIST, UPDATE, BUY, RELEASE) that were signed by a user rather than the admin. Null for admin-signed actions and SETPRICE events."
          }
        }
      },
      "EventsResult": {
        "type": "object",
        "required": ["events", "total"],
        "properties": {
          "events": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Event" },
            "description": "Matching events, newest first"
          },
          "total": {
            "type": "integer",
            "description": "Total number of matching events (before limit/offset)"
          }
        }
      },
      "StatusResult": {
        "type": "object",
        "required": ["synced_height", "admin_pubkey", "uivk", "address", "registered", "listed", "pricing"],
        "properties": {
          "synced_height": {
            "type": "integer",
            "description": "Latest block height the indexer has processed"
          },
          "admin_pubkey": {
            "type": "string",
            "description": "Ed25519 public key (32 raw bytes) used to verify all signed assertions in this API, encoded as standard base64 with padding (44 characters). See the 'Signature Verification' section in the API description for the full verification procedure."
          },
          "uivk": {
            "type": "string",
            "description": "Unified incoming viewing key the indexer watches for memos"
          },
          "address": {
            "type": "string",
            "description": "Zcash unified address corresponding to the watched UIVK"
          },
          "registered": {
            "type": "integer",
            "description": "Total number of registered names"
          },
          "listed": {
            "type": "integer",
            "description": "Number of names currently listed for sale"
          },
          "pricing": {
            "type": "object",
            "description": "Current pricing configuration, or null if SETPRICE has never been issued. NOTE: this object carries no signature and cannot be cryptographically verified via this endpoint. See the 'Verifying pricing' subsection in the API description for details on how clients can obtain a trust-minimized view of pricing.",
            "oneOf": [
              {
                "type": "object",
                "required": ["nonce", "height", "tiers"],
                "properties": {
                  "nonce": {
                    "type": "integer",
                    "description": "Replay-protection counter for the pricing record"
                  },
                  "height": {
                    "type": "integer",
                    "description": "Block height at which this pricing was set"
                  },
                  "tiers": {
                    "type": "array",
                    "items": { "type": "integer" },
                    "description": "Claim costs in zatoshis, indexed by name length (index 0 = 1-char names)"
                  }
                }
              },
              { "type": "null" }
            ]
          }
        }
      }
    },
    "errors": {
      "ParseError": {
        "code": -32700,
        "message": "Parse error"
      },
      "InvalidRequest": {
        "code": -32600,
        "message": "Invalid request"
      },
      "MethodNotFound": {
        "code": -32601,
        "message": "Method not found"
      },
      "InvalidParams": {
        "code": -32602,
        "message": "Invalid params: missing 'query'"
      },
      "InternalError": {
        "code": -32603,
        "message": "Internal error"
      }
    }
  }
}
