Quickstart
This page will guide you through the steps to get your first selective indexer up and running in a few minutes without getting too deep into the details.
Let's create an indexer for tzBTC FA1.2 token contract. Our goal is to save all token transfers to the database, then calculate some statistics of its' holders' activity.

Install framework

A Linux environment with Python 3.8+ installed is required to use DipDup.
1
poetry init -n
2
poetry add dipdup
3
poetry shell
Copied!

pip

1
python -m venv .venv
2
source .venv/bin/activate
3
pip install dipdup
Copied!
See Installation for details.

Write a configuration file

DipDup configuration is stored in YAML files of a specific format. Create a new file named dipdup.yml in your current working directory with the following content:
1
spec_version: 1.2
2
package: demo_tzbtc
3
4
database:
5
kind: sqlite
6
path: demo_tzbtc.sqlite3
7
8
contracts:
9
tzbtc_mainnet:
10
address: KT1PWx2mnDueood7fEmfbBDKx1D9BAnnXitn
11
typename: tzbtc
12
13
datasources:
14
tzkt_mainnet:
15
kind: tzkt
16
url: https://api.tzkt.io
17
18
indexes:
19
tzbtc_holders_mainnet:
20
kind: operation
21
datasource: tzkt_mainnet
22
contracts:
23
- tzbtc_mainnet
24
handlers:
25
- callback: on_transfer
26
pattern:
27
- destination: tzbtc_mainnet
28
entrypoint: transfer
29
- callback: on_mint
30
pattern:
31
- destination: tzbtc_mainnet
32
entrypoint: mint
Copied!
See Config reference for details.

Initialize project tree

Now it's time to generate typeclasses and callback stubs. Run the following command:
1
dipdup init
Copied!
DipDup will create a Python package demo_tzbtc having the following structure:
1
demo_tzbtc
2
├── graphql
3
├── handlers
4
│ ├── __init__.py
5
│ ├── on_mint.py
6
│ └── on_transfer.py
7
├── hooks
8
│ ├── __init__.py
9
│ ├── on_reindex.py
10
│ ├── on_restart.py
11
│ ├── on_rollback.py
12
│ └── on_synchronized.py
13
├── __init__.py
14
├── models.py
15
├── sql
16
│ ├── on_reindex
17
│ ├── on_restart
18
│ ├── on_rollback
19
│ └── on_synchronized
20
└── types
21
├── __init__.py
22
└── tzbtc
23
├── __init__.py
24
├── parameter
25
│ ├── __init__.py
26
│ ├── mint.py
27
│ └── transfer.py
28
└── storage.py
Copied!
That's a lot of files and directories! But don't worry, in this guide, we will need only models.py and handlers modules.
See Project structure for details.

Define data models

Our schema will consist of a single model Holder having several fields:
  • address — account address
  • balance — in tzBTC
  • volume — total transfer/mint amount bypassed
  • tx_count — number of transfers/mints
  • last_seen — time of the last transfer/mint
Put the following content in models.py file:
1
from tortoise import Model, fields
2
3
4
class Holder(Model):
5
address = fields.CharField(max_length=36, pk=True)
6
balance = fields.DecimalField(decimal_places=8, max_digits=20, default=0)
7
volume = fields.DecimalField(decimal_places=8, max_digits=20, default=0)
8
tx_count = fields.BigIntField(default=0)
9
last_seen = fields.DatetimeField(null=True)
Copied!
See Defining models for details.

Implement handlers

Everything's ready to implement an actual indexer logic.
Our task is to index all the balance updates, so we'll start with a helper method to handle them. Create a file named on_balance_update.py in handlers package with the following content:
1
from decimal import Decimal
2
import demo_tzbtc.models as models
3
4
5
async def on_balance_update(
6
address: str,
7
balance_update: Decimal,
8
timestamp: str
9
) -> None:
10
holder, _ = await models.Holder.get_or_create(address=address)
11
holder.balance += balance_update
12
holder.tx_count += 1
13
holder.last_seen = timestamp
14
assert holder.balance >= 0, address
15
await holder.save()
Copied!
That was pretty straightforward 👍🏻
Three methods of tzBTC contract can alter token balances — transfer, mint and burn. The last one is omitted in this tutorial for simplicity. Edit corresponding handlers to call on_balance_update method with data from matched operations:
on_transfer.py
1
from typing import Optional
2
from decimal import Decimal
3
4
from dipdup.models import Transaction
5
from dipdup.context import HandlerContext
6
7
import demo_tzbtc.models as models
8
9
from demo_tzbtc.types.tzbtc.parameter.transfer import TransferParameter
10
from demo_tzbtc.types.tzbtc.storage import TzbtcStorage
11
from demo_tzbtc.handlers.on_balance_update import on_balance_update
12
13
14
async def on_transfer(
15
ctx: HandlerContext,
16
transfer: Transaction[TransferParameter, TzbtcStorage],
17
) -> None:
18
if transfer.parameter.from_ == transfer.parameter.to:
19
# NOTE: Internal tzBTC transaction
20
return
21
22
amount = Decimal(transfer.parameter.value) / (10 ** 8)
23
await on_balance_update(
24
address=transfer.parameter.from_,
25
balance_update=-amount,
26
timestamp=transfer.data.timestamp,
27
)
28
await on_balance_update(address=transfer.parameter.to,
29
balance_update=amount,
30
timestamp=transfer.data.timestamp)
Copied!
on_mint.py
1
from typing import Optional
2
from decimal import Decimal
3
4
from dipdup.models import Transaction
5
from dipdup.context import HandlerContext
6
7
import demo_tzbtc.models as models
8
9
from demo_tzbtc.types.tzbtc.parameter.mint import MintParameter
10
from demo_tzbtc.types.tzbtc.storage import TzbtcStorage
11
from demo_tzbtc.handlers.on_balance_update import on_balance_update
12
13
14
async def on_mint(
15
ctx: HandlerContext,
16
mint: Transaction[MintParameter, TzbtcStorage],
17
) -> None:
18
amount = Decimal(mint.parameter.value) / (10 ** 8)
19
await on_balance_update(
20
address=mint.parameter.to,
21
balance_update=amount,
22
timestamp=mint.data.timestamp
23
)
Copied!
And that's all! We can run the indexer now.
See Implementing handlers for details.

Run your indexer

1
dipdup run
Copied!
DipDup will fetch all the historical data then switch to realtime updates. Your application data has been successfully indexed!
See Command-line reference for details.
Last modified 10d ago