package transfer import ( "context" "errors" "strings" "github.com/tech/sendico/gateway/chain/internal/service/gateway/driver" "github.com/tech/sendico/gateway/chain/internal/service/gateway/shared" "github.com/tech/sendico/pkg/api/routers/gsresponse" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mservice" chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1" "go.uber.org/zap" ) type estimateTransferFeeCommand struct { deps Deps } func NewEstimateTransfer(deps Deps) *estimateTransferFeeCommand { return &estimateTransferFeeCommand{deps: deps} } func (c *estimateTransferFeeCommand) Execute(ctx context.Context, req *chainv1.EstimateTransferFeeRequest) gsresponse.Responder[chainv1.EstimateTransferFeeResponse] { if err := c.deps.EnsureRepository(ctx); err != nil { c.deps.Logger.Warn("Repository unavailable", zap.Error(err)) return gsresponse.Unavailable[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, err) } if req == nil { c.deps.Logger.Warn("Empty request received") return gsresponse.InvalidArgument[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("request is required")) } sourceWalletRef := strings.TrimSpace(req.GetSourceWalletRef()) if sourceWalletRef == "" { c.deps.Logger.Warn("Source wallet ref missing") return gsresponse.InvalidArgument[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("source_wallet_ref is required")) } amount := req.GetAmount() if amount == nil || strings.TrimSpace(amount.GetAmount()) == "" || strings.TrimSpace(amount.GetCurrency()) == "" { c.deps.Logger.Warn("Amount missing or incomplete") return gsresponse.InvalidArgument[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("amount is required")) } sourceWallet, err := c.deps.Storage.Wallets().Get(ctx, sourceWalletRef) if err != nil { if errors.Is(err, merrors.ErrNoData) { c.deps.Logger.Warn("Source wallet not found", zap.String("source_wallet_ref", sourceWalletRef)) return gsresponse.NotFound[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, err) } c.deps.Logger.Warn("Storage get wallet failed", zap.Error(err), zap.String("source_wallet_ref", sourceWalletRef)) return gsresponse.Auto[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, err) } networkKey := strings.ToLower(strings.TrimSpace(sourceWallet.Network)) networkCfg, ok := c.deps.Networks.Network(networkKey) if !ok { c.deps.Logger.Warn("Unsupported chain", zap.String("network", networkKey)) return gsresponse.InvalidArgument[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported chain for wallet")) } if c.deps.Drivers == nil { c.deps.Logger.Warn("Chain drivers missing", zap.String("network", networkKey)) return gsresponse.Internal[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, merrors.Internal("chain drivers not configured")) } chainDriver, err := c.deps.Drivers.Driver(networkKey) if err != nil { c.deps.Logger.Warn("Unsupported chain driver", zap.String("network", networkKey), zap.Error(err)) return gsresponse.InvalidArgument[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported chain for wallet")) } dest, err := resolveDestination(ctx, c.deps, req.GetDestination(), sourceWallet) if err != nil { if errors.Is(err, merrors.ErrNoData) { c.deps.Logger.Warn("Destination not found", zap.String("destination_wallet_ref", req.GetDestination().GetManagedWalletRef())) return gsresponse.NotFound[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, err) } c.deps.Logger.Warn("Invalid destination", zap.Error(err)) return gsresponse.InvalidArgument[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, err) } destinationAddress, err := destinationAddress(ctx, c.deps, chainDriver, dest) if err != nil { c.deps.Logger.Warn("Failed to resolve destination address", zap.Error(err)) return gsresponse.InvalidArgument[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, err) } walletForFee := sourceWallet nativeCurrency := shared.NativeCurrency(networkCfg) if nativeCurrency != "" && strings.EqualFold(nativeCurrency, amount.GetCurrency()) { copyWallet := *sourceWallet copyWallet.ContractAddress = "" copyWallet.TokenSymbol = nativeCurrency walletForFee = ©Wallet } driverDeps := driver.Deps{ Logger: c.deps.Logger, Registry: c.deps.Networks, RPCTimeout: c.deps.RPCTimeout, } feeMoney, err := chainDriver.EstimateFee(ctx, driverDeps, networkCfg, walletForFee, destinationAddress, amount) if err != nil { c.deps.Logger.Warn("Fee estimation failed", zap.Error(err)) return gsresponse.Auto[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, err) } contextLabel := "erc20_transfer" if strings.TrimSpace(walletForFee.ContractAddress) == "" { contextLabel = "native_transfer" } resp := &chainv1.EstimateTransferFeeResponse{ NetworkFee: feeMoney, EstimationContext: contextLabel, } return gsresponse.Success(resp) }