Topic Managers
Setting up a backend with a Topic Manager
This deep-dive shows you how to build and wire up a custom Topic Manager for the BSV Overlay Services Engine, using the backend-project-template as your starting point and emulating the behavior of the hello-overlay example.
Why Topic Managers Matter
A Topic Manager listens for broadcasted on-chain events (UTXO creations, spends, etc.) and filters/admits those outputs into named “topics” that your Lookup Service and front-end can query. This separation makes your overlay:
- Modular and testable
- Scalable by having processes run independently
- Easy to extend with new topics
Prerequisites
Before you begin, ensure you have the following:
- 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
To scaffold your overlay backend, you can choose one of the following options:
Option 1: Quickstart
Follow the Adding a Backend Server to Your LARS and CARS Project. You will have to modify the backend/src directory to only include the sub-folders topic-managers and lookup-services along with any helper files that may be referenced in them.
To set up the topic-managers directory:
1mkdir -p src/topic-managers
2touch src/topic-managers/DemoTopicManager.ts
3touch src/topic-managers/DemoWorldTopicDocs.tsOption 2: Clone the template
Cloning either the template for LARS and CARS projects that only uses a backend or the one that uses both a frontend and backend
1git clone https://github.com/p2ppsr/backend-project-template.git my-project
2cd my-project/backend
3npm installAfter setting up your project, the general structure should resemble:
1my-project/
2├── backend/
3│ ├── src/
4│ │ ├── lookup-services/
5│ │ ├── topic-managers/
6│ │ │ ├── TemplateTopicDocs.md.ts
7│ │ │ └── TemplateTopicManager.ts
8│ │ └── types.ts
9│ └── package.json
10├── deployment-info.json
11└── package.jsonImplement the Topic Manager
HelloWorld Tokens in a nutshell
As mentioned before, this guide will cover how to implement an overlay service similar to the hello-overlay project. This topic manager will admit “hello-tokens” which can be created and broadcasted to be received by your topic manager from a React frontend by adding this:
1const wallet = new WalletClient('auto');
2const outputScript = await new PushDrop(wallet).lock(
3 [Utils.toArray(message)],
4 [1, "HelloWorld"],
5 "1",
6 "anyone",
7 true
8);
9
10const { tx } = await wallet.createAction({
11 outputs: [{
12 satoshis: 1,
13 lockingScript: outputScript.toHex(),
14 outputDescription: "New HelloWorld Token"
15 }],
16 options: { acceptDelayedBroadcast: false, randomizeOutputs: false },
17 description: "Create a HelloWorld token"
18});
19
20if (!tx) throw new Error('Failed to create transaction')
21
22const broadcaster = new TopicBroadcaster(['tm_demo'], { // change to use the name of your topic manager
23 networkPreset: (await wallet.getNetwork()).network
24});
25
26await broadcaster.broadcast(Transaction.fromAtomicBEEF(tx));Note that these guides work primarily with these hello-tokens but in the future you will likely want to create your own unique tokens with fields that are specific to your application.
DemoTopicManager.ts
Here's a working implementation of a topic manager that would admit a hello-token:
1import { AdmittanceInstructions, TopicManager } from '@bsv/overlay'
2import docs from './DemoTopicDocs.md.js'
3import { PushDrop, Signature, Transaction, Utils } from '@bsv/sdk'
4
5export default class DemoTopicManager implements TopicManager {
6 /** Identify which outputs to admit into the topic */
7 async identifyAdmissibleOutputs(
8 beef: number[],
9 previousCoins: number[]
10 ): Promise<AdmittanceInstructions> {
11 const outputsToAdmit: number[] = [];
12
13 try {
14 console.log("DemoTopicManager invoked");
15 const parsedTx = Transaction.fromBEEF(beef);
16
17 if (!Array.isArray(parsedTx.outputs) || parsedTx.outputs.length === 0) {
18 throw new Error("Missing parameter: outputs");
19 }
20
21 for (const [index, output] of parsedTx.outputs.entries()) {
22 try {
23 // decode a PushDrop “hello token” payload
24 const result = PushDrop.decode(output.lockingScript);
25 const signature = result.fields.pop();
26
27 // 1) Exactly one field before signature
28 if (!result.fields || result.fields.length !== 1) continue;
29
30 const message = Utils.toUTF8(result.fields[0]);
31 // 2) Message must be ≥2 chars
32 if (message.length < 2) continue;
33
34 // 3) Verify signature
35 if (!result.lockingPublicKey || !signature) continue;
36
37 const data = result.fields.flat();
38 const valid = result.lockingPublicKey.verify(
39 data,
40 Signature.fromDER(signature)
41 );
42 if (!valid) throw new Error("Invalid signature");
43
44 outputsToAdmit.push(index);
45 } catch (err) {
46 console.error(`Output ${index} skipped:`, err);
47 }
48 }
49
50 if (outputsToAdmit.length === 0) {
51 throw new Error("No outputs admitted");
52 }
53
54 console.log(
55 `Admitted ${outputsToAdmit.length} ` +
56 `${outputsToAdmit.length === 1 ? "output" : "outputs"}!`
57 );
58 } catch (err) {
59 if (outputsToAdmit.length === 0 && !previousCoins.length) {
60 console.error("Error identifying admissible outputs:", err);
61 }
62 }
63
64 return { outputsToAdmit, coinsToRetain: [] };
65 }
66
67 /** Provide topic-specific documentation */
68 async getDocumentation(): Promise<string> {
69 return docs;
70 }
71
72 /** Provide metadata for the topic (e.g. schema version) */
73 async getMetaData(): Promise<{
74 name: string
75 shortDescription: string
76 iconURL?: string
77 version?: string
78 informationURL?: string
79 }> {
80 return {
81 name: 'Demo Topic Manager',
82 shortDescription: 'Admit outputs into a topic'
83 }
84 }
85}Configure LARS/CARS
Before you deploy using either of the deployment tools, add your Topic Manager to deployment-info.json so LARS and CARS know what to load. Replace "tm_demo" with a unique name for your own topic manager:
1{
2 "schema": "bsv-app",
3 "schemaVersion": "1.0",
4 "topicManagers": {
5 "tm_demo": "./backend/src/topic-managers/DemoTopicManager.ts"
6 },
7 …,
8}What's Next
Now that we have a functional Topic Manager, you can now move onto:
- Implement a Lookup Service to store and query the admitted outputs.
- Add proper documentation and rename the Topic Manager to better suit your project.
- Integrate with your React front-end.
