diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..c72a4fc --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +.turbo diff --git a/examples/starknet-client/src/main.ts b/examples/starknet-client/src/main.ts index 11c12cd..cf67dc8 100644 --- a/examples/starknet-client/src/main.ts +++ b/examples/starknet-client/src/main.ts @@ -54,41 +54,19 @@ const command = defineCommand({ console.log(response); const filter = Filter.make({ - transactions: [ + events: [ { - includeEvents: true, - includeMessages: true, + fromAddress: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", includeReceipt: true, - includeReverted: true, - }, - ], - // header: { - // always: true, - // }, - // events: [ - // { - // // fromAddress: "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", - // // fromAddress: "0x07b696af58c967c1b14c9dde0ace001720635a660a8e90c565ea459345318b30", - // // fromAddress: "0x0", - // // includeReceipt: true, - // // includeTransaction: true, - // // includeSiblings: true, - // // includeMessages: true, - // // includeReverted: true, - // // keys: [null] - // // keys: [] - // } - // ], - // transactions: [{ - // includeEvents: true, - // }], + } + ] }); const request = StarknetStream.Request.make({ filter: [filter], finality: "accepted", startingCursor: { - orderKey: 500_000n, + orderKey: 80_000n, }, }); diff --git a/examples/starknet-indexer/package.json b/examples/starknet-indexer/package.json new file mode 100644 index 0000000..2ef54e0 --- /dev/null +++ b/examples/starknet-indexer/package.json @@ -0,0 +1,32 @@ +{ + "name": "example-starknet-indexer", + "version": "1.0.0", + "private": true, + "scripts": { + "start": "jiti ./src/main.ts", + "typecheck": "tsc --noEmit", + "lint": "biome check .", + "lint:fix": "pnpm lint --write", + "format": "biome format . --write" + }, + "dependencies": { + "@apibara/indexer": "workspace:*", + "@apibara/protocol": "workspace:*", + "@apibara/starknet": "workspace:*", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/exporter-trace-otlp-proto": "^0.52.0", + "@opentelemetry/resources": "^1.25.0", + "@opentelemetry/sdk-node": "^0.52.0", + "@opentelemetry/sdk-trace-base": "^1.25.0", + "@opentelemetry/semantic-conventions": "^1.25.0", + "citty": "^0.1.6", + "consola": "^3.2.3", + "csv-stringify": "^6.5.0", + "sqlite": "^5.1.1", + "viem": "^2.12.4" + }, + "devDependencies": { + "@types/node": "^20.12.12", + "jiti": "^1.21.0" + } +} \ No newline at end of file diff --git a/examples/starknet-indexer/src/indexer.ts b/examples/starknet-indexer/src/indexer.ts new file mode 100644 index 0000000..cb48c64 --- /dev/null +++ b/examples/starknet-indexer/src/indexer.ts @@ -0,0 +1,39 @@ +import assert from "node:assert"; +import { getReceipt, getTransaction, StarknetStream } from "@apibara/starknet"; +import { defineIndexer, useIndexerContext } from "@apibara/indexer"; +import consola from "consola"; + +export function createIndexerConfig(streamUrl: string) { + return defineIndexer(StarknetStream)({ + streamUrl, + finality: "accepted", + startingCursor: { + orderKey: 300_000n, + }, + filter: { + events: [ + { + fromAddress: "0x00000005dd3D2F4429AF886cD1a3b08289DBcEa99A294197E9eB43b0e0325b4b", + includeReceipt: true, + includeTransaction: true, + } + ] + }, + async transform({ block: { header, events, transactions, receipts } }) { + const ts = header?.timestamp!; + for (const event of events) { + // Use helpers to access transaction and receipt + const tx = getTransaction(event.transactionIndex!, transactions ?? []); + const receipt = getReceipt(event.transactionIndex!, receipts ?? []); + + consola.info({ + ts, + eventIndex: event.eventIndex, + actualFee: receipt?.meta?.actualFee, + txType: tx?.transaction?._tag, + }); + } + return []; + }, + }); +} diff --git a/examples/starknet-indexer/src/main.ts b/examples/starknet-indexer/src/main.ts new file mode 100644 index 0000000..ca0c83d --- /dev/null +++ b/examples/starknet-indexer/src/main.ts @@ -0,0 +1,38 @@ +import { createIndexer, run } from "@apibara/indexer"; +import { createClient } from "@apibara/protocol"; +import { defineCommand, runMain } from "citty"; +import consola from "consola"; + +import { createIndexerConfig } from "./indexer"; + +const command = defineCommand({ + meta: { + name: "example-starknet-indexer", + version: "1.0.0", + description: "Example showing how to run an indexer", + }, + args: { + stream: { + type: "string", + default: "http://127.0.0.1:7007", + description: "Starknet stream URL", + }, + authToken: { + type: "string", + description: "DNA auth token", + }, + }, + async run({ args }) { + consola.info("Connecting to Starknet stream", args.stream); + + const indexer = createIndexer(createIndexerConfig(args.stream)); + const client = createClient( + indexer.streamConfig, + indexer.options.streamUrl, + ); + + await run(client, indexer); + }, +}); + +runMain(command); diff --git a/examples/starknet-indexer/tsconfig.json b/examples/starknet-indexer/tsconfig.json new file mode 100644 index 0000000..9d05974 --- /dev/null +++ b/examples/starknet-indexer/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "declarationDir": "dist", + "noEmit": false, + "rootDir": "src", + "types": ["node"] + }, + "include": ["src/"] +} diff --git a/packages/indexer/package.json b/packages/indexer/package.json index 2f57d4a..03e6b23 100644 --- a/packages/indexer/package.json +++ b/packages/indexer/package.json @@ -1,6 +1,6 @@ { "name": "@apibara/indexer", - "version": "2.0.0", + "version": "2.0.0-beta.0", "type": "module", "source": "./src/index.ts", "main": "./src/index.ts", diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 1965279..7d7cbe3 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@apibara/protocol", - "version": "2.0.0", + "version": "2.0.0-beta.0", "type": "module", "source": "./src/index.ts", "main": "./src/index.ts", diff --git a/packages/starknet/package.json b/packages/starknet/package.json index da27083..861ba42 100644 --- a/packages/starknet/package.json +++ b/packages/starknet/package.json @@ -1,6 +1,6 @@ { "name": "@apibara/starknet", - "version": "2.0.0", + "version": "2.0.0-beta.0", "type": "module", "source": "./src/index.ts", "main": "./src/index.ts", diff --git a/packages/starknet/src/access.ts b/packages/starknet/src/access.ts new file mode 100644 index 0000000..070909f --- /dev/null +++ b/packages/starknet/src/access.ts @@ -0,0 +1,50 @@ +import type { Transaction, TransactionReceipt } from "./block"; + +/** Returns the transaction receipt for the given transaction index. */ +export function getReceipt( + transactionIndex: number, + receipts: readonly TransactionReceipt[], +): TransactionReceipt | undefined { + return binarySearch( + transactionIndex, + receipts, + (receipt) => receipt.meta?.transactionIndex ?? 0, + ); +} + +/** Returns the transaction for the given transaction index. */ +export function getTransaction( + transactionIndex: number, + transactions: readonly Transaction[], +): Transaction | undefined { + return binarySearch( + transactionIndex, + transactions, + (transaction) => transaction.meta?.transactionIndex ?? 0, + ); +} + +function binarySearch( + index: number, + arr: readonly T[], + getIndex: (item: T) => number, +): T | undefined { + let left = 0; + let right = arr.length - 1; + + while (left <= right) { + const mid = Math.floor((left + right) / 2); + const item = arr[mid]; + const itemIndex = getIndex(item); + + if (itemIndex === index) { + return item; + } + + if (itemIndex < index) { + left = mid + 1; + } else { + right = mid - 1; + } + } +} diff --git a/packages/starknet/src/index.ts b/packages/starknet/src/index.ts index 2d74796..484ca98 100644 --- a/packages/starknet/src/index.ts +++ b/packages/starknet/src/index.ts @@ -8,6 +8,8 @@ export * from "./common"; export * from "./filter"; export * from "./block"; +export * from "./access"; + export const StarknetStream = new StreamConfig( FilterFromBytes, BlockFromBytes, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fab0c6d..26eec90 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -157,6 +157,58 @@ importers: specifier: ^1.21.0 version: 1.21.0 + examples/starknet-indexer: + dependencies: + '@apibara/indexer': + specifier: workspace:* + version: link:../../packages/indexer + '@apibara/protocol': + specifier: workspace:* + version: link:../../packages/protocol + '@apibara/starknet': + specifier: workspace:* + version: link:../../packages/starknet + '@opentelemetry/api': + specifier: ^1.9.0 + version: 1.9.0 + '@opentelemetry/exporter-trace-otlp-proto': + specifier: ^0.52.0 + version: 0.52.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': + specifier: ^1.25.0 + version: 1.25.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-node': + specifier: ^0.52.0 + version: 0.52.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': + specifier: ^1.25.0 + version: 1.25.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': + specifier: ^1.25.0 + version: 1.25.0 + citty: + specifier: ^0.1.6 + version: 0.1.6 + consola: + specifier: ^3.2.3 + version: 3.2.3 + csv-stringify: + specifier: ^6.5.0 + version: 6.5.0 + sqlite: + specifier: ^5.1.1 + version: 5.1.1 + viem: + specifier: ^2.12.4 + version: 2.13.8(typescript@5.4.5) + devDependencies: + '@types/node': + specifier: ^20.12.12 + version: 20.14.0 + jiti: + specifier: ^1.21.0 + version: 1.21.0 + packages/beaconchain: dependencies: '@apibara/evm':