diff --git a/docs/build/SUMMARY.md b/docs/build/SUMMARY.md index 9426615189..10231e612d 100644 --- a/docs/build/SUMMARY.md +++ b/docs/build/SUMMARY.md @@ -81,6 +81,22 @@ * [Implementation](post-conditions/implementation.md) * [Examples](post-conditions/examples.md) +## Chainhooks + +* [Overview](chainhooks/overview.md) +* [Introduction](chainhooks/quickstart.md) +* [Create Chainhooks](chainhooks/create.md) +* [Fetch Chainhooks](chainhooks/fetch.md) +* [Edit Chainhooks](chainhooks/update.md) +* [Replay a Block](chainhooks/evaluate.md) +* [Manage Secrets](chainhooks/secrets.md) +* [Migration Guide](chainhooks/migration.md) +* [FAQ](chainhooks/faq.md) +* [Reference](chainhooks/reference/README.md) + * [Filters](chainhooks/reference/filters.md) + * [Options](chainhooks/reference/options.md) + * [Payload Anatomy](chainhooks/reference/payload-anatomy.md) + ## More Guides * [sBTC](more-guides/sbtc/README.md) diff --git a/docs/build/chainhooks/create.md b/docs/build/chainhooks/create.md new file mode 100644 index 0000000000..3a8ff96c5e --- /dev/null +++ b/docs/build/chainhooks/create.md @@ -0,0 +1,141 @@ +--- +description: Create and activate chainhooks using the Chainhooks SDK +--- + +# Create chainhooks + +{% hint style="info" %} +This page is migrated from the [Hiro documentation](https://docs.hiro.so/en/tools/chainhooks/create). +{% endhint %} + +Creates a new chainhook configuration. By default, new chainhooks are created in a disabled state unless `enable_on_registration` is set to `true`. + +## registerChainhook + +### Example + +```ts +import { ChainhooksClient, CHAINHOOKS_BASE_URL } from '@hirosystems/chainhooks-client'; + +const client = new ChainhooksClient({ + baseUrl: CHAINHOOKS_BASE_URL.testnet, + apiKey: process.env.HIRO_API_KEY!, +}); + +const chainhook = await client.registerChainhook({ + version: '1', + name: 'my-chainhook', + chain: 'stacks', + network: 'testnet', + filters: { + events: [ + { + type: 'contract_call', + contract_identifier: 'SP...XYZ.counter', + function_name: 'increment', + }, + ], + }, + action: { + type: 'http_post', + url: 'https://example.com/webhooks', + }, + options: { + decode_clarity_values: true, + enable_on_registration: true, + }, +}); + +console.log('Chainhooks UUID:', chainhook.uuid); +console.log('Enabled:', chainhook.status.enabled); // true +``` + +If you don't set `enable_on_registration`, the chainhook will be created but disabled by default. + +--- + +## enableChainhook + +Enable or disable a single chainhook by UUID. This allows you to enable chainhooks after registration or pause without deleting it. + +### Examples + +```typescript +// Enable a chainhook +await client.enableChainhook('chainhook-uuid', true); + +// Disable a chainhook +await client.enableChainhook('chainhook-uuid', false); +``` + +Returns HTTP `204 No Content` on success. + +--- + +## bulkEnableChainhooks + +Enable or disable multiple chainhooks at once using filters. This is useful for managing many chainhooks programmatically. + +### By UUIDs + +Enable specific chainhooks by their UUIDs (maximum 200): + +```typescript +await client.bulkEnableChainhooks({ + enabled: true, + filters: { + uuids: [ + 'uuid-1', + 'uuid-2', + 'uuid-3', + ], + }, +}); +``` + +### By Webhook URL + +Enable all chainhooks that POST to a specific URL: + +```typescript +await client.bulkEnableChainhooks({ + enabled: true, + filters: { + webhook_url: 'https://example.com/webhooks', + }, +}); +``` + +### By Status + +Enable all chainhooks with a specific status: + +```typescript +await client.bulkEnableChainhooks({ + enabled: true, + filters: { + statuses: ['inactive'], + }, +}); +``` + +### Combined Filters + +Use multiple filters together: + +```typescript +await client.bulkEnableChainhooks({ + enabled: false, // Disable matching chainhooks + filters: { + webhook_url: 'https://old-server.com/webhooks', + statuses: ['active'], + }, +}); +``` + +This will disable all active chainhooks that POST to the old webhook URL. + +## Next steps + +- [Evaluate](evaluate.md): Test chainhooks against past blocks +- [Filter Reference](reference/filters.md): Explore all filter options diff --git a/docs/build/chainhooks/evaluate.md b/docs/build/chainhooks/evaluate.md new file mode 100644 index 0000000000..c9835264f6 --- /dev/null +++ b/docs/build/chainhooks/evaluate.md @@ -0,0 +1,71 @@ +--- +description: Replay any block height or hash against a chainhook to debug, backfill, or re-process historical events. +--- + +# Replay a block + +{% hint style="info" %} +This page is migrated from the [Hiro documentation](https://docs.hiro.so/en/tools/chainhooks/evaluate). +{% endhint %} + +The evaluate endpoint replays any single block you choose—live or historical—against one of your registered chainhooks so you can validate filters without waiting for live traffic. Provide either a block height or block hash to target the exact block you care about. + +Use it to reproduce missed deliveries, inspect payload schemas after filter changes, or test webhook infrastructure with known blocks before enabling a hook in production. + +## evaluateChainhook + +### Evaluation Methods + +| Method | Parameter | Example | +|--------|-----------|---------| +| **By height** | `block_height` | `{ block_height: 100000 }` | +| **By hash** | `index_block_hash` | `{ index_block_hash: '0xa204...' }` | + +### Example + +{% tabs %} +{% tab title="with-block-height.ts" %} +```ts +import { ChainhooksClient, CHAINHOOKS_BASE_URL } from '@hirosystems/chainhooks-client'; + +const client = new ChainhooksClient({ + baseUrl: CHAINHOOKS_BASE_URL.testnet, + apiKey: process.env.HIRO_API_KEY!, +}); + +await client.evaluateChainhook('chainhook-uuid', { + block_height: 100000, +}); +``` +{% endtab %} + +{% tab title="with-block-hash.ts" %} +```ts +import { ChainhooksClient, CHAINHOOKS_BASE_URL } from '@hirosystems/chainhooks-client'; + +const client = new ChainhooksClient({ + baseUrl: CHAINHOOKS_BASE_URL.testnet, + apiKey: process.env.HIRO_API_KEY!, +}); + +await client.evaluateChainhook('chainhook-uuid', { + index_block_hash: '0xa204...', +}); +``` +{% endtab %} +{% endtabs %} + +Returns HTTP `204 No Content`. If filters match, webhook payload is sent to your `action.url`. + +## Use Cases + +| Use Case | Description | Example | +|----------|-------------|---------| +| **Debug** | Investigate missed events | Replay a block height that should have triggered | +| **Backfill** | Index historical data | Process older blocks created before the hook existed | +| **Re-process** | Fix webhook handler issues | Re-evaluate a block after patching infrastructure | + +## Next steps + +- [Filter Reference](reference/filters.md): Configure which events to match +- [Evaluate endpoint](https://docs.hiro.so/apis/chainhooks-api/reference/chainhooks/evaluate-chainhook): Replay past blocks through the API diff --git a/docs/build/chainhooks/faq.md b/docs/build/chainhooks/faq.md new file mode 100644 index 0000000000..d473e004d0 --- /dev/null +++ b/docs/build/chainhooks/faq.md @@ -0,0 +1,127 @@ +--- +description: Frequently asked questions about Chainhooks 2.0 (Beta) +--- + +# FAQ + +{% hint style="info" %} +This page is migrated from the [Hiro documentation](https://docs.hiro.so/en/tools/chainhooks/faq). +{% endhint %} + +## Chainhooks 2.0 (Beta) + +
+ +What is the goal/purpose of the Chainhooks 2.0 (Beta)? + +Our goal during the Beta is to learn as much as possible about the reliability, performance and developer experience of Chainhooks 2.0 in anticipation of the full release. If you encounter any issues, have any questions, or would like to share feedback during the Beta, please reach out to [beta@hiro.so](mailto:beta@hiro.so). + +
+ +
+ +Is the Chainhooks 2.0 (Beta) free? + +Yes! The Chainhooks 2.0 Beta is free for all participants. + +
+ +
+ +Will there be configuration or rate limits imposed during the Beta? + +All Beta users will initially be constrained to a limit of 10 Chainhooks configurations. + +
+ +
+ +How will Chainhooks be priced following the Beta? + +Chainhooks will be charged using a credit model for each delivery, with users able to select different credit limits depending on their usage. For Chainhooks 2.0 Beta users, your post-beta limits will initially be determined by your existing Hiro subscription tier. + +
+ +## Version Management + +
+ +What will happen to existing legacy Chainhooks running on v1.0? + +Users with Chainhooks running on v1.0 will still be able to view them and receive deliveries, but once the beta launches, all new Chainhooks created in the Platform will run on v2.0. + +The API will also not support Chainhooks running on v1.0. + +{% hint style="info" %} +Learn how to migrate your v1 chainhooks to v2 in the [Migration Guide](migration.md). +{% endhint %} + +
+ +
+ +If v2.0 and v1.0 will be live at the same time, how do we manage both? + +In the Platform, v1 chainhooks are read-only. You can view them and they will continue to deliver events, but you cannot modify them through the Platform UI. + +To modify v1 chainhooks programmatically, use the [Platform API](https://docs.hiro.so/apis/platform-api). However, we recommend migrating to v2 chainhooks instead. See the [Migration Guide](migration.md) for a complete walkthrough. + +
+ +
+ +How do I migrate my v1 chainhooks to v2? + +To migrate your v1 chainhooks to v2, follow the steps outlined in the [Migration Guide](migration.md). + +
+ +## Platform vs SDK + +
+ +Can I edit my v2 Chainhooks? + +Yes! you can edit your v2 Chainhooks using the [Chainhooks SDK](update.md) or [Chainhooks API](https://docs.hiro.so/apis/chainhooks-api). The Platform does not currently support editing v2 Chainhooks. + +
+ +## Common Questions + +
+ +Can I filter by multiple event types? + +Yes! See examples in the [Filter reference](reference/filters.md#combining-filters) guide for examples of combining filters. + +
+ +
+ +What happens if my webhook endpoint is down? + +Chainhooks will retry webhook deliveries and then "pause" your Chainhooks, giving you time to fix the issue and re-enable. Extended downtime may result in missed events, but you can use the [Evaluate](evaluate.md) feature to replay missed blocks. + +
+ +
+ +Can I test chainhooks locally? + +Currently, no. But we are working on a new feature that will help you quickly test chainhook payloads locally without requiring much setup. + +
+ +--- + +## Where to Get Help + +- **Discord**: Join the **#chainhook** channel on [Discord](https://stacks.chat/) under Hiro Developer Tools +- **Email Support**: [beta@hiro.so](mailto:beta@hiro.so) + +--- + +## Next Steps + +- [Create chainhooks](create.md): Register and enable chainhooks +- [Payload anatomy](reference/payload-anatomy.md): Dive into the anatomy of a chainhook payload diff --git a/docs/build/chainhooks/fetch.md b/docs/build/chainhooks/fetch.md new file mode 100644 index 0000000000..5f72147305 --- /dev/null +++ b/docs/build/chainhooks/fetch.md @@ -0,0 +1,59 @@ +--- +description: Retrieve chainhook information using the SDK +--- + +# Fetch chainhooks + +{% hint style="info" %} +This page is migrated from the [Hiro documentation](https://docs.hiro.so/en/tools/chainhooks/fetch). +{% endhint %} + +## getChainhooks + +Retrieve a paginated list of all your chainhooks. + +### Example + +```typescript +import { ChainhooksClient, CHAINHOOKS_BASE_URL } from '@hirosystems/chainhooks-client'; + +const client = new ChainhooksClient({ + baseUrl: CHAINHOOKS_BASE_URL.testnet, + apiKey: process.env.HIRO_API_KEY!, +}); + +// Get first page (default: 20 results) +const chainhooks = await client.getChainhooks(); + +console.log('Total chainhooks:', chainhooks.total); +console.log('Results:', chainhooks.results.length); +console.log('Limit:', chainhooks.limit); +console.log('Offset:', chainhooks.offset); +``` + +### With options + +```typescript +// Get specific page with 50 chainhooks starting from position 100 +const chainhooks = await client.getChainhooks({ + limit: 50, + offset: 100, +}); +``` + +--- + +## getChainhook + +Retrieve a specific chainhook by UUID. + +### Example + +```typescript +const chainhook = await client.getChainhook('be4ab3ed-b606-4fe0-97c4-6c0b1ac9b185'); +``` + +## Next steps + +- [Edit & Update](update.md): Modify existing chainhooks +- [Register & Enable](create.md): Create new chainhooks diff --git a/docs/build/chainhooks/migration.md b/docs/build/chainhooks/migration.md new file mode 100644 index 0000000000..6acc5cfd7b --- /dev/null +++ b/docs/build/chainhooks/migration.md @@ -0,0 +1,244 @@ +--- +description: Guide for migrating legacy chainhooks to v2 +--- + +# Migration guide + +{% hint style="info" %} +This page is migrated from the [Hiro documentation](https://docs.hiro.so/en/tools/chainhooks/migration). +{% endhint %} + +{% hint style="warning" %} +### Deprecation of Chainhooks v1 +Legacy v1 chainhooks will be deprecated on March 9th, 2026. +{% endhint %} + +## What you'll learn + +- Capture and analyze all existing v1 chainhooks. +- Convert predicate-based filters into explicit v2 event definitions. +- Register v2 chainhooks, test delivery, and retire the originals safely. + +## Prerequisites + +- Hiro API key for access to both the Platform and Chainhooks API. +- (optional) If you use TypeScript and Node.js, you can install the [Chainhooks client library](https://www.npmjs.com/package/@hirosystems/chainhooks-client) to easily parse incoming Chainhooks v2 payloads + +{% stepper %} +{% step %} +### Get a list of v1 chainhooks + +Use the Platform API to fetch the chainhooks you want to migrate. + +```ts +const response = await fetch(`https://api.platform.hiro.so/v1/ext/${process.env.HIRO_API_KEY}/chainhooks`, { + headers: { + 'content-type': 'application/json' + } +}); + +const chainhooks = await response.json(); +``` +{% endstep %} + +{% step %} +### Start mapping to the new v2 format + +Translate v1 structures to v2 formats before creating new hooks. The following table shows the mapping between v1 and v2 structures: + +| v1 | v2 | Notes | +|------------|-----------|-------| +| `if_this.scope` | `filters.events[].type` | Replace `scope/action` combos with a single event type. | +| `if_this.actions` | `type` | `transfer` maps to `*_transfer`. | +| `then_that.http_post.url` | `action.url` | v2 handles secrets automatically. | +| `networks.mainnet` | `network: "mainnet"` | Create one v2 hook per network. | +| `authorization_header` | Webhook secret management | Use `rotateConsumerSecret()` after registration. | + +#### Example + +{% tabs %} +{% tab title="Legacy" %} +```json +{ + "name": "stx-transfers", + "networks": { + "mainnet": { + "if_this": { "scope": "stx_event", "actions": ["transfer"] }, + "then_that": { "http_post": { "url": "https://example.com/webhooks" } } + } + } +} +``` +{% endtab %} + +{% tab title="v2" %} +```json +{ + "version": "1", + "name": "stx-transfers", + "chain": "stacks", + "network": "mainnet", + "filters": { + "events": [{ "type": "stx_transfer" }] + }, + "action": { + "type": "http_post", + "url": "https://example.com/webhooks" + }, + "options": { + "decode_clarity_values": true, + "enable_on_registration": true + } +} +``` +{% endtab %} +{% endtabs %} + +For more details on the format changes, see the [Filters](reference/filters.md) reference guide. +{% endstep %} + +{% step %} +### Create v2 chainhooks + +Register each chainhook with the SDK or REST API, mirroring the mapped filters. + +{% tabs %} +{% tab title="api.ts" %} +```ts +const v2Chainhook = { + "version": "1", + "name": "stx-transfers", + "chain": "stacks", + "network": "mainnet", + "filters": { + "events": [{ "type": "stx_transfer" }] + }, + "action": { + "type": "http_post", + "url": "https://example.com/webhooks" + }, + "options": { + "decode_clarity_values": true, + "enable_on_registration": true + } +}; + +const response = await fetch('https://api.hiro.so/chainhooks/v1/me/', { + method: 'POST', + headers: { + 'x-api-key': process.env.HIRO_API_KEY!, + 'content-type': 'application/json', + }, + body: JSON.stringify(v2Chainhook), +}); +``` +{% endtab %} + +{% tab title="sdk.ts" %} +```ts +const client = new ChainhooksClient({ + baseUrl: CHAINHOOKS_BASE_URL.mainnet, + apiKey: process.env.HIRO_API_KEY!, +}); +const chainhook = await client.registerChainhook( + { + "version": "1", + "name": "stx-transfers", + "chain": "stacks", + "network": "mainnet", + "filters": { + "events": [{ "type": "stx_transfer" }] + }, + "action": { + "type": "http_post", + "url": "https://example.com/webhooks" + }, + "options": { + "decode_clarity_values": true, + "enable_on_registration": true + } + } +); +``` +{% endtab %} +{% endtabs %} +{% endstep %} + +{% step %} +### Validate and cleanup legacy chainhooks + +Stream events through both versions, confirm delivery, then clean up the legacy definitions. + +{% hint style="info" %} +### Best practices +Keep both v1 and v2 hooks active until you verify delivery parity. +{% endhint %} + +#### Enablement checks on v2 + +{% tabs %} +{% tab title="api.ts" %} +```ts +const response = await fetch(`https://api.hiro.so/chainhooks/v1/me/${uuid}`, { + method: 'GET', + headers: { + 'x-api-key': process.env.HIRO_API_KEY!, + 'content-type': 'application/json', + }, +}); + +const chainhook = await response.json(); +console.log(chainhook.status.enabled); +``` +{% endtab %} + +{% tab title="sdk.ts" %} +```ts +const chainhook = await client.getChainhook(uuid); +console.log(chainhook.status.enabled); +``` +{% endtab %} +{% endtabs %} + +#### Delete legacy chainhooks + +```ts +const response = await fetch( + `https://api.platform.hiro.so/v1/ext/${process.env.HIRO_API_KEY}/chainhooks/${uuid}`,{ + method: 'DELETE', + headers: { + 'content-type': 'application/json' + } + } +); + +await response.json(); +``` +{% endstep %} +{% endstepper %} + +## Filter reference + +### Common scopes + +| v1 | Typical Actions | v2 | Extras | +|----------|-----------------|-----------|--------| +| `stx_event` | `transfer` | `stx_transfer` | Include `sender` or `recipient` filters as needed. | +| `contract_call` | n/a | `contract_call` | Add `contract_identifier` and `function_name`. | +| `ft_event` | `transfer` | `ft_transfer` | Specify `asset_identifier`. | +| `nft_event` | `transfer`, `mint` | `nft_transfer` / `nft_mint` | Provide `asset_identifier`. | + +For more details, check out the [Filters](reference/filters.md) reference page. + +## Replaying past blocks + +Chainhooks v2 does not have the ability to specify a **start block** in a hook's configuration for past block scans. + +However, you can use the [Replay Block API endpoint](evaluate.md) to request any block replay you need at any time. Once requested, you will receive that block's information through the same webhook payload endpoint you use for real-time updates within a couple seconds. + +This method ensures you have complete control over which blocks you need to replay whenever you need to. + +## Next steps + +- [FAQ](faq.md): Chainhooks 2.0 (Beta) FAQ +- [Filter Reference](reference/filters.md): Filter Reference diff --git a/docs/build/chainhooks/overview.md b/docs/build/chainhooks/overview.md new file mode 100644 index 0000000000..e27c7daf98 --- /dev/null +++ b/docs/build/chainhooks/overview.md @@ -0,0 +1,34 @@ +--- +description: Chainhooks is a webhook service for the Stacks blockchain that lets you register event streams and define precise filters to capture on-chain data as it happens. +--- + +# Chainhooks + +{% hint style="info" %} +This page is migrated from the [Hiro documentation](https://docs.hiro.so/en/tools/chainhooks). +{% endhint %} + +## Overview + +Chainhooks makes it easy to subscribe to blockchain activity on Stacks by registering event streams, filtering for exactly the data you care about, and sending it straight to your app in real time. + +With Chainhooks 2.0 (Beta), you can manage chainhooks through: +- **[Chainhooks SDK](quickstart.md)** - TypeScript/JavaScript client for programmatic management +- **[Hiro Platform](https://platform.hiro.so)** - Web-based UI for visual chainhook creation +- **[Chainhooks API](https://docs.hiro.so/apis/chainhooks-api)** - Direct REST API access + +## Key Features + +- **Reorg-aware indexing** - Automatically handles blockchain forks and reorganizations +- **Event filtering** - Define custom logic to trigger actions on specific blockchain events +- **Historical evaluation** - Test chainhooks against past blocks for indexing or debugging + +## Next steps + +- [SDK introduction](quickstart.md): Get started with the Chainhooks SDK +- [Migration guide](migration.md): Migration guide for upgrading to Chainhooks 2.0 (Beta) + +{% hint style="info" %} +### Need help with Chainhooks? +Reach out to us on the **#chainhook** channel on [Discord](https://stacks.chat/) under the Hiro Developer Tools section. +{% endhint %} diff --git a/docs/build/chainhooks/quickstart.md b/docs/build/chainhooks/quickstart.md new file mode 100644 index 0000000000..d8394f33a7 --- /dev/null +++ b/docs/build/chainhooks/quickstart.md @@ -0,0 +1,175 @@ +--- +description: TypeScript/JavaScript SDK for managing chainhooks programmatically +--- + +# Introduction + +{% hint style="info" %} +This page is migrated from the [Hiro documentation](https://docs.hiro.so/en/tools/chainhooks/introduction). +{% endhint %} + +## Overview + +The Chainhooks SDK (`@hirosystems/chainhooks-client`) provides a TypeScript/JavaScript client for programmatically managing chainhooks. + +## Installation + +{% tabs %} +{% tab title="npm" %} +```bash +npm install @hirosystems/chainhooks-client +``` +{% endtab %} + +{% tab title="yarn" %} +```bash +yarn add @hirosystems/chainhooks-client +``` +{% endtab %} + +{% tab title="pnpm" %} +```bash +pnpm add @hirosystems/chainhooks-client +``` +{% endtab %} + +{% tab title="bun" %} +```bash +bun add @hirosystems/chainhooks-client +``` +{% endtab %} +{% endtabs %} + +## Quick Example + +```typescript +import { ChainhooksClient, CHAINHOOKS_BASE_URL } from '@hirosystems/chainhooks-client'; + +const client = new ChainhooksClient({ + baseUrl: CHAINHOOKS_BASE_URL.testnet, // or CHAINHOOKS_BASE_URL.mainnet + apiKey: process.env.HIRO_API_KEY!, +}); + +// Register and enable a chainhook +const chainhook = await client.registerChainhook({ + version: '1', + name: 'my-first-chainhook', + chain: 'stacks', + network: 'testnet', + filters: { + events: [ + { + type: 'contract_call', + contract_identifier: 'SP...XYZ.counter', + function_name: 'increment', + }, + ], + }, + action: { + type: 'http_post', + url: 'https://example.com/webhooks', + }, + options: { + decode_clarity_values: true, + enable_on_registration: true, + }, +}); + +console.log('Chainhooks created:', chainhook.uuid); +``` + +## Base URLs + +The SDK provides network-specific constants: + +| Network | Constant | URL | +|---------|----------|-----| +| Testnet | `CHAINHOOKS_BASE_URL.testnet` | https://api.testnet.hiro.so | +| Mainnet | `CHAINHOOKS_BASE_URL.mainnet` | https://api.mainnet.hiro.so | + +```typescript +import { CHAINHOOKS_BASE_URL } from '@hirosystems/chainhooks-client'; + +// Testnet (for development) +const testnetClient = new ChainhooksClient({ + baseUrl: CHAINHOOKS_BASE_URL.testnet, + apiKey: process.env.HIRO_API_KEY!, +}); + +// Mainnet (for production) +const mainnetClient = new ChainhooksClient({ + baseUrl: CHAINHOOKS_BASE_URL.mainnet, + apiKey: process.env.HIRO_API_KEY!, +}); +``` + +## Authentication + +### Get Your API Key + +1. Visit [platform.hiro.so](https://platform.hiro.so) +2. Sign in or create an account +3. Navigate to API Keys section +4. Generate or copy your API key + +### Configure Client + +```typescript +const client = new ChainhooksClient({ + baseUrl: CHAINHOOKS_BASE_URL.testnet, + apiKey: process.env.HIRO_API_KEY!, // Store securely in environment variables +}); +``` + +{% hint style="warning" %} +### API keys +Never commit API keys to version control. Always use environment variables or secure secret management. +{% endhint %} + +## SDK Methods + +The SDK provides the following methods: + +### Core Methods + +| Method | Description | +|--------|-------------| +| `registerChainhook()` | Create a new chainhook | +| `getChainhooks()` | List all your chainhooks (with pagination) | +| `getChainhook()` | Get a chainhook by UUID | +| `updateChainhook()` | Update an existing chainhook | +| `deleteChainhook()` | Delete a chainhook | + +### Activation Methods + +| Method | Description | +|--------|-------------| +| `enableChainhook()` | Enable or disable a single chainhook | +| `bulkEnableChainhooks()` | Enable or disable multiple chainhooks with filters | + +### Utility Methods + +| Method | Description | +|--------|-------------| +| `evaluateChainhook()` | Evaluate a chainhook against specific past blocks | +| `rotateConsumerSecret()` | Rotate the webhook secret for payload verification | + +## TypeScript Support + +### Available Types + +The SDK provides full TypeScript type definitions: + +```typescript +import type { + ChainhookDefinitionSchema, // Chainhooks configuration + ChainhookStatusSchema, // Status and activity info + EvaluateChainhookRequest, // Evaluation parameters + BulkEnableChainhooksRequest, // Bulk operation filters +} from '@hirosystems/chainhooks-client'; +``` + +## Next Steps + +- [Create chainhooks](create.md): Create and activate chainhooks +- [Manage consumer secrets](secrets.md): Learn how to validate Chainhooks delivery diff --git a/docs/build/chainhooks/reference/README.md b/docs/build/chainhooks/reference/README.md new file mode 100644 index 0000000000..973778c7de --- /dev/null +++ b/docs/build/chainhooks/reference/README.md @@ -0,0 +1,15 @@ +--- +description: Reference documentation for Chainhooks filters, options, and payload anatomy +--- + +# Reference + +{% hint style="info" %} +This page is migrated from the [Hiro documentation](https://docs.hiro.so/en/tools/chainhooks/reference). +{% endhint %} + +This section contains the complete reference documentation for Chainhooks: + +- [Filters](filters.md): Complete reference for all Chainhooks filter types +- [Options](options.md): Complete reference for all Chainhooks configuration options +- [Payload Anatomy](payload-anatomy.md): Understanding the structure of Chainhooks webhook payloads diff --git a/docs/build/chainhooks/reference/filters.md b/docs/build/chainhooks/reference/filters.md new file mode 100644 index 0000000000..bc751bf8a9 --- /dev/null +++ b/docs/build/chainhooks/reference/filters.md @@ -0,0 +1,373 @@ +--- +description: Complete reference for all Chainhooks filter types +--- + +# Filters + +{% hint style="info" %} +This page is migrated from the [Hiro documentation](https://docs.hiro.so/en/tools/chainhooks/reference/filters). +{% endhint %} + +Filters define which blockchain events will trigger your chainhook. + +Here is a complete reference for all Chainhooks filter types: + +| Filter | When to use | +|--------|-------------| +| `ft_event` | Catch *every* SIP-010 transfer, mint, or burn across assets. | +| `ft_transfer` | Follow a single asset such as USDC; optionally add `sender`/`receiver` for wallet-level triggers. | +| `ft_mint` | Track supply expansions or bridge inflows for one asset (set `asset_identifier`). | +| `ft_burn` | Track redemptions or supply contractions for one asset (set `asset_identifier`). | +| `nft_event` | Monitor every transfer, mint, or burn for all collections. | +| `nft_transfer` | Follow a SIP-009 collection; add `sender`, `receiver`, or `value` for owner/token targeting. | +| `nft_mint` | Watch every new mint for a collection (set `asset_identifier`). | +| `nft_burn` | Catch burns or redemptions (set `asset_identifier`). | +| `stx_event` | Capture all native transfers, mints, or burns. | +| `stx_transfer` | Track every STX transfer; add `sender` or `receiver` to spotlight specific principals. | +| `contract_deploy` | React to new contracts entering the network. | +| `contract_call` | Observe every invocation; narrow with `contract_identifier` and `function_name`. | +| `contract_log` | Catch `print`/log output from a contract (set `contract_identifier`). | +| `coinbase` | Watch miner rewards hitting the chain. | +| `tenure_change` | Track Proof-of-Transfer tenure updates; add `cause` (`"block_found"` or `"extended"`) for specificity. | + +## Fungible Token Events (FT) + +### Any FT Event + +Match any fungible token event (transfer, burn, or mint): + +```json +{ + "type": "ft_event" +} +``` + +### FT Transfer + +Match FT transfers for a specific asset: + +```json +{ + "type": "ft_transfer", + "asset_identifier": "SP...ABC.ft::usdc" +} +``` + +Filter by sender: + +```json +{ + "type": "ft_transfer", + "asset_identifier": "SP...ABC.ft::usdc", + "sender": "SP...FROM" +} +``` + +Filter by receiver: + +```json +{ + "type": "ft_transfer", + "asset_identifier": "SP...ABC.ft::usdc", + "receiver": "SP...TO" +} +``` + +### FT Mint + +Match FT mint events: + +```json +{ + "type": "ft_mint", + "asset_identifier": "SP...ABC.ft::usdc" +} +``` + +### FT Burn + +Match FT burn events: + +```json +{ + "type": "ft_burn", + "asset_identifier": "SP...ABC.ft::usdc" +} +``` + +--- + +## Non-Fungible Token Events (NFT) + +### Any NFT Event + +Match any NFT event (transfer, burn, or mint): + +```json +{ + "type": "nft_event" +} +``` + +### NFT Transfer + +Match NFT transfers for a specific collection: + +```json +{ + "type": "nft_transfer", + "asset_identifier": "SP...COLL.nft::collectible" +} +``` + +Filter by sender: + +```json +{ + "type": "nft_transfer", + "asset_identifier": "SP...COLL.nft::collectible", + "sender": "SP...FROM" +} +``` + +Filter by receiver: + +```json +{ + "type": "nft_transfer", + "asset_identifier": "SP...COLL.nft::collectible", + "receiver": "SP...TO" +} +``` + +Filter by specific token ID: + +```json +{ + "type": "nft_transfer", + "asset_identifier": "SP...COLL.nft::collectible", + "value": "u123" +} +``` + +### NFT Mint + +Match NFT mint events: + +```json +{ + "type": "nft_mint", + "asset_identifier": "SP...COLL.nft::collectible" +} +``` + +### NFT Burn + +Match NFT burn events: + +```json +{ + "type": "nft_burn", + "asset_identifier": "SP...COLL.nft::collectible" +} +``` + +--- + +## STX Events + +### Any STX Event + +Match any STX event (transfer, burn, or mint): + +```json +{ + "type": "stx_event" +} +``` + +### STX Transfer + +Match any STX transfer: + +```json +{ + "type": "stx_transfer" +} +``` + +Filter by sender: + +```json +{ + "type": "stx_transfer", + "sender": "SP...SENDER" +} +``` + +Filter by receiver: + +```json +{ + "type": "stx_transfer", + "receiver": "SP...RECEIVER" +} +``` + +--- + +## Contract Events + +### Contract Deploy + +Match any contract deployment: + +```json +{ + "type": "contract_deploy" +} +``` + +Filter by deployer: + +```json +{ + "type": "contract_deploy", + "sender": "SP...DEPLOYER" +} +``` + +### Contract Call + +Match any contract call: + +```json +{ + "type": "contract_call" +} +``` + +Match calls to a specific contract: + +```json +{ + "type": "contract_call", + "contract_identifier": "SP...XYZ.counter" +} +``` + +Match calls to a specific function: + +```json +{ + "type": "contract_call", + "contract_identifier": "SP...XYZ.counter", + "function_name": "increment" +} +``` + +Filter by caller: + +```json +{ + "type": "contract_call", + "contract_identifier": "SP...XYZ.counter", + "function_name": "increment", + "sender": "SP...CALLER" +} +``` + +### Contract Log + +Match any print events: + +```json +{ + "type": "contract_log" +} +``` + +Match contract print events: + +```json +{ + "type": "contract_log", + "contract_identifier": "SP...XYZ.counter" +} +``` + +Filter by transaction sender: + +```json +{ + "type": "contract_log", + "contract_identifier": "SP...XYZ.counter", + "sender": "SP...SENDER" +} +``` + +--- + +## System Events + +### Coinbase + +Match coinbase events (block rewards): + +```json +{ + "type": "coinbase" +} +``` + +### Tenure Change + +Match any tenure change: + +```json +{ + "type": "tenure_change" +} +``` + +Match tenure changes by cause (block found): + +```json +{ + "type": "tenure_change", + "cause": "block_found" +} +``` + +Match tenure changes by cause (extended): + +```json +{ + "type": "tenure_change", + "cause": "extended" +} +``` + +--- + +## Combining Filters + +You can combine multiple filters in the `filters.events` array. A chainhook will trigger if **any** of the filters match: + +```json +{ + "filters": { + "events": [ + { + "type": "ft_transfer", + "asset_identifier": "SP...ABC.token::diko" + }, + { + "type": "contract_call", + "contract_identifier": "SP...XYZ.counter", + "function_name": "increment" + } + ] + } +} +``` diff --git a/docs/build/chainhooks/reference/options.md b/docs/build/chainhooks/reference/options.md new file mode 100644 index 0000000000..4b33d58c8b --- /dev/null +++ b/docs/build/chainhooks/reference/options.md @@ -0,0 +1,210 @@ +--- +description: Complete reference for all Chainhooks configuration options +--- + +# Options + +{% hint style="info" %} +This page is migrated from the [Hiro documentation](https://docs.hiro.so/en/tools/chainhooks/reference/options). +{% endhint %} + +Options control payload enrichment and evaluation windows for your chainhook. The `options` field is optional and can be omitted or set to `null`. + +{% hint style="info" %} +All boolean options default to `false` if omitted. Integer options are optional. +{% endhint %} + +--- + +## Payload options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `decode_clarity_values` | boolean | `false` | Include human-readable Clarity values | +| `include_contract_abi` | boolean | `false` | Include contract ABI on deployments | +| `include_contract_source_code` | boolean | `false` | Include source code on deployments | +| `include_post_conditions` | boolean | `false` | Include post-conditions in metadata | +| `include_raw_transactions` | boolean | `false` | Include raw transaction hex | +| `include_block_metadata` | boolean | `false` | Include block metadata (Stacks & Bitcoin) | +| `include_block_signatures` | boolean | `false` | Include block signatures and signers | +| `enable_on_registration` | boolean | `false` | Enable chainhook immediately on creation | +| `expire_after_evaluations` | integer | none | Expire after N blocks evaluated | +| `expire_after_occurrences` | integer | none | Expire after N matches | + +### decode_clarity_values + +Include human-readable `repr` (and inferred types) alongside `hex` for arguments, logs, and results. + +- **Type**: `boolean` +- **Default**: `false` + +```json +{ + "options": { + "decode_clarity_values": true + } +} +``` + +--- + +### include_contract_abi + +Include the contract ABI on deployment operations. This improves argument typing when decoding. + +- **Type**: `boolean` +- **Default**: `false` + +```json +{ + "options": { + "include_contract_abi": true + } +} +``` + +--- + +### include_contract_source_code + +Include the contract source code on deployment operations. + +- **Type**: `boolean` +- **Default**: `false` + +```json +{ + "options": { + "include_contract_source_code": true + } +} +``` + +--- + +### include_post_conditions + +Include decoded post-conditions in transaction metadata. + +- **Type**: `boolean` +- **Default**: `false` + +```json +{ + "options": { + "include_post_conditions": true + } +} +``` + +--- + +### include_raw_transactions + +Include raw transaction hex in transaction metadata. + +- **Type**: `boolean` +- **Default**: `false` + +```json +{ + "options": { + "include_raw_transactions": true + } +} +``` + +--- + +### include_block_metadata + +Include block metadata for both Stacks and Bitcoin blocks, with execution costs, transaction count, etc. + +- **Type**: `boolean` +- **Default**: `false` + +```json +{ + "options": { + "include_block_metadata": true + } +} +``` + +--- + +### include_block_signatures + +Include signer information and signatures in block metadata. + +- **Type**: `boolean` +- **Default**: `false` + +```json +{ + "options": { + "include_block_signatures": true + } +} +``` + +--- + +## Activation Options + +### enable_on_registration + +Enable the chainhook immediately upon registration/creation. By default, a new chainhook is disabled upon creation. + +- **Type**: `boolean` +- **Default**: `false` + +```json +{ + "options": { + "enable_on_registration": true + } +} +``` + +When set to `true`, the response will include `status.enabled = true` and the chainhook will start processing events immediately. + +--- + +## Expiration Options + +### expire_after_evaluations + +Automatically expire the chainhook after evaluating this many blocks. + +- **Type**: `integer` +- **Default**: None (no expiration) + +```json +{ + "options": { + "expire_after_evaluations": 10000 + } +} +``` + +This chainhook will expire after evaluating 10,000 blocks. + +--- + +### expire_after_occurrences + +Automatically expire the chainhook after this many matching occurrences. + +- **Type**: `integer` +- **Default**: None (no expiration) + +```json +{ + "options": { + "expire_after_occurrences": 250 + } +} +``` + +This chainhook will expire after triggering 250 times. diff --git a/docs/build/chainhooks/reference/payload-anatomy.md b/docs/build/chainhooks/reference/payload-anatomy.md new file mode 100644 index 0000000000..a4013aa567 --- /dev/null +++ b/docs/build/chainhooks/reference/payload-anatomy.md @@ -0,0 +1,250 @@ +--- +description: Understanding the structure of Chainhooks webhook payloads +--- + +# Payload anatomy + +{% hint style="info" %} +This page is migrated from the [Hiro documentation](https://docs.hiro.so/en/tools/chainhooks/reference/payload-anatomy). +{% endhint %} + +## Payload Overview + +A Chainhooks payload consists of two main sections: **event** and **chainhook**: + +- `event` contains the blockchain data (blocks, transactions, operations) +- `chainhook` contains metadata about the chainhook that triggered + +--- + +## Basic Structure + +```json +{ + "event": { + "apply": [], + "rollback": [], + "chain": "stacks", + "network": "mainnet" + }, + "chainhook": { + "name": "my-chainhook", + "uuid": "be4ab3ed-b606-4fe0-97c4-6c0b1ac9b185" + } +} +``` + +--- + +## Event Section + +### Apply Array + +The `apply` array contains blocks being added to the canonical chain. Each block includes: + +```json +{ + "timestamp": 1757977309, + "block_identifier": { + "hash": "0xa204da7...", + "index": 3549902 + }, + "parent_block_identifier": { + "hash": "0xad0acff...", + "index": 3549901 + }, + "transactions": [] +} +``` + +### Rollback Array + +The `rollback` array contains blocks being removed during a chain reorganization. Same structure as `apply`. + +{% hint style="info" %} +Chainhooks automatically handles reorgs by sending rollback events. Your application should reverse any state changes from rolled-back blocks. +{% endhint %} + +--- + +## Transaction Structure + +Each transaction in the `transactions` array contains: + +### Metadata + +Transaction-level information: + +```json +{ + "metadata": { + "type": "contract_call", + "nonce": 6689, + "result": { + "hex": "0x0703", + "repr": "(ok true)" + }, + "status": "success", + "fee_rate": "3000", + "position": { + "index": 0, + "microblock_identifier": null + }, + "execution_cost": { + "runtime": "7807", + "read_count": "5", + "read_length": "2441", + "write_count": "2", + "write_length": "1" + }, + "sender_address": "SPV2YDN4RZ506655KZK493YKM31JQPKJ9NN3QCX9", + "sponsor_address": null, + "canonical": true, + "sponsored": false, + "microblock_canonical": true + } +} +``` + +### Transaction Identifier + +Unique identifier for the transaction: + +```json +{ + "transaction_identifier": { + "hash": "0x09defc9a6cd9318b5c458389d4dd57597203ec539818aec0de3cfcfd7af0c2ab" + } +} +``` + +--- + +## Operations Array + +Each transaction includes an `operations` array describing state changes. Operations are indexed sequentially. + +### Fee Operation + +Transaction fee paid: + +```json +{ + "type": "fee", + "amount": { + "value": "-3000", + "currency": { + "symbol": "STX", + "decimals": 6 + } + }, + "status": "success", + "account": { + "address": "SPV2YDN4RZ506655KZK493YKM31JQPKJ9NN3QCX9" + }, + "metadata": { + "sponsored": false + }, + "operation_identifier": { + "index": 0 + } +} +``` + +### Contract Call Operation + +Contract function invocation: + +```json +{ + "type": "contract_call", + "status": "success", + "account": { + "address": "SPV2YDN4RZ506655KZK493YKM31JQPKJ9NN3QCX9" + }, + "metadata": { + "contract_identifier": "SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.arkadiko-token", + "function_name": "transfer", + "args": [ + { + "hex": "0x01000000000000000000000002690ed9fe", + "name": "amount", + "repr": "u10352515582", + "type": "uint" + }, + { + "hex": "0x0516362f36a4c7ca0318a59fe6448fd3a0c32bda724d", + "name": "sender", + "repr": "'SPV2YDN4RZ506655KZK493YKM31JQPKJ9NN3QCX9", + "type": "principal" + }, + { + "hex": "0x05161740f4690c79466e3389a13476586e0cb3e1dfbf", + "name": "recipient", + "repr": "'SPBM1X391HWMCVHKH6GK8XJRDR6B7REZQYQ8KBCK", + "type": "principal" + }, + { + "hex": "0x09", + "name": "memo", + "repr": "none", + "type": "(optional (buff 34))" + } + ] + }, + "operation_identifier": { + "index": 1 + } +} +``` + +{% hint style="info" %} +The `args` array includes `repr` and `type` fields when `decode_clarity_values` is enabled in options. +{% endhint %} + +### Token Transfer Operation + +FT/NFT/STX transfers: + +```json +{ + "type": "token_transfer", + "amount": { + "value": "-10352515582", + "currency": { + "symbol": "", + "decimals": 0, + "metadata": { + "token_type": "ft", + "asset_identifier": "SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.arkadiko-token::diko" + } + } + }, + "status": "success", + "account": { + "address": "SPV2YDN4RZ506655KZK493YKM31JQPKJ9NN3QCX9" + }, + "operation_identifier": { + "index": 2 + } +} +``` + +### Contract Log Operation + +Print statements from contracts: + +```json +{ + "type": "contract_log", + "status": "success", + "metadata": { + "topic": "print", + "value": "0x09", + "contract_identifier": "SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.arkadiko-token" + }, + "operation_identifier": { + "index": 4 + } +} +``` diff --git a/docs/build/chainhooks/secrets.md b/docs/build/chainhooks/secrets.md new file mode 100644 index 0000000000..7e96e3de1e --- /dev/null +++ b/docs/build/chainhooks/secrets.md @@ -0,0 +1,63 @@ +--- +description: Rotate consumer secrets and validate every Chainhooks delivery +--- + +# Manage secrets + +{% hint style="info" %} +This page is migrated from the [Hiro documentation](https://docs.hiro.so/en/tools/chainhooks/secrets). +{% endhint %} + +## What you'll learn + +- Create/rotate a Chainhooks consumer secret. +- Validate webhook requests by checking the `Authorization` header. + +## Prerequisites + +- Hiro API key +- Node.js (server example uses Fastify). + +## Validating webhook requests with a consumer secret + +When you create a secret, our Chainhooks service attaches an `Authorization: Bearer ` header to every webhook attempt, giving you a simple shared-secret handshake. Here's how to get started: + +1. Rotate the secret with `rotateConsumerSecret` (or the `/chainhooks/{uuid}/secret` API) whenever you need to initialize or create a new token. +2. Reject webhook deliveries whose `Authorization` header does not equal `Bearer `. + +### Create/rotate consumer secret + +```ts +import { ChainhooksClient, CHAINHOOKS_BASE_URL } from '@hirosystems/chainhooks-client'; + +const client = new ChainhooksClient({ + baseUrl: CHAINHOOKS_BASE_URL.mainnet, // or .testnet / custom URL + apiKey: process.env.HIRO_API_KEY!, +}); + +// Store this value securely and use it to validate webhook requests +const secret = await client.rotateConsumerSecret().secret; +``` + +### Example Fastify server + +```ts +server.post('/webhook', async (request, reply) => { + if (!secret) { + reply.code(503).send({ error: 'consumer secret unavailable' }); + return; + } + + const authHeader = request.headers.authorization; + if (authHeader !== `Bearer ${secret}`) { + reply.code(401).send({ error: 'invalid consumer secret' }); + return; + } + + const event = request.body; + console.log(`received chainhook ${event.chainhook.uuid}`); + reply.code(204).send(); +}); + +await server.listen({ port: Number(process.env.PORT) || 3000 }); +``` diff --git a/docs/build/chainhooks/update.md b/docs/build/chainhooks/update.md new file mode 100644 index 0000000000..56562fe826 --- /dev/null +++ b/docs/build/chainhooks/update.md @@ -0,0 +1,76 @@ +--- +description: Modify existing chainhooks +--- + +# Edit chainhooks + +{% hint style="info" %} +This page is migrated from the [Hiro documentation](https://docs.hiro.so/en/tools/chainhooks/update). +{% endhint %} + +{% hint style="info" %} +The Platform UI does not currently support editing chainhooks. You must use the SDK or API to make updates. +{% endhint %} + +With `updateChainhook`, you can adjust an existing definition without downtime: change the webhook URL, capture new events, or fine-tune configuration options as requirements evolve. + +## updateChainhook + +### Basic Update Example + +```typescript +import { ChainhooksClient, CHAINHOOKS_BASE_URL } from '@hirosystems/chainhooks-client'; + +const client = new ChainhooksClient({ + baseUrl: CHAINHOOKS_BASE_URL.testnet, + apiKey: process.env.HIRO_API_KEY!, +}); + +await client.updateChainhook('chainhook-uuid', { + name: 'Updated chainhook name', + filters: { + events: [{ type: 'ft_transfer', asset_identifier: 'SP...ABC.token::usdc' }], + }, +}); +``` + +### Add event filter (while preserving existing events) + +In order to add a new event filter to an existing chainhook, you can fetch the current definition, modify it, and then update it. + +```typescript +// Good: Fetch first +const current = await client.getChainhook(uuid); +await client.updateChainhook('chainhook-uuid', { + filters: { + events: [ + ...(current.definition.filters.events ?? []), + { type: 'contract_call', contract_identifier: 'SP...XYZ.counter' }, + ], + }, +}); + +// Bad: Will overwrite any existing events +await client.updateChainhook(uuid, { + filters: { events: { type: 'contract_call', contract_identifier: 'SP...XYZ.counter' } }, +}); +``` + +### Update Multiple Fields + +```typescript +await client.updateChainhook('chainhook-uuid', { + name: 'Updated name', + filters: { events: [{ type: 'stx_transfer', sender: 'SP...SENDER' }] }, + action: { type: 'http_post', url: 'https://new-url.com/webhooks' }, + options: { decode_clarity_values: true }, +}); + +const updated = await client.getChainhook('chainhook-uuid'); +console.log('Updated:', updated.definition.name); +``` + +## Next steps + +- [Get chainhooks](fetch.md): Retrieve chainhook information before updating +- [Filter Reference](reference/filters.md): Explore all filter options diff --git a/docs/build/get-started/clarity-crash-course.md b/docs/build/get-started/clarity-crash-course.md index 983c5dd94a..3841ac48ec 100644 --- a/docs/build/get-started/clarity-crash-course.md +++ b/docs/build/get-started/clarity-crash-course.md @@ -360,7 +360,7 @@ The [Contract Monitoring](https://platform.hiro.so/contract-monitoring) features Chainhooks -[Chainhooks](https://docs.hiro.so/tools/chainhook) indexes Stacks & Bitcoin data, exposes queryable APIs, tracks contract state and transactions. You simply describe your chainhook filters and consume webhooks. The service handles queueing, retries, tier limits, observability, and parity between testnet and mainnet. +[Chainhooks](../chainhooks/overview.md) indexes Stacks & Bitcoin data, exposes queryable APIs, tracks contract state and transactions. You simply describe your chainhook filters and consume webhooks. The service handles queueing, retries, tier limits, observability, and parity between testnet and mainnet.