# Lookup Services

Canonical: https://docs.projectbabbage.com/docs/guides/overlays/lookup-services

Adding a Lookup Service to a Backend

This deep-dive shows you how to build and wire up a custom Lookup Service for the BSV Overlay Services Engine, using the [backend-project-template](https://github.com/p2ppsr/backend-project-template) as your starting point and extending the functionality provided by your Topic Manager (see the [Topic Manager](https://docs.projectbabbage.com/docs/guides/overlays/topic-managers) guide).

----------

## Why Lookup Services Matter

A **Lookup Service** stores the outputs admitted by your Topic Manager so that front-ends and other clients can query on-chain data efficiently. With it, you can:

- Persist data from transactions to be used from a frontend application
- Provide paginated, filtered queries for UTXOs on-chain
- Enforce access controls and authentication

----------

## Prerequisites

Before you begin, ensure you have:

-   Completed the [**Topic Manager**](https://docs.projectbabbage.com/docs/guides/overlays/topic-managers) guide
-   **Node.js** and **Docker**
-   **LARS** & **CARS** installed and initialized
-   A **BRC-100 wallet** such as BSV Desktop installed and running
-   **Git** (optional, but recommended)
    

----------

## Structure your overlay backend

Start from the project you used for your Topic Manager. You have two options:

### **Option 1: Manual setup**

1.  Follow the **Option 1: Quickstart** in [Topic Manager](https://docs.projectbabbage.com/docs/guides/overlays/topic-managers).
2.  Under `backend/src`, add a new `lookup-services/` folder alongside `topic-managers/`.

```bash
cd backend/src
mkdir -p lookup-services
touch lookup-services/DemoStorage.ts
touch lookup-services/DemoLookupServiceFactory.ts
```

### **Option 2: Clone the template**

Clone the backend or full-stack template and re-run the Topic Manager setup:

```bash
git clone https://github.com/p2ppsr/backend-project-template.git my-project
cd my-project/backend
npm install
```

Then follow your Topic Manager guide to register `DemoTopicManager` before proceeding.

After setup, your directory should resemble:

```text
my-project/
├── backend/
│   ├── src/
│   │   ├── lookup-services/
│   │   │   ├── DemoLookupService.ts
│   │   │   └── DemoStorage.ts
│   │   ├── topic-managers/
│   │   │   ├── DemoTopicDocs.md.ts
│   │   │   └── DemoTopicManager.ts
│   │   └── types.ts
│   └── package.json
├── deployment-info.json
└── package.json
```

----------

## Implement the Storage Layer

Your **DemoStorage.ts** defines how to persist, remove, and retrieve admitted outputs in your database. Here’s the template from the backend-project-template modified slightly for this demo:

```ts
// src/lookup-services/DemoStorage.ts
import { Collection, Db } from 'mongodb'
import { DemoRecord, UTXOReference } from '../types'

export class DemoStorage {
  private readonly records: Collection<DemoRecord>

  constructor(private readonly db: Db) {
    this.records = db.collection<DemoRecord>('demo_record')
  }

  async storeRecord(txid: string, outputIndex: number): Promise<void> {
    await this.records.insertOne({ txid, outputIndex, createdAt: new Date() })
  }

  async deleteRecord(txid: string, outputIndex: number): Promise<void> {
    await this.records.deleteOne({ txid, outputIndex })
  }

  async findAll(): Promise<UTXOReference[]> {
    return this.records
      .find({})
      .project<UTXOReference>({ txid: 1, outputIndex: 1 })
      .toArray()
      .then(results => results.map(r => ({ txid: r.txid, outputIndex: r.outputIndex })))
  }
}
```

### Customize for Hello-Tokens

For our hello-token example, we’ll store the `message` field too:

```ts
async storeRecord(
  txid: string,
  outputIndex: number,
  message: string
): Promise<void> {
  await this.records.insertOne({
    txid,
    outputIndex,
    message,
    createdAt: new Date()
  })
}
```

And add a custom query for the messages that we store:

```ts
async findByMessage(
  message: string,
  limit = 50,
  skip = 0,
  sortOrder: 'asc' | 'desc' = 'desc'
): Promise<UTXOReference[]> {
  const direction = sortOrder === 'asc' ? 1 : -1
  return this.records
    .find({ $text: { $search: message } }, { projection: { txid: 1, outputIndex: 1, createdAt: 1 } })
    .sort({ createdAt: direction })
    .skip(skip)
    .limit(limit)
    .toArray()
    .then(results => results.map(r => ({ txid: r.txid, outputIndex: r.outputIndex })))
}
```

----------


----------

## Implement the DemoLookupServiceFactory

Below we break the factory into focused pieces. You’ll facilitate the functionality of your lookup service to store and query the transaction data of hello-tokens by combining these pieces with the template.

### 1. Configure admission & spend modes

These two properties tell the overlay engine how this service receives events. For our hello-token example, we admit via the locking script and request no data within spend notifications:

```ts
export class DemoLookupService implements LookupService {
  readonly admissionMode: AdmissionMode = 'locking-script'
  readonly spendNotificationMode: SpendNotificationMode = 'none'

  constructor(private storage: DemoStorage) {}
  // …
}
```

These values control what data is provided within event payloads.

----------

### 2. Persist admitted outputs

When a new UTXO is admitted by your Topic Manager, the engine calls `outputAdmittedByTopic`. Decode the PushDrop payload, validate it, then store it:

```ts
/** Called when Topic Manager admits an output */
async outputAdmittedByTopic(payload: OutputAdmittedByTopic): Promise<void> {
  // 1. Ensure the engine is using the correct admission mode
  if (payload.mode !== this.admissionMode) throw new Error('Invalid mode')

  // 2. Only handle our demo topic, change to fit your topic manager's name
  if (payload.topic !== 'tm_demo') throw new Error(`Unexpected topic: ${payload.topic}`)

  try {
    // 3. Decode the PushDrop “hello token”
    const result = PushDrop.decode(payload.lockingScript)
    const message = Utils.toUTF8(result.fields[0])
    if (message.length < 2) throw new Error('Invalid hello-token: message too short')

    // 4. Persist for future lookup
    await this.storage.storeRecord(payload.txid, payload.outputIndex, message)
  } catch (err) {
    console.error(`DemoLookupService: failed to index ${payload.txid}.${payload.outputIndex}`, err)
  }
}
```

> The `payload` includes `txid`, `outputIndex`, and raw `lockingScript`.

----------

### 3. Respond to lookup queries

Clients invoke `lookup(...)` to retrieve matching UTXOs. Here we branch by `message` keyword or return all records:

```ts
/** Answers a lookup query */
async lookup(question: LookupQuestion): Promise<LookupAnswer | LookupFormula> {
  if (!question) throw new Error('A valid query must be provided!')
  if (question.service !== 'ls_demo') throw new Error('Lookup service not supported!')

  const { message, limit = 50, skip = 0 } = question.query as DemoQuery

  // Basic validation
  if (limit < 0 || skip < 0) throw new Error('Limit and skip must be non-negative')

  // 1. If a message filter is provided, use the message search
  if (message) {
    return this.storage.findByMessage(message, limit, skip)
  }

  // 2. Otherwise, return all records
  return this.storage.findAll()
}
```

> Every output returned here includes `txid` and `outputIndex`; frontends can decode the BEEF to extract token fields.

With these pieces in place, your `DemoLookupService` is ready to be registered in `DemoLookupServiceFactory.ts`. The last step is to add the factory to your `deployment-info.json`:

```diff
{
  "schema": "bsv-app",
  "schemaVersion": "1.0",
  "topicManagers": {
    "tm_demo": "./backend/src/topic-managers/DemoTopicManager.ts"
  },
  "lookupServices": {
    "ls_demo": {
      "serviceFactory": "./backend/src/lookup-services/DemoLookupServiceFactory.ts",
      "hydrateWith": "mongo"
    }
  },
  …,
}

```

----------

## Querying from your Front-End

Here’s how your React app can query admitted hello-tokens:

```ts
import { LookupResolver } from '@bsv/overlay'
import { Transaction, PushDrop, Utils } from '@bsv/sdk'

export async function queryTokens(message: string): Promise<void> {
  const resolver = new LookupResolver({ networkPreset: 'local' }) // or mainnet/testnet
  const response = await resolver.query({
    service: 'ls_demo',
    query: { message }
  })

  if (response.type !== 'output-list') throw new Error('Expected output-list')

  response.outputs.forEach(({ beef, outputIndex }) => {
    const tx = Transaction.fromBEEF(beef)
    const decoded = PushDrop.decode(tx.outputs[outputIndex].lockingScript)
    console.log('Message:', Utils.toUTF8(decoded.fields[0]))
  })
}
```

----------

Congratulations! You now have a fully functional overlay system—Topic Manager **and** Lookup Service—ready to deploy with LARS or CARS. Happy building!
