Quick Start
Dappetizer is a framework for building Tezos indexer apps using TypeScript (or JavaScript). Its main strength is its versatility - it can be used to rapidly develop a simple smart contract indexer or collect particular block data, but also to index data from the entire blockchain or compose multiple indexers.

Prerequisites

Before you start, you will need the following:
  • Node.js 14 or higher
  • SQLite CLI (to check the stored data). If you want to use Dappetizer with PostgreSQL, please also check our PostgreSQL guide to learn more.

Generating a new indexer

For this example, we will create a simple app that tracks registrations of new domain names in Tezos Domains. Start by creating a new folder:
mkdir dappetizer-quickstart
cd dappetizer-quickstart
Install Dappetizer itself:
npm install @tezos-dappetizer/cli
To initialize a new indexer for our registration contract, run Dappetizer with the init command:
npx dappetizer init --contractName=TezosDomains KT191reDVKrLxU9rjTSxg53wRqj6zh8pnHgr

Adding a database entity

An indexer module consists of:
  • Indexers which can act on new contract calls or whole blocks. An indexer is automatically created for you when you run dappetizer init
  • The database entities that will be used by the indexers to store data.
We will have one database entity that represents a successful domain registration:
TypeScript
src/entities.ts
import { commonDbColumns } from '@tezos-dappetizer/database';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Registration {
@PrimaryGeneratedColumn()
id!: number;
@Column()
domain!: string;
@Column()
owner!: string;
@Column()
duration!: number;
@Column(commonDbColumns.blockHash)
block!: string;
}
To add the Registration entity to the module, you will need to edit the index.ts file and add Registration to the dbEntities property:
TypeScript
src/index.ts
import { IndexerModuleUsingDb } from '@tezos-dappetizer/database';
import { createContractIndexerFromDecorators } from '@tezos-dappetizer/decorators';
import { Registration } from './entities';
import { TezosDomainsIndexer } from './tezos-domains-indexer';
export const indexerModule: IndexerModuleUsingDb = {
name: 'MyModule',
dbEntities: [
// Register your DB entity classes to TypeORM here:
Registration,
],
contractIndexers: [
// Create your contract indexers here:
createContractIndexerFromDecorators(new TezosDomainsIndexer()),
],
};

Implementing indexing logic

The smart contract we are going to index has a fairly simple structure. The buy entrypoint accepts a few parameters, like the label (i.e. the domain name without .tez), the new owner and the registration duration in days.
If you wish to learn more details about this particular contract, you can check:
Our generated indexer module already has an indexer for the contract in tezos-domains-indexer.ts. It contains an empty method called indexBuy where we can put the code that will take care of saving the parameters of each call to the database using TypeORM. We can get rid of the remaining indexing methods which we won't need for this example.
The final file will look like this:
TypeScript
src/tezos-domains-indexer.ts
import { DbContext } from '@tezos-dappetizer/database';
import { contractFilter, indexEntrypoint } from '@tezos-dappetizer/decorators';
import { TransactionIndexingContext } from '@tezos-dappetizer/indexer';
import { Registration } from './entities';
import { TezosDomainsBuyParameter } from './tezos-domains-indexer-interfaces.generated';
@contractFilter({ name: 'TezosDomains' })
export class TezosDomainsIndexer {
@indexEntrypoint('buy')
async indexBuy(
parameter: TezosDomainsBuyParameter,
dbContext: DbContext,
indexingContext: TransactionIndexingContext,
): Promise<void> {
const registration = {
domain: Buffer.from(parameter.label, 'hex') + '.tez',
owner: parameter.owner,
duration: parameter.duration.toNumber(),
block: indexingContext.block.hash,
};
await dbContext.transaction.insert(Registration, registration);
}
}

Configuring

The configuration file (called dappetizer.config.ts) is already created for you. We will just need to change the starting block level to 1468130 (the first level where the indexed contract appears) so we don't needlessly go over blocks that don't contain anything relevant:
dappetizer.config.ts
import { DappetizerConfigUsingDb } from '@tezos-dappetizer/database';
export const config: DappetizerConfigUsingDb = {
modules: [{
id: '.', // This project is the indexer module itself.
}],
networks: {
mainnet: {
indexing: {
contracts: ['KT191reDVKrLxU9rjTSxg53wRqj6zh8pnHgr'],
fromBlockLevel: 1468130,
},
tezosNode: {
url: 'https://prod.tcinfra.net/rpc/mainnet/',
},
},
},
database: {
type: 'sqlite',
database: 'database.sqlite',
},
};
export default config;
As you can see, this configuration stores data to a local SQLite database file database.sqlite. Check our guide for using PostgreSQL if you want to start using Dappetizer in a production environment.

Compiling TypeScript & running

All we need to do now is compiling the source code...
npm run build
... and running the indexer app.
npx dappetizer start
If everything works, you should see a console output showing the indexing progress with each individual block. For now, let's kill the process by Ctrl+C after letting it run for a short while.

Checking the generated data

To check the generated data, we can use the sqlite CLI:
sqlite3 database.sqlite
To select what registrations have been saved by the indexer, we will run a short SELECT query:
sqlite> select * from registration limit 10;
And get the following as a result:
1|agile-ventures.tez|tz1ZfcTEym7AHcDxtJGreZMYmSeq3HGDftbt|1095|BM5aLMjQCLZ1JpZwS3msppVYsxqpvFMkfmg13itXZeReBpxWBjn
2|stablecoin.tez|tz1d473kD8ctsHnJVtmoLAGW1wBZzYzMuU7n|365|BLyN1RRFk3DTv9f1quGWTEwMgft9fsSNLRFNacEURWoQ8jD5zYf
3|bigboy.tez|tz1RyhUycXcivQ3jzF7ZpFZNcoLcgmXqcrSz|365|BLEd6V5YcKRD9mEee38Q7L2Hk6GPSU1q9MGc89FBQ1P5yMdBKSF
4|unipolsai.tez|tz1Nz2nsUkHevijqoMxEBCk5fQqpVGg1WzEP|365|BLmUXjD8potMZCYXjEGFBhSHHtdgM2aug4GL5DZefvzdXKDTisH
5|platin.tez|tz1iYRo8biqmXame67aega6rkJVGjq9tX4fV|365|BLmUXjD8potMZCYXjEGFBhSHHtdgM2aug4GL5DZefvzdXKDTisH
6|gonzalez.tez|tz1apFmasAsLtMPBgmmxCLLnApBbKbhiUDtY|3650|BL1Psn7Rqa39JGbLUAmdQ1DAGyZuvj7mZxeksp8UifcATUJfW7k
7|blank.tez|tz1f4zxfTtjTKkrzzXNtQU73zGBcasW3xEBS|365|BMTDSyhRTwEt5ta3WbY95s8vgyVAtEPYa4X3nWd9RpiWUDzkPEC
8|credit.tez|tz1QknxZX2qrWQrHrzp8pARxDrqUSsnA9A9g|365|BKsbtmF9ZRKPvNysDWFc9wteA7Z44jwGdbNEsHx6dNfXLhfREi5
9|intesasanpaolo.tez|tz1Nz2nsUkHevijqoMxEBCk5fQqpVGg1WzEP|365|BKsbtmF9ZRKPvNysDWFc9wteA7Z44jwGdbNEsHx6dNfXLhfREi5
10|mogster.tez|tz1Webr8gQjmogyUS1FoWhAEqqn8nBdT3M2V|365|BKtqyjhjfnaN14QKBdAZMnUabXxDZMrhk4pvr2aHQcZg1Cuj1FZ
As you can see, the indexer is saving successful registrations. If you run the indexer again it will pick up from the last indexed block and continue on until it reaches the latest block. After that, it will wait for each new block and index it as well.