synapsecns/sanguine

View on GitHub
contrib/opbot/botmd/botmd.go

Summary

Maintainability
A
50 mins
Test Coverage
package botmd

import (
    "context"
    "fmt"

    "github.com/slack-io/slacker"
    "github.com/synapsecns/sanguine/contrib/opbot/config"
    "github.com/synapsecns/sanguine/contrib/opbot/internal"
    "github.com/synapsecns/sanguine/contrib/opbot/signoz"
    screenerClient "github.com/synapsecns/sanguine/contrib/screener-api/client"
    "github.com/synapsecns/sanguine/core/dbcommon"
    "github.com/synapsecns/sanguine/core/metrics"
    "github.com/synapsecns/sanguine/core/metrics/instrumentation/slackertrace"
    experimentalLogger "github.com/synapsecns/sanguine/core/metrics/logger"
    signerConfig "github.com/synapsecns/sanguine/ethergo/signer/config"
    "github.com/synapsecns/sanguine/ethergo/signer/signer"
    "github.com/synapsecns/sanguine/ethergo/submitter"
    cctpSql "github.com/synapsecns/sanguine/services/cctp-relayer/db/sql"
    omnirpcClient "github.com/synapsecns/sanguine/services/omnirpc/client"
    "github.com/uptrace/opentelemetry-go-extra/otelzap"
    "golang.org/x/sync/errgroup"
)

// Bot represents the bot server.
type Bot struct {
    handler       metrics.Handler
    server        *slacker.Slacker
    cfg           config.Config
    signozClient  *signoz.Client
    signozEnabled bool
    rpcClient     omnirpcClient.RPCClient
    rfqClient     internal.RFQClient
    signer        signer.Signer
    submitter     submitter.TransactionSubmitter
    screener      screenerClient.ScreenerClient
    logger        experimentalLogger.ExperimentalLogger
}

// NewBot creates a new bot server.
func NewBot(handler metrics.Handler, cfg config.Config) *Bot {
    server := slacker.NewClient(cfg.SlackBotToken, cfg.SlackAppToken)

    sugaredLogger := otelzap.New(experimentalLogger.MakeZapLogger()).Sugar()

    bot := Bot{
        handler:   handler,
        cfg:       cfg,
        server:    server,
        logger:    experimentalLogger.MakeWrappedSugaredLogger(sugaredLogger),
        rfqClient: internal.NewRFQClient(handler, cfg.RFQIndexerAPIURL),
    }

    // you should be able to run opbot even without signoz.
    if cfg.SignozPassword != "" && cfg.SignozEmail != "" && cfg.SignozBaseURL != "" {
        bot.signozClient = signoz.NewClientFromUser(handler, cfg.SignozBaseURL, cfg.SignozEmail, cfg.SignozPassword)
        bot.signozEnabled = true
    }

    bot.rpcClient = omnirpcClient.NewOmnirpcClient(cfg.OmniRPCURL, handler, omnirpcClient.WithCaptureReqRes())

    bot.addMiddleware(slackertrace.TracingMiddleware(handler), slackertrace.MetricsMiddleware())
    bot.addCommands(bot.traceCommand(), bot.rfqLookupCommand(), bot.rfqRefund())

    return &bot
}

func (b *Bot) addMiddleware(middlewares ...slacker.CommandMiddlewareHandler) {
    for _, middleware := range middlewares {
        b.server.AddCommandMiddleware(middleware)
    }
}

func (b *Bot) addCommands(commands ...*slacker.CommandDefinition) {
    for _, command := range commands {
        b.server.AddCommand(command)
    }
}

// Start starts the bot server.
// nolint: wrapcheck
func (b *Bot) Start(ctx context.Context) (err error) {
    if err := b.cfg.Validate(); err != nil {
        return fmt.Errorf("config validation failed: %w", err)
    }

    b.signer, err = signerConfig.SignerFromConfig(ctx, b.cfg.Signer)
    if err != nil {
        return fmt.Errorf("failed to create signer: %w", err)
    }

    dbType, err := dbcommon.DBTypeFromString(b.cfg.Database.Type)
    if err != nil {
        return fmt.Errorf("could not get db type: %w", err)
    }

    store, err := cctpSql.Connect(ctx, dbType, b.cfg.Database.DSN, b.handler)
    if err != nil {
        return fmt.Errorf("could not connect to database: %w", err)
    }

    b.submitter = submitter.NewTransactionSubmitter(b.handler, b.signer, b.rpcClient, store.SubmitterDB(), &b.cfg.SubmitterConfig)

    screenerClient, err := screenerClient.NewClient(b.handler, b.cfg.ScreenerURL)
    if err != nil {
        return fmt.Errorf("could not create screener client: %w", err)
    }
    b.screener = screenerClient

    g, ctx := errgroup.WithContext(ctx)
    g.Go(func() error {
        return b.submitter.Start(ctx)
    })

    g.Go(func() error {
        return b.server.Listen(ctx)
    })

    // nolint: wrapcheck
    return g.Wait()
}