> For the complete documentation index, see [llms.txt](https://docs.mercurydata.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.mercurydata.app/smart-wallet-indexers/introduction-1.md).

# Passkey Indexer

Mercury runs a free, public indexer for [passkey-kit](https://github.com/kalepail/passkey-kit) smart wallets on Stellar. It indexes both event generations — the legacy `sw_v1` contracts (live on mainnet and testnet) and the v1 contract vocabulary — live on both networks, with the full wallet history held by Mercury already indexed.

The indexer is currently free to use, and for now requires no authentication or API key.

### Base URLs

| Network | Base URL                                               |
| ------- | ------------------------------------------------------ |
| Testnet | `https://testnet.mercurydata.app/rest/passkey-indexer` |
| Mainnet | `https://mainnet.mercurydata.app/rest/passkey-indexer` |

### Endpoints

All endpoints are `GET`, public, and return JSON.

| Endpoint                       | Description                                                                    |
| ------------------------------ | ------------------------------------------------------------------------------ |
| `/`                            | Health check: `{"status":"ok","service":"passkey-indexer"}`                    |
| `/api/lookup/:credentialId`    | Find wallets by WebAuthn credential ID (hex, `0x` prefix optional, lowercased) |
| `/api/lookup/address/:address` | Find wallets by ed25519 (`G...`) or policy (`C...`) signer address             |
| `/api/wallet/:contractId`      | Full signer state for one wallet, including removed signers                    |
| `/api/stats`                   | Indexer statistics: totals, ledger range, per-event-type counts per generation |

Lookups match **active** signers only. `/api/wallet/:contractId` returns `404` with `{"error":"Wallet not found"}` only when the contract has no indexed signers at all; a wallet whose signers were all removed still returns `200` with tombstoned signers.

Example:

```bash
curl https://mainnet.mercurydata.app/rest/passkey-indexer/api/lookup/1a9eb168624d3ecfeb964f58c5f7ddf00e78fa74
```

```json
{
  "credentialId": "1a9eb168624d3ecfeb964f58c5f7ddf00e78fa74",
  "wallets": [
    {
      "contract_id": "C...",
      "generation": "legacy",
      "signer_count": 1,
      "first_seen_ledger": 54309985,
      "last_seen_ledger": 54309985
    }
  ],
  "count": 1
}
```

`generation` is `"legacy"` or `"v1"` — the generation of the latest event seen for the wallet (an upgraded wallet reads `"v1"`).

### Wallet signers

`/api/wallet/:contractId` returns:

```json
{
  "contractId": "C...",
  "generation": "legacy",
  "first_seen_ledger": 54309985,
  "last_seen_ledger": 54309985,
  "signers": [
    {
      "key": { "type": "secp256r1", "value": "1a9eb168..." },
      "publicKey": "045fb62d...",
      "expiration": 4102444800,
      "expiration_unit": "unix",
      "limits": { "C...": null },
      "storage": "persistent",
      "status": "live"
    }
  ]
}
```

**`key`** — `type` is `"secp256r1"`, `"ed25519"`, or `"policy"`; `value` is the WebAuthn credential ID (lowercase hex) for secp256r1, a `G...` strkey for ed25519, a `C...` strkey for policy.

**`publicKey`** — 65-byte SEC-1 uncompressed secp256r1 public key, lowercase hex. Present for secp256r1 signers only.

**`expiration` / `expiration_unit`** — the raw on-chain number; semantics depend on the generation that wrote the signer: `"ledger"` (legacy) is a ledger sequence, `"unix"` (v1) is UNIX seconds. Both are inclusive: the signer is still valid at the expiration ledger/second itself. Omitted when the signer never expires.

**`limits`** — preserves the on-chain SignerLimits semantics exactly: field omitted = unlimited; `{}` (empty object) = fail-closed deny-all (not the same as unlimited); otherwise an object mapping contract address to either `null` (any key context) or an array of key objects the signer is restricted to.

**`storage`** — `"persistent"` or `"temporary"`.

**`status`** — `"live"`, `"expired"` (active but past expiration, computed per unit against the current ledger or clock), or `"removed"` (tombstone; last-known fields kept). `"evicted"` is intentionally never derived: temporary-storage eviction is not observable from events, so SDKs needing eviction status should probe RPC for the ledger entry.
