func NewWatcher(logger logrus.FieldLogger, network multichain.Network, selector tx.Selector, bindings binding.Bindings, burnLogFetcher BurnLogFetcher, blockHeightFetcher BlockHeightFetcher, resolver jsonrpc.Resolver, cache redis.Cmdable, pollInterval time.Duration, maxBlockAdvance uint64, confidenceInterval uint64) Watcher {
	return Watcher{
		logger:             logger,
		network:            network,
		selector:           selector,
		bindings:           bindings,
		burnLogFetcher:     burnLogFetcher,
		blockHeightFetcher: blockHeightFetcher,
		resolver:           resolver,
		cache:              cache,
		pollInterval:       pollInterval,
		maxBlockAdvance:    maxBlockAdvance,
		confidenceInterval: confidenceInterval,
	}
}
NewWatcher returns a new Watcher.
package watcher
import (
	"context"
	"encoding/binary"
	"fmt"
	"time"
	"github.com/btcsuite/btcd/chaincfg"
	solanaSDK "github.com/dfuse-io/solana-go"
	solanaRPC "github.com/dfuse-io/solana-go/rpc"
	"github.com/ethereum/go-ethereum/accounts/abi/bind"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/go-redis/redis/v7"
	"github.com/jbenet/go-base58"
	"github.com/near/borsh-go"
	"github.com/renproject/darknode/binding"
	"github.com/renproject/darknode/binding/gatewaybinding"
	"github.com/renproject/darknode/binding/solanastate"
	"github.com/renproject/darknode/engine"
	"github.com/renproject/darknode/jsonrpc"
	"github.com/renproject/darknode/tx"
	v0 "github.com/renproject/lightnode/compat/v0"
	"github.com/renproject/multichain"
	"github.com/renproject/multichain/chain/bitcoin"
	"github.com/renproject/multichain/chain/bitcoincash"
	"github.com/renproject/multichain/chain/digibyte"
	"github.com/renproject/multichain/chain/dogecoin"
	"github.com/renproject/multichain/chain/filecoin"
	"github.com/renproject/multichain/chain/solana"
	"github.com/renproject/multichain/chain/terra"
	"github.com/renproject/multichain/chain/zcash"
	"github.com/renproject/pack"
	"github.com/sirupsen/logrus"
)
type BurnInfo struct {
	Txid        pack.Bytes
	Amount      pack.U256
	ToBytes     []byte
	Nonce       pack.Bytes32
	BlockNumber pack.U64
}
type BurnLogResult struct {
	Result BurnInfo
	Error  error
}
type BurnLogFetcher interface {
	FetchBurnLogs(ctx context.Context, from uint64, to uint64) (chan BurnLogResult, error)
}
type EthBurnLogFetcher struct {
	bindings *gatewaybinding.MintGatewayLogicV1
}
func NewEthBurnLogFetcher(bindings *gatewaybinding.MintGatewayLogicV1) EthBurnLogFetcher {
	return EthBurnLogFetcher{
		bindings: bindings,
	}
}
func (fetcher EthBurnLogFetcher) FetchBurnLogs(ctx context.Context, from uint64, to uint64) (chan BurnLogResult, error) {
	iter, err := fetcher.bindings.FilterLogBurn(
		&bind.FilterOpts{
			Context: ctx,
			Start:   from,
			End:     &to,
		},
		nil,
		nil,
	)
	if err != nil {
		return nil, err
	}
	resultChan := make(chan BurnLogResult)
	go func() {
		func() {
			defer close(resultChan)
			for iter.Next() {
				nonce := iter.Event.N.Uint64()
				var nonceBytes pack.Bytes32
				copy(nonceBytes[:], pack.NewU256FromU64(pack.NewU64(nonce)).Bytes())
				result := BurnInfo{
					Txid:        iter.Event.Raw.TxHash.Bytes(),
					Amount:      pack.NewU256FromInt(iter.Event.Amount),
					ToBytes:     iter.Event.To,
					Nonce:       nonceBytes,
					BlockNumber: pack.NewU64(iter.Event.Raw.BlockNumber),
				}
				select {
				case <-ctx.Done():
					resultChan <- BurnLogResult{Error: ctx.Err()}
					return
				default:
					resultChan <- BurnLogResult{Result: result}
				}
			}
		}()
		err = iter.Close()
		if err != nil {
			resultChan <- BurnLogResult{Error: err}
			return
		}
		err := iter.Error()
		if err != nil {
			resultChan <- BurnLogResult{Error: err}
			return
		}
	}()
	return resultChan, nil
}
type SolFetcher struct {
	client           *solanaRPC.Client
	gatewayStatePubk solanaSDK.PublicKey
	gatewayAddress   string
}
func NewSolFetcher(client *solanaRPC.Client, gatewayAddress string) SolFetcher {
	seeds := []byte("GatewayStateV0.1.4")
	programDerivedAddress := solana.ProgramDerivedAddress(pack.Bytes(seeds), multichain.Address(gatewayAddress))
	programPubk, err := solanaSDK.PublicKeyFromBase58(string(programDerivedAddress))
	if err != nil {
		panic("invalid pubk")
	}
	return SolFetcher{
		client:           client,
		gatewayStatePubk: programPubk,
		gatewayAddress:   gatewayAddress,
	}
}
func (fetcher SolFetcher) FetchBurnLogs(ctx context.Context, from uint64, to uint64) (chan BurnLogResult, error) {
	resultChan := make(chan BurnLogResult)
	go func() {
		defer close(resultChan)
		for i := from; i < to; i++ {
			nonce := i
			b := make([]byte, 8)
			binary.LittleEndian.PutUint64(b, i)
			var nonceBytes pack.Bytes32
			copy(nonceBytes[:], pack.NewU256FromU64(pack.NewU64(nonce)).Bytes())
			burnLogDerivedAddress := solana.ProgramDerivedAddress(b, multichain.Address(fetcher.gatewayAddress))
			burnLogPubk, err := solanaSDK.PublicKeyFromBase58(string(burnLogDerivedAddress))
			if err != nil {
				resultChan <- BurnLogResult{Error: fmt.Errorf("getting burn log account: %v", err)}
				return
			}
			accountInfo, err := fetcher.client.GetAccountInfo(ctx, burnLogPubk)
			if err != nil {
				resultChan <- BurnLogResult{Error: fmt.Errorf("getting burn log data for burn: %v err: %v", i, err)}
				return
			}
			data := accountInfo.Value.Data
			amount, recipient, err := solanastate.DecodeBurnLog(data)
			if err != nil {
				resultChan <- BurnLogResult{Error: fmt.Errorf("failed to decode burn log :  %v", err)}
				return
			}
			signatures, err := fetcher.client.GetSignaturesForAddress(ctx, burnLogPubk, &solanaRPC.GetSignaturesForAddressOpts{})
			if err != nil {
				legacySignatures, err2 := fetcher.client.GetConfirmedSignaturesForAddress2(ctx, burnLogPubk, &solanaRPC.GetConfirmedSignaturesForAddress2Opts{})
				if err2 != nil {
					resultChan <- BurnLogResult{Error: fmt.Errorf("getting burn log txes: (current: %v) (legacy: %v)", err, err2)}
					return
				}
				signatures = solanaRPC.GetSignaturesForAddressResult(legacySignatures)
			}
			if len(signatures) == 0 {
				resultChan <- BurnLogResult{Error: fmt.Errorf("Burn signature not confirmed")}
				return
			}
			result := BurnInfo{
				Txid:        base58.Decode(signatures[0].Signature),
				Amount:      amount,
				ToBytes:     []byte(recipient),
				Nonce:       nonceBytes,
				BlockNumber: pack.NewU64(i),
			}
			select {
			case <-ctx.Done():
				resultChan <- BurnLogResult{Error: ctx.Err()}
				return
			default:
				resultChan <- BurnLogResult{Result: result}
			}
		}
	}()
	return resultChan, nil
}
type BlockHeightFetcher interface {
	FetchBlockHeight(ctx context.Context) (uint64, error)
}
type EthBlockHeightFetcher struct {
	client *ethclient.Client
}
func NewEthBlockHeightFetcher(ethClient *ethclient.Client) EthBlockHeightFetcher {
	return EthBlockHeightFetcher{
		client: ethClient,
	}
}
func (fetcher EthBlockHeightFetcher) FetchBlockHeight(ctx context.Context) (uint64, error) {
	currentBlock, err := fetcher.client.HeaderByNumber(ctx, nil)
	if err != nil {
		return 0, err
	}
	return currentBlock.Number.Uint64(), nil
}
func (fetcher SolFetcher) FetchBlockHeight(ctx context.Context) (uint64, error) {
	accountData, err := fetcher.client.GetAccountInfo(ctx, fetcher.gatewayStatePubk)
	if err != nil {
		return 0, fmt.Errorf("getting gateway data: %v", err)
	}
	gateway := solanastate.Gateway{}
	if err = borsh.Deserialize(&gateway, accountData.Value.Data); err != nil {
		return 0, fmt.Errorf("deserializing account data: %v", err)
	}
	return uint64(gateway.BurnCount) + 1, nil
}
type Watcher struct {
	network            multichain.Network
	logger             logrus.FieldLogger
	selector           tx.Selector
	bindings           binding.Bindings
	burnLogFetcher     BurnLogFetcher
	blockHeightFetcher BlockHeightFetcher
	resolver           jsonrpc.Resolver
	cache              redis.Cmdable
	pollInterval       time.Duration
	maxBlockAdvance    uint64
	confidenceInterval uint64
}