Skip to content

tony1908/chainhooks-client-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Chainhooks Client - Go

A comprehensive Go client library for interacting with the Hiro Chainhooks API. Chainhooks is a blockchain monitoring and event triggering system for the Stacks blockchain that enables developers to register webhooks that detect and react to specific blockchain events.

Features

  • âś… Full support for all Chainhooks API endpoints
  • âś… Complete type safety with Go structs
  • âś… Support for 16 different blockchain event types
  • âś… Flexible authentication (API Key and JWT)
  • âś… Pagination support for list operations
  • âś… Builder pattern for easy chainhook definition construction
  • âś… Comprehensive error handling with HTTP context
  • âś… Context support for cancellation and timeouts
  • âś… Minimal dependencies (uses only Go standard library)

Installation

go get github.com/tony1908/chainhooks-client-go

Quick Start

Basic Usage

package main

import (
	"context"
	"log"

	"github.com/tony1908/chainhooks-client-go"
)

func main() {
	// Create a client
	client := chainhooks.NewClient(chainhooks.ChainhooksBaseURLs[chainhooks.NetworkMainnet])
	client.SetAPIKey("your-api-key")

	// Create a simple chainhook definition
	definition := &chainhooks.ChainhookDefinition{
		Name:    "my-stx-hook",
		Version: "1",
		Chain:   chainhooks.ChainStacks,
		Network: chainhooks.NetworkMainnet,
		Filters: chainhooks.ChainhookFilters{
			Events: []interface{}{
				&chainhooks.STXTransferFilter{
					Type: chainhooks.EventTypeSTXTransfer,
				},
			},
		},
		Action: chainhooks.ChainhookAction{
			Type: "http_post",
			URL:  "https://example.com/webhook",
		},
	}

	// Register the chainhook
	ctx := context.Background()
	hook, err := client.RegisterChainhook(ctx, definition)
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Chainhook registered: %s", hook.UUID)
}

Using the Builder Pattern

// Create a chainhook using the fluent builder API
definition, err := chainhooks.NewChainhookBuilder("stx-transfer-hook", chainhooks.NetworkMainnet).
	WithWebhookURL("https://example.com/webhook").
	AddSTXTransfer(
		chainhooks.PrincipalStandard("SP..."),
		chainhooks.PrincipalStandard("SP..."),
		chainhooks.StringPtr("1000000"),
	).
	WithEnableOnRegistration(true).
	Build()

if err != nil {
	log.Fatal(err)
}

hook, err := client.RegisterChainhook(context.Background(), definition)
if err != nil {
	log.Fatal(err)
}

Client Configuration

Basic Client

client := chainhooks.NewClient(
	chainhooks.ChainhooksBaseURLs[chainhooks.NetworkMainnet],
)

Client with Configuration

client := chainhooks.NewClientWithConfig(&chainhooks.ClientConfig{
	BaseURL:   "https://api.mainnet.hiro.so",
	APIKey:    chainhooks.StringPtr("your-api-key"),
	JWT:       chainhooks.StringPtr("your-jwt-token"),
	Timeout:   30 * time.Second,
	UserAgent: "my-app/1.0.0",
})

Setting Authentication

client.SetAPIKey("your-api-key")
// OR
client.SetJWT("your-jwt-token")
// OR both
client.SetAPIKey("your-api-key")
client.SetJWT("your-jwt-token")

API Methods

Chainhook Management

Register a Chainhook

definition := &chainhooks.ChainhookDefinition{
	Name:    "my-hook",
	Version: "1",
	Chain:   chainhooks.ChainStacks,
	Network: chainhooks.NetworkMainnet,
	Filters: chainhooks.ChainhookFilters{
		Events: []interface{}{
			&chainhooks.STXTransferFilter{
				Type: chainhooks.EventTypeSTXTransfer,
			},
		},
	},
	Action: chainhooks.ChainhookAction{
		Type: "http_post",
		URL:  "https://example.com/webhook",
	},
}

hook, err := client.RegisterChainhook(context.Background(), definition)

Get Chainhook

hook, err := client.GetChainhook(context.Background(), "uuid-string")

List Chainhooks (with pagination)

opts := &chainhooks.PaginationOptions{
	Offset: 0,
	Limit:  10,
}

response, err := client.GetChainhooks(context.Background(), opts)
if err != nil {
	log.Fatal(err)
}

for _, hook := range response.Chainhooks {
	log.Printf("Hook: %s (Status: %s)", hook.UUID, hook.Status.Status)
}

Update Chainhook

Note: Update requires a complete chainhook definition with all required fields (name, version, chain, network, filters, action).

updated, err := chainhooks.NewChainhookBuilder("updated-name", chainhooks.NetworkMainnet).
	WithWebhookURL("https://example.com/webhook-updated").
	AddSTXTransfer(nil, nil, nil).
	Build()

if err != nil {
	log.Fatal(err)
}

hook, err := client.UpdateChainhook(context.Background(), "uuid-string", updated)

Enable/Disable Chainhook

// Enable
err := client.EnableChainhook(context.Background(), "uuid-string", true)

// Disable
err := client.EnableChainhook(context.Background(), "uuid-string", false)

Bulk Enable/Disable

// By UUIDs
request := chainhooks.BulkEnableUUIDs(
	true,
	chainhooks.UUID("uuid-1"),
	chainhooks.UUID("uuid-2"),
)

response, err := client.BulkEnableChainhooks(context.Background(), request)

// By Webhook URL
request := chainhooks.BulkEnableByWebhook(
	false,
	"https://example.com/webhook",
)

// By Status
request := chainhooks.BulkEnableByStatus(
	true,
	chainhooks.ChainhookStatusStreaming,
)

Delete Chainhook

err := client.DeleteChainhook(context.Background(), "uuid-string")

Consumer Secrets

Get Consumer Secret

secret, err := client.GetConsumerSecret(context.Background())
log.Println(secret.Secret)

Rotate Consumer Secret

secret, err := client.RotateConsumerSecret(context.Background())
log.Println(secret.Secret)

Delete Consumer Secret

err := client.DeleteConsumerSecret(context.Background())

Evaluation

Evaluate Chainhook

err := client.EvaluateChainhook(context.Background(), "uuid-string", 100000)

API Status

Get Status

status, err := client.GetStatus(context.Background())
log.Printf("Status: %s, Version: %s", status.Status, status.Version)

Event Types

The client supports 16 different blockchain event types:

Token Events

// Fungible Token Transfer
&chainhooks.FTTransferFilter{
	Type:      chainhooks.EventTypeFTTransfer,
	Asset:     "USDA",
	Sender:    chainhooks.PrincipalStandard("SP..."),
	Recipient: chainhooks.PrincipalStandard("SP..."),
	Amount:    chainhooks.StringPtr("1000000"),
}

// Fungible Token Mint
&chainhooks.FTMintFilter{
	Type:      chainhooks.EventTypeFTMint,
	Asset:     "USDA",
	Recipient: chainhooks.PrincipalStandard("SP..."),
	Amount:    chainhooks.StringPtr("1000000"),
}

// Fungible Token Burn
&chainhooks.FTBurnFilter{
	Type:   chainhooks.EventTypeFTBurn,
	Asset:  "USDA",
	Sender: chainhooks.PrincipalStandard("SP..."),
	Amount: chainhooks.StringPtr("1000000"),
}

NFT Events

// NFT Transfer
&chainhooks.NFTTransferFilter{
	Type:      chainhooks.EventTypeNFTTransfer,
	Asset:     "nft-collection",
	Sender:    chainhooks.PrincipalStandard("SP..."),
	Recipient: chainhooks.PrincipalStandard("SP..."),
}

// NFT Mint
&chainhooks.NFTMintFilter{
	Type:      chainhooks.EventTypeNFTMint,
	Asset:     "nft-collection",
	Recipient: chainhooks.PrincipalStandard("SP..."),
}

// NFT Burn
&chainhooks.NFTBurnFilter{
	Type:   chainhooks.EventTypeNFTBurn,
	Asset:  "nft-collection",
	Sender: chainhooks.PrincipalStandard("SP..."),
}

STX (Native Token) Events

// STX Transfer
&chainhooks.STXTransferFilter{
	Type:      chainhooks.EventTypeSTXTransfer,
	Sender:    chainhooks.PrincipalStandard("SP..."),
	Recipient: chainhooks.PrincipalStandard("SP..."),
	Amount:    chainhooks.StringPtr("1000000"),
}

// STX Mint
&chainhooks.STXMintFilter{
	Type:      chainhooks.EventTypeSTXMint,
	Recipient: chainhooks.PrincipalStandard("SP..."),
	Amount:    chainhooks.StringPtr("1000000"),
}

// STX Burn
&chainhooks.STXBurnFilter{
	Type:   chainhooks.EventTypeSTXBurn,
	Sender: chainhooks.PrincipalStandard("SP..."),
	Amount: chainhooks.StringPtr("1000000"),
}

Smart Contract Events

// Contract Deployment
&chainhooks.ContractDeployFilter{
	Type:              chainhooks.EventTypeContractDeploy,
	DeployerPrincipal: chainhooks.PrincipalStandard("SP..."),
}

// Contract Call
&chainhooks.ContractCallFilter{
	Type:               chainhooks.EventTypeContractCall,
	ContractIdentifier: chainhooks.StringPtr("SP...contract_name"),
	Method:             chainhooks.StringPtr("method_name"),
	Sender:             chainhooks.PrincipalStandard("SP..."),
}

// Contract Log (emit events)
&chainhooks.ContractLogFilter{
	Type:               chainhooks.EventTypeContractLog,
	ContractIdentifier: chainhooks.StringPtr("SP...contract_name"),
}

System Events

// Balance Change
&chainhooks.BalanceChangeFilter{
	Type:      chainhooks.EventTypeBalanceChange,
	Principal: chainhooks.PrincipalStandard("SP..."),
}

// Coinbase (mining rewards)
&chainhooks.CoinbaseFilter{
	Type:      chainhooks.EventTypeCoinbase,
	Recipient: chainhooks.PrincipalStandard("SP..."),
}

// Tenure Change
&chainhooks.TenureChangeFilter{
	Type: chainhooks.EventTypeTenureChange,
}

Builder Pattern Examples

Complex Chainhook with Multiple Filters

definition, err := chainhooks.NewChainhookBuilder(
	"multi-filter-hook",
	chainhooks.NetworkMainnet,
).
	WithWebhookURL("https://example.com/webhook").
	AddSTXTransfer(
		chainhooks.PrincipalStandard("SP..."),
		nil,
		chainhooks.StringPtr("1000000"),
	).
	AddFTTransfer(
		"USDA",
		chainhooks.PrincipalStandard("SP..."),
		nil,
		nil,
	).
	AddContractCall(
		chainhooks.StringPtr("SP...my_contract"),
		chainhooks.StringPtr("swap"),
		nil,
	).
	WithEnableOnRegistration(true).
	WithExpireAfterOccurrences(100).
	WithIncludeContractABI(true).
	WithDecodeClarityValues(true).
	Build()

if err != nil {
	log.Fatal(err)
}

Using ChainhookOptionsBuilder

options := chainhooks.NewChainhookOptionsBuilder().
	EnableOnRegistration(true).
	ExpireAfterEvaluations(10000).
	DecodeClarityValues(true).
	IncludeContractABI(true).
	Build()

definition := &chainhooks.ChainhookDefinition{
	Name:    "my-hook",
	Version: "1",
	Chain:   chainhooks.ChainStacks,
	Network: chainhooks.NetworkMainnet,
	Filters: chainhooks.ChainhookFilters{
		Events: []interface{}{
			&chainhooks.STXTransferFilter{
				Type: chainhooks.EventTypeSTXTransfer,
			},
		},
	},
	Action: chainhooks.ChainhookAction{
		Type: "http_post",
		URL:  "https://example.com/webhook",
	},
	Options: options,
}

Error Handling

The client provides robust error handling with helpful utilities:

hook, err := client.GetChainhook(ctx, "uuid")
if err != nil {
	// Check if it's an HTTP error
	if chainhooks.IsHttpError(err) {
		httpErr, _ := chainhooks.AsHttpError(err)
		log.Printf("HTTP Error: %d %s", httpErr.StatusCode, httpErr.Body)
	}

	// Check specific error conditions
	if chainhooks.IsNotFound(err) {
		log.Println("Chainhook not found")
	} else if chainhooks.IsUnauthorized(err) {
		log.Println("Unauthorized - check API key")
	} else if chainhooks.IsServerError(err) {
		log.Println("Server error - retry later")
	}
}

Error Types

  • HttpError - HTTP request/response errors with full context
  • ValidationError - Validation errors when building requests
  • ConfigError - Configuration errors

Helper Functions

The client includes several utility functions for common tasks:

// Create pointers to basic types
name := chainhooks.StringPtr("my-hook")
enabled := chainhooks.BoolPtr(true)
count := chainhooks.Uint64Ptr(100)

// Create principals
standardAddr := chainhooks.PrincipalStandard("SP...")
contractAddr := chainhooks.PrincipalContract("SP...contract_name")

// Create pagination options
opts := chainhooks.NewPaginationOptions(0, 10)

// Bulk operation helpers
bulkByUUIDs := chainhooks.BulkEnableUUIDs(true, uuid1, uuid2)
bulkByWebhook := chainhooks.BulkEnableByWebhook(false, "https://example.com/webhook")
bulkByStatus := chainhooks.BulkEnableByStatus(true, chainhooks.ChainhookStatusStreaming)

// Error checking helpers
isNotFound := chainhooks.IsNotFound(err)
isUnauth := chainhooks.IsUnauthorized(err)
isServer := chainhooks.IsServerError(err)
isClient := chainhooks.IsClientError(err)

Response Structure

When you retrieve a chainhook, the API returns a nested structure:

type Chainhook struct {
	UUID       UUID                 `json:"uuid"`
	Definition *ChainhookDefinition `json:"definition"`
	Status     ChainhookStatusInfo  `json:"status"`
}

type ChainhookStatusInfo struct {
	Status                    ChainhookStatus `json:"status"`
	Enabled                   bool            `json:"enabled"`
	CreatedAt                 int64           `json:"created_at"`
	LastEvaluatedAt           *int64          `json:"last_evaluated_at"`
	LastEvaluatedBlockHeight  *uint64         `json:"last_evaluated_block_height"`
	LastOccurrenceAt          *int64          `json:"last_occurrence_at"`
	LastOccurrenceBlockHeight *uint64         `json:"last_occurrence_block_height"`
	EvaluatedBlockCount       uint64          `json:"evaluated_block_count"`
	OccurrenceCount           uint64          `json:"occurrence_count"`
}

Access fields like this:

hook, err := client.GetChainhook(ctx, uuid)
if err != nil {
	log.Fatal(err)
}

log.Printf("Name: %s", hook.Definition.Name)
log.Printf("Webhook URL: %s", hook.Definition.Action.URL)
log.Printf("Status: %s", hook.Status.Status)
log.Printf("Enabled: %v", hook.Status.Enabled)
log.Printf("Created: %d", hook.Status.CreatedAt)
log.Printf("Occurrence count: %d", hook.Status.OccurrenceCount)

Type Reference

Networks

chainhooks.NetworkMainnet // "mainnet"
chainhooks.NetworkTestnet  // "testnet"

Chainhook Status

chainhooks.ChainhookStatusNew
chainhooks.ChainhookStatusStreaming
chainhooks.ChainhookStatusExpired
chainhooks.ChainhookStatusInterrupted

Event Types

See the Event Types section above for all 16 supported event types.

Context Support

All API methods support context for cancellation and timeouts:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

hook, err := client.GetChainhook(ctx, "uuid")

Thread Safety

The client is safe for concurrent use across multiple goroutines, as each request creates its own HTTP request objects.

Base URLs

chainhooks.ChainhooksBaseURLs[chainhooks.NetworkMainnet]
// Returns: "https://api.mainnet.hiro.so"

chainhooks.ChainhooksBaseURLs[chainhooks.NetworkTestnet]
// Returns: "https://api.testnet.hiro.so"

Examples

Complete Example: Create and Monitor a Chainhook

package main

import (
	"context"
	"log"
	"time"

	"github.com/tony1908/chainhooks-client-go"
)

func main() {
	// Create client
	client := chainhooks.NewClientWithConfig(&chainhooks.ClientConfig{
		BaseURL: chainhooks.ChainhooksBaseURLs[chainhooks.NetworkTestnet],
	})
	client.SetAPIKey("your-api-key")

	ctx := context.Background()

	// Define chainhook
	definition, err := chainhooks.NewChainhookBuilder(
		"testnet-stx-monitor",
		chainhooks.NetworkTestnet,
	).
		WithWebhookURL("https://example.com/webhook").
		AddSTXTransfer(nil, nil, nil).
		WithEnableOnRegistration(true).
		WithDecodeClarityValues(true).
		Build()

	if err != nil {
		log.Fatal(err)
	}

	// Register chainhook
	hook, err := client.RegisterChainhook(ctx, definition)
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Registered chainhook: %s", hook.UUID)
	log.Printf("Status: %s, Enabled: %v", hook.Status.Status, hook.Status.Enabled)

	// List chainhooks
	response, err := client.GetChainhooks(ctx, &chainhooks.PaginationOptions{
		Offset: 0,
		Limit:  10,
	})
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Total chainhooks: %d", response.Total)

	// Get specific chainhook
	time.Sleep(1 * time.Second)
	hook, err = client.GetChainhook(ctx, hook.UUID)
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Latest status: %s", hook.Status.Status)

	// Disable chainhook
	err = client.EnableChainhook(ctx, hook.UUID, false)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("Chainhook disabled")

	// Clean up
	err = client.DeleteChainhook(ctx, hook.UUID)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("Chainhook deleted")
}

Contributing

Contributions are welcome! Please feel free to submit a pull request.

License

Apache-2.0

Resources

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages