Chainsauce 💃
General-purpose Ethereum blockchain indexing library.
Chainsauce is a general-purpose Ethereum indexer that sources contract events from a JSON-RPC endpoint.
Installation
$ npm install boudra/chainsauce
Basic usage
import { createIndexer, createHttpRpcClient } from "chainsauce";
import { erc20ABI } from "./erc20ABI.ts";
const MyContracts = {
ERC20: erc20ABI,
};
const indexer = createIndexer({
chain: {
id: 1,
rpcClient: createHttpRpcClient({
url: "https://mainnet.infura.io/v3/...",
}),
},
contracts: MyContracts,
});
indexer.on("ERC20:Approval", async ({ event }) => {
console.log("Approval event:", event.params);
});
indexer.on("event", async ({ event }) => {
console.log("Event:", event.params);
});
indexer.subscribeToContract({
contract: "ERC20",
address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
fromBlock: 18363594n,
toBlock: "latest"
});
await indexer.indexToBlock("latest");
indexer.on("error", (error) => {
console.error("whoops", error);
});
indexer.watch();
Event handler types
Event handlers should be automatically inferred when used like this:
indexer.on("ERC20:Approval", async ({ event, context }) => {
});
But if you need to split out event handler function to other files, you can type them like this;
import { Indexer as ChainsauceIndexer } from "chainsauce";
type MyContext = {
db: DatabaseConnection
};
const MyContracts = {
ERC20: erc20ABI,
};
type Indexer = ChainsauceIndexer<typeof MyContracts, MyContext>;
const indexer: Indexer = createIndexer({
...
context: { db: new DatabaseConnection() }
});
async function handleTransfer({
event, context: { db }
}: EventHandlerArgs<Indexer, "ERC20", "Transfer">) {
}
indexer.on("ERC20:Transfer", handleTransfer);
How to define ABIs
ABIs in Chainsauce are of type type Abi in abitype.
ABIs must be defined in Typescript if you want automatic typing of events and contract reads, make sure you cast ABIs as const
:
const myAbi = [
...
] as const;
Factory Contracts
You can subscribe to new deployed contracts in your event handlers by using the function subscribeToContract
:
indexer.on("FactoryContract:ContractCreated", async ({ event, context, subscribeToContract }) => {
subscribeToContract({
contract: "MyContract",
address: event.params.contractAddress,
toBlock: "latest"
});
});
Caching events and contract reads
Chainsauce comes with a cache to speed up reindexing, all you have to do is pass a cache when creating the indexer.
Currently only a SQLite version is available, but other options are planned.
import { createIndexer, createSqliteCache } from "chainsauce";
const indexer = createIndexer({
cache: createSqliteCache("./chainsauce.db"),
});
Complete examples