Commit fd48574b authored by luxq's avatar luxq

add backend code

parent e15355cc
.idea
build
data
FROM golang:1.24-alpine AS build
# Set up dependencies
ENV PACKAGES build-base
# Install dependencies
RUN apk add --update $PACKAGES
# Add source files
WORKDIR /build
COPY ./ /build/bridge-backend
RUN cd /build/bridge-backend && go mod tidy && go build -ldflags="-s -w" -o /tmp/bridgebackend ./cmd/backend
FROM alpine
WORKDIR /app
COPY ./config.toml /app/config.toml
COPY --from=build /tmp/bridgebackend /usr/bin/bridgebackend
EXPOSE 8080
\ No newline at end of file
.PHONY: default all clean
GOBIN = $(shell pwd)/build/bin
BUILD_FLAGS = -ldflags "-s -w"
default: bridgebackend
bridgebackend:
go build $(BUILD_FLAGS) -v -o=${GOBIN}/$@ ./cmd/backend
docker:
docker build --no-cache -t bridgebackend:latest -f ./Dockerfile .
docker tag bridgebackend:latest bridgebackend:$${TAG:-latest}
.PHONY: docker
start:
docker compose up -d
.PHONY: start
stop:
docker compose down
.PHONY: stop
clean:stop
sudo rm -rf data
.PHONY: clean
\ No newline at end of file
# bridge-backend # How to run
1. make docker
2. modify config.yml for chain and mysql password.
3. modify docker-compose.yml for mysql password.
4. docker compose up -d
5. docker compose down
# How to use
1. make docker
2. prepare a validator private key, write it to `val.pk`
3. prepare a aes key.
```
docker run -it -v "${PWD}:/app" --rm token-bridge:latest validator genkey --output aes.key
```
4. crypt the private key with aes key, named it as validator.fpk
```
docker run -it -v "${PWD}:/app" --rm token-bridge:latest validator encrypt --in val.pk --out val.fpk --aes aes.key
```
5. generate one-time-key for the validator.
```
docker run -it -v "${PWD}:/app" --rm token-bridge:latest validator onetime --in val.fpk --out val.otp
```
6. keep the `val.pk` safe and delete `val.pk`, `val.fpk` from the server. Must keep `aes.key` and `val.otp` exist.
7. start the validator service.
```
docker compose up -d
```
Tips: once you need restart the validator service, you need to generate a new one-time-key with the same command in step 5.
package chain
import (
"code.wuban.net.cn/movabridge/bridge-backend/config"
"code.wuban.net.cn/movabridge/bridge-backend/contract/bridge"
"code.wuban.net.cn/movabridge/bridge-backend/dao"
"context"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
log "github.com/sirupsen/logrus"
"math/big"
"strings"
"sync"
"time"
)
var _ dao.ChainInterface = (*ChainSync)(nil)
type ChainSync struct {
chain *config.ChainConfig
d *dao.Dao
name string
heightKey string
bridgeCa *bridge.BridgeContract
quit chan struct{}
stopOnce sync.Once
wg sync.WaitGroup
syncmode bool
}
func (s *ChainSync) ParseTransferOut(log types.Log) (*bridge.BridgeContractTransferOut, error) {
return s.bridgeCa.ParseTransferOut(log)
}
func (s *ChainSync) ParseTransferIn(log types.Log) (*bridge.BridgeContractTransferIn, error) {
return s.bridgeCa.ParseTransferIn(log)
}
func (s *ChainSync) ParseTransferInExecution(log types.Log) (*bridge.BridgeContractTransferInExecution, error) {
return s.bridgeCa.ParseTransferInExecution(log)
}
func (s *ChainSync) ParseTransferInRejection(log types.Log) (*bridge.BridgeContractTransferInRejection, error) {
return s.bridgeCa.ParseTransferInRejection(log)
}
func (s *ChainSync) ParseTransferInConfirmation(log types.Log) (*bridge.BridgeContractTransferInConfirmation, error) {
return s.bridgeCa.ParseTransferInConfirmation(log)
}
func (s *ChainSync) ParseTokenConfigChanged(log types.Log) (*bridge.BridgeContractTokenConfigChanged, error) {
return s.bridgeCa.ParseTokenConfigChanged(log)
}
func (s *ChainSync) GetReceiveToken(token common.Address, toChainId int64) (string, error) {
callOpt := &bind.CallOpts{
BlockNumber: nil,
From: common.HexToAddress(s.chain.BridgeContract),
Context: context.TODO(),
}
param, err := s.bridgeCa.OutConfiguration(callOpt, token, big.NewInt(toChainId))
if err != nil {
return "", err
}
return strings.ToLower(param.ReceiveToken.Hex()), nil
}
func NewChainSync(_chain *config.ChainConfig, _d *dao.Dao) (sync *ChainSync) {
bridgeCa, err := bridge.NewBridgeContract(common.HexToAddress(_chain.BridgeContract), _d.ChainClient(_chain.ChainId))
if err != nil {
panic(err)
}
sync = &ChainSync{
chain: _chain,
d: _d,
name: _chain.Name,
heightKey: fmt.Sprintf("%d_%s", _chain.ChainId, "height"),
bridgeCa: bridgeCa,
quit: make(chan struct{}),
syncmode: true,
}
return sync
}
func (s *ChainSync) loop() {
defer s.wg.Done()
finishedHeight, err := s.d.GetStorageHeight(s.heightKey)
if err != nil {
if err == dao.ErrRecordNotFound {
finishedHeight = s.chain.InitialHeight
} else {
log.WithField("chain", s.name).WithField("chain", s.name).WithError(err).Error("get last block height")
return
}
}
var latestHeight int64
var beginHeight = finishedHeight + 1
log.WithField("chain", s.name).WithField("begin height", beginHeight).Info("last backend block height")
tm := time.NewTicker(time.Second)
defer tm.Stop()
for {
select {
case <-s.quit:
log.WithField("chain", s.name).Info("chain sync stopped")
return
case <-tm.C:
var endHeight = beginHeight + int64(s.chain.BatchBlock)
latestHeight, err = s.d.GetBlockHeight(s.chain, s.chain.BehindBlock)
if err != nil {
log.WithField("chain", s.name).WithError(err).Error("get latest block height")
continue
}
if latestHeight <= beginHeight {
s.syncmode = false
continue
}
if beginTime, err := s.d.GetBlockTime(s.chain, beginHeight); err == nil {
blockTime := time.Unix(int64(beginTime), 0)
if time.Since(blockTime) < time.Minute {
s.syncmode = false
} else {
s.syncmode = true
}
log.WithField("chain", s.name).WithFields(log.Fields{
"begin height": beginHeight,
"block time": blockTime,
"sync mode": s.syncmode,
"sinceTime": time.Since(blockTime).String(),
}).Debug("begin block time")
}
if latestHeight < endHeight {
endHeight = latestHeight
}
if err := s.SyncLogs(beginHeight, endHeight); err != nil {
log.WithField("chain", s.name).WithFields(log.Fields{
"begin height": beginHeight,
"end height": endHeight,
}).WithError(err).Error("sync logs failed")
continue
}
if err = s.d.SetStorageHeight(s.heightKey, endHeight); err != nil {
log.WithField("chain", s.name).WithError(err).Error("set last block height")
}
log.WithField("chain", s.name).WithFields(log.Fields{
"begin height": beginHeight,
"end height": endHeight,
"latest height": latestHeight,
"diff height": latestHeight - endHeight,
}).Info("backend block")
beginHeight = endHeight + 1
}
}
}
func (s *ChainSync) Name() string {
return s.name
}
func (s *ChainSync) GetChain() *config.ChainConfig {
return s.chain
}
func (s *ChainSync) Start() {
s.wg.Add(1)
go s.loop()
}
func (s *ChainSync) Stop() {
close(s.quit)
s.wg.Wait()
}
func (s *ChainSync) SyncLogs(beginHeight, endHeight int64) error {
if endHeight < 0 {
return nil
}
if beginHeight < 0 {
beginHeight = 0
}
topics := []string{
dao.TransferOutEvent.ID.Hex(),
dao.TransferInEvent.ID.Hex(),
//dao.TransferInConfirmationEvent.ID.Hex(),
dao.TransferInRejectionEvent.ID.Hex(),
dao.TransferInExecutionEvent.ID.Hex(),
dao.TokenConfigChangedEvent.ID.Hex(),
}
logs, err := s.d.GetLogs(s.chain, beginHeight, endHeight, topics, []string{
s.chain.BridgeContract,
})
if err != nil {
log.WithField("chain", s.name).WithFields(log.Fields{"begin": beginHeight, "end": endHeight}).WithError(err).Error("rpc: get logs")
return err
}
if len(logs) > 0 {
log.WithField("chain", s.name).WithFields(log.Fields{"begin": beginHeight, "end": endHeight}).Infof("get %d logs", len(logs))
}
return s.d.HandleEvents(s, logs)
}
func (s *ChainSync) IsSyncing() bool {
return s.syncmode
}
package chainlist
import (
"code.wuban.net.cn/movabridge/bridge-backend/types"
"encoding/json"
"errors"
log "github.com/sirupsen/logrus"
"os"
"sync"
"time"
)
// get latest chain list by download and parse https://chainlist.org/rpcs.json.
type ChainRepo struct {
repo map[int]types.ChainInfo
lock sync.RWMutex
quit chan struct{}
}
type ChainData struct {
Name string `json:"name"`
Chain string `json:"chain"`
Rpc []struct {
Url string `json:"url"`
} `json:"rpc"`
NativeCurrency struct {
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals int `json:"decimals"`
} `json:"nativeCurrency"`
InfoURL string `json:"infoURL"`
ShortName string `json:"shortName"`
ChainId int `json:"chainId"`
NetworkId int `json:"networkId"`
Icon string `json:"icon"`
Explorers []struct {
Name string `json:"name"`
Url string `json:"url"`
Standard string `json:"standard"`
} `json:"explorers"`
}
func New(local string) *ChainRepo {
cr := &ChainRepo{
repo: make(map[int]types.ChainInfo),
quit: make(chan struct{}),
}
if newRepo, err := cr.loadChainFromFile(local); err != nil {
log.WithError(err).Errorf("load chainlist from file %s failed", local)
} else {
cr.repo = newRepo
log.Infof("load chainlist from file %s, total %d chains", local, len(cr.repo))
}
return cr
}
func (cr *ChainRepo) loadChainFromFile(fil string) (map[int]types.ChainInfo, error) {
if fil == "" {
return nil, errors.New("file name is empty")
}
data, err := os.ReadFile(fil)
if err != nil {
return nil, err
}
return cr.parseChainDataToMap(data)
}
func (cr *ChainRepo) parseChainDataToMap(data []byte) (map[int]types.ChainInfo, error) {
chainList := make([]ChainData, 0)
err := json.Unmarshal(data, &chainList)
if err != nil {
return nil, err
}
newMap := make(map[int]types.ChainInfo)
for _, cd := range chainList {
ci := types.ChainInfo{
ChainId: int64(cd.ChainId),
Chain: cd.Chain,
Name: cd.Name,
Explorer: "",
Rpc: "",
}
if len(cd.Rpc) > 0 {
ci.Rpc = cd.Rpc[0].Url
}
if len(cd.Explorers) > 0 {
ci.Explorer = cd.Explorers[0].Url
}
newMap[cd.ChainId] = ci
}
return newMap, nil
}
func (cr *ChainRepo) Get(chainId int64) (types.ChainInfo, bool) {
cr.lock.RLock()
defer cr.lock.RUnlock()
ci, ok := cr.repo[int(chainId)]
return ci, ok
}
func (cr *ChainRepo) Start() {
go cr.loop()
}
func (cr *ChainRepo) Stop() {
close(cr.quit)
}
func (cr *ChainRepo) loop() {
tc := time.NewTicker(time.Minute * 5)
defer tc.Stop()
for {
select {
case <-cr.quit:
return
case <-tc.C:
var data = []byte{}
// todo: fetch data from https://chainlist.org/rpcs.json
if len(data) == 0 {
continue
}
if newRepo, err := cr.parseChainDataToMap(data); err != nil {
log.WithError(err).Error("parse chainlist data failed")
} else {
cr.lock.Lock()
cr.repo = newRepo
cr.lock.Unlock()
log.Infof("update chainlist from remote, total %d chains", len(cr.repo))
}
}
}
}
package main
import (
"code.wuban.net.cn/movabridge/bridge-backend/chain"
"code.wuban.net.cn/movabridge/bridge-backend/chainlist"
"code.wuban.net.cn/movabridge/bridge-backend/config"
"code.wuban.net.cn/movabridge/bridge-backend/dao"
"code.wuban.net.cn/movabridge/bridge-backend/server"
"os"
"os/signal"
"syscall"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
confPath string
)
func init() {
log.SetFormatter(&log.TextFormatter{
FullTimestamp: true,
})
}
var rootCmd = &cobra.Command{
Use: "backend",
Short: "Token bridge backend service",
Long: "A backend service for the token bridge system that syncs with multiple blockchain networks",
Run: func(cmd *cobra.Command, args []string) {
conf, err := config.New(confPath)
if err != nil {
panic(err)
}
if conf.Debug {
log.SetLevel(log.DebugLevel)
}
chainRepo := chainlist.New(conf.ChainListFile)
d, err := dao.New(conf, chainRepo)
if err != nil {
panic(err)
}
go func() {
if err := server.StartRestApi(d, conf); err != nil {
log.WithError(err).Error("start rest api")
}
}()
syncers := make([]*chain.ChainSync, 0)
for _, chainConfig := range conf.Chains {
syncer := chain.NewChainSync(chainConfig, d)
syncer.Start()
syncers = append(syncers, syncer)
d.AddSyncer(chainConfig.ChainId, syncer)
}
// Set up signal handling
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
// Wait for termination signal
sig := <-sigCh
log.WithField("signal", sig.String()).Info("received termination signal, shutting down")
// Stop all chain sync instances
for _, syncer := range syncers {
syncer.Stop()
}
log.Info("graceful shutdown completed")
os.Exit(0)
},
}
func init() {
rootCmd.Flags().StringVarP(&confPath, "config", "c", "config.toml", "config file path")
}
func main() {
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
}
}
debug = true
chain_list_file = "rpcs.json"
[[chains]]
name = "hole"
rpc = "https://rpc.hole.bitheart.org"
initial_height = 868351
batch_block = 1000
confirm_block_count = 2
bridge_contract = "0xceEC8799139C698De532e363DA7395E25F409775"
#[[chains]]
#name = "movadev"
#rpc = "https://rpc.mova.bitheart.org"
#initial_height = 869401
#batch_block = 1000
#confirm_block_count = 2
#bridge_contract = "0xA2d532F956770611647EcBab19d87d380145d0Cf"
[mongodb]
#host = "bridgedb"
host = "localhost"
port = 27017
database = "bridge"
username = "root"
password = "XN2UARuys3zy4Oux"
[server]
listen = ":8080"
invalid_headers = [
"Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1)",
]
\ No newline at end of file
package config
import (
"github.com/BurntSushi/toml"
)
type Config struct {
Debug bool
ChainListFile string `toml:"chain_list_file"`
Chains map[string]*ChainConfig `toml:"chains"`
Mongo MongoConfig `toml:"mongodb"`
Server ServerConfig `toml:"server"`
}
type ChainConfig struct {
Name string `toml:"name"`
RPC string `toml:"rpc"`
InitialHeight int64 `toml:"initial_height"`
BatchBlock int `toml:"batch_block"`
BehindBlock int `toml:"behind_block"`
BridgeContract string `toml:"bridge_contract"`
ChainId int64 `toml:"chain_id"` // Will be populated by code
}
type MySQLConfig struct {
Host string
Port int
User string
Password string
Database string
MaxConn int `toml:"max_conn"`
MaxIdleConn int `toml:"max_idle_conn"`
}
type MongoConfig struct {
Host string `toml:"host"`
Port int `toml:"port"`
User string `toml:"username"`
Password string `toml:"password"`
Database string `toml:"database"`
}
type ServerConfig struct {
Listen string
InvalidHeaders []string `toml:"invalid_headers"`
}
func New(confPath string) (*Config, error) {
var cfg Config
cfg.Chains = make(map[string]*ChainConfig)
// Parse the TOML configuration
_, err := toml.DecodeFile(confPath, &cfg)
if err != nil {
return nil, err
}
// Process the chains from array to map using name as key
var chainArray []ChainConfig
_, err = toml.DecodeFile(confPath, &struct {
Chains *[]ChainConfig `toml:"chains"`
*Config
}{
Chains: &chainArray,
Config: &cfg,
})
if err != nil {
return nil, err
}
// Convert to map for easier access
for _, chain := range chainArray {
chainCopy := chain // Create a copy to avoid pointer issues
cfg.Chains[chain.Name] = &chainCopy
}
return &cfg, nil
}
package constant
const JwtSecret = "uEj7AgDNCREwsvnTaCEtzDXt0I5eFDl8"
const (
InvalidParam = "invalid param"
UnsupportedPlatform = "unsupported platform"
InternalError = "internal error"
)
type TransferStatus int
const (
TransferChainNoProcess TransferStatus = iota
TransferChainWaitConfirm TransferStatus = 1
TransferChainExecuted TransferStatus = 2
TransferChainRejected TransferStatus = 3
)
func (ts TransferStatus) String() string {
switch ts {
case TransferChainNoProcess:
return "NoProcess"
case TransferChainWaitConfirm:
return "WaitConfirm"
case TransferChainExecuted:
return "Executed"
case TransferChainRejected:
return "Rejected"
default:
return "Unknown"
}
}
const (
ValidatorStatusNoPrecess = 0
ValidatorStatusConfirmation = 1
ValidatorStatusRejection = 2
ValidatorStatusFailure = 3
)
const (
EVENT_TRANSFER_OUT = "TransferOut"
EVENT_TRANSFER_IN = "TransferIn"
EVENT_TRANSFER_IN_CONFIRMATION = "TransferInConfirmation"
EVENT_TRANSFER_IN_REJECTION = "TransferInRejection"
EVENT_TRANSFER_IN_EXECUTION = "TransferInExecution"
EVENT_TOKENCONFIGCHANGED = "TokenConfigChanged"
)
type ValidatorOp int
func (op ValidatorOp) String() string {
switch op {
case ValidatorStatusConfirmation:
return "TransferInConfirmation"
case ValidatorStatusRejection:
return "TransferInRejection"
default:
return "Unknown"
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
[
{
"inputs": [
{
"internalType": "address",
"name": "initialOwner",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "OwnableInvalidOwner",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "OwnableUnauthorizedAccount",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "prior",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "present",
"type": "uint256"
}
],
"name": "RequirementChange",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "toChainID",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"name": "TokenConfigChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "fromChainID",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "outId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "inId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "TransferIn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "inId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "validator",
"type": "address"
}
],
"name": "TransferInConfirmation",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "inId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "TransferInExecution",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "inId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "validator",
"type": "address"
}
],
"name": "TransferInRejection",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "outId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fromChainID",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fee",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "toChainID",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "receiveToken",
"type": "address"
}
],
"name": "TransferOut",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "oldTreasury",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newTreasury",
"type": "address"
}
],
"name": "TreasuryChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "minReserve",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "reserveRatio",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"name": "TreasuryConfigChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "contractBalance",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "treasuryBalance",
"type": "uint256"
}
],
"name": "TreasuryTransfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "validator",
"type": "address"
}
],
"name": "ValidatorAddition",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "validator",
"type": "address"
}
],
"name": "ValidatorRemoval",
"type": "event"
},
{
"inputs": [],
"name": "_inID",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "_outID",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "validator",
"type": "address"
}
],
"name": "addValidator",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "autoTransferToTreasury",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "calculateTreasuryTransfer",
"outputs": [
{
"internalType": "bool",
"name": "canTransfer",
"type": "bool"
},
{
"internalType": "uint256",
"name": "transferAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "willReserve",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "currentBalance",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "toChainID",
"type": "uint256"
},
{
"components": [
{
"internalType": "address",
"name": "receiveToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "fee",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "limit",
"type": "uint256"
},
{
"internalType": "bool",
"name": "isBurn",
"type": "bool"
},
{
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"internalType": "struct Bridge.OutConfig",
"name": "config",
"type": "tuple"
}
],
"name": "changeOutConfig",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "required",
"type": "uint256"
}
],
"name": "changeValidRequired",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "inId",
"type": "uint256"
}
],
"name": "confirmInTransfer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "emergencyWithdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "getInId",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "toChainID",
"type": "uint256"
}
],
"name": "getSupportedTokensOut",
"outputs": [
{
"internalType": "address[]",
"name": "",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "inTotal",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "inTransfers",
"outputs": [
{
"internalType": "uint256",
"name": "inId",
"type": "uint256"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"internalType": "uint256",
"name": "fee",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "fromChainID",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "outId",
"type": "uint256"
},
{
"internalType": "bool",
"name": "executed",
"type": "bool"
},
{
"internalType": "uint256",
"name": "confirmCounter",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "rejectCounter",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "inId",
"type": "uint256"
}
],
"name": "isChecked",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "inId",
"type": "uint256"
},
{
"internalType": "address",
"name": "validator",
"type": "address"
}
],
"name": "isConfirmed",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "inId",
"type": "uint256"
},
{
"internalType": "address",
"name": "validator",
"type": "address"
}
],
"name": "isRejected",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "isTokenSupportedOut",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "toChainID",
"type": "uint256"
}
],
"name": "isTokenTransferEnabled",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "isValidator",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "outConfiguration",
"outputs": [
{
"internalType": "address",
"name": "receiveToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "fee",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "limit",
"type": "uint256"
},
{
"internalType": "bool",
"name": "isBurn",
"type": "bool"
},
{
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "outTotal",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "toChainID",
"type": "uint256"
},
{
"internalType": "address",
"name": "receiver",
"type": "address"
}
],
"name": "outTransfer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "outTransfers",
"outputs": [
{
"internalType": "uint256",
"name": "outId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "fromChainID",
"type": "uint256"
},
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "fee",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "toChainID",
"type": "uint256"
},
{
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"internalType": "address",
"name": "receiveToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "receiveAmount",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "signature",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "inId",
"type": "uint256"
}
],
"name": "rejectInTransfer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "validator",
"type": "address"
}
],
"name": "removeValidator",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "validator",
"type": "address"
},
{
"internalType": "address",
"name": "newValidator",
"type": "address"
}
],
"name": "replaceValidator",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "inId",
"type": "uint256"
},
{
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "retryTransfer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newTreasury",
"type": "address"
}
],
"name": "setTreasury",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "minReserve",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "reserveRatio",
"type": "uint256"
},
{
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"name": "setTreasuryConfig",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "toChainID",
"type": "uint256"
},
{
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "outId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "fromChainID",
"type": "uint256"
},
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "address",
"name": "sendToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "sendAmount",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "signature",
"type": "bytes32"
}
],
"internalType": "struct Bridge.submitParams",
"name": "params",
"type": "tuple"
}
],
"name": "submitInTransfer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "supportedTokensOut",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "treasury",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "treasuryConfigs",
"outputs": [
{
"internalType": "uint256",
"name": "minReserve",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "reserveRatio",
"type": "uint256"
},
{
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "treasuryTotal",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "validRequired",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
package dao
import (
"code.wuban.net.cn/movabridge/bridge-backend/config"
. "code.wuban.net.cn/movabridge/bridge-backend/constant"
"code.wuban.net.cn/movabridge/bridge-backend/contract/bridge"
dbModel "code.wuban.net.cn/movabridge/bridge-backend/model/db"
"context"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
log "github.com/sirupsen/logrus"
"math/big"
"strings"
)
type ChainInterface interface {
Name() string
GetChain() *config.ChainConfig
IsSyncing() bool
ParseTransferOut(log types.Log) (*bridge.BridgeContractTransferOut, error)
ParseTransferIn(log types.Log) (*bridge.BridgeContractTransferIn, error)
ParseTransferInExecution(log types.Log) (*bridge.BridgeContractTransferInExecution, error)
ParseTransferInRejection(log types.Log) (*bridge.BridgeContractTransferInRejection, error)
ParseTransferInConfirmation(log types.Log) (*bridge.BridgeContractTransferInConfirmation, error)
ParseTokenConfigChanged(log types.Log) (*bridge.BridgeContractTokenConfigChanged, error)
GetReceiveToken(token common.Address, toChainId int64) (string, error)
}
var (
bridgeAbi, _ = bridge.BridgeContractMetaData.GetAbi()
TransferOutEvent = bridgeAbi.Events[EVENT_TRANSFER_OUT]
TransferInEvent = bridgeAbi.Events[EVENT_TRANSFER_IN]
TransferInExecutionEvent = bridgeAbi.Events[EVENT_TRANSFER_IN_EXECUTION]
TransferInRejectionEvent = bridgeAbi.Events[EVENT_TRANSFER_IN_REJECTION]
TransferInConfirmationEvent = bridgeAbi.Events[EVENT_TRANSFER_IN_CONFIRMATION]
TokenConfigChangedEvent = bridgeAbi.Events[EVENT_TOKENCONFIGCHANGED]
)
func (s *Dao) HandleEvents(chain ChainInterface, logs []types.Log) error {
s.handleMux.Lock()
defer s.handleMux.Unlock()
cname := chain.Name()
// begin orm transaction
ctx := context.Background()
ormTx, err := s.BeginTx(ctx)
if err != nil {
log.WithField("chain", cname).WithError(err).Error("begin db transaction")
return err
}
var ormTxErr error
for _, txLog := range logs {
if err := s.filterTransferOut(chain, txLog, ormTx); err != nil {
ormTxErr = err
break
}
if err := s.filterTransferIn(chain, txLog, ormTx); err != nil {
ormTxErr = err
break
}
if err := s.filterTokenConfigChanged(chain, txLog, ormTx); err != nil {
ormTxErr = err
break
}
}
// Commit or rollback transaction based on error
if ormTxErr != nil {
if rbErr := ormTx.Rollback(); rbErr != nil {
log.WithField("chain", cname).WithError(rbErr).Error("failed to rollback transaction")
}
log.WithField("chain", cname).WithError(ormTxErr).Error("error processing logs, transaction rolled back")
} else {
if cmtErr := ormTx.Commit(); cmtErr != nil {
log.WithField("chain", cname).WithError(cmtErr).Error("failed to commit transaction")
}
}
return nil
}
// filterTransferOut 用户从当前链跨出事件.
func (s *Dao) filterTransferOut(chain ChainInterface, txLog types.Log, tx *Transaction) error {
if len(txLog.Topics) == 0 {
return nil
}
if txLog.Topics[0].Hex() == TransferOutEvent.ID.Hex() {
event, err := chain.ParseTransferOut(txLog)
if err != nil {
log.WithField("chain", chain.Name()).WithError(err).Error("parse TransferOut log")
return err
}
blocktime, _ := s.GetBlockTime(chain.GetChain(), int64(txLog.BlockNumber))
dbEvent := &dbModel.BridgeEvent{
FromChain: event.FromChainID.Int64(),
OutId: event.OutId.Int64(),
OutTimestamp: int64(blocktime),
FromChainTxHash: strings.ToLower(txLog.TxHash.String()),
FromAddress: strings.ToLower(event.Sender.String()),
FromToken: strings.ToLower(event.Token.String()),
SendAmount: event.Amount.Text(10),
FeeAmount: event.Fee.Text(10),
ToChain: event.ToChainID.Int64(),
Receiver: strings.ToLower(event.Receiver.String()),
ToToken: strings.ToLower(event.ReceiveToken.String()),
ReceiveAmount: new(big.Int).Sub(event.Amount, event.Fee).Text(10),
ToChainStatus: int(TransferChainNoProcess),
}
if err := s.FillOutTransferEventInfo(tx, dbEvent); err != nil {
log.WithField("chain", chain.Name()).WithError(err).Error("fill out transfer event info")
return err
}
return nil
}
return nil
}
// filterTransferIn 用户从目标链跨入事件及执行结束事件.
func (s *Dao) filterTransferIn(chain ChainInterface, txLog types.Log, tx *Transaction) error {
if len(txLog.Topics) == 0 {
return nil
}
switch txLog.Topics[0].Hex() {
case TransferInEvent.ID.Hex():
event, err := chain.ParseTransferIn(txLog)
if err != nil {
log.WithField("chain", chain.Name()).WithError(err).Error("parse TransferIn log")
return err
}
blocktime, _ := s.GetBlockTime(chain.GetChain(), int64(txLog.BlockNumber))
dbEvent := &dbModel.BridgeEvent{
FromChain: event.FromChainID.Int64(),
OutId: event.OutId.Int64(),
InId: event.InId.Int64(),
Receiver: strings.ToLower(event.Receiver.String()),
ToToken: strings.ToLower(event.Token.String()),
ReceiveAmount: event.Amount.Text(10),
ToChainStatus: int(TransferChainWaitConfirm),
ToContract: strings.ToLower(txLog.Address.String()),
InTimestamp: int64(blocktime),
ToChain: chain.GetChain().ChainId,
ToChainTxHash: strings.ToLower(txLog.TxHash.String()),
}
if err := s.FillInTransferEventInfo(tx, dbEvent); err != nil {
log.WithField("chain", chain.Name()).WithFields(log.Fields{
"error": err.Error(),
}).Error("db fill in transfer event")
return err
}
return nil
case TransferInExecutionEvent.ID.Hex():
event, err := chain.ParseTransferInExecution(txLog)
if err != nil {
log.WithField("chain", chain.Name()).WithError(err).Error("parse TransferInExecution log")
return err
}
if err := s.UpdateBridgeResult(tx, chain.GetChain().ChainId, event.InId.Int64(), TransferChainExecuted); err != nil {
log.WithField("chain", chain.Name()).WithFields(log.Fields{
"error": err.Error(),
}).Error("db update transfer in execution event")
return err
}
case TransferInRejectionEvent.ID.Hex():
event, err := chain.ParseTransferInRejection(txLog)
if err != nil {
log.WithField("chain", chain.Name()).WithError(err).Error("parse TransferInExecution log")
return err
}
if err := s.UpdateBridgeResult(tx, chain.GetChain().ChainId, event.InId.Int64(), TransferChainRejected); err != nil {
log.WithField("chain", chain.Name()).WithFields(log.Fields{
"error": err.Error(),
}).Error("db update transfer in execution event")
return err
}
}
return nil
}
func (s *Dao) filterTokenConfigChanged(chain ChainInterface, txLog types.Log, tx *Transaction) error {
if len(txLog.Topics) == 0 {
return nil
}
if txLog.Topics[0].Hex() == TokenConfigChangedEvent.ID.Hex() {
configure, err := chain.ParseTokenConfigChanged(txLog)
if err != nil {
return err
}
info := &dbModel.BridgeTokenInfo{
ChainId: chain.GetChain().ChainId,
Token: strings.ToLower(configure.Token.String()),
ToChainId: configure.ToChainID.Int64(),
Enabled: configure.Enabled,
Contract: strings.ToLower(txLog.Address.String()),
}
// get receive token from contract.
info.ToToken, err = chain.GetReceiveToken(configure.Token, info.ToChainId)
if err != nil {
log.WithFields(log.Fields{
"chain": chain.Name(),
"token": configure.Token.Hex(),
"toChainId": info.ToChainId,
}).WithError(err).Error("get receive token config failed")
return err
}
err = s.CreateOrUpdateBridgeTokenInfo(tx, info)
if err != nil {
log.WithFields(log.Fields{
"chain": chain.Name(),
"token": configure.Token.Hex(),
"toChainId": info.ToChainId,
"enabled": info.Enabled,
}).WithError(err).Error("db create or update token config failed")
return err
}
}
return nil
}
package dao
import (
"code.wuban.net.cn/movabridge/bridge-backend/chainlist"
"code.wuban.net.cn/movabridge/bridge-backend/config"
"context"
"crypto/ecdsa"
"fmt"
"github.com/ethereum/go-ethereum/ethclient"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"sync"
)
type ChainInfo struct {
conf *config.ChainConfig
cli *ethclient.Client
}
type Dao struct {
c *config.Config
db *mongo.Database
chainGroup map[int64]ChainInfo
syncer map[int64]ChainInterface
wg sync.WaitGroup
handleMux sync.Mutex
validatorPk *ecdsa.PrivateKey
chainList *chainlist.ChainRepo
}
func New(_c *config.Config, clist *chainlist.ChainRepo) (dao *Dao, err error) {
dao = &Dao{
c: _c,
chainGroup: make(map[int64]ChainInfo),
syncer: make(map[int64]ChainInterface),
chainList: clist,
}
// Connect to all configured chains
for name, chainConfig := range _c.Chains {
var client *ethclient.Client
client, err = ethclient.Dial(chainConfig.RPC)
if err != nil {
return nil, fmt.Errorf("failed to connect to %s chain: %w", name, err)
}
// Get and store chain ID
chainId, err := client.ChainID(context.Background())
if err != nil {
return nil, fmt.Errorf("failed to get %s chain ID: %w", name, err)
}
// Update the chain ID in the config
chainConfig.ChainId = chainId.Int64()
dao.chainGroup[chainId.Int64()] = ChainInfo{
conf: chainConfig,
cli: client,
}
fmt.Printf("Connected to %s chain with ID %d\n", name, chainConfig.ChainId)
}
// MongoDB connection
mongoURI := fmt.Sprintf("mongodb://%s:%s@%s:%d/%s?authSource=admin&authMechanism=SCRAM-SHA-256",
_c.Mongo.User, _c.Mongo.Password, _c.Mongo.Host, _c.Mongo.Port, _c.Mongo.Database)
clientOptions := options.Client().ApplyURI(mongoURI)
mongoClient, err := mongo.Connect(context.Background(), clientOptions)
if err != nil {
return nil, fmt.Errorf("failed to connect to MongoDB: %w", err)
}
// Ping to verify connection
err = mongoClient.Ping(context.Background(), nil)
if err != nil {
return nil, fmt.Errorf("failed to ping MongoDB: %w", err)
}
dao.db = mongoClient.Database(_c.Mongo.Database)
return dao, nil
}
func (d *Dao) ChainClient(chainId int64) *ethclient.Client {
chainInfo := d.chainGroup[chainId]
if chainInfo.cli != nil {
return chainInfo.cli
}
return nil
}
func (d *Dao) Quit() {
if d.db != nil {
d.db.Client().Disconnect(context.Background())
}
d.chainGroup = nil
d.db = nil
d.c = nil
}
func (d *Dao) AddSyncer(chainId int64, syncer ChainInterface) {
d.handleMux.Lock()
defer d.handleMux.Unlock()
d.syncer[chainId] = syncer
}
package dao
import (
"code.wuban.net.cn/movabridge/bridge-backend/constant"
apiModel "code.wuban.net.cn/movabridge/bridge-backend/model/api"
"context"
log "github.com/sirupsen/logrus"
"sort"
"time"
dbModel "code.wuban.net.cn/movabridge/bridge-backend/model/db"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// GetStorageHeight 获取上次缓存的高度
func (d *Dao) GetStorageHeight(key string) (value int64, err error) {
collection := d.db.Collection(new(dbModel.Height).TableName())
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var storage dbModel.Height
err = collection.FindOne(ctx, bson.M{"key": key}).Decode(&storage)
if err == mongo.ErrNoDocuments {
return 0, ErrRecordNotFound
}
return storage.IntValue, err
}
// SetStorageHeight 设置上次缓存的高度
func (d *Dao) SetStorageHeight(key string, intValue int64) (err error) {
collection := d.db.Collection(new(dbModel.Height).TableName())
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
filter := bson.M{"key": key}
update := bson.M{
"$set": bson.D{
{"key", key},
{"int_value", intValue},
},
}
opts := options.Update().SetUpsert(true)
_, err = collection.UpdateOne(ctx, filter, update, opts)
return err
}
func (d *Dao) GetBridgeConfig() (config apiModel.BridgeConfig, err error) {
collection := d.db.Collection(new(dbModel.BridgeTokenInfo).TableName())
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cursor, err := collection.Find(ctx, bson.M{})
if err != nil {
return config, err
}
defer cursor.Close(ctx)
var tokens []dbModel.BridgeTokenInfo
err = cursor.All(ctx, &tokens)
if err != nil {
return config, err
}
config.Chains = make(map[string]apiModel.ChainConfig)
// Convert database model to API model
for _, info := range tokens {
if !info.Enabled {
continue
}
chainInfo, exist := d.chainList.Get(info.ChainId)
if !exist {
log.WithFields(log.Fields{
"chain_id": info.ChainId,
"token": info.Token,
}).Error("not found chain info with chainlist, skip bridge config")
continue
}
var chainConfig apiModel.ChainConfig
if _chainConfig, exist := config.Chains[chainInfo.Name]; !exist {
chainConfig = apiModel.ChainConfig{
ChainId: info.ChainId,
Chain: chainInfo.Name,
RpcUrl: chainInfo.Rpc,
ExplorerUrl: chainInfo.Explorer,
BridgeContract: info.Contract,
SupportTokens: make(map[string]apiModel.ToToken),
}
} else {
chainConfig = _chainConfig
}
chainConfig.SupportTokens[info.Token] = apiModel.ToToken{
ToChainId: info.ToChainId,
ToToken: info.ToToken,
}
config.Chains[chainInfo.Name] = chainConfig
}
return config, nil
}
func (d *Dao) GetHistoryInfo(user string) (history apiModel.History, err error) {
collection := d.db.Collection(new(dbModel.BridgeEvent).TableName())
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cursor, err := collection.Find(ctx, bson.M{"from_address": user})
if err != nil {
return history, err
}
defer cursor.Close(ctx)
var events []dbModel.BridgeEvent
err = cursor.All(ctx, &events)
if err != nil {
return history, err
}
pendingHistory := make([]*apiModel.HistoryInfo, 0, 1000)
completedHistory := make([]*apiModel.HistoryInfo, 0, 1000)
for _, event := range events {
if event.FromChainTxHash == "" || event.ToChainTxHash == "" {
continue
}
fromChain, _ := d.chainList.Get(event.FromChain)
toChain, _ := d.chainList.Get(event.ToChain)
record := &apiModel.HistoryInfo{
FromChain: fromChain.Chain,
ToChain: toChain.Chain,
TxHash: event.FromChainTxHash,
CreateTime: event.OutTimestamp,
Amount: event.SendAmount,
Token: event.FromToken,
Status: constant.TransferStatus(event.ToChainStatus).String(),
}
if event.ToChainStatus <= int(constant.TransferChainWaitConfirm) {
pendingHistory = append(pendingHistory, record)
} else {
completedHistory = append(completedHistory, record)
}
}
// sort pending by CreateTime desc
sort.Slice(pendingHistory, func(i, j int) bool {
return pendingHistory[i].CreateTime > pendingHistory[j].CreateTime
})
sort.Slice(completedHistory, func(i, j int) bool {
return completedHistory[i].CreateTime > completedHistory[j].CreateTime
})
// sort completed by CreateTime desc
history.Pending = pendingHistory
history.Finish = completedHistory
return history, nil
}
package dao
import (
"code.wuban.net.cn/movabridge/bridge-backend/constant"
dbModel "code.wuban.net.cn/movabridge/bridge-backend/model/db"
"context"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var (
ErrRecordNotFound = mongo.ErrNoDocuments
)
// Transaction represents a MongoDB session with transaction
type Transaction struct {
session mongo.Session
ctx context.Context
}
// BeginTx starts a new MongoDB transaction
func (d *Dao) BeginTx(ctx context.Context) (*Transaction, error) {
client := d.db.Client()
session, err := client.StartSession()
if err != nil {
return nil, err
}
err = session.StartTransaction()
if err != nil {
session.EndSession(ctx)
return nil, err
}
return &Transaction{
session: session,
ctx: mongo.NewSessionContext(ctx, session),
}, nil
}
// Commit commits the transaction
func (tx *Transaction) Commit() error {
defer tx.session.EndSession(tx.ctx)
return tx.session.CommitTransaction(tx.ctx)
}
// Rollback aborts the transaction
func (tx *Transaction) Rollback() error {
defer tx.session.EndSession(tx.ctx)
return tx.session.AbortTransaction(tx.ctx)
}
func (d *Dao) FillInTransferEventInfo(tx *Transaction, inEvent *dbModel.BridgeEvent) error {
collection := d.db.Collection(inEvent.TableName())
filter := bson.M{"from_chain": inEvent.FromChain, "out_id": inEvent.OutId}
update := bson.M{
"$set": bson.M{
"from_chain": inEvent.FromChain,
"out_id": inEvent.OutId,
"in_id": inEvent.InId,
"receiver": inEvent.Receiver,
"to_token": inEvent.ToToken,
"receive_amount": inEvent.ReceiveAmount,
"to_chain_status": inEvent.ToChainStatus,
},
}
_, err := collection.UpdateOne(tx.ctx, filter, update)
return err
}
func (d *Dao) FillOutTransferEventInfo(tx *Transaction, outEvent *dbModel.BridgeEvent) error {
collection := d.db.Collection(outEvent.TableName())
filter := bson.M{"from_chain": outEvent.FromChain, "out_id": outEvent.OutId}
update := bson.M{
"$set": bson.M{
"from_chain": outEvent.FromChain,
"out_id": outEvent.OutId,
"out_timestamp": outEvent.OutTimestamp,
"from_chain_tx_hash": outEvent.FromChainTxHash,
"from_address": outEvent.FromAddress,
"from_token": outEvent.FromToken,
"send_amount": outEvent.SendAmount,
"fee_amount": outEvent.FeeAmount,
"to_chain": outEvent.ToChain,
"receiver": outEvent.Receiver,
"to_token": outEvent.ToToken,
"receive_amount": outEvent.ReceiveAmount,
},
}
_, err := collection.UpdateOne(tx.ctx, filter, update)
return err
}
func (d *Dao) UpdateBridgeResult(tx *Transaction, toChainId int64, inId int64, result constant.TransferStatus) error {
collection := d.db.Collection(new(dbModel.BridgeEvent).TableName())
filter := bson.M{"to_chain": toChainId, "in_id": inId}
update := bson.M{
"$set": bson.M{
"to_chain_status": int(result),
},
}
_, err := collection.UpdateOne(tx.ctx, filter, update)
return err
}
func (d *Dao) CreateOrUpdateBridgeTokenInfo(tx *Transaction, info *dbModel.BridgeTokenInfo) error {
collection := d.db.Collection(info.TableName())
filter := bson.M{"chain_id": info.ChainId, "token": info.Token, "to_chain_id": info.ToChainId}
update := bson.D{
{"$set", info},
}
opts := options.Update().SetUpsert(true)
_, err := collection.UpdateOne(tx.ctx, filter, update, opts)
return err
}
package dao
import (
"code.wuban.net.cn/movabridge/bridge-backend/config"
"context"
"errors"
"github.com/ethereum/go-ethereum/ethclient"
"math/big"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
func (d *Dao) GetBlockHeight(chain *config.ChainConfig, behindBlock ...int) (height int64, err error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if _, ok := d.chainGroup[chain.ChainId]; !ok {
return 0, errors.New("chain client not support")
}
chaininfo, ok := d.chainGroup[chain.ChainId]
if !ok {
return 0, errors.New("chain client not support")
}
n, err := chaininfo.cli.BlockNumber(ctx)
if len(behindBlock) > 0 {
n -= uint64(behindBlock[0])
if n < 0 {
n = 0
}
}
return int64(n), err
}
func (d *Dao) GetLatestBockHash(chain *config.ChainConfig) (hash string, err error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
chainInfo, ok := d.chainGroup[chain.ChainId]
if !ok {
return "", errors.New("chain client not support")
}
block, err := chainInfo.cli.BlockByNumber(ctx, nil)
if err != nil {
return
}
return block.Hash().Hex(), nil
}
func (d *Dao) GetBlockTime(chain *config.ChainConfig, height int64) (timestamp int64, err error) {
chainInfo, ok := d.chainGroup[chain.ChainId]
if !ok {
return 0, errors.New("chain client not support")
}
for i := 0; i < 2; i++ {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
block, err := chainInfo.cli.BlockByNumber(ctx, big.NewInt(int64(height)))
if err == nil {
return int64(block.Time()), nil
}
}
return
}
func (d *Dao) GetLogs(chain *config.ChainConfig, beginHeight, endHeight int64, topics, addresses []string) (logs []types.Log, err error) {
chainInfo, ok := d.chainGroup[chain.ChainId]
if !ok {
return nil, errors.New("chain client not support")
}
for i := 0; i < 2; i++ {
// 重试2次
logs, err = d.getLogs(chainInfo.cli, beginHeight, endHeight, topics, addresses)
if err == nil {
return logs, nil
}
}
return
}
func (d *Dao) getLogs(client *ethclient.Client, beginHeight, endHeight int64, topics []string, addresses []string) (logs []types.Log, err error) {
addrs := make([]common.Address, 0)
for _, addr := range addresses {
addrs = append(addrs, common.HexToAddress(addr))
}
q := ethereum.FilterQuery{
FromBlock: big.NewInt(int64(beginHeight)),
ToBlock: big.NewInt(int64(endHeight)),
Topics: [][]common.Hash{{}},
Addresses: addrs,
}
for _, topic := range topics {
q.Topics[0] = append(q.Topics[0], common.HexToHash(topic))
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
return client.FilterLogs(ctx, q)
}
func (d *Dao) CheckEventValid() bool {
// Implement the logic to check if the event is valid.
// This is a placeholder implementation.
return true
}
package dao
import (
"code.wuban.net.cn/movabridge/bridge-backend/contract/bridge"
"context"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"math/big"
"testing"
)
func TestGetReceiveToken(t *testing.T) {
client, _ := ethclient.Dial("http://rpc.hole.bitheart.org")
ct, _ := bridge.NewBridgeContract(common.HexToAddress("0xceEC8799139C698De532e363DA7395E25F409775"), client)
latest, _ := client.BlockNumber(context.Background())
opt := &bind.CallOpts{
Pending: false,
Context: context.Background(),
BlockNumber: big.NewInt(int64(latest)),
From: common.HexToAddress("0xfeed6dB33622Fb526a89c84A0861C29f483f1d0E"),
}
result, err := ct.OutConfiguration(opt, common.HexToAddress("0x9F225b7BCC4697414D4F6aC1CB985D07d34dAe0a"), big.NewInt(8891))
if err != nil {
t.Fatal(err)
}
fmt.Println(result)
}
networks:
default:
name: bridge-network
services:
bridgedb:
image: mongo:7
volumes:
- ./data/db:/data/db
environment:
MONGO_INITDB_ROOT_USERNAME: "root"
MONGO_INITDB_ROOT_PASSWORD: "XN2UARuys3zy4Oux"
MONGO_INITDB_DATABASE: "bridge"
ports:
- "27017:27017"
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
backend:
image: bridgebackend:latest
depends_on:
bridgedb:
condition: service_healthy
volumes:
- ./config.toml:/app/config.toml
- ./testrpcs.json:/app/rpcs.json
command:
- "/bin/sh"
- "-c"
- "/usr/bin/bridgebackend -c /app/config.toml"
restart: unless-stopped
\ No newline at end of file
module code.wuban.net.cn/movabridge/bridge-backend
go 1.24.0
require (
github.com/BurntSushi/toml v1.5.0
github.com/ethereum/go-ethereum v1.10.24
github.com/gin-contrib/cors v1.7.6
github.com/gin-gonic/gin v1.10.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
go.mongodb.org/mongo-driver v1.17.4
)
require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/bits-and-blooms/bitset v1.20.0 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/bytedance/sonic v1.13.3 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/consensys/gnark-crypto v0.18.0 // indirect
github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
github.com/deckarep/golang-set v1.8.0 // indirect
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect
github.com/ethereum/go-verkle v0.2.2 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/holiman/uint256 v1.3.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/rjeczalik/notify v0.9.1 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/supranational/blst v0.3.14 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
golang.org/x/arch v0.18.0 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI=
github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU=
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8=
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4=
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
github.com/cockroachdb/pebble v1.1.5 h1:5AAWCBWbat0uE0blr8qzufZP5tBjkRyy/jWe1QWLnvw=
github.com/cockroachdb/pebble v1.1.5/go.mod h1:17wO9el1YEigxkP/YtV8NtCivQDgoCyBg5c4VR/eOWo=
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/consensys/gnark-crypto v0.18.0 h1:vIye/FqI50VeAr0B3dx+YjeIvmc3LWz4yEfbWBpTUf0=
github.com/consensys/gnark-crypto v0.18.0/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/crate-crypto/go-eth-kzg v1.3.0 h1:05GrhASN9kDAidaFJOda6A4BEvgvuXbazXg/0E3OOdI=
github.com/crate-crypto/go-eth-kzg v1.3.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg=
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiDR1gg0=
github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A=
github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w=
github.com/ethereum/c-kzg-4844/v2 v2.1.0/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E=
github.com/ethereum/go-ethereum v1.10.24 h1:16KV6vdc4T4VHn6UgGlTFs0S71YpbUpZd6BmGcxFXag=
github.com/ethereum/go-ethereum v1.10.24/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg=
github.com/ethereum/go-ethereum v1.16.3 h1:nDoBSrmsrPbrDIVLTkDQCy1U9KdHN+F2PzvMbDoS42Q=
github.com/ethereum/go-ethereum v1.16.3/go.mod h1:Lrsc6bt9Gm9RyvhfFK53vboCia8kpF9nv+2Ukntnl+8=
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY=
github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0=
github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4=
github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc=
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k=
github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8=
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c h1:qSHzRbhzK8RdXOsAdfDgO49TtqC1oZ+acxPrkfTxcCs=
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM=
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0=
github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ=
github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM=
github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE=
github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo=
github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
package middleware
import (
"math/rand"
"strings"
"time"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func CheckHeaderMiddleware(invalidHeaders []string) gin.HandlerFunc {
for _, invalidHeader := range invalidHeaders {
log.WithField("invalid-header", invalidHeader).Debug("init invalid header")
}
return func(c *gin.Context) {
// 获取user-agent
userAgent := c.Request.Header.Get("User-Agent")
for _, invalidHeader := range invalidHeaders {
if strings.Contains(userAgent, invalidHeader) {
time.Sleep(time.Millisecond * time.Duration(rand.Intn(100)+10))
c.JSON(200, gin.H{
"code": 0,
"msg": "ok",
"data": "",
})
log.WithFields(log.Fields{"user-agent": userAgent}).Debug("invalid header, return fake data")
c.Abort()
return
}
}
c.Next()
}
}
package middleware
import (
"bytes"
"io"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func PrintRequestResponseBodyMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 读取请求 body
var requestBody []byte
if c.Request.Body != nil {
requestBody, _ = io.ReadAll(c.Request.Body)
c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
}
log.WithFields(log.Fields{"method": c.Request.Method, "uri": c.Request.RequestURI, "body": string(requestBody)}).Debug("request body")
bodyWriter := &responseBodyWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
c.Writer = bodyWriter
c.Next()
responseBody := bodyWriter.body.String()
log.WithFields(log.Fields{"status": c.Writer.Status(), "body": responseBody}).Debug("response body")
}
}
type responseBodyWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (r *responseBodyWriter) Write(b []byte) (int, error) {
r.body.Write(b)
return r.ResponseWriter.Write(b)
}
package middleware
package api
type ToToken struct {
ToChainId int64 `json:"to_chain_id" bson:"to_chain_id"`
ToToken string `json:"to_token" bson:"to_token"`
}
type ChainConfig struct {
Chain string `json:"chain" bson:"chain"`
ChainId int64 `json:"chain_id" bson:"chain_id"`
BridgeContract string `json:"contract" bson:"contract"`
SupportTokens map[string]ToToken `json:"support_tokens" bson:"support_tokens"`
ExplorerUrl string `json:"explorer_url" bson:"explorer_url"`
RpcUrl string `json:"rpc" bson:"rpc"`
}
type BridgeConfig struct {
Chains map[string]ChainConfig `json:"chains" bson:"chains"`
}
type HistoryInfo struct {
FromChain string `json:"from_chain" bson:"from_chain"`
ToChain string `json:"to_chain" bson:"to_chain"`
TxHash string `json:"tx_hash" bson:"tx_hash"`
CreateTime int64 `json:"create_time" bson:"create_time"`
Amount string `json:"amount" bson:"amount"`
Token string `json:"token" bson:"token"`
Status string `json:"status" bson:"status"`
}
type History struct {
Pending []*HistoryInfo `json:"pending" bson:"pending"`
Finish []*HistoryInfo `json:"finish" bson:"finish"`
}
type Querier interface {
GetBridgeConfig() (config BridgeConfig, err error)
GetHistoryInfo(user string) (history History, err error)
}
package dbModel
import (
"go.mongodb.org/mongo-driver/bson/primitive"
)
type Height struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Key string `bson:"key"`
IntValue int64 `bson:"int_value"`
}
func (h *Height) TableName() string {
return "heights"
}
type BridgeEvent struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
FromChain int64 `bson:"from_chain"`
OutTimestamp int64 `bson:"out_timestamp"`
FromToken string `bson:"from_token"`
FromAddress string `bson:"from_address"`
FromChainTxHash string `bson:"from_chain_tx_hash"`
SendAmount string `bson:"send_amount"`
FeeAmount string `bson:"fee_amount"`
Receiver string `bson:"receiver"`
ToChain int64 `bson:"to_chain"`
ToToken string `bson:"to_token"`
ReceiveAmount string `bson:"receive_amount"`
OutId int64 `bson:"out_id"`
InTimestamp int64 `bson:"in_timestamp"`
InId int64 `bson:"in_id"`
ToContract string `bson:"to_contract"`
ToChainTxHash string `bson:"to_chain_tx_hash"`
ToChainStatus int `bson:"to_chain_status"`
}
func (b *BridgeEvent) TableName() string {
return "bridge_events"
}
type BridgeTokenInfo struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
ChainId int64 `bson:"chain_id"`
Contract string `bson:"contract"`
Token string `bson:"token"`
ToChainId int64 `bson:"to_chain_id"`
ToToken string `bson:"to_token"`
Enabled bool `bson:"enabled"`
}
func (b *BridgeTokenInfo) TableName() string {
return "bridge_token_info"
}
This source diff could not be displayed because it is too large. You can view the blob instead.
package server
import (
"code.wuban.net.cn/movabridge/bridge-backend/constant"
"github.com/ethereum/go-ethereum/common"
log "github.com/sirupsen/logrus"
"strings"
"github.com/gin-gonic/gin"
)
func getParam(c *gin.Context) {
if _querier == nil {
log.Error("querier is nil")
c.JSON(500, withError(constant.InternalError))
return
}
config, err := _querier.GetBridgeConfig()
if err != nil {
log.Errorf("get bridge config error: %v", err)
c.JSON(500, withError(constant.InternalError))
return
}
c.JSON(200, withSuccess(config))
}
func getHistory(c *gin.Context) {
address := c.DefaultQuery("address", "")
if !common.IsHexAddress(address) {
c.JSON(200, withError(constant.InvalidParam))
return
}
if _querier == nil {
log.Error("querier is nil")
c.JSON(500, withError(constant.InternalError))
return
}
history, err := _querier.GetHistoryInfo(strings.ToLower(common.HexToAddress(address).Hex()))
if err != nil {
log.Errorf("get history error: %v", err)
c.JSON(500, withError(constant.InternalError))
return
}
c.JSON(200, withSuccess(history))
}
package server
import (
"code.wuban.net.cn/movabridge/bridge-backend/config"
"code.wuban.net.cn/movabridge/bridge-backend/middleware"
"github.com/gin-gonic/gin"
)
func initRouter(conf *config.Config, e *gin.Engine) {
e.Use(middleware.PrintRequestResponseBodyMiddleware())
e.Use(middleware.CheckHeaderMiddleware(conf.Server.InvalidHeaders))
v1 := e.Group("/api/v1")
{
user := v1.Group("/user")
user.GET("/history", getHistory)
}
v1.GET("/params", getParam)
}
package server
import (
"code.wuban.net.cn/movabridge/bridge-backend/config"
apiModel "code.wuban.net.cn/movabridge/bridge-backend/model/api"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
var (
_querier apiModel.Querier
)
func StartRestApi(querier apiModel.Querier, _conf *config.Config) error {
if !_conf.Debug {
gin.SetMode(gin.ReleaseMode)
}
_querier = querier
engine := gin.Default()
_cors := cors.DefaultConfig()
_cors.AllowAllOrigins = true
_cors.AllowHeaders = []string{"*"}
engine.Use(cors.New(_cors))
initRouter(_conf, engine)
log.Infof("start http server listening %s", _conf.Server.Listen)
return engine.Run(_conf.Server.Listen)
}
func withSuccess(obj interface{}) interface{} {
return gin.H{
"code": 0,
"msg": "ok",
"data": obj,
}
}
func withError(msg string) interface{} {
return gin.H{
"code": 1,
"error": msg,
"data": "",
}
}
[{
"name": "Mova Dev",
"chain": "MOVA",
"icon": "mova",
"rpc": [
{"url": "https://rpc.mova.bitheart.org"}
],
"features": [
{
"name": "EIP155"
}
],
"faucets": [],
"nativeCurrency": {
"name": "MARS",
"symbol": "MARS",
"decimals": 18
},
"infoURL": "https://movachain.com",
"shortName": "mova-dev",
"chainId": 8891,
"networkId": 8891,
"explorers": [
{
"name": "scan",
"url": "https://scan.mova.bitheart.org",
"standard": "EIP3091"
}
]
},
{
"name": "HOLE Test",
"chain": "HOLE",
"icon": "hole",
"rpc": [
{"url": "https://rpc.hole.bitheart.org"}
],
"features": [
{
"name": "EIP155"
}
],
"faucets": [],
"nativeCurrency": {
"name": "HOLE",
"symbol": "HOLE",
"decimals": 18
},
"infoURL": "https://movachain.com",
"shortName": "",
"chainId": 6174,
"networkId": 6174,
"explorers": [
{
"name": "scan",
"url": "https://holescan.bitheart.org/",
"standard": "EIP3091"
}
]
},
{
"name": "Ethereum Mainnet",
"chain": "ETH",
"icon": "ethereum",
"rpc": [
{
"url": "https://eth.llamarpc.com",
"tracking": "none",
"isOpenSource": true
},
{
"url": "https://go.getblock.io/aefd01aa907c4805ba3c00a9e5b48c6b",
"tracking": "none"
},
{
"url": "https://eth-mainnet.nodereal.io/v1/1659dfb40aa24bbb8153a677b98064d7",
"tracking": "yes"
},
{
"url": "https://ethereum-rpc.publicnode.com",
"tracking": "none"
},
{
"url": "wss://ethereum-rpc.publicnode.com",
"tracking": "none"
},
{
"url": "https://1rpc.io/eth",
"tracking": "none"
},
{
"url": "https://rpc.builder0x69.io",
"tracking": "none"
},
{
"url": "https://rpc.mevblocker.io",
"tracking": "none"
},
{
"url": "https://rpc.flashbots.net",
"tracking": "none"
},
{
"url": "https://virginia.rpc.blxrbdn.com",
"tracking": "yes"
},
{
"url": "https://uk.rpc.blxrbdn.com",
"tracking": "yes"
},
{
"url": "https://singapore.rpc.blxrbdn.com",
"tracking": "yes"
},
{
"url": "https://eth.rpc.blxrbdn.com",
"tracking": "yes"
},
{
"url": "https://cloudflare-eth.com",
"tracking": "yes"
},
{
"url": "https://eth-mainnet.public.blastapi.io",
"tracking": "limited"
},
{
"url": "https://api.securerpc.com/v1",
"tracking": "unspecified"
},
{
"url": "https://openapi.bitstack.com/v1/wNFxbiJyQsSeLrX8RRCHi7NpRxrlErZk/DjShIqLishPCTB9HiMkPHXjUM9CNM9Na/ETH/mainnet",
"tracking": "yes"
},
{
"url": "https://ethereum-public.nodies.app",
"tracking": "limited"
},
{
"url": "https://eth-mainnet-public.unifra.io",
"tracking": "limited"
},
{
"url": "https://ethereum.public.blockpi.network/v1/rpc/public",
"tracking": "limited"
},
{
"url": "https://rpc.payload.de",
"tracking": "none"
},
{
"url": "https://api.zmok.io/mainnet/oaen6dy8ff6hju9k",
"tracking": "none"
},
{
"url": "https://eth-mainnet.g.alchemy.com/v2/demo",
"tracking": "yes"
},
{
"url": "https://core.gashawk.io/rpc",
"tracking": "yes"
},
{
"url": "https://mainnet.eth.cloud.ava.do"
},
{
"url": "https://ethereumnodelight.app.runonflux.io"
},
{
"url": "https://eth-mainnet.rpcfast.com?api_key=xbhWBI1Wkguk8SNMu1bvvLurPGLXmgwYeC4S6g2H7WdwFigZSmPWVZRxrskEQwIf"
},
{
"url": "https://main-light.eth.linkpool.io"
},
{
"url": "https://rpc.eth.gateway.fm",
"tracking": "yes"
},
{
"url": "https://rpc.chain49.com/ethereum?api_key=14d1a8b86d8a4b4797938332394203dc",
"tracking": "yes"
},
{
"url": "https://eth.meowrpc.com",
"tracking": "none"
},
{
"url": "https://eth.drpc.org",
"tracking": "none"
},
{
"url": "https://mainnet.gateway.tenderly.co",
"tracking": "yes"
},
{
"url": "https://virtual.mainnet.rpc.tenderly.co/7355b215-ef17-4e3e-8f64-d494284ef18a",
"tracking": "yes"
},
{
"url": "https://virtual.mainnet.rpc.tenderly.co/5804dcf7-70e6-4988-b2b0-3672193e0c91",
"tracking": "yes"
},
{
"url": "https://gateway.tenderly.co/public/mainnet",
"tracking": "yes"
},
{
"url": "https://api.zan.top/eth-mainnet",
"tracking": "limited"
},
{
"url": "https://eth-mainnet.diamondswap.org/rpc",
"tracking": "limited"
},
{
"url": "https://rpc.notadegen.com/eth"
},
{
"url": "https://eth.merkle.io",
"tracking": "none"
},
{
"url": "https://rpc.lokibuilder.xyz/wallet",
"tracking": "none"
},
{
"url": "https://services.tokenview.io/vipapi/nodeservice/eth?apikey=qVHq2o6jpaakcw3lRstl",
"tracking": "yes"
},
{
"url": "https://eth.nodeconnect.org",
"tracking": "yes"
},
{
"url": "https://api.stateless.solutions/ethereum/v1/demo",
"tracking": "none"
},
{
"url": "https://rpc.polysplit.cloud/v1/chain/1",
"tracking": "none"
},
{
"url": "https://public.stackup.sh/api/v1/node/ethereum-mainnet",
"tracking": "limited"
},
{
"url": "https://ethereum-mainnet.gateway.tatum.io",
"tracking": "yes"
},
{
"url": "https://public-eth.nownodes.io",
"tracking": "yes"
},
{
"url": "https://rpc.nodifi.ai/api/rpc/free",
"tracking": "none"
},
{
"url": "https://ethereum.rpc.subquery.network/public"
},
{
"url": "https://rpc.graffiti.farm",
"tracking": "limited"
},
{
"url": "https://rpc.public.curie.radiumblock.co/http/ethereum",
"tracking": "none"
},
{
"url": "https://eth-mainnet.4everland.org/v1/37fa9972c1b1cd5fab542c7bdd4cde2f",
"tracking": "limited"
},
{
"url": "wss://eth-mainnet.4everland.org/ws/v1/37fa9972c1b1cd5fab542c7bdd4cde2f",
"tracking": "limited"
},
{
"url": "https://rpc.public.curie.radiumblock.co/ws/ethereum",
"tracking": "none"
},
{
"url": "wss://ws-rpc.graffiti.farm",
"tracking": "limited"
},
{
"url": "wss://ethereum.callstaticrpc.com",
"tracking": "none"
},
{
"url": "https://eth.blockrazor.xyz",
"tracking": "none"
},
{
"url": "https://endpoints.omniatech.io/v1/eth/mainnet/public",
"tracking": "none"
},
{
"url": "https://eth1.lava.build",
"tracking": "yes"
},
{
"url": "https://0xrpc.io/eth",
"tracking": "limited"
},
{
"url": "wss://0xrpc.io/eth",
"tracking": "limited"
},
{
"url": "https://rpc.owlracle.info/eth/70d38ce1826c4a60bb2a8e05a6c8b20f",
"tracking": "limited"
},
{
"url": "https://ethereum.therpc.io",
"tracking": "limited"
},
{
"url": "https://eth.api.onfinality.io/public",
"tracking": "limited"
},
{
"url": "https://ethereum-json-rpc.stakely.io",
"tracking": "none"
},
{
"url": "https://rpc.poolz.finance/eth",
"tracking": "limited"
},
{
"url": "https://eth.rpc.grove.city/v1/01fdb492",
"tracking": "yes"
},
{
"url": "https://api.mycryptoapi.com/eth"
},
{
"url": "wss://mainnet.gateway.tenderly.co"
},
{
"url": "https://rpc.blocknative.com/boost"
},
{
"url": "https://rpc.flashbots.net/fast"
},
{
"url": "https://rpc.mevblocker.io/fast"
},
{
"url": "https://rpc.mevblocker.io/noreverts"
},
{
"url": "https://rpc.mevblocker.io/fullprivacy"
},
{
"url": "wss://eth.drpc.org"
}
],
"features": [
{
"name": "EIP155"
},
{
"name": "EIP1559"
}
],
"faucets": [],
"nativeCurrency": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"infoURL": "https://ethereum.org",
"shortName": "eth",
"chainId": 1,
"networkId": 1,
"slip44": 60,
"ens": {
"registry": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
},
"explorers": [
{
"name": "etherscan",
"url": "https://etherscan.io",
"standard": "EIP3091"
},
{
"name": "blockscout",
"url": "https://eth.blockscout.com",
"icon": "blockscout",
"standard": "EIP3091"
},
{
"name": "dexguru",
"url": "https://ethereum.dex.guru",
"icon": "dexguru",
"standard": "EIP3091"
},
{
"name": "Routescan",
"url": "https://ethereum.routescan.io",
"standard": "EIP3091"
}
],
"tvl": 200220759747.01547,
"chainSlug": "ethereum"
},
{
"name": "BNB Smart Chain Mainnet",
"chain": "BSC",
"rpc": [
{
"url": "https://binance.llamarpc.com",
"tracking": "none",
"isOpenSource": true
},
{
"url": "https://bsc-dataseed.bnbchain.org"
},
{
"url": "https://bsc-dataseed1.defibit.io"
},
{
"url": "https://bsc-dataseed1.ninicoin.io"
},
{
"url": "https://bsc-dataseed2.defibit.io"
},
{
"url": "https://bsc-dataseed3.defibit.io"
},
{
"url": "https://bsc-dataseed4.defibit.io"
},
{
"url": "https://bsc-dataseed2.ninicoin.io"
},
{
"url": "https://bsc-dataseed3.ninicoin.io"
},
{
"url": "https://bsc-dataseed4.ninicoin.io"
},
{
"url": "https://bsc-dataseed1.bnbchain.org"
},
{
"url": "https://bsc-dataseed2.bnbchain.org"
},
{
"url": "https://bsc-dataseed3.bnbchain.org"
},
{
"url": "https://bsc-dataseed4.bnbchain.org"
},
{
"url": "https://bsc-dataseed6.dict.life"
},
{
"url": "https://rpc-bsc.48.club",
"tracking": "limited"
},
{
"url": "https://0.48.club",
"tracking": "limited"
},
{
"url": "wss://rpc-bsc.48.club/ws",
"tracking": "limited"
},
{
"url": "https://binance-smart-chain-public.nodies.app",
"tracking": "limited"
},
{
"url": "https://bsc-mainnet.nodereal.io/v1/64a9df0874fb4a93b9d0a3849de012d3",
"tracking": "yes"
},
{
"url": "https://go.getblock.io/cc778cdbdf5c4b028ec9456e0e6c0cf3",
"tracking": "limited"
},
{
"url": "https://bscrpc.com"
},
{
"url": "https://bsc.rpcgator.com"
},
{
"url": "https://binance.nodereal.io",
"tracking": "yes"
},
{
"url": "https://bsc-mainnet.rpcfast.com?api_key=xbhWBI1Wkguk8SNMu1bvvLurPGLXmgwYeC4S6g2H7WdwFigZSmPWVZRxrskEQwIf"
},
{
"url": "https://nodes.vefinetwork.org/smartchain"
},
{
"url": "https://1rpc.io/bnb",
"tracking": "none"
},
{
"url": "https://bsc.rpc.blxrbdn.com",
"tracking": "yes"
},
{
"url": "https://bsc.blockpi.network/v1/rpc/private",
"tracking": "limited"
},
{
"url": "https://bnb.api.onfinality.io/public",
"tracking": "limited"
},
{
"url": "https://bsc-rpc.publicnode.com",
"tracking": "none"
},
{
"url": "wss://bsc-rpc.publicnode.com",
"tracking": "none"
},
{
"url": "https://bsc-mainnet.public.blastapi.io",
"tracking": "limited"
},
{
"url": "https://bsc.meowrpc.com",
"tracking": "none"
},
{
"url": "https://api.zan.top/bsc-mainnet",
"tracking": "limited"
},
{
"url": "https://bsc.drpc.org",
"tracking": "none"
},
{
"url": "https://services.tokenview.io/vipapi/nodeservice/bsc?apikey=gVFJX5OyPdc2kHH7youg",
"tracking": "yes"
},
{
"url": "https://rpc.polysplit.cloud/v1/chain/56",
"tracking": "none"
},
{
"url": "https://public.stackup.sh/api/v1/node/bsc-mainnet",
"tracking": "limited"
},
{
"url": "https://bsc-mainnet.gateway.tatum.io",
"tracking": "yes"
},
{
"url": "https://public-bsc.nownodes.io",
"tracking": "yes"
},
{
"url": "https://bsc-mainnet.4everland.org/v1/37fa9972c1b1cd5fab542c7bdd4cde2f",
"tracking": "limited"
},
{
"url": "wss://bsc-mainnet.4everland.org/ws/v1/37fa9972c1b1cd5fab542c7bdd4cde2f",
"tracking": "limited"
},
{
"url": "https://bnb.rpc.subquery.network/public"
},
{
"url": "wss://bsc.callstaticrpc.com",
"tracking": "none"
},
{
"url": "https://bsc.blockrazor.xyz",
"tracking": "none"
},
{
"url": "https://endpoints.omniatech.io/v1/bsc/mainnet/public",
"tracking": "none"
},
{
"url": "https://rpc.owlracle.info/bsc/70d38ce1826c4a60bb2a8e05a6c8b20f",
"tracking": "limited"
},
{
"url": "https://bsc.therpc.io",
"tracking": "limited"
},
{
"url": "https://rpc.poolz.finance/bsc",
"tracking": "limited"
},
{
"url": "https://bsc.rpc.grove.city/v1/01fdb492",
"tracking": "yes"
},
{
"url": "wss://bsc-ws-node.nariox.org"
}
],
"faucets": [],
"nativeCurrency": {
"name": "BNB Chain Native Token",
"symbol": "BNB",
"decimals": 18
},
"infoURL": "https://www.bnbchain.org/en",
"shortName": "bnb",
"chainId": 56,
"networkId": 56,
"slip44": 714,
"explorers": [
{
"name": "bscscan",
"url": "https://bscscan.com",
"standard": "EIP3091"
},
{
"name": "dexguru",
"url": "https://bnb.dex.guru",
"icon": "dexguru",
"standard": "EIP3091"
}
],
"tvl": 11425725441.662361,
"chainSlug": "binance"
},
{
"name": "Arbitrum One",
"chainId": 42161,
"shortName": "arb1",
"chain": "ETH",
"networkId": 42161,
"nativeCurrency": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
"rpc": [
{
"url": "https://arb1.arbitrum.io/rpc"
},
{
"url": "https://1rpc.io/arb",
"tracking": "none"
},
{
"url": "https://arbitrum-one-public.nodies.app",
"tracking": "limited"
},
{
"url": "https://arb-mainnet.g.alchemy.com/v2/demo",
"tracking": "yes"
},
{
"url": "https://arbitrum.public.blockpi.network/v1/rpc/public",
"tracking": "limited"
},
{
"url": "https://arbitrum-one.public.blastapi.io",
"tracking": "limited"
},
{
"url": "https://arb-mainnet-public.unifra.io",
"tracking": "limited"
},
{
"url": "https://rpc.arb1.arbitrum.gateway.fm",
"tracking": "yes"
},
{
"url": "https://arbitrum-one-rpc.publicnode.com",
"tracking": "none"
},
{
"url": "wss://arbitrum-one-rpc.publicnode.com",
"tracking": "none"
},
{
"url": "https://arbitrum.meowrpc.com",
"tracking": "none"
},
{
"url": "https://api.zan.top/arb-one",
"tracking": "limited"
},
{
"url": "https://arbitrum.drpc.org",
"tracking": "none"
},
{
"url": "https://public.stackup.sh/api/v1/node/arbitrum-one",
"tracking": "limited"
},
{
"url": "https://api.stateless.solutions/arbitrum-one/v1/demo",
"tracking": "none"
},
{
"url": "https://arbitrum.rpc.subquery.network/public"
},
{
"url": "https://arbitrum.gateway.tenderly.co",
"tracking": "yes"
},
{
"url": "wss://arbitrum.callstaticrpc.com",
"tracking": "none"
},
{
"url": "https://endpoints.omniatech.io/v1/arbitrum/one/public",
"tracking": "none"
},
{
"url": "https://arb1.lava.build",
"tracking": "yes"
},
{
"url": "https://rpc.owlracle.info/arb/70d38ce1826c4a60bb2a8e05a6c8b20f",
"tracking": "limited"
},
{
"url": "https://arbitrum.therpc.io",
"tracking": "limited"
},
{
"url": "https://arbitrum.api.onfinality.io/public",
"tracking": "limited"
},
{
"url": "https://arb-one-mainnet.gateway.tatum.io",
"tracking": "yes"
},
{
"url": "https://rpc.poolz.finance/arbitrum",
"tracking": "limited"
},
{
"url": "https://arbitrum-one.rpc.grove.city/v1/01fdb492",
"tracking": "yes"
},
{
"url": "https://arb-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}"
}
],
"faucets": [],
"explorers": [
{
"name": "Arbiscan",
"url": "https://arbiscan.io",
"standard": "EIP3091"
},
{
"name": "Arbitrum Explorer",
"url": "https://explorer.arbitrum.io",
"standard": "EIP3091"
},
{
"name": "dexguru",
"url": "https://arbitrum.dex.guru",
"icon": "dexguru",
"standard": "EIP3091"
}
],
"infoURL": "https://arbitrum.io",
"parent": {
"type": "L2",
"chain": "eip155-1",
"bridges": [
{
"url": "https://bridge.arbitrum.io"
}
]
},
"tvl": 4128977415.702092,
"chainSlug": "arbitrum"
}
]
\ No newline at end of file
package types
type ChainInfo struct {
ChainId int64 `json:"chainId,omitempty"`
Chain string `json:"chain,omitempty"`
Name string `json:"name,omitempty"`
Rpc string `json:"rpc,omitempty"`
Explorer string `json:"explorer,omitempty"`
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment