代码结构设计得最好的多链支持的 HD 钱包服务端代码

代码库地址: https://github.com/SavourDao/savour-hd

Savour HD 是 Savour 项目的钱包的 HD. 后端服务,使用 golang 编写,提供 grpc 接口给上层服务访问

这是一个支持多链的 HD 钱包的服务端代码,包含对接个链的 wallet 模块,rpc f服务端,代码设计是清晰,规整;今天我把他推荐给大家使用。

1. 项目运行

1.1. 安装依赖

go mod tidy

1.2. 构建程序

go build 或者 go install savour-hd

1.3. 启动程序

./savour-hd -c ./config.yml

. 1.4. 启动 RPC 接口测试界面

grpcui -plaintext 127.0.0.1:8089

2. 项目简介

上图是该项目的一个大概的设计图,整个项目包含了 RPC 服务,钱包 factory 和与各个底层链的对接。

func main() {
	var f = flag.String("c", "config.yml", "config path")
	flag.Parse()
	conf, err := config.New(*f)
	if err != nil {
		panic(err)
	}
	dispatcher, err := walletdispatcher.New(conf)
	if err != nil {
		log.Error("Setup dispatcher failed", "err", err)
		panic(err)
	}

	grpcServer := grpc.NewServer(grpc.UnaryInterceptor(dispatcher.Interceptor))
	defer grpcServer.GracefulStop()

	wallet2.RegisterWalletServiceServer(grpcServer, dispatcher)

	listen, err := net.Listen("tcp", ":"+conf.Server.Port)
	if err != nil {
		log.Error("net listen failed", "err", err)
		panic(err)
	}
	reflection.Register(grpcServer)

	log.Info("savour dao start success", "port", conf.Server.Port)

	if err := grpcServer.Serve(listen); err != nil {
		log.Error("grpc server serve failed", "err", err)
		panic(err)
	}
}
package walletdispatcher

import (
	"context"
	"github.com/SavourDao/savour-hd/rpc/common"
	"github.com/SavourDao/savour-hd/wallet"
	"github.com/SavourDao/savour-hd/wallet/solana"
	"runtime/debug"
	"strings"

	"github.com/SavourDao/savour-hd/config"
	wallet2 "github.com/SavourDao/savour-hd/rpc/wallet"
	"github.com/SavourDao/savour-hd/wallet/bitcoin"
	"github.com/SavourDao/savour-hd/wallet/ethereum"
	"github.com/ethereum/go-ethereum/log"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type CommonRequest interface {
	GetChain() string
}

type CommonReply = wallet2.SupportCoinsResponse

type ChainType = string

type WalletDispatcher struct {
	registry map[ChainType]wallet.WalletAdaptor
}

func (d *WalletDispatcher) mustEmbedUnimplementedWalletServiceServer() {
	//TODO implement me
	panic("implement me")
}

func New(conf *config.Config) (*WalletDispatcher, error) {
	dispatcher := WalletDispatcher{
		registry: make(map[ChainType]wallet.WalletAdaptor),
	}
	walletAdaptorFactoryMap := map[string]func(conf *config.Config) (wallet.WalletAdaptor, error){
		bitcoin.ChainName:  bitcoin.NewChainAdaptor,
		ethereum.ChainName: ethereum.NewChainAdaptor,
		solana.ChainName:   solana.NewChainAdaptor,
	}
	supportedChains := []string{bitcoin.ChainName, ethereum.ChainName, solana.ChainName}
	for _, c := range conf.Chains {
		if factory, ok := walletAdaptorFactoryMap[c]; ok {
			adaptor, err := factory(conf)
			if err != nil {
				log.Crit("failed to setup chain", "chain", c, "error", err)
			}
			dispatcher.registry[c] = adaptor
		} else {
			log.Error("unsupported chain", "chain", c, "supportedChains", supportedChains)
		}
	}
	return &dispatcher, nil
}

func NewLocal(network config.NetWorkType) *WalletDispatcher {
	dispatcher := WalletDispatcher{
		registry: make(map[ChainType]wallet.WalletAdaptor),
	}

	walletAdaptorFactoryMap := map[string]func(network config.NetWorkType) wallet.WalletAdaptor{
		bitcoin.ChainName:  bitcoin.NewLocalChainAdaptor,
		ethereum.ChainName: ethereum.NewLocalWalletAdaptor,
	}
	supportedChains := []string{bitcoin.ChainName, ethereum.ChainName}

	for _, c := range supportedChains {
		if factory, ok := walletAdaptorFactoryMap[c]; ok {
			dispatcher.registry[c] = factory(network)
		}
	}
	return &dispatcher
}

func (d *WalletDispatcher) Interceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
	defer func() {
		if e := recover(); e != nil {
			log.Error("panic error", "msg", e)
			log.Debug(string(debug.Stack()))
			err = status.Errorf(codes.Internal, "Panic err: %v", e)
		}
	}()

	pos := strings.LastIndex(info.FullMethod, "/")
	method := info.FullMethod[pos+1:]

	chain := req.(CommonRequest).GetChain()
	log.Info(method, "chain", chain, "req", req)

	resp, err = handler(ctx, req)
	log.Debug("Finish handling", "resp", resp, "err", err)
	return
}

func (d *WalletDispatcher) preHandler(req interface{}) (resp *CommonReply) {
	chain := req.(CommonRequest).GetChain()
	if _, ok := d.registry[chain]; !ok {
		return &CommonReply{
			Code:    common.ReturnCode_ERROR,
			Msg:     config.UnsupportedOperation,
			Support: false,
		}
	}
	return nil
}

func (d *WalletDispatcher) GetSupportCoins(ctx context.Context, request *wallet2.SupportCoinsRequest) (*wallet2.SupportCoinsResponse, error) {
	//TODO implement me
	panic("implement me")
}

func (d *WalletDispatcher) GetNonce(ctx context.Context, request *wallet2.NonceRequest) (*wallet2.NonceResponse, error) {
	resp := d.preHandler(request)
	if resp != nil {
		return &wallet2.NonceResponse{
			Code: common.ReturnCode_ERROR,
			Msg:  config.UnsupportedOperation,
		}, nil
	}
	return d.registry[request.Chain].GetNonce(request)
}

func (d *WalletDispatcher) GetGasPrice(ctx context.Context, request *wallet2.GasPriceRequest) (*wallet2.GasPriceResponse, error) {
	resp := d.preHandler(request)
	if resp != nil {
		return &wallet2.GasPriceResponse{
			Code: common.ReturnCode_ERROR,
			Msg:  config.UnsupportedOperation,
			Gas:  "",
		}, nil
	}
	return d.registry[request.Chain].GetGasPrice(request)
}

func (d *WalletDispatcher) SendTx(ctx context.Context, request *wallet2.SendTxRequest) (*wallet2.SendTxResponse, error) {
	resp := d.preHandler(request)
	if resp != nil {
		return &wallet2.SendTxResponse{
			Code:   common.ReturnCode_ERROR,
			Msg:    config.UnsupportedOperation,
			TxHash: "",
		}, nil
	}
	return d.registry[request.Chain].SendTx(request)
}

func (d *WalletDispatcher) GetBalance(ctx context.Context, request *wallet2.BalanceRequest) (*wallet2.BalanceResponse, error) {
	resp := d.preHandler(request)
	if resp != nil {
		return &wallet2.BalanceResponse{
			Code:    common.ReturnCode_ERROR,
			Msg:     config.UnsupportedOperation,
			Balance: "",
		}, nil
	}
	return d.registry[request.Chain].GetBalance(request)
}

func (d *WalletDispatcher) GetTxByAddress(ctx context.Context, request *wallet2.TxAddressRequest) (*wallet2.TxAddressResponse, error) {
	resp := d.preHandler(request)
	if resp != nil {
		return &wallet2.TxAddressResponse{
			Code: common.ReturnCode_ERROR,
			Msg:  config.UnsupportedOperation,
			Tx:   nil,
		}, nil
	}
	return d.registry[request.Chain].GetTxByAddress(request)
}

func (d *WalletDispatcher) GetTxByHash(ctx context.Context, request *wallet2.TxHashRequest) (*wallet2.TxHashResponse, error) {
	resp := d.preHandler(request)
	if resp != nil {
		return &wallet2.TxHashResponse{
			Code: common.ReturnCode_ERROR,
			Msg:  config.UnsupportedOperation,
			Tx:   nil,
		}, nil
	}
	return d.registry[request.Chain].GetTxByHash(request)
}

func (d *WalletDispatcher) GetAccount(ctx context.Context, request *wallet2.AccountRequest) (*wallet2.AccountResponse, error) {
	resp := d.preHandler(request)
	if resp != nil {
		return &wallet2.AccountResponse{
			Code:          common.ReturnCode_ERROR,
			Msg:           config.UnsupportedOperation,
			AccountNumber: "",
			Sequence:      "",
		}, nil
	}
	return d.registry[request.Chain].GetAccount(request)
}

func (d *WalletDispatcher) GetUtxo(ctx context.Context, request *wallet2.UtxoRequest) (*wallet2.UtxoResponse, error) {
	resp := d.preHandler(request)
	if resp != nil {
		return &wallet2.UtxoResponse{
			Code: common.ReturnCode_ERROR,
			Msg:  config.UnsupportedOperation,
		}, nil
	}
	return d.registry[request.Chain].GetUtxo(request)
}

func (d *WalletDispatcher) GetMinRent(ctx context.Context, request *wallet2.MinRentRequest) (*wallet2.MinRentResponse, error) {
	resp := d.preHandler(request)
	if resp != nil {
		return &wallet2.MinRentResponse{
			Code:  common.ReturnCode_ERROR,
			Msg:   config.UnsupportedOperation,
			Value: "",
		}, nil
	}
	return d.registry[request.Chain].GetMinRent(request)
}

具体的详情大家可以看项目源码。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值