{"id":18263715,"url":"https://github.com/yearn/kong","last_synced_at":"2025-08-08T14:24:20.714Z","repository":{"id":188875839,"uuid":"678974541","full_name":"yearn/kong","owner":"yearn","description":"Real-time/historical EVM indexer x Analytics","archived":false,"fork":false,"pushed_at":"2024-10-26T04:41:25.000Z","size":3341,"stargazers_count":10,"open_issues_count":1,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-26T12:30:36.336Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://kong.yearn.farm","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yearn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-15T20:29:18.000Z","updated_at":"2024-10-26T04:31:56.000Z","dependencies_parsed_at":null,"dependency_job_id":"dae78956-7b33-4fdc-837c-e441590a747d","html_url":"https://github.com/yearn/kong","commit_stats":null,"previous_names":["murderteeth/kong","yearn/kong"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yearn%2Fkong","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yearn%2Fkong/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yearn%2Fkong/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yearn%2Fkong/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yearn","download_url":"https://codeload.github.com/yearn/kong/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247246337,"owners_count":20907779,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-05T11:12:24.782Z","updated_at":"2025-08-08T14:24:20.696Z","avatar_url":"https://github.com/yearn.png","language":"TypeScript","funding_links":[],"categories":["Indexers"],"sub_categories":["Market Intelligence \u0026 Analysis"],"readme":"# Kong\n### Real-time/Historical EVM Indexer x Analytics\n\n![figure](packages/web/public/figure.png)\n\nKong is an integrated set of services and tools that make it easy to index EVM logs and state, enrich your data with custom hooks, query your data over graphql. Kong is designed to be cheap, reliable, easy to maintain, and simplify the process of updating your index.\n\nKong comes configured with an index over Yearn Finance's v2 and v3 vault ecosystems.\n\n\n## Requirements\n- node, yarn, bun, make, tmux, docker, docker compose, postgresql-client\n- ♥ for zoo animals\n\n\n## Quick start\n```bash\nbun install\ncp .env.example .env\n# configure .env\nmake dev\n```\n`dash` - http://localhost:3001\n\n`graphql explorer ` - http://localhost:3001/api/gql\n\n\n## Yearn Vaults Index\nKong's Yearn index covers the v3 and v2 vault ecosystems:\n\n- Regular contract snapshots of each registry, vault, strategy, trade handler, accountant, and debt allocator.\n\n- Full event history for each of the above (*with limited history on transfers, deposits, withdraws, and approves).\n\n- Snapshot hooks for computing vault-strategies relationships, debts, fees, and rewards.\n\n- Snapshot hooks for integrating offchain risk and meta data.\n\n- Event hooks for tracking new vaults and strategies, computing spot harvest aprs, and pricing transfers.\n\n- Timeseries hooks for computing APY and TVL.\n\n## Index with Kong\n\n### abis.yaml x ingest/abis\nKong implements a convention-based relationship between `abis.yaml` and the special repo path `packages/ingest/abis`. Add a contract to the index like this:\n\n- Make a path under `ingest/abis`, eg `ingest/abis/yearn/3/registry`\n\n- Add the contract's abi to the project as abi.ts, eg `ingest/abis/yearn/3/registry/abi.ts`\n\n- Update `config/abis.yaml` with the contract's abi path and sources\n\n- Sources can be static addresses or a domain type called a \"thing\".\n\n- \"Things\" in kong are analogous to \"entities\" in conventional etl design.\n\n- Use hooks to create things.\n\n- Use things as abi sources for more indexing.\n\n- Yearn Example. Registry event hooks create vault things. Vault things are used as the source for indexing vault abis. This triggers vault event hooks which create strategy things. Strategy things are then used as the source for indexing strategy abis. And so on.\n\n- Hooks have a convention-based implementation as well. They are co-located with abis, the hook type indicated by the use of `snapshot`, `event`, or `timeseries` in the path name. The hook itself is always named `hook.ts`. Kong's hook resolver supports \"hoisting\" so you can, for example, write one transfer event hook to price transfer events across different contracts.\n\n- `abis.yaml` supports several options for defining and fine tuning the index.\n\nFor example,\n```yaml\n- abiPath: 'yearn/3/registry'\n  skip: true\n  sources: [\n    { chainId: 137, address: '0xfF5e3A7C4cBfA9Dd361385c24C3a0A4eE63CE500', inceptBlock: 49100596 }\n  ]\n\n- abiPath: 'yearn/3/registry2'\n  sources: [\n    { only: true, chainId: 1, address: '0xff31A1B020c868F6eA3f61Eb953344920EeCA3af', inceptBlock: 19072527 },\n    { chainId: 137, address: '0xff31A1B020c868F6eA3f61Eb953344920EeCA3af', inceptBlock: 52488140 },\n    { chainId: 42161, address: '0xff31A1B020c868F6eA3f61Eb953344920EeCA3af', inceptBlock: 171850013 },\n  ]\n\n- abiPath: 'yearn/3/vault'\n  things: {\n    label: 'vault',\n    filter: [{ field: 'apiVersion', op: '\u003e=', value: '3.0.0' }]\n  }\n```\nThree abis are configured, two registries and a vault. The first registry is being skipped by setting the optional `skip` to true. The second registry specifies three static addresses as sources, but uses the optional `only` flag to narrow the sources to one.\n\nThe vault abi sources addresses from things labeled 'vault' and filters them by apiVersion. For this to work, a registry event hook would loads new vaults as things. For example,\n```typescript\nexport const topics = [\n  `event NewVault(address indexed token, uint256 indexed vaultId, uint256 vaultType, address vault, string apiVersion)`\n].map(e =\u003e toEventSelector(e))\n\nexport default async function process(chainId: number, address: `0x${string}`, data: any) {\n\n  // processing and extract code\n\n  await mq.add(mq.job.load.thing, ThingSchema.parse({\n    chainId,\n    address: vault,\n    label: 'vault',\n    defaults: {\n      apiVersion,\n      registry,\n      asset,\n      decimals,\n      inceptBlock,\n      inceptTime\n    }\n  }))\n}\n```\n\n\n### Run an index\nFrom the command line run `make dev`. After the indexer boots you will see Kong's terminal UI running in the bottom left tmux pane. Select `ingest` then `fanout abis`. This tells the indexer to query abis.yaml and queue fanout jobs. Fanout jobs detect missing index data and queue extract jobs to fill it in. Extract jobs call external apis, execute hooks, then queue results in the load queue. Load queue jobs store results in the database.\n\nTo initialize an index you typically run `fanout abis` several times. In the case of the Yearn index, the first run detects vaults by extracting registry logs. The second run checks registries again, but also extracts vault logs and gets strategies. And so on.\n\nOnce an index is initialized, `fanout abis` can be run on a schedule, eg every 15 minutes.\n\n### Replay an index\nMade a mistake in one of your hooks? Patch your code and replay, no need to re-extract. From the command line run `make dev`. From the Kong's terminal UI select `ingest` then `fanout replays`.\n\n### Postgres schema\n`evmlog` - raw evm logs + event hook data\n\n`evmlog_strides` - state of event block coverage\n\n`snapshot` - latest snapshot of each contract + snapshot hook data\n\n`thing` - domain object definitions\n\n`output` - timeseries hook data\n\n`price` - price data\n\n`latest_block` - latest block numbers\n\n`monitor` - system stats\n\n\n## Cheats\n\n### make\n`make dev` - run eveything in dev\n\n`make test` - test everything\n\n`make down` - 'make' sure your dev environment is shutdown lol\n\n### testing\n`make test` - test everything\n\n`bun --filter \u003cworkspace\u003e test` - test individual workspaces\n\n```bash\nbun --filter ingest test\n```\n\n### tmux\n`quit` - `ctrl+b`, `:` then `kill-session` (your dev environment will also shutdown gracefully)\n\n`pane navigation` - `ctrl+b` then `arrow keys`\n\n`zoom\\unzoom pane` - `ctrl+b` then `z`\n\n`scroll` - `ctrl+b` then `[` then `arrow keys` or `page up\\down keys` then `q` to quit scroll mode\n\n### database migrations\n**create** - `bun --filter db migrate create \u003cmigration-name\u003e --sql-file`\n\n**up** - `bun --filter db migrate up [name|-c count|...]`\n\n**down** - `bun --filter db migrate down [-c count|...]`\n\n### timescale\n**hypertable size** - `SELECT hypertable_size('table name');`\n\n\n## Monorepo layout\nKong resources are managed monorepo style using a workspace.\n\n`.env` - core config\n\n`config/abis.yaml` - indexer config\n\n`config/abis.local.yaml` - local indexer override (optional)\n\n`packages/db` - postgres migrations (db-migrate)\n\n`packages/ingest` - core indexer logic\n\n`packages/ingest/abis` - custom indexer logic\n\n`packages/lib` - shared code\n\n`packages/terminal` - cli app for interacting with kong at runtime\n\n`packages/web` - gqphql api and runtime dash\n\n\n## Architecture\n\n`Ingest` - Ingest is a nodejs service that orchestrates and excutes all the various indexing activities. It's designed to scale horizontally, no need for beefy infra.\n\n`Message Queue` - Indexing activities are coordinated using BullMQ message queues on Redis. This provides a simple, observable concurrency plane, decouples moving parts, and paves the way to scaling and non-TS language integration.\n\n`Event Source` - Kong stores EVM logs and contract snapshots in postgres without transform. Optional hooks perform transform operations _on top of_ of event and snapshot data. In this way, the data model supports enhanced debugging, index replay, and decouples domain modeling from the underlying postgres schema.\n\n`Hooks` - Hooks are custom logic used to enrich the dataset. Hooks come in three flavors: Snapshot, Event, and Timeseries. Hook execution is replayable.\n\n`NextJS\\Graphql` - Raw x enriched data are made available over graphql running in a serverless nextjs function call.\n\n`Testing` - Kong uses mocha\\chai for testing. Tests are co-located with the code they test.\n\n`yaml config` - Kong's indexing set is defined by yaml file.\n\n`.env config` - RPC urls and other core settings are defined in .env.\n\n![image](https://github.com/murderteeth/kong/assets/89237203/c9c70016-9de4-418f-a0bb-06b9fd9da549)\n\n\n## Schema\n\n### thing\nThing records define domain objects tracked by the indexer.\n\n| column_name | data_type | is_nullable | column_default |\n|-------------|-----------|-------------|----------------|\n| chain_id    | integer   | NO          |                |\n| address     | text      | NO          |                |\n| label       | text      | NO          |                |\n| defaults    | jsonb     | YES         |                |\n\nExamples are vaults and strategies, ie `select * from thing where label IN ('vault', 'strategy');`.\n\nVarious invariant properties of a domain object are stored in the `defaults` json field (eg vault apiVersion).\n\n### snapshot\nRecurring snapshots of the various domain objects tracked by the indexer are stored here.\n\n| column_name  | data_type                 | is_nullable | column_default |\n|--------------|---------------------------|-------------|----------------|\n| chain_id     | integer                   | NO          |                |\n| address      | text                      | NO          |                |\n| snapshot     | jsonb                     | YES         |                |\n| hook         | jsonb                     | YES         |                |\n| block_number | bigint                    | NO          |                |\n| block_time   | timestamp with time zone  | YES         |                |\n\nThe `snapshot` json field contains a key value collection of all contract fields. Query like this,\n```\nselect snapshot #\u003e\u003e '{totalSupply}' from snapshot where chain_id = 1 and address = '0x028eC7330ff87667b6dfb0D94b954c820195336c';\n```\n\nThe `hook` json field contains a key value collection of all hook fields. Query like this,\n```\nselect hook #\u003e\u003e '{tvl}' from snapshot where chain_id = 1 and address = '0x028eC7330ff87667b6dfb0D94b954c820195336c';\nselect hook #\u003e\u003e '{tvl, close}' from snapshot where chain_id = 1 and address = '0x028eC7330ff87667b6dfb0D94b954c820195336c';\nselect hook #\u003e\u003e '{apy}' from snapshot where chain_id = 1 and address = '0x028eC7330ff87667b6dfb0D94b954c820195336c';\nselect hook #\u003e\u003e '{apy, close}' from snapshot where chain_id = 1 and address = '0x028eC7330ff87667b6dfb0D94b954c820195336c';\n```\n\n### evmlog\nAll logs for all domain objects are stored in the evmlog table with limits on Transfers, Approves, Deposits, and Withdraws.\n\n| column_name       | data_type                 | is_nullable | column_default |\n|-------------------|---------------------------|-------------|----------------|\n| chain_id          | integer                   | NO          |                |\n| address           | text                      | NO          |                |\n| event_name        | text                      | NO          |                |\n| signature         | text                      | NO          |                |\n| topics            | ARRAY                     | NO          |                |\n| args              | jsonb                     | YES         |                |\n| hook              | jsonb                     | YES         |                |\n| block_number      | bigint                    | NO          |                |\n| block_time        | timestamp with time zone  | YES         |                |\n| log_index         | integer                   | NO          |                |\n| transaction_hash  | text                      | NO          |                |\n| transaction_index | integer                   | NO          |                |\n\nThe `args` json field contains a key value collection of a log's args.\n```\nselect\n  args #\u003e\u003e '{gain}',\n  args #\u003e\u003e '{loss}'\nfrom evmlog\nwhere\n  chain_id = 1\n  and address = '0x028eC7330ff87667b6dfb0D94b954c820195336c'\n  and event_name = 'StrategyReported'\norder by block_number\ndesc limit 1;\n```\n\nThe `hook` json field contains a key value collection of the log's hook fields. Query like this,\n```\nselect\n  hook #\u003e\u003e '{gainUsd}',\n  hook #\u003e\u003e '{lossUsd}'\nfrom evmlog\nwhere\n  chain_id = 1\n  and address = '0x028eC7330ff87667b6dfb0D94b954c820195336c'\n  and event_name = 'StrategyReported'\norder by block_number\ndesc limit 1;\n```\n\n\n### evmlog_strides\nThe strides table records which blocks have been queried for logs for all of the indexer's domain objects.\n\n| column_name | data_type | is_nullable | column_default |\n|-------------|-----------|-------------|----------------|\n| chain_id    | integer   | NO          |                |\n| address     | text      | NO          |                |\n| strides     | text      | NO          |                |\n\nThe `strides` field is a json formatted string representing ranges of blocks.\n\nA strides array that looks like `[{\"from\":\"19419991\",\"to\":\"19813291\"}]` tells the indexer everything between 19419991 and 19813291 has been indexed.\n\nA strides array that looks like `[{\"from\":\"19419991\",\"to\":\"19800000\"}, {\"from\":\"19800100\",\"to\":\"19813291\"}]` tells the indexer there's a gap between 19800000 and 19800100 that needs to be indexed.\n\n\n\n## Motivation\nRobust indexing is tough. Some observations,\n\n- Indexers spend most of their time waiting for external things to respond. But lots of threads waiting on stuff is wasteful. Kong emphasizes concurrency over threading.\n\n- Deterministic indexing is expensive and cumbersome. So kong is designed to handle events out of order. This improves historical indexing times and enables the replay of arbitrary block ranges on any domain object.\n\n- It's hard to separate domain from indexer logic, but crucial for testing and growth. Kong uses indexer hooks to separate these concerns.\n\n\n## Greatfully Informed by and borrowed from\nKong is the result of hours spent reviewing and contributing on other indexing projects. Kong chest pounds with pride atop these shoulders: [ydaemon](https://github.com/yearn/ydaemon), [yexporter](https://github.com/yearn/yearn-exporter), (subsquid)[https://github.com/subsquid/squid-sdk], (The Graph)[https://github.com/graphprotocol], various projects by (BobTheBuidler)[https://github.com/BobTheBuidler].\n\n\n## Dev Notes\n\n### how to baseline a production db in-flight\nWe started using db-migrate after the db was already in production. But db-migrate doesn't provide support retro-fitting a production database with migrations. So here's what we did:\n\n- create a baseline migration that is only applied via `migrate up` in dev, `20231222031425-baseline`\n\n- in the production db, manually create the migrations table with\n```sql\nCREATE TABLE migrations (\n  id SERIAL PRIMARY KEY,\n  name VARCHAR(255) NOT NULL,\n  run_on TIMESTAMP NOT NULL\n);\n```\n\n- in the production db, manually insert a row into the migrations table for the baseline migration\n```sql\nINSERT INTO migrations (name, run_on) VALUES ('/20231222031425-baseline', CURRENT_TIMESTAMP);\n```\n\nThis way production thinks it was migrated starting from the baseline and handles future migrations normally.\n\n### postgres x timescale\nLocally you can run postgres and timescale from a docker image, eg using `docker compose up postgres`. connect to your local with\n```\nPGPASSWORD=password psql --host=localhost \\\n  --port=5432 \\\n  --username=user \\\n  --dbname=user\n```\n\nTimescale has to be manually installed on top of postgres in the render environment:\n- assuming a postgres instance is already running on render\n- in the render dashboard, find the Access Control panel for the pg instance, add your IP\n- connect to the instance using psql from your terminal\n- `CREATE EXTENSION IF NOT EXISTS timescaledb;` to install the timescale extension\n- `\\dx` to verify the install\n- logout, remove your ip from the Access Control panel\n\n\n### viem, https://viem.sh\nKong uses viem to interface with rpcs. Because viem is new and changing often, all of kong's package.json files are hardcoded with the same viem version. To upgrade viem, manually update all package/package.json files then run `bun install` from root.\n\n### bun\nBun has a built in flag on their cli which collapses the texts called [ellide](https://bun.sh/blog/bun-v1.1.43#elide-lines-n-controls-filter-output-line-length), you can use `--elide-lines 0` on each cli command, but it has a lot of bugs.\nThere are some tickets related to this, that's why we preferred to still have yarn for our command running on `Makefile`\n\n\n## Production\n\n| component | host |\n|-------------|-----------|\n| Ingest    | [render.com](render.com)   |\n| Redis    | [render.com](render.com)   |\n| Postgres    | [neon.tech](neon.tech)   |\n| GraphQL    | [vercel.com](vercel.com)   |\n| Cache    | [turso.com](turso.com)   |\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyearn%2Fkong","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyearn%2Fkong","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyearn%2Fkong/lists"}