ChainIO Module

The BVS SDK consists of several components, with ChainIO being the primary one—and likely the only one you’ll interact with. ChainIO acts as an abstraction layer over the base CosmWasm API used for communicating with a CosmWasm node. It simplifies tasks like data querying, contract execution, and event indexing/sequencing. ChainIO is the main interface for interacting with a BVS contract.

Creating a context

The BVS SDK makes heavy use of Golang's gorutines, channels and thus the context package. We recommend using context.Background as it fits most usage scenarios.

import "context"
ctx := context.Background()

ChainIO

To use ChainIO against the SatLayer testnet, initialize a CosmWasm client pointing at our network with your keys.

import "github.com/satlayer/satlayer-api/signer"
client, err := signer.NewCosmosClient(
	"sat-bbn-testnet1",		// Chain-id. For SatLayer testnet it's sat-bbn-testnet1
	"https://rpc.satlayer.net",	// URL to an RPC node
	".",				// Directory that stores the key. Only used if the keyring needs it
	"your-key-name-here",		// name of the key
	"test",				// Keyring backend. Valid values includes test, os, kwallet or file
	"bbn" 				// Address prefix of the network
)

Querying contract state

The QueryContract method allows you to query contract states (equivalent to the command babylond query wasm contract-state smart <contract-address> <query-data> .

QueryContract(cosmosClient *signer.CosmosClient, opts types.QueryOptions) (*wasmtypes.QuerySmartContractStateResponse, error)

In which the QueryOptions struct is defined as follows.

type QueryOptions struct {
    ContractAddr string // Address of the smart contract
    QueryMsg     []byte // JSON query converted to bytes
}

Executing contract

The ExecuteContract method runs a contract. It works exactly like QueryContract but with options to configure gas, fees and more. It is equivalent to the babylond tx wasm execute command

ExecuteContract(cosmosClient *signer.CosmosClient, opts types.ExecuteOptions) (*sdktypes.TxResponse, error)

The ExecuteOptions is defined as follow:

type ExecuteOptions struct {
	ContractAddr  string  // Address of the smart contract
	ExecuteMsg    []byte  // Message to be executed, represented as a struct
	Funds         string  // Amount of funds to send to the contract, represented as a string
	GasAdjustment float64 // Gas adjustment factor for adjusting the estimated gas amount
	GasPrice      string  // Gas price, represented as a string (ex. "1bbn")
	Gas           uint64  // Amount of gas reserved for transaction execution
	Memo          string  // Transaction memo (if any, could be empty)
	Simulate      bool    // Whether to dry-run the transaction. Useful to get an estimate of the needed gas amount
}

Alternative to ExecuteContract, the SendTranscation function does the same thing but it provides automatic retry and observation. Depending on the use case, the SendTransaction function could be a better option.

SendTransaction(ctx context.Context, opts types.ExecuteOptions) (*coretypes.ResultTx, error)

Querying node status

The QueryNodeStatus method helps you check if a node is online and healthy.

QueryNodeStatus(cosmosClient *signer.CosmosClient) (*coretypes.ResultStatus, error)

Query a historical transaction

The QueryTransaction method queries a past transaction and returns the transaction result.

Not all nodes are so called "archiver" nodes, which stores and indexes all past transactions. By default, nodes will forget transactions beyond a predefined time in order to save disk space. Within the official SatLayer testnet nodes, only rpc.satlayer.net is an archiver. Expect querying of old transactions to fail against a non archiver.

QueryTransaction(cosmosClient *signer.CosmosClient, txHash string) (*coretypes.ResultTx, error)

Listening for events

A key function of ChainIO is listening for events generated by a given contract, which is essential for tracking task generations and submissions within a BVS. Setting up an event listener is a bit more complicated but remains straightforward. Let’s assume the cosmosClient from previous examples is still accessible.

res, err := cosmosClient.ClientCtx.Client.Status(ctx)
if err != nil {
    panic(err)
}

latestBlock := res.SyncInfo.LatestBlockHeight
evtIndexer := indexer.NewEventIndexer(
    cosmosClient.ClientCtx,                // client context
    avsDriverContract,                     // Address of your AVS driver contact
    latestBlock,                           // We start listening from the last known blokc
    []string{"wasm-ExecuteBVSOffchain"},   // List of events to listen to (usually prefixed w/ wasm-)
    1,                                     // Rate-lmiting, max events per second
    10                                     // How many retries (aginst the node) is allowed
)

evtChain, err := evtIndexer.Run(ctx)
if err != nil {
    panic(err)
}

// Now read from the channel 
for evt := range evtChain {
    switch evt.EventType {
    case "wasm-ExecuteBVSOffchain":
        do_processing_for_bvs_task()
        // taskId := evt.AttrMap["taskId"] // ..for example
        // fmt.Println("taskId: ", taskId)
    default:
        fmt.Println("unhandled event: ", evt.EventType)
    }
}

Contract API wrappers

ChainIO also provides abstractions of SatLayer core contacts by allowing programmatic execution and query of the core contracts. These are simple, strongly-typed wrappers for ease of use.

Wrappers are available for:

  • BVS Directory

  • Delegation Manager

  • Rewards Coordinator

  • Slash Manager

  • State Bank

  • Strategy Base/TVL Limits/Factory/Manager

To access them, run:

import "github.com/satlayer/satlayer-sdk/chainio/api"

Please refer to the SatLayer Core Contracts section for what each contract does.

Logger

The BVS SDK includes a generic logger that is integrated into components of the SDK.

The following snippet shows how the logger can be used.

package main

import "github.com/satlayer/satlayer-sdk/logger"

func main() {
    l := logging.NewELKLogger("bvs1")
    l.SetLogLevel("debug")
    // info demo
    l.INFO("this is a info log test")
    // warn demo
    l.WARN("this is a warn log test")
    // error demo
    l.ERROR("this is a error log test",
        logging.WithFiled("age", 100),
        logging.WithFiled("gender", "man"),
    )
    // debug demo
    l.DEBUG("this is a debug log test")
    // fatal demo
    l.FATAL("this is a fatal log test")
}

Last updated