root 6 månader sedan
incheckning
2ffe7e5d86
20 ändrade filer med 1797 tillägg och 0 borttagningar
  1. 7 0
      Makefile
  2. 34 0
      conf/conf.go
  3. 12 0
      conf/conf.yaml
  4. 12 0
      conf/conf_test.go
  5. 37 0
      dao/dao.go
  6. 22 0
      dao/transaction.go
  7. 65 0
      go.mod
  8. 252 0
      go.sum
  9. 81 0
      main.go
  10. 30 0
      proto/walletServer.proto
  11. 86 0
      readme.md
  12. 24 0
      service/const.go
  13. 119 0
      service/eth.go
  14. 211 0
      service/node.go
  15. 25 0
      service/node_test.go
  16. 357 0
      service/server.go
  17. 24 0
      service/tron.go
  18. 294 0
      service/walletServer.pb.go
  19. 105 0
      service/walletServer_grpc.pb.go
  20. BIN
      wallet-server

+ 7 - 0
Makefile

@@ -0,0 +1,7 @@
+SHELL=/usr/bin/env bash
+
+.PHONY: build
+build: build
+	go mod tidy
+	rm -rf wallet-server
+	go build -o wallet-server main.go

+ 34 - 0
conf/conf.go

@@ -0,0 +1,34 @@
+package conf
+
+import (
+	"github.com/jinzhu/configor"
+)
+
+type mysqlSetting struct {
+	User     string `yaml:"user"`
+	Password string `yaml:"password"`
+	Host     string `yaml:"host"`
+	DB       string `yaml:"db"`
+}
+
+type Conf struct {
+	Mysql          mysqlSetting `yaml:"mysql"`
+	KeyManagerAddr string       `yaml:"keymanageraddr"`
+	EthRpcAddrs    []string     `json:"ethrpcaddrs"`
+	TronRpcAddrs   []string     `json:"tronrpcaddrs"`
+}
+
+var conf Conf
+
+func InitConfig(path string) error {
+	err := configor.Load(&conf, path)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func GetConfig() Conf {
+	return conf
+}

+ 12 - 0
conf/conf.yaml

@@ -0,0 +1,12 @@
+mysql:
+  user: lance918
+  password: Wang110@
+  host: hk-cdb-b5rihmy5.sql.tencentcdb.com:24838
+  db: wallet-server
+
+keymanageraddr: "127.0.0.1:5556"
+
+ethrpcaddrs:
+  - "https://mainnet.infura.io/v3/2eba501820ce490f8dd5117d9ddb374d"
+tronrpcaddrs:
+  - "grpc.trongrid.io:50051"

+ 12 - 0
conf/conf_test.go

@@ -0,0 +1,12 @@
+package conf
+
+import "testing"
+
+func TestInitConfig(t *testing.T) {
+	err := InitConfig("./conf.yaml")
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Log(conf.EthRpcAddrs)
+	t.Log(conf.TronRpcAddrs)
+}

+ 37 - 0
dao/dao.go

@@ -0,0 +1,37 @@
+package dao
+
+import (
+	"fmt"
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
+	"gorm.io/gorm/logger"
+	"wallet-server/conf"
+)
+
+type Dao struct {
+	db *gorm.DB
+}
+
+func InitMysqlDB() (*Dao, error) {
+	conf := conf.GetConfig()
+	dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local",
+		conf.Mysql.User,
+		conf.Mysql.Password,
+		conf.Mysql.Host,
+		conf.Mysql.DB,
+	)
+
+	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
+		//Logger: logger.Default.LogMode(logger.Info),
+		Logger: logger.Default.LogMode(logger.Silent),
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	db.AutoMigrate(&Transaction{})
+
+	return &Dao{
+		db: db,
+	}, nil
+}

+ 22 - 0
dao/transaction.go

@@ -0,0 +1,22 @@
+package dao
+
+import "gorm.io/gorm"
+
+type Transaction struct {
+	gorm.Model
+	TxId    string `gorm:"column:tx_id" json:"tx_id"`
+	Network string `gorm:"column:network" json:"network"`
+	Coin    string `gorm:"column:coin" json:"coin"`
+	From    string `gorm:"column:from" json:"from"`
+	To      string `gorm:"column:to" json:"to"`
+	Amount  string `gorm:"column:amount" json:"amount"`
+	TxHash  string `gorm:"type:varchar(200);column:tx_hash;uniqueIndex" json:"tx_hash"`
+}
+
+func (t *Transaction) TableName() string {
+	return "wallet_server_transaction"
+}
+
+func (dao *Dao) CreateTransaction(data *Transaction) error {
+	return dao.db.Create(&data).Error
+}

+ 65 - 0
go.mod

@@ -0,0 +1,65 @@
+module wallet-server
+
+go 1.19
+
+require (
+	github.com/ethereum/go-ethereum v1.12.2
+	github.com/fbsobreira/gotron-sdk v0.0.0-20230907131216-1e824406fe8c
+	github.com/ipfs/go-log/v2 v2.5.1
+	github.com/jinzhu/configor v1.2.1
+	github.com/urfave/cli/v2 v2.24.1
+	google.golang.org/grpc v1.57.0
+	google.golang.org/protobuf v1.31.0
+	gorm.io/driver/mysql v1.5.1
+	gorm.io/gorm v1.25.3
+	key-manager v0.0.0
+)
+
+replace key-manager v0.0.0 => ../key-manager
+
+require (
+	github.com/BurntSushi/toml v1.2.1 // indirect
+	github.com/btcsuite/btcd v0.22.1 // indirect
+	github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
+	github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
+	github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect
+	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
+	github.com/deckarep/golang-set v1.8.0 // indirect
+	github.com/deckarep/golang-set/v2 v2.1.0 // indirect
+	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
+	github.com/go-ole/go-ole v1.2.6 // indirect
+	github.com/go-sql-driver/mysql v1.7.0 // indirect
+	github.com/go-stack/stack v1.8.1 // indirect
+	github.com/golang/protobuf v1.5.3 // indirect
+	github.com/google/uuid v1.3.0 // indirect
+	github.com/gorilla/websocket v1.4.2 // indirect
+	github.com/holiman/uint256 v1.2.3 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
+	github.com/mattn/go-isatty v0.0.16 // indirect
+	github.com/mitchellh/go-homedir v1.1.0 // indirect
+	github.com/pborman/uuid v1.2.1 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/rjeczalik/notify v0.9.3 // indirect
+	github.com/russross/blackfriday/v2 v2.1.0 // indirect
+	github.com/shengdoushi/base58 v1.0.0 // indirect
+	github.com/shirou/gopsutil v3.21.11+incompatible // indirect
+	github.com/tklauser/go-sysconf v0.3.12 // indirect
+	github.com/tklauser/numcpus v0.6.1 // indirect
+	github.com/tyler-smith/go-bip39 v1.1.0 // indirect
+	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
+	github.com/yusufpapurcu/wmi v1.2.3 // indirect
+	go.uber.org/atomic v1.7.0 // indirect
+	go.uber.org/multierr v1.6.0 // indirect
+	go.uber.org/zap v1.19.1 // indirect
+	golang.org/x/crypto v0.9.0 // indirect
+	golang.org/x/exp v0.0.0-20230810033253-352e893a4cad // indirect
+	golang.org/x/net v0.10.0 // indirect
+	golang.org/x/sys v0.11.0 // indirect
+	golang.org/x/text v0.9.0 // indirect
+	google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect
+	gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
+	gopkg.in/yaml.v2 v2.4.0 // indirect
+)

+ 252 - 0
go.sum

@@ -0,0 +1,252 @@
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
+github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8=
+github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o=
+github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
+github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo=
+github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
+github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c=
+github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y=
+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/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
+github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
+github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
+github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ=
+github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o=
+github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
+github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
+github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
+github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
+github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8=
+github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
+github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk=
+github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ=
+github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
+github.com/consensys/gnark-crypto v0.10.0 h1:zRh22SR7o4K35SoNqouS9J/TKHTyU2QWaj5ldehyXtA=
+github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/crate-crypto/go-kzg-4844 v0.3.0 h1:UBlWE0CgyFqqzTI+IFyCzA7A3Zw4iip6uzRv5NIXG0A=
+github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+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/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.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI=
+github.com/deckarep/golang-set/v2 v2.1.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/ethereum/c-kzg-4844 v0.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg=
+github.com/ethereum/go-ethereum v1.12.2 h1:eGHJ4ij7oyVqUQn48LBz3B7pvQ8sV0wGJiIE6gDq/6Y=
+github.com/ethereum/go-ethereum v1.12.2/go.mod h1:1cRAEV+rp/xX0zraSCBnu9Py3HQ+geRMj3HdR+k0wfI=
+github.com/fbsobreira/gotron-sdk v0.0.0-20230907131216-1e824406fe8c h1:7NIY9Q4Kpjxja807mi3PJieLX63c/Gm35L8ffCemNUA=
+github.com/fbsobreira/gotron-sdk v0.0.0-20230907131216-1e824406fe8c/go.mod h1:uxY3MGTmqItqUr8gJzmpo8vrBAUHKW2JrGp3yYcL8us=
+github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
+github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
+github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
+github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
+github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.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/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
+github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZKHF9GxxWKDJGj8I0IqOUol//sw=
+github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
+github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o=
+github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
+github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=
+github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
+github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
+github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
+github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
+github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
+github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
+github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
+github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
+github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
+github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+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.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
+github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
+github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI=
+github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
+github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=
+github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
+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/shengdoushi/base58 v1.0.0 h1:tGe4o6TmdXFJWoI31VoSWvuaKxf0Px3gqa3sUWhAxBs=
+github.com/shengdoushi/base58 v1.0.0/go.mod h1:m5uIILfzcKMw6238iWAhP4l3s5+uXyF3+bJKUNhAL9I=
+github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
+github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4=
+github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
+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/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
+github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
+github.com/urfave/cli/v2 v2.24.1 h1:/QYYr7g0EhwXEML8jO+8OYt5trPnLHS0p3mrgExJ5NU=
+github.com/urfave/cli/v2 v2.24.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
+github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4=
+go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
+go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
+golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
+golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
+golang.org/x/exp v0.0.0-20230810033253-352e893a4cad h1:g0bG7Z4uG+OgH2QDODnjp6ggkk1bJDsINcuWmJN1iJU=
+golang.org/x/exp v0.0.0-20230810033253-352e893a4cad/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+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-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g=
+google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8=
+google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q=
+google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 h1:eSaPbMR4T7WfH9FvABk36NBMacoTUKdWCvV0dx+KfOg=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I=
+google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
+google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+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.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw=
+gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o=
+gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
+gorm.io/gorm v1.25.3 h1:zi4rHZj1anhZS2EuEODMhDisGy+Daq9jtPrNGgbQYD8=
+gorm.io/gorm v1.25.3/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
+rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=

+ 81 - 0
main.go

@@ -0,0 +1,81 @@
+package main
+
+import (
+	"fmt"
+	logging "github.com/ipfs/go-log/v2"
+	"github.com/urfave/cli/v2"
+	"google.golang.org/grpc"
+	"net"
+	"os"
+	"wallet-server/conf"
+	"wallet-server/dao"
+	"wallet-server/service"
+)
+
+var log = logging.Logger("main")
+
+func main() {
+	_ = logging.SetLogLevel("*", "INFO")
+
+	app := cli.App{
+		Name:  "wallet-server",
+		Usage: "eth and tron wallet service",
+		Commands: []*cli.Command{
+			runCmd,
+		},
+		EnableBashCompletion: true,
+	}
+
+	if err := app.Run(os.Args); err != nil {
+		fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err)
+		os.Exit(1)
+	}
+}
+
+var runCmd = &cli.Command{
+	Name:  "run",
+	Usage: "run wallet server process",
+	Flags: []cli.Flag{
+		&cli.StringFlag{
+			Name:  "listen",
+			Usage: "The host address and port on which the key manager will listen",
+			Value: "127.0.0.1:6667",
+		},
+		&cli.StringFlag{
+			Name:  "config",
+			Usage: "config file path",
+		},
+	},
+	Action: func(cctx *cli.Context) error {
+		err := conf.InitConfig(cctx.String("config"))
+		if err != nil {
+			return err
+		}
+		db, err := dao.InitMysqlDB()
+		if err != nil {
+			return err
+		}
+
+		listen := cctx.String("listen")
+		listener, err := net.Listen("tcp", listen)
+		if err != nil {
+			return err
+		}
+
+		log.Infof("grpc server Listing on: %s", listen)
+
+		grpcServer := grpc.NewServer()
+		server, err := service.NewWalletServer(db)
+		if err != nil {
+			return err
+		}
+
+		service.RegisterWalletServerServer(grpcServer, server)
+		if err = grpcServer.Serve(listener); err != nil {
+			log.Error(err)
+			return err
+		}
+
+		return nil
+	},
+}

+ 30 - 0
proto/walletServer.proto

@@ -0,0 +1,30 @@
+syntax = "proto3";
+package service;
+
+option java_package = "proto.walletServer";
+option java_outer_classname = "walletServerProto";
+
+option go_package = "../service";
+
+message TransferRequest {
+    string id = 1; // 转账id,供业务层识别
+    string network = 2; // 链网络名称,"eth" 或 "tron"
+    string coin = 3; // 转账的币种,eth / trx / usdt / usdc
+    string from = 4; // 转账的发起地址
+    string to = 5; // 代币的接收地址
+    // amount单位是实际精度,
+    // 如:eth的精度是18,转账金额为: "12.345678",传递的参数amount为: "12345678000000000000"
+    // trx/usdt/usdc精度是6,转账余额为: "1.21",传递的参数amount为: "1210000"
+    string amount = 6; // 转账的金额
+}
+
+message TransferResponse {
+    TransferRequest req = 1; // 转账请求详情
+    string code = 2;  // 响应码, 成功是200
+    string msg = 3;   // 响应描述信息
+    string txHash = 4; // 交易发到链上的交易hash,业务层应该检测该hash的是否上链,上链才代表着转账成功
+}
+
+service WalletServer {
+    rpc Transfer(TransferRequest) returns(TransferResponse) {}
+}

+ 86 - 0
readme.md

@@ -0,0 +1,86 @@
+# wallet-server
+
+## 编译
+
+make build
+
+## 使用
+
+```
+./wallet-server -h                                             
+NAME:
+   wallet-server - eth and tron wallet service
+
+USAGE:
+   wallet-server [global options] command [command options] [arguments...]
+
+COMMANDS:
+   run      run wallet server process
+   help, h  Shows a list of commands or help for one command
+
+GLOBAL OPTIONS:
+   --help, -h  show help
+
+```
+
+### 步骤
+
+#### 1.配置
+
+```
+mysql:
+  user: root
+  password: 123456789
+  host: 127.0.0.1:3306
+  db: wallet-server
+
+keymanageraddr: "127.0.0.1:5556"
+
+ethrpcaddrs:
+  - "https://mainnet.infura.io/v3/2eba501820ce490f8dd5117d9ddb374d"
+tronrpcaddrs:
+  - "grpc.trongrid.io:50051"
+```
+
+服务依赖于mysql
+
+- 用来持久化发送的交易记录
+
+区块链节点客户端rpc
+
+- 用户构建/广播交易
+- 支持多个,服务将自动扫描发现更优的节点使用。建议在实际运行时,配置多个rpc地址,多个rpc地址示例:
+
+  ```
+  ethrpcaddrs:
+    - "https://mainnet.infura.io/v3/2eba501820ce490f8dd5117d9ddb374d"
+    - "https://mainnet.infura.io/v3/2eba501820ce490f8dd5117d9ddb374d"
+  tronrpcaddrs:
+    - "grpc.trongrid.io:50051"
+    - "grpc.trongrid.io:50051"
+  ```
+
+#### 2.启动
+
+```
+ ./wallet-server run -h                                         
+NAME:
+   wallet-server run - run wallet server process
+
+USAGE:
+   wallet-server run [command options] [arguments...]
+
+OPTIONS:
+   --listen value  The host address and port on which the key manager will listen (default: "127.0.0.1:6667")
+   --config value  config file path
+   --help, -h      show help
+
+```
+
+- --listen 可指定服务启动的端口
+
+命令示例:
+
+`wallet-server run --config ./conf/conf.yaml`
+
+

+ 24 - 0
service/const.go

@@ -0,0 +1,24 @@
+package service
+
+const (
+	okCode = "200"
+	okMsg  = "success"
+
+	dbErrCode = "1000"
+
+	addressErrCode = "1007"
+	addressErrMsg  = "Address format failed"
+
+	networkErrCode = "1008"
+	networkErrMsg  = "Network must be eth or tron"
+
+	serverErrCode = "1100"
+
+	amountErrCode = "1101"
+	amountErrMsg  = "Invalid amount"
+
+	coinErrCode = "1102"
+	coinErrMsg  = "Coin must be eth / trx / usdt / usdc"
+
+	keyManagerErrCode = "1103"
+)

+ 119 - 0
service/eth.go

@@ -0,0 +1,119 @@
+package service
+
+import (
+	"context"
+	"encoding/hex"
+	"github.com/ethereum/go-ethereum"
+	"github.com/ethereum/go-ethereum/accounts/abi"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/ethclient"
+	"math/big"
+	"time"
+)
+
+var (
+	ethUsdt = common.HexToAddress("0xdAC17F958D2ee523a2206206994597C13D831ec7")
+	ethUsdc = common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
+)
+
+var (
+	transferId, _ = hex.DecodeString("a9059cbb")
+)
+
+var (
+	addressTy, _ = abi.NewType("address", "", nil)
+	uint256Ty, _ = abi.NewType("uint256", "", nil)
+	arguments    = abi.Arguments{{Type: addressTy}, {Type: uint256Ty}}
+)
+
+func buildTransferEth(ethClient *ethclient.Client, from, to string, amount *big.Int) (*types.Transaction, error) {
+	fromAddr := common.HexToAddress(from)
+	toAddr := common.HexToAddress(to)
+
+	return createTransaction(ethClient, &fromAddr, &toAddr, amount, nil)
+}
+
+func buildTransferUsdtOfEth(ethClient *ethclient.Client, from, to string, amount *big.Int) (*types.Transaction, error) {
+	fromAddr := common.HexToAddress(from)
+	toAddr := common.HexToAddress(to)
+
+	data, err := arguments.Pack(toAddr, amount)
+	if err != nil {
+		return nil, err
+	}
+	return createTransaction(ethClient, &fromAddr, &ethUsdt, big.NewInt(0), append(transferId, data...))
+}
+
+func buildTransferUsdcOfEth(ethClient *ethclient.Client, from, to string, amount *big.Int) (*types.Transaction, error) {
+	fromAddr := common.HexToAddress(from)
+	toAddr := common.HexToAddress(to)
+
+	data, err := arguments.Pack(toAddr, amount)
+	if err != nil {
+		return nil, err
+	}
+	return createTransaction(ethClient, &fromAddr, &ethUsdc, big.NewInt(0), append(transferId, data...))
+}
+
+func createTransaction(ethClient *ethclient.Client, from, to *common.Address, amount *big.Int, data []byte) (*types.Transaction, error) {
+	tx := &types.DynamicFeeTx{
+		ChainID: big.NewInt(1),
+		To:      to,
+		Value:   amount,
+		Data:    data,
+	}
+
+	err := estimateGas(ethClient, from, tx)
+	if err != nil {
+		return nil, err
+	}
+
+	return types.NewTx(tx), nil
+}
+
+func estimateGas(ethClient *ethclient.Client, from *common.Address, tx *types.DynamicFeeTx) error {
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
+	defer cancel()
+
+	head, err := ethClient.HeaderByNumber(ctx, nil)
+	if err != nil {
+		return err
+	}
+
+	gasTipCap, err := ethClient.SuggestGasTipCap(ctx)
+	if err != nil {
+		return err
+	}
+	gasFeeCap := new(big.Int).Add(
+		gasTipCap,
+		new(big.Int).Mul(head.BaseFee, big.NewInt(2)),
+	)
+
+	msg := ethereum.CallMsg{
+		From:      *from,
+		To:        tx.To,
+		GasPrice:  nil,
+		GasTipCap: gasTipCap,
+		GasFeeCap: gasFeeCap,
+		Value:     tx.Value,
+		Data:      tx.Data,
+	}
+
+	gasLimit, err := ethClient.EstimateGas(ctx, msg)
+	if err != nil {
+		return err
+	}
+
+	nonce, err := ethClient.PendingNonceAt(ctx, *from)
+	if err != nil {
+		return err
+	}
+
+	tx.Nonce = nonce
+	tx.Gas = gasLimit
+	tx.GasFeeCap = gasFeeCap
+	tx.GasTipCap = gasTipCap
+
+	return nil
+}

+ 211 - 0
service/node.go

@@ -0,0 +1,211 @@
+package service
+
+import (
+	"context"
+	"errors"
+	"github.com/ethereum/go-ethereum/ethclient"
+	"github.com/fbsobreira/gotron-sdk/pkg/client"
+	logging "github.com/ipfs/go-log/v2"
+	"google.golang.org/grpc"
+	"regexp"
+	"strconv"
+	"time"
+)
+
+var log = logging.Logger("server")
+
+type Node struct {
+	ethRpcAddrs    []string
+	ethBlockNumber uint64
+	ethClient      *ethclient.Client
+
+	tronRpcAddrs    []string
+	tronBlockNumber int64
+	tronClient      *client.GrpcClient
+}
+
+func NewNode(ethRpcAddrs []string, tronRpcAddrs []string) (*Node, error) {
+	ethC, ethBlockNumber, err := getBestEthNode(ethRpcAddrs)
+	if err != nil {
+		return nil, err
+	}
+	tronC, tronBlockNumber, err := getBestTronNode(tronRpcAddrs)
+	if err != nil {
+		return nil, err
+	}
+
+	n := &Node{
+		ethRpcAddrs:     ethRpcAddrs,
+		ethBlockNumber:  ethBlockNumber,
+		ethClient:       ethC,
+		tronRpcAddrs:    tronRpcAddrs,
+		tronBlockNumber: tronBlockNumber,
+		tronClient:      tronC,
+	}
+
+	go n.monitor()
+
+	return n, nil
+}
+
+func (n *Node) monitor() {
+	ticker := time.NewTicker(time.Second * 60)
+	for {
+		select {
+		case <-ticker.C:
+			ethBlockNumber := n.keepEthClientConnect()
+			tronBlockNumber := n.keepTronClientConnect()
+			log.Infow("node monitor", "ethBlockNumber", ethBlockNumber, "tronBlockNumber", tronBlockNumber)
+		}
+	}
+}
+
+func (n *Node) keepEthClientConnect() uint64 {
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
+	defer cancel()
+	number, err := n.ethClient.BlockNumber(ctx)
+	if err != nil || n.ethBlockNumber > number {
+		ethC, ethBlockNumber, err := getBestEthNode(n.ethRpcAddrs)
+		if err != nil {
+			log.Errorw("keepEthClientConnect: getBestEthNode", "err", err)
+			return 0
+		}
+		n.ethClient.Close()
+		n.ethBlockNumber = ethBlockNumber
+		n.ethClient = ethC
+	}
+
+	return n.ethBlockNumber
+}
+
+func (n *Node) keepTronClientConnect() int64 {
+	num, err := getTronBlockNumber(n.tronClient)
+	if err != nil || n.tronBlockNumber > num {
+		tronC, tronBlockNumber, err := getBestTronNode(n.tronRpcAddrs)
+		if err != nil {
+			log.Errorw("keepTronClientConnect: getBestTronNode", "err", err)
+			return 0
+		}
+		n.tronClient.Stop()
+		n.tronBlockNumber = tronBlockNumber
+		n.tronClient = tronC
+	}
+
+	return n.tronBlockNumber
+}
+
+func getBestEthNode(ethRpcAddrs []string) (*ethclient.Client, uint64, error) {
+	var cs = make([]*ethclient.Client, 0, len(ethRpcAddrs))
+	for _, addr := range ethRpcAddrs {
+		c, err := ethclient.Dial(addr)
+		if err != nil {
+			log.Warnw("getBestEthNode: bad rpc", "rpc", addr)
+			continue
+		}
+		tem := *c
+		cs = append(cs, &tem)
+	}
+
+	if len(cs) == 0 {
+		return nil, 0, errors.New("no active eth node")
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+
+	blockNumber := uint64(0)
+	bestIndex := -1
+
+	for i, c := range cs {
+		b, err := c.BlockNumber(ctx)
+		if err != nil {
+			log.Warnw("getBestEthNode: bad client", "err", err)
+			continue
+		}
+
+		if b > blockNumber {
+			bestIndex = i
+			blockNumber = b
+		}
+	}
+
+	if bestIndex == -1 {
+		return nil, 0, errors.New("no active eth node")
+	}
+
+	for i, c := range cs {
+		if i == bestIndex {
+			continue
+		}
+		c.Close()
+	}
+
+	return cs[bestIndex], blockNumber, nil
+}
+
+func getBestTronNode(tronRpcAddrs []string) (*client.GrpcClient, int64, error) {
+	var cs = make([]*client.GrpcClient, 0, len(tronRpcAddrs))
+	for _, addr := range tronRpcAddrs {
+		c := client.NewGrpcClient(addr)
+		err := c.Start(grpc.WithInsecure())
+		if err != nil {
+			log.Warnw("getBestTronNode: bad rpc", "rpc", addr)
+			continue
+		}
+
+		tem := *c
+		cs = append(cs, &tem)
+	}
+
+	if len(cs) == 0 {
+		return nil, 0, errors.New("no active tron node")
+	}
+
+	blockNumber := int64(0)
+	bestIndex := -1
+
+	for i, c := range cs {
+		num, err := getTronBlockNumber(c)
+		if err != nil {
+			log.Warnw("getBestTronNode: bad client", "err", err)
+			continue
+		}
+
+		if num > blockNumber {
+			bestIndex = i
+			blockNumber = num
+		}
+	}
+
+	if bestIndex == -1 {
+		return nil, 0, errors.New("no active tron node")
+	}
+
+	for i, c := range cs {
+		if i == bestIndex {
+			continue
+		}
+		c.Stop()
+	}
+
+	return cs[bestIndex], blockNumber, nil
+}
+
+func getTronBlockNumber(c *client.GrpcClient) (int64, error) {
+	b, err := c.GetNodeInfo()
+	if err != nil {
+		return 0, err
+	}
+
+	re := regexp.MustCompile(`Num:(\d+)`)
+	match := re.FindStringSubmatch(b.Block)
+	if len(match) < 2 {
+		return 0, err
+	}
+	num, err := strconv.ParseInt(match[1], 10, 64)
+	if err != nil {
+		return 0, err
+	}
+
+	return num, nil
+}

+ 25 - 0
service/node_test.go

@@ -0,0 +1,25 @@
+package service
+
+import (
+	"testing"
+	"wallet-server/conf"
+)
+
+func TestNode(t *testing.T) {
+	err := conf.InitConfig("../conf/conf.yaml")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	config := conf.GetConfig()
+	n, err := NewNode(config.EthRpcAddrs, config.TronRpcAddrs)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	t.Log("eth block number", n.ethBlockNumber)
+	t.Log("tron block number", n.tronBlockNumber)
+
+	n.ethClient.Close()
+	n.tronClient.Stop()
+}

+ 357 - 0
service/server.go

@@ -0,0 +1,357 @@
+package service
+
+import (
+	"context"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+	tronaddr "github.com/fbsobreira/gotron-sdk/pkg/address"
+	troncommon "github.com/fbsobreira/gotron-sdk/pkg/common"
+	"github.com/fbsobreira/gotron-sdk/pkg/proto/api"
+	troncore "github.com/fbsobreira/gotron-sdk/pkg/proto/core"
+	"google.golang.org/grpc"
+	"google.golang.org/protobuf/proto"
+	"key-manager/service"
+	"math/big"
+	"sync"
+	"wallet-server/conf"
+	"wallet-server/dao"
+)
+
+const (
+	ethNetwork  = "eth"
+	tronNetwork = "tron"
+	ethCoin     = "eth"
+	trxCoin     = "trx"
+	usdtCoin    = "usdt"
+	usdcCoin    = "usdc"
+)
+
+type WalletServer struct {
+	UnimplementedWalletServerServer
+	lk         sync.Mutex
+	node       *Node
+	dao        *dao.Dao
+	keyManager service.KeyManagerClient
+}
+
+func NewWalletServer(dao *dao.Dao) (*WalletServer, error) {
+	config := conf.GetConfig()
+	node, err := NewNode(config.EthRpcAddrs, config.TronRpcAddrs)
+	if err != nil {
+		return nil, err
+	}
+
+	keyManagerClient, _, err := newKeyManagerClient(config.KeyManagerAddr)
+	if err != nil {
+		return nil, err
+	}
+
+	return &WalletServer{
+		node:       node,
+		dao:        dao,
+		keyManager: keyManagerClient,
+	}, nil
+}
+
+func newKeyManagerClient(keyManagerAddr string) (service.KeyManagerClient, *grpc.ClientConn, error) {
+	conn, err := grpc.Dial(keyManagerAddr, grpc.WithInsecure())
+	if err != nil {
+		return nil, nil, err
+	}
+	client := service.NewKeyManagerClient(conn)
+	return client, conn, nil
+}
+
+func (w *WalletServer) Transfer(ctx context.Context, req *TransferRequest) (*TransferResponse, error) {
+	network := req.Network
+	if network != ethNetwork && network != tronNetwork {
+		return &TransferResponse{
+			Req:    req,
+			Code:   networkErrCode,
+			Msg:    networkErrMsg,
+			TxHash: "",
+		}, nil
+	}
+
+	coin := req.Coin
+	if coin != ethCoin && coin != trxCoin && coin != usdcCoin && coin != usdtCoin {
+		return &TransferResponse{
+			Req:    req,
+			Code:   coinErrCode,
+			Msg:    coinErrMsg,
+			TxHash: "",
+		}, nil
+	}
+
+	from, err1 := format(network, req.From)
+	to, err2 := format(network, req.To)
+	if err1 != nil || err2 != nil {
+		return &TransferResponse{
+			Req:    req,
+			Code:   addressErrCode,
+			Msg:    addressErrMsg,
+			TxHash: "",
+		}, nil
+	}
+
+	req.From = from
+	req.To = to
+
+	amount, ok := big.NewInt(0).SetString(req.Amount, 10)
+	if !ok || amount.Cmp(big.NewInt(0)) <= 0 {
+		return &TransferResponse{
+			Req:    req,
+			Code:   amountErrCode,
+			Msg:    amountErrMsg,
+			TxHash: "",
+		}, nil
+	}
+
+	res, err := w.keyManager.GetIndex(ctx, &service.GetIndexRequest{
+		Network: network,
+		Address: from,
+	})
+	if err != nil {
+		return &TransferResponse{
+			Req:    req,
+			Code:   keyManagerErrCode,
+			Msg:    err.Error(),
+			TxHash: "",
+		}, nil
+	}
+	if res.Code != okCode {
+		return &TransferResponse{
+			Req:    req,
+			Code:   res.Code,
+			Msg:    res.Msg,
+			TxHash: "",
+		}, nil
+	}
+
+	var txStr string
+	var txHash string
+	if network == ethNetwork {
+		var ethTx *types.Transaction
+		code := serverErrCode
+		if coin == ethCoin {
+			ethTx, err = buildTransferEth(w.node.ethClient, req.From, req.To, amount)
+		} else if coin == usdcCoin {
+			ethTx, err = buildTransferUsdcOfEth(w.node.ethClient, req.From, req.To, amount)
+		} else if coin == usdtCoin {
+			ethTx, err = buildTransferUsdtOfEth(w.node.ethClient, req.From, req.To, amount)
+		} else {
+			code = coinErrCode
+			err = errors.New("coin and network do not match")
+		}
+		if err != nil {
+			return &TransferResponse{
+				Req:    req,
+				Code:   code,
+				Msg:    err.Error(),
+				TxHash: "",
+			}, nil
+		}
+
+		txStr, err = marshalJEthTx(ethTx)
+		if err != nil {
+			return &TransferResponse{
+				Req:    req,
+				Code:   serverErrCode,
+				Msg:    err.Error(),
+				TxHash: "",
+			}, nil
+		}
+	} else {
+		var tronTx *api.TransactionExtention
+		code := serverErrCode
+		if coin == trxCoin {
+			tronTx, err = buildTransferTrx(w.node.tronClient, req.From, req.To, amount.Int64())
+		} else if coin == usdcCoin {
+			tronTx, err = buildTransferUsdcOfTron(w.node.tronClient, req.From, req.To, amount)
+		} else if coin == usdtCoin {
+			tronTx, err = buildTransferUsdtOfTron(w.node.tronClient, req.From, req.To, amount)
+		} else {
+			code = coinErrCode
+			err = errors.New("coin and network do not match")
+		}
+		if err != nil {
+			return &TransferResponse{
+				Req:    req,
+				Code:   code,
+				Msg:    err.Error(),
+				TxHash: "",
+			}, nil
+		}
+		txStr, err = marshalJTronTx(tronTx.Transaction)
+		if err != nil {
+			return &TransferResponse{
+				Req:    req,
+				Code:   serverErrCode,
+				Msg:    err.Error(),
+				TxHash: "",
+			}, nil
+		}
+		txHash = troncommon.BytesToHexString(tronTx.GetTxid())
+	}
+
+	signRes, err := w.keyManager.Sign(ctx, &service.SignRequest{
+		Network: network,
+		Sender:  from,
+		Tx:      txStr,
+	})
+	if err != nil {
+		return &TransferResponse{
+			Req:    req,
+			Code:   keyManagerErrCode,
+			Msg:    err.Error(),
+			TxHash: "",
+		}, nil
+	}
+
+	if signRes.Code != okCode {
+		return &TransferResponse{
+			Req:    req,
+			Code:   signRes.Code,
+			Msg:    signRes.Msg,
+			TxHash: "",
+		}, nil
+	}
+
+	if network == ethNetwork {
+		tx, err := unmarshalJEthTx(signRes.SignedTx)
+		if err != nil {
+			return &TransferResponse{
+				Req:    req,
+				Code:   serverErrCode,
+				Msg:    err.Error(),
+				TxHash: "",
+			}, nil
+		}
+
+		err = w.node.ethClient.SendTransaction(ctx, tx)
+		if err != nil {
+			return &TransferResponse{
+				Req:    req,
+				Code:   serverErrCode,
+				Msg:    err.Error(),
+				TxHash: "",
+			}, nil
+		}
+
+		txHash = tx.Hash().String()
+	} else {
+		tx, err := unmarshalJTronTx(signRes.SignedTx)
+		if err != nil {
+			return &TransferResponse{
+				Req:    req,
+				Code:   serverErrCode,
+				Msg:    err.Error(),
+				TxHash: "",
+			}, nil
+		}
+
+		result, err := w.node.tronClient.Broadcast(tx)
+		if result.Code != 0 || !result.Result {
+			d, _ := json.Marshal(result)
+			return &TransferResponse{
+				Req:    req,
+				Code:   serverErrCode,
+				Msg:    string(d),
+				TxHash: "",
+			}, nil
+		}
+	}
+
+	err = w.dao.CreateTransaction(&dao.Transaction{
+		TxId:    req.Id,
+		Network: req.Network,
+		Coin:    req.Coin,
+		From:    req.From,
+		To:      req.To,
+		Amount:  req.Amount,
+		TxHash:  txHash,
+	})
+	if err != nil {
+		log.Warnw("save transaction to mysql failed", "id", req.Id, "network", req.Network, "coin", req.Coin, "from", req.From, "to", req.To, "amount", req.Amount, "txhash", txHash, "err", err)
+	}
+
+	log.Infow("send transaction to chain", "id", req.Id, "network", req.Network, "coin", req.Coin, "from", req.From, "to", req.To, "amount", req.Amount, "txhash", txHash)
+
+	return &TransferResponse{
+		Req:    req,
+		Code:   okCode,
+		Msg:    okMsg,
+		TxHash: txHash,
+	}, nil
+}
+
+func format(network, address string) (string, error) {
+	if network == ethNetwork {
+		if address[:2] != "0x" {
+			return "", errors.New("invalid address")
+		}
+
+		address = common.HexToAddress(address).String()
+	} else {
+		if address[:1] != "T" {
+			return "", errors.New("invalid address")
+		}
+		addr, err := tronaddr.Base58ToAddress(address)
+		if err != nil {
+			return "", err
+		}
+		address = addr.String()
+	}
+	return address, nil
+}
+
+func marshalJEthTx(transaction *types.Transaction) (string, error) {
+	b, err := transaction.MarshalJSON()
+	if err != nil {
+		return "", err
+	}
+
+	return hex.EncodeToString(b), nil
+}
+
+func unmarshalJEthTx(tx string) (*types.Transaction, error) {
+	b, err := hex.DecodeString(tx)
+	if err != nil {
+		return nil, err
+	}
+
+	var transaction = &types.Transaction{}
+	err = transaction.UnmarshalJSON(b)
+	if err != nil {
+		return nil, err
+	}
+
+	return transaction, err
+}
+
+func marshalJTronTx(transaction *troncore.Transaction) (string, error) {
+	b, err := proto.Marshal(transaction)
+	if err != nil {
+		return "", err
+	}
+
+	return hex.EncodeToString(b), nil
+}
+
+func unmarshalJTronTx(tx string) (*troncore.Transaction, error) {
+	b, err := hex.DecodeString(tx)
+	if err != nil {
+		return nil, err
+	}
+
+	var transaction troncore.Transaction
+	err = proto.Unmarshal(b, &transaction)
+	if err != nil {
+		return nil, err
+	}
+
+	return &transaction, nil
+}

+ 24 - 0
service/tron.go

@@ -0,0 +1,24 @@
+package service
+
+import (
+	"github.com/fbsobreira/gotron-sdk/pkg/client"
+	"github.com/fbsobreira/gotron-sdk/pkg/proto/api"
+	"math/big"
+)
+
+const (
+	tronUsdt = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
+	tronUsdc = "TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8"
+)
+
+func buildTransferTrx(tronClient *client.GrpcClient, from, to string, amount int64) (*api.TransactionExtention, error) {
+	return tronClient.Transfer(from, to, amount)
+}
+
+func buildTransferUsdtOfTron(tronClient *client.GrpcClient, from, to string, amount *big.Int) (*api.TransactionExtention, error) {
+	return tronClient.TRC20Send(from, to, tronUsdt, amount, 100000000)
+}
+
+func buildTransferUsdcOfTron(tronClient *client.GrpcClient, from, to string, amount *big.Int) (*api.TransactionExtention, error) {
+	return tronClient.TRC20Send(from, to, tronUsdc, amount, 100000000)
+}

+ 294 - 0
service/walletServer.pb.go

@@ -0,0 +1,294 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.27.1
+// 	protoc        v3.19.3
+// source: walletServer.proto
+
+package service
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type TransferRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Id      string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`           // 转账id,供业务层识别
+	Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"` // 链网络名称,"eth" 或 "tron"
+	Coin    string `protobuf:"bytes,3,opt,name=coin,proto3" json:"coin,omitempty"`       // 转账的币种,eth / trx / usdt / usdc
+	From    string `protobuf:"bytes,4,opt,name=from,proto3" json:"from,omitempty"`       // 转账的发起地址
+	To      string `protobuf:"bytes,5,opt,name=to,proto3" json:"to,omitempty"`           // 代币的接收地址
+	// amount单位是实际精度,
+	// 如:eth的精度是18,转账金额为: "12.345678",传递的参数amount为: "12345678000000000000"
+	// trx/usdt/usdc精度是6,转账余额为: "1.21",传递的参数amount为: "1210000"
+	Amount string `protobuf:"bytes,6,opt,name=amount,proto3" json:"amount,omitempty"` // 转账的金额
+}
+
+func (x *TransferRequest) Reset() {
+	*x = TransferRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_walletServer_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *TransferRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TransferRequest) ProtoMessage() {}
+
+func (x *TransferRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_walletServer_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use TransferRequest.ProtoReflect.Descriptor instead.
+func (*TransferRequest) Descriptor() ([]byte, []int) {
+	return file_walletServer_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *TransferRequest) GetId() string {
+	if x != nil {
+		return x.Id
+	}
+	return ""
+}
+
+func (x *TransferRequest) GetNetwork() string {
+	if x != nil {
+		return x.Network
+	}
+	return ""
+}
+
+func (x *TransferRequest) GetCoin() string {
+	if x != nil {
+		return x.Coin
+	}
+	return ""
+}
+
+func (x *TransferRequest) GetFrom() string {
+	if x != nil {
+		return x.From
+	}
+	return ""
+}
+
+func (x *TransferRequest) GetTo() string {
+	if x != nil {
+		return x.To
+	}
+	return ""
+}
+
+func (x *TransferRequest) GetAmount() string {
+	if x != nil {
+		return x.Amount
+	}
+	return ""
+}
+
+type TransferResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Req    *TransferRequest `protobuf:"bytes,1,opt,name=req,proto3" json:"req,omitempty"`       // 转账请求详情
+	Code   string           `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"`     // 响应码, 成功是200
+	Msg    string           `protobuf:"bytes,3,opt,name=msg,proto3" json:"msg,omitempty"`       // 响应描述信息
+	TxHash string           `protobuf:"bytes,4,opt,name=txHash,proto3" json:"txHash,omitempty"` // 交易发到链上的交易hash,业务层应该检测该hash的是否上链,上链才代表着转账成功
+}
+
+func (x *TransferResponse) Reset() {
+	*x = TransferResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_walletServer_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *TransferResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TransferResponse) ProtoMessage() {}
+
+func (x *TransferResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_walletServer_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use TransferResponse.ProtoReflect.Descriptor instead.
+func (*TransferResponse) Descriptor() ([]byte, []int) {
+	return file_walletServer_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *TransferResponse) GetReq() *TransferRequest {
+	if x != nil {
+		return x.Req
+	}
+	return nil
+}
+
+func (x *TransferResponse) GetCode() string {
+	if x != nil {
+		return x.Code
+	}
+	return ""
+}
+
+func (x *TransferResponse) GetMsg() string {
+	if x != nil {
+		return x.Msg
+	}
+	return ""
+}
+
+func (x *TransferResponse) GetTxHash() string {
+	if x != nil {
+		return x.TxHash
+	}
+	return ""
+}
+
+var File_walletServer_proto protoreflect.FileDescriptor
+
+var file_walletServer_proto_rawDesc = []byte{
+	0x0a, 0x12, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x8b, 0x01,
+	0x0a, 0x0f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
+	0x64, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x63,
+	0x6f, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x69, 0x6e, 0x12,
+	0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66,
+	0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x02, 0x74, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x7c, 0x0a, 0x10, 0x54,
+	0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
+	0x2a, 0x0a, 0x03, 0x72, 0x65, 0x71, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73,
+	0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x03, 0x72, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x63,
+	0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12,
+	0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73,
+	0x67, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x32, 0x51, 0x0a, 0x0c, 0x57, 0x61, 0x6c,
+	0x6c, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x08, 0x54, 0x72, 0x61,
+	0x6e, 0x73, 0x66, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e,
+	0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
+	0x19, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66,
+	0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x33, 0x0a, 0x12,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76,
+	0x65, 0x72, 0x42, 0x11, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
+	0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x0a, 0x2e, 0x2e, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
+	0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_walletServer_proto_rawDescOnce sync.Once
+	file_walletServer_proto_rawDescData = file_walletServer_proto_rawDesc
+)
+
+func file_walletServer_proto_rawDescGZIP() []byte {
+	file_walletServer_proto_rawDescOnce.Do(func() {
+		file_walletServer_proto_rawDescData = protoimpl.X.CompressGZIP(file_walletServer_proto_rawDescData)
+	})
+	return file_walletServer_proto_rawDescData
+}
+
+var file_walletServer_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_walletServer_proto_goTypes = []interface{}{
+	(*TransferRequest)(nil),  // 0: service.TransferRequest
+	(*TransferResponse)(nil), // 1: service.TransferResponse
+}
+var file_walletServer_proto_depIdxs = []int32{
+	0, // 0: service.TransferResponse.req:type_name -> service.TransferRequest
+	0, // 1: service.WalletServer.Transfer:input_type -> service.TransferRequest
+	1, // 2: service.WalletServer.Transfer:output_type -> service.TransferResponse
+	2, // [2:3] is the sub-list for method output_type
+	1, // [1:2] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_walletServer_proto_init() }
+func file_walletServer_proto_init() {
+	if File_walletServer_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_walletServer_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*TransferRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_walletServer_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*TransferResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_walletServer_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_walletServer_proto_goTypes,
+		DependencyIndexes: file_walletServer_proto_depIdxs,
+		MessageInfos:      file_walletServer_proto_msgTypes,
+	}.Build()
+	File_walletServer_proto = out.File
+	file_walletServer_proto_rawDesc = nil
+	file_walletServer_proto_goTypes = nil
+	file_walletServer_proto_depIdxs = nil
+}

+ 105 - 0
service/walletServer_grpc.pb.go

@@ -0,0 +1,105 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.2.0
+// - protoc             v3.19.3
+// source: walletServer.proto
+
+package service
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+// WalletServerClient is the client API for WalletServer service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type WalletServerClient interface {
+	Transfer(ctx context.Context, in *TransferRequest, opts ...grpc.CallOption) (*TransferResponse, error)
+}
+
+type walletServerClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewWalletServerClient(cc grpc.ClientConnInterface) WalletServerClient {
+	return &walletServerClient{cc}
+}
+
+func (c *walletServerClient) Transfer(ctx context.Context, in *TransferRequest, opts ...grpc.CallOption) (*TransferResponse, error) {
+	out := new(TransferResponse)
+	err := c.cc.Invoke(ctx, "/service.WalletServer/Transfer", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// WalletServerServer is the server API for WalletServer service.
+// All implementations must embed UnimplementedWalletServerServer
+// for forward compatibility
+type WalletServerServer interface {
+	Transfer(context.Context, *TransferRequest) (*TransferResponse, error)
+	mustEmbedUnimplementedWalletServerServer()
+}
+
+// UnimplementedWalletServerServer must be embedded to have forward compatible implementations.
+type UnimplementedWalletServerServer struct {
+}
+
+func (UnimplementedWalletServerServer) Transfer(context.Context, *TransferRequest) (*TransferResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Transfer not implemented")
+}
+func (UnimplementedWalletServerServer) mustEmbedUnimplementedWalletServerServer() {}
+
+// UnsafeWalletServerServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to WalletServerServer will
+// result in compilation errors.
+type UnsafeWalletServerServer interface {
+	mustEmbedUnimplementedWalletServerServer()
+}
+
+func RegisterWalletServerServer(s grpc.ServiceRegistrar, srv WalletServerServer) {
+	s.RegisterService(&WalletServer_ServiceDesc, srv)
+}
+
+func _WalletServer_Transfer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(TransferRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(WalletServerServer).Transfer(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/service.WalletServer/Transfer",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(WalletServerServer).Transfer(ctx, req.(*TransferRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+// WalletServer_ServiceDesc is the grpc.ServiceDesc for WalletServer service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var WalletServer_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "service.WalletServer",
+	HandlerType: (*WalletServerServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "Transfer",
+			Handler:    _WalletServer_Transfer_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "walletServer.proto",
+}

BIN
wallet-server