168 lines
7.2 KiB
Go
168 lines
7.2 KiB
Go
package wallet
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
|
"github.com/tech/sendico/gateway/chain/storage/model"
|
|
"github.com/tech/sendico/pkg/api/routers/gsresponse"
|
|
"github.com/tech/sendico/pkg/merrors"
|
|
pkgmodel "github.com/tech/sendico/pkg/model"
|
|
"github.com/tech/sendico/pkg/mservice"
|
|
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type createManagedWalletCommand struct {
|
|
deps Deps
|
|
}
|
|
|
|
func NewCreateManagedWallet(deps Deps) *createManagedWalletCommand {
|
|
return &createManagedWalletCommand{deps: deps}
|
|
}
|
|
|
|
func (c *createManagedWalletCommand) Execute(ctx context.Context, req *chainv1.CreateManagedWalletRequest) gsresponse.Responder[chainv1.CreateManagedWalletResponse] {
|
|
if err := c.deps.EnsureRepository(ctx); err != nil {
|
|
c.deps.Logger.Warn("Repository unavailable", zap.Error(err))
|
|
return gsresponse.Unavailable[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, err)
|
|
}
|
|
if req == nil {
|
|
c.deps.Logger.Warn("Nil request")
|
|
return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("nil request"))
|
|
}
|
|
|
|
idempotencyKey := strings.TrimSpace(req.GetIdempotencyKey())
|
|
if idempotencyKey == "" {
|
|
c.deps.Logger.Warn("Missing idempotency key")
|
|
return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("idempotency_key is required"))
|
|
}
|
|
organizationRef := strings.TrimSpace(req.GetOrganizationRef())
|
|
if organizationRef == "" {
|
|
c.deps.Logger.Warn("Missing organization ref")
|
|
return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("organization_ref is required"))
|
|
}
|
|
ownerRef := strings.TrimSpace(req.GetOwnerRef())
|
|
if ownerRef == "" {
|
|
c.deps.Logger.Warn("Missing owner ref")
|
|
return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("owner_ref is required"))
|
|
}
|
|
|
|
asset := req.GetAsset()
|
|
if asset == nil {
|
|
c.deps.Logger.Warn("Missing asset")
|
|
return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("asset is required"))
|
|
}
|
|
|
|
chainKey, _ := shared.ChainKeyFromEnum(asset.GetChain())
|
|
if chainKey == "" {
|
|
c.deps.Logger.Warn("Unsupported chain", zap.Any("chain", asset.GetChain()))
|
|
return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported chain"))
|
|
}
|
|
networkCfg, ok := c.deps.Networks.Network(chainKey)
|
|
if !ok {
|
|
c.deps.Logger.Warn("Unsupported chain in config", zap.String("chain", chainKey))
|
|
return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported chain"))
|
|
}
|
|
if c.deps.Drivers == nil {
|
|
c.deps.Logger.Warn("Chain drivers missing", zap.String("chain", chainKey))
|
|
return gsresponse.Internal[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.Internal("chain drivers not configured"))
|
|
}
|
|
chainDriver, err := c.deps.Drivers.Driver(chainKey)
|
|
if err != nil {
|
|
c.deps.Logger.Warn("Unsupported chain driver", zap.String("chain", chainKey), zap.Error(err))
|
|
return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported chain"))
|
|
}
|
|
|
|
tokenSymbol := strings.ToUpper(strings.TrimSpace(asset.GetTokenSymbol()))
|
|
if tokenSymbol == "" {
|
|
c.deps.Logger.Warn("Missing token symbol")
|
|
return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("asset.token_symbol is required"))
|
|
}
|
|
contractAddress := strings.ToLower(strings.TrimSpace(asset.GetContractAddress()))
|
|
if contractAddress == "" {
|
|
if !strings.EqualFold(tokenSymbol, networkCfg.NativeToken) {
|
|
contractAddress = shared.ResolveContractAddress(networkCfg.TokenConfigs, tokenSymbol)
|
|
if contractAddress == "" {
|
|
c.deps.Logger.Warn("Unsupported token", zap.String("token", tokenSymbol), zap.String("chain", chainKey))
|
|
return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported token for chain"))
|
|
}
|
|
}
|
|
}
|
|
|
|
walletRef := shared.GenerateWalletRef()
|
|
if c.deps.KeyManager == nil {
|
|
c.deps.Logger.Warn("Key manager missing")
|
|
return gsresponse.Internal[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.Internal("key manager not configured"))
|
|
}
|
|
|
|
keyInfo, err := c.deps.KeyManager.CreateManagedWalletKey(ctx, walletRef, chainKey)
|
|
if err != nil {
|
|
c.deps.Logger.Warn("Key manager error", zap.Error(err))
|
|
return gsresponse.Auto[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, err)
|
|
}
|
|
if keyInfo == nil || strings.TrimSpace(keyInfo.Address) == "" {
|
|
c.deps.Logger.Warn("Key manager returned empty address")
|
|
return gsresponse.Internal[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.Internal("key manager returned empty address"))
|
|
}
|
|
depositAddress, err := chainDriver.FormatAddress(keyInfo.Address)
|
|
if err != nil {
|
|
c.deps.Logger.Warn("Invalid derived deposit address", zap.String("wallet_ref", walletRef), zap.Error(err))
|
|
return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, err)
|
|
}
|
|
|
|
metadata := shared.CloneMetadata(req.GetMetadata())
|
|
desc := req.GetDescribable()
|
|
name := strings.TrimSpace(desc.GetName())
|
|
if name == "" {
|
|
name = strings.TrimSpace(metadata["name"])
|
|
}
|
|
var description *string
|
|
if desc != nil && desc.Description != nil {
|
|
if trimmed := strings.TrimSpace(desc.GetDescription()); trimmed != "" {
|
|
description = &trimmed
|
|
}
|
|
}
|
|
if description == nil {
|
|
if trimmed := strings.TrimSpace(metadata["description"]); trimmed != "" {
|
|
description = &trimmed
|
|
}
|
|
}
|
|
if name == "" {
|
|
name = walletRef
|
|
}
|
|
|
|
wallet := &model.ManagedWallet{
|
|
Describable: pkgmodel.Describable{
|
|
Name: name,
|
|
},
|
|
IdempotencyKey: idempotencyKey,
|
|
WalletRef: walletRef,
|
|
OrganizationRef: organizationRef,
|
|
OwnerRef: ownerRef,
|
|
Network: chainKey,
|
|
TokenSymbol: tokenSymbol,
|
|
ContractAddress: contractAddress,
|
|
DepositAddress: depositAddress,
|
|
KeyReference: keyInfo.KeyID,
|
|
Status: model.ManagedWalletStatusActive,
|
|
Metadata: metadata,
|
|
}
|
|
if description != nil {
|
|
wallet.Describable.Description = description
|
|
}
|
|
|
|
created, err := c.deps.Storage.Wallets().Create(ctx, wallet)
|
|
if err != nil {
|
|
if errors.Is(err, merrors.ErrDataConflict) {
|
|
c.deps.Logger.Debug("Wallet already exists", zap.String("wallet_ref", walletRef), zap.String("idempotency_key", idempotencyKey))
|
|
return gsresponse.Success(&chainv1.CreateManagedWalletResponse{Wallet: toProtoManagedWallet(created)})
|
|
}
|
|
c.deps.Logger.Warn("Storage create failed", zap.Error(err), zap.String("wallet_ref", walletRef))
|
|
return gsresponse.Auto[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, err)
|
|
}
|
|
|
|
return gsresponse.Success(&chainv1.CreateManagedWalletResponse{Wallet: toProtoManagedWallet(created)})
|
|
}
|