215 lines
7.2 KiB
Go
215 lines
7.2 KiB
Go
package gateway
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/tech/sendico/chain/gateway/internal/keymanager"
|
|
"github.com/tech/sendico/chain/gateway/storage"
|
|
"github.com/tech/sendico/chain/gateway/storage/model"
|
|
"github.com/tech/sendico/pkg/api/routers"
|
|
"github.com/tech/sendico/pkg/api/routers/gsresponse"
|
|
clockpkg "github.com/tech/sendico/pkg/clock"
|
|
msg "github.com/tech/sendico/pkg/messaging"
|
|
"github.com/tech/sendico/pkg/mlogger"
|
|
"github.com/tech/sendico/pkg/mservice"
|
|
gatewayv1 "github.com/tech/sendico/pkg/proto/chain/gateway/v1"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
"google.golang.org/grpc"
|
|
)
|
|
|
|
type serviceError string
|
|
|
|
func (e serviceError) Error() string {
|
|
return string(e)
|
|
}
|
|
|
|
var (
|
|
errStorageUnavailable = serviceError("chain_gateway: storage not initialised")
|
|
)
|
|
|
|
// Service implements the ChainGatewayService RPC contract.
|
|
type Service struct {
|
|
logger mlogger.Logger
|
|
storage storage.Repository
|
|
producer msg.Producer
|
|
clock clockpkg.Clock
|
|
|
|
networks map[string]Network
|
|
serviceWallet ServiceWallet
|
|
keyManager keymanager.Manager
|
|
executor TransferExecutor
|
|
|
|
gatewayv1.UnimplementedChainGatewayServiceServer
|
|
}
|
|
|
|
// NewService constructs the chain gateway service skeleton.
|
|
func NewService(logger mlogger.Logger, repo storage.Repository, producer msg.Producer, opts ...Option) *Service {
|
|
svc := &Service{
|
|
logger: logger.Named("chain_gateway"),
|
|
storage: repo,
|
|
producer: producer,
|
|
clock: clockpkg.System{},
|
|
networks: map[string]Network{},
|
|
}
|
|
|
|
initMetrics()
|
|
|
|
for _, opt := range opts {
|
|
if opt != nil {
|
|
opt(svc)
|
|
}
|
|
}
|
|
|
|
if svc.clock == nil {
|
|
svc.clock = clockpkg.System{}
|
|
}
|
|
if svc.networks == nil {
|
|
svc.networks = map[string]Network{}
|
|
}
|
|
|
|
return svc
|
|
}
|
|
|
|
// Register wires the service onto the provided gRPC router.
|
|
func (s *Service) Register(router routers.GRPC) error {
|
|
return router.Register(func(reg grpc.ServiceRegistrar) {
|
|
gatewayv1.RegisterChainGatewayServiceServer(reg, s)
|
|
})
|
|
}
|
|
|
|
func (s *Service) CreateManagedWallet(ctx context.Context, req *gatewayv1.CreateManagedWalletRequest) (*gatewayv1.CreateManagedWalletResponse, error) {
|
|
return executeUnary(ctx, s, "CreateManagedWallet", s.createManagedWalletHandler, req)
|
|
}
|
|
|
|
func (s *Service) GetManagedWallet(ctx context.Context, req *gatewayv1.GetManagedWalletRequest) (*gatewayv1.GetManagedWalletResponse, error) {
|
|
return executeUnary(ctx, s, "GetManagedWallet", s.getManagedWalletHandler, req)
|
|
}
|
|
|
|
func (s *Service) ListManagedWallets(ctx context.Context, req *gatewayv1.ListManagedWalletsRequest) (*gatewayv1.ListManagedWalletsResponse, error) {
|
|
return executeUnary(ctx, s, "ListManagedWallets", s.listManagedWalletsHandler, req)
|
|
}
|
|
|
|
func (s *Service) GetWalletBalance(ctx context.Context, req *gatewayv1.GetWalletBalanceRequest) (*gatewayv1.GetWalletBalanceResponse, error) {
|
|
return executeUnary(ctx, s, "GetWalletBalance", s.getWalletBalanceHandler, req)
|
|
}
|
|
|
|
func (s *Service) SubmitTransfer(ctx context.Context, req *gatewayv1.SubmitTransferRequest) (*gatewayv1.SubmitTransferResponse, error) {
|
|
return executeUnary(ctx, s, "SubmitTransfer", s.submitTransferHandler, req)
|
|
}
|
|
|
|
func (s *Service) GetTransfer(ctx context.Context, req *gatewayv1.GetTransferRequest) (*gatewayv1.GetTransferResponse, error) {
|
|
return executeUnary(ctx, s, "GetTransfer", s.getTransferHandler, req)
|
|
}
|
|
|
|
func (s *Service) ListTransfers(ctx context.Context, req *gatewayv1.ListTransfersRequest) (*gatewayv1.ListTransfersResponse, error) {
|
|
return executeUnary(ctx, s, "ListTransfers", s.listTransfersHandler, req)
|
|
}
|
|
|
|
func (s *Service) EstimateTransferFee(ctx context.Context, req *gatewayv1.EstimateTransferFeeRequest) (*gatewayv1.EstimateTransferFeeResponse, error) {
|
|
return executeUnary(ctx, s, "EstimateTransferFee", s.estimateTransferFeeHandler, req)
|
|
}
|
|
|
|
func (s *Service) ensureRepository(ctx context.Context) error {
|
|
if s.storage == nil {
|
|
return errStorageUnavailable
|
|
}
|
|
return s.storage.Ping(ctx)
|
|
}
|
|
|
|
func executeUnary[TReq any, TResp any](ctx context.Context, svc *Service, method string, handler func(context.Context, *TReq) gsresponse.Responder[TResp], req *TReq) (*TResp, error) {
|
|
start := svc.clock.Now()
|
|
resp, err := gsresponse.Unary(svc.logger, mservice.ChainGateway, handler)(ctx, req)
|
|
observeRPC(method, err, svc.clock.Now().Sub(start))
|
|
return resp, err
|
|
}
|
|
|
|
func resolveContractAddress(tokens []TokenContract, symbol string) string {
|
|
upper := strings.ToUpper(symbol)
|
|
for _, token := range tokens {
|
|
if strings.EqualFold(token.Symbol, upper) && token.ContractAddress != "" {
|
|
return strings.ToLower(token.ContractAddress)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func generateWalletRef() string {
|
|
return primitive.NewObjectID().Hex()
|
|
}
|
|
|
|
func generateTransferRef() string {
|
|
return primitive.NewObjectID().Hex()
|
|
}
|
|
|
|
func chainKeyFromEnum(chain gatewayv1.ChainNetwork) (string, gatewayv1.ChainNetwork) {
|
|
if name, ok := gatewayv1.ChainNetwork_name[int32(chain)]; ok {
|
|
key := strings.ToLower(strings.TrimPrefix(name, "CHAIN_NETWORK_"))
|
|
return key, chain
|
|
}
|
|
return "", gatewayv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED
|
|
}
|
|
|
|
func chainEnumFromName(name string) gatewayv1.ChainNetwork {
|
|
if name == "" {
|
|
return gatewayv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED
|
|
}
|
|
upper := strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(name, " ", "_"), "-", "_"))
|
|
key := "CHAIN_NETWORK_" + upper
|
|
if val, ok := gatewayv1.ChainNetwork_value[key]; ok {
|
|
return gatewayv1.ChainNetwork(val)
|
|
}
|
|
return gatewayv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED
|
|
}
|
|
|
|
func managedWalletStatusToProto(status model.ManagedWalletStatus) gatewayv1.ManagedWalletStatus {
|
|
switch status {
|
|
case model.ManagedWalletStatusActive:
|
|
return gatewayv1.ManagedWalletStatus_MANAGED_WALLET_ACTIVE
|
|
case model.ManagedWalletStatusSuspended:
|
|
return gatewayv1.ManagedWalletStatus_MANAGED_WALLET_SUSPENDED
|
|
case model.ManagedWalletStatusClosed:
|
|
return gatewayv1.ManagedWalletStatus_MANAGED_WALLET_CLOSED
|
|
default:
|
|
return gatewayv1.ManagedWalletStatus_MANAGED_WALLET_STATUS_UNSPECIFIED
|
|
}
|
|
}
|
|
|
|
func transferStatusToModel(status gatewayv1.TransferStatus) model.TransferStatus {
|
|
switch status {
|
|
case gatewayv1.TransferStatus_TRANSFER_PENDING:
|
|
return model.TransferStatusPending
|
|
case gatewayv1.TransferStatus_TRANSFER_SIGNING:
|
|
return model.TransferStatusSigning
|
|
case gatewayv1.TransferStatus_TRANSFER_SUBMITTED:
|
|
return model.TransferStatusSubmitted
|
|
case gatewayv1.TransferStatus_TRANSFER_CONFIRMED:
|
|
return model.TransferStatusConfirmed
|
|
case gatewayv1.TransferStatus_TRANSFER_FAILED:
|
|
return model.TransferStatusFailed
|
|
case gatewayv1.TransferStatus_TRANSFER_CANCELLED:
|
|
return model.TransferStatusCancelled
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func transferStatusToProto(status model.TransferStatus) gatewayv1.TransferStatus {
|
|
switch status {
|
|
case model.TransferStatusPending:
|
|
return gatewayv1.TransferStatus_TRANSFER_PENDING
|
|
case model.TransferStatusSigning:
|
|
return gatewayv1.TransferStatus_TRANSFER_SIGNING
|
|
case model.TransferStatusSubmitted:
|
|
return gatewayv1.TransferStatus_TRANSFER_SUBMITTED
|
|
case model.TransferStatusConfirmed:
|
|
return gatewayv1.TransferStatus_TRANSFER_CONFIRMED
|
|
case model.TransferStatusFailed:
|
|
return gatewayv1.TransferStatus_TRANSFER_FAILED
|
|
case model.TransferStatusCancelled:
|
|
return gatewayv1.TransferStatus_TRANSFER_CANCELLED
|
|
default:
|
|
return gatewayv1.TransferStatus_TRANSFER_STATUS_UNSPECIFIED
|
|
}
|
|
}
|