{"id":30830204,"url":"https://github.com/psyprotocol/txindex","last_synced_at":"2025-10-04T06:19:32.270Z","repository":{"id":254271853,"uuid":"845557294","full_name":"PsyProtocol/txindex","owner":"PsyProtocol","description":"A reorg-tolerant, modular framework for building ordinals/transaction indexers on Bitcoin and Dogecoin","archived":false,"fork":false,"pushed_at":"2024-08-23T09:10:05.000Z","size":117,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-06T15:42:01.102Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PsyProtocol.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,"zenodo":null}},"created_at":"2024-08-21T13:31:50.000Z","updated_at":"2025-08-05T15:57:43.000Z","dependencies_parsed_at":"2025-04-20T15:16:43.326Z","dependency_job_id":null,"html_url":"https://github.com/PsyProtocol/txindex","commit_stats":null,"previous_names":["qedprotocol/txindex","psyprotocol/txindex"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/PsyProtocol/txindex","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PsyProtocol%2Ftxindex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PsyProtocol%2Ftxindex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PsyProtocol%2Ftxindex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PsyProtocol%2Ftxindex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PsyProtocol","download_url":"https://codeload.github.com/PsyProtocol/txindex/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PsyProtocol%2Ftxindex/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278273815,"owners_count":25959801,"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","status":"online","status_checked_at":"2025-10-04T02:00:05.491Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":"2025-09-06T15:36:02.839Z","updated_at":"2025-10-04T06:19:32.264Z","avatar_url":"https://github.com/PsyProtocol.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# txindex\nA modular, reorg/fork-tolerant framework for writing composable transaction/ordinal indexers on Bitcoin and Dogecoin.\n\n## WARNING\n***This software is still in development, please DO NOT use in production yet.***\n\n## Motivation\nPrevious ordinal/transaction indexers have been plagued issues when chain forks occur.\ntxindex seeks to solve this by abstracting the data flow so it is guaranteed to be fork-safe, and to allow roll backs to any block.\n\n**With txindex, developers only have to worry about implementing their indexer's core logic!**\n\nIn addition, we want to be able to consume indexers/APIs as modular rust packages so one server can support multiple indexers at once, so txindex allows you to compose/add new indexers whenever you like.\n\n## Usage\n#### 1. Implement the database tables you need:\n```rust \nuse kvq::traits::KVQSerializable;\nuse serde::{Deserialize, Serialize};\nuse txindex_common::db::table::core::{KVQTable, TABLE_TYPE_FUZZY_BLOCK_INDEX};\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, PartialOrd)]\npub struct SimpleTxCounterDB {\n    pub spend_count: u64,\n}\nimpl KVQSerializable for SimpleTxCounterDB {\n    fn to_bytes(\u0026self) -\u003e anyhow::Result\u003cVec\u003cu8\u003e\u003e {\n        bincode::serialize(\u0026self).map_err(|err| anyhow::anyhow!(\"Error serializing SimpleTxCounterDB: {:?}\", err))\n    }\n\n    fn from_bytes(bytes: \u0026[u8]) -\u003e anyhow::Result\u003cSelf\u003e {\n        bincode::deserialize(bytes).map_err(|err| anyhow::anyhow!(\"Error deserializing SimpleTxCounterDB: {:?}\", err))\n    }\n}\n\nimpl KVQTable for SimpleTxCounterDB {\n    type Key = [u8; 32];\n    type Value = Self;\n    \n    const TABLE_NAME: \u0026'static str = \"simple_tx_counter\";\n    \n    const TABLE_ID: u32 = 0x100;\n    \n    const TABLE_TYPE: u8 = TABLE_TYPE_FUZZY_BLOCK_INDEX;\n}\n```\n\n#### 2. Implement one or more indexer/worker(s)\n```rust\nuse std::{marker::PhantomData, sync::Arc};\n\nuse bitcoin::{Block, Transaction};\nuse kvq::{cache::KVQBinaryStoreCached, traits::KVQBinaryStoreImmutable};\nuse txindex_common::{\n    db::{chain::TxIndexChainAPI, indexed_block_db::IndexedBlockDBStore},\n    worker::traits::TxIndexWorker,\n};\nuse txindex_server::daemon::schema::compute_script_hash;\n\nuse crate::tables::tx_counter::SimpleTxCounterDB;\n\npub struct TxCounterWorker\u003cKVQ: KVQBinaryStoreImmutable, T: TxIndexChainAPI\u003e {\n    pub _kvq: PhantomData\u003cKVQ\u003e,\n    pub _chain: PhantomData\u003cT\u003e,\n}\nimpl\u003cKVQ: KVQBinaryStoreImmutable, T: TxIndexChainAPI\u003e TxCounterWorker\u003cKVQ, T\u003e {\n    fn process_tx(\n        db: \u0026mut IndexedBlockDBStore\u003cKVQBinaryStoreCached\u003cKVQ\u003e\u003e,\n        _q: Arc\u003cT\u003e,\n        _block_number: u64,\n        _block: \u0026Block,\n        tx: \u0026Transaction,\n    ) -\u003e anyhow::Result\u003c()\u003e {\n        for input in tx.output.iter() {\n            let hash = compute_script_hash(\u0026input.script_pubkey);\n            let ctr = db\n                .get::\u003cSimpleTxCounterDB\u003e(\u0026hash)?\n                .or(Some(SimpleTxCounterDB { spend_count: 0 }))\n                .unwrap();\n            db.put::\u003cSimpleTxCounterDB\u003e(\n                \u0026hash,\n                \u0026SimpleTxCounterDB {\n                    spend_count: ctr.spend_count + 1,\n                },\n            )?;\n        }\n        Ok(())\n    }\n}\nimpl\u003cKVQ: KVQBinaryStoreImmutable, T: TxIndexChainAPI\u003e TxIndexWorker\u003cKVQ, T\u003e\n    for TxCounterWorker\u003cKVQ, T\u003e\n{\n    fn process_block(\n        db: \u0026mut IndexedBlockDBStore\u003cKVQBinaryStoreCached\u003cKVQ\u003e\u003e,\n        q: Arc\u003cT\u003e,\n        block_number: u64,\n        block: \u0026Block,\n    ) -\u003e anyhow::Result\u003c()\u003e {\n        for tx in block.txdata.iter() {\n            Self::process_tx(db, q.clone(), block_number, block, tx)?;\n        }\n\n        Ok(())\n    }\n}\n```\n\n#### 3. Implement any REST APIs you want to expose (with prefix /indexer/)\n```rust\nuse std::sync::Arc;\nuse txindex_common::{\n    api::{response::TxIndexAPIResponse, traits::TxIndexAPIHandler},\n    chain::Network,\n    db::{\n        chain::TxIndexChainAPI, indexed_block_db::IndexedBlockDBStoreReader, kvstore::BaseKVQStore,\n    },\n};\nuse txindex_server::api::chain::to_scripthash;\nuse crate::tables::tx_counter::SimpleTxCounterDB;\n\npub struct TxCounterAPI\u003cT: TxIndexChainAPI\u003e {\n    _chain: std::marker::PhantomData\u003cT\u003e,\n}\nimpl\u003cT: TxIndexChainAPI\u003e TxCounterAPI\u003cT\u003e {\n    fn handle_get_request_json(\n        network: Network,\n        uri: String,\n        _chain: Arc\u003cT\u003e,\n        indexer_db: IndexedBlockDBStoreReader\u003cBaseKVQStore\u003e,\n    ) -\u003e anyhow::Result\u003cVec\u003cu8\u003e\u003e {\n        let address_str = uri.split('/').last().unwrap();\n        let sh = to_scripthash(\"address\", address_str, network)\n            .map_err(|_| anyhow::anyhow!(\"invalid address\"))?;\n\n        let db = indexer_db\n            .get::\u003cSimpleTxCounterDB\u003e(\u0026sh)?\n            .or(Some(SimpleTxCounterDB { spend_count: 0 }))\n            .unwrap();\n        Ok(serde_json::to_vec(\u0026db)?)\n    }\n}\nimpl\u003cT: TxIndexChainAPI\u003e TxIndexAPIHandler\u003cT\u003e for TxCounterAPI\u003cT\u003e {\n    const PATH_SLUG: \u0026'static str = \"/indexer/tx_counter/\";\n\n    fn handle_get_request(\n        network: Network,\n        uri: String,\n        chain: std::sync::Arc\u003cT\u003e,\n        indexer_db: IndexedBlockDBStoreReader\u003cBaseKVQStore\u003e,\n    ) -\u003e TxIndexAPIResponse {\n        Self::json_response(Self::handle_get_request_json(\n            network, uri, chain, indexer_db,\n        ))\n    }\n}\n```\n\n\n### 4. Setup your root indexer and API handler\nRoot Indexer/Worker:\n```rust\nuse std::sync::Arc;\n\nuse bitcoin::Block;\nuse kvq::cache::KVQBinaryStoreCached;\nuse tx_counter::TxCounterWorker;\nuse txindex_common::{\n    db::{indexed_block_db::IndexedBlockDBStore, kvstore::BaseKVQStore},\n    worker::traits::TxIndexWorker,\n};\nuse txindex_server::daemon::schema::ChainQuery;\n\npub mod tx_counter;\n\npub struct ExampleRootWorker {}\n\nimpl TxIndexWorker\u003cBaseKVQStore, ChainQuery\u003e for ExampleRootWorker {\n    fn process_block(\n        db: \u0026mut IndexedBlockDBStore\u003cKVQBinaryStoreCached\u003cBaseKVQStore\u003e\u003e,\n        q: Arc\u003cChainQuery\u003e,\n        block_number: u64,\n        block: \u0026Block,\n    ) -\u003e anyhow::Result\u003c()\u003e {\n        TxCounterWorker::\u003cBaseKVQStore, ChainQuery\u003e::process_block(db, q, block_number, block)?;\n        Ok(())\n    }\n}\n```\n\nRoot API handler\n```rust\nuse std::sync::Arc;\n\nuse hyper::{Method, Response};\nuse tx_counter::TxCounterAPI;\nuse txindex_common::{api::traits::TxIndexAPIHandler, config::Config, db::indexed_block_db::IndexedBlockDBStoreReader};\nuse txindex_server::{api::{core::HttpError, traits::{BoxBody, TxIndexRESTHandler}, TxIndexAPIResponseHelper}, daemon::{query::Query, schema::ChainQuery}};\n\npub mod tx_counter;\n#[derive(Clone, Debug, Copy)]\npub struct ExampleRESTHandler {\n\n}\n\nimpl TxIndexRESTHandler for ExampleRESTHandler {\n    fn handle_request(\n      _method: Method,\n      uri: hyper::Uri,\n      _body: hyper::body::Bytes,\n      q: Arc\u003cQuery\u003e,\n      config: Arc\u003cConfig\u003e,\n  ) -\u003e Result\u003cResponse\u003cBoxBody\u003e, HttpError\u003e {\n\n    if uri.path().starts_with(TxCounterAPI::\u003cChainQuery\u003e::PATH_SLUG){\n        Ok(TxCounterAPI::\u003cChainQuery\u003e::handle_get_request(config.network_type, uri.to_string(), q.get_chain_query(), IndexedBlockDBStoreReader{\n            store: q.get_kvq_db().clone(),\n        }).into_response())\n    }else{\n        Err(HttpError::not_found(\"not found\".to_string()))\n    }\n    \n  }\n    \n}\n```\n\n### 5. Call start_txindex_server 🎉\n\n```rust\nfn main() {\n    start_txindex_server::\u003cExampleRESTHandler, ExampleRootWorker\u003e();\n}\n```\n\n### License\nCopyright 2024 QED, MIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpsyprotocol%2Ftxindex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpsyprotocol%2Ftxindex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpsyprotocol%2Ftxindex/lists"}