142 lines
5.2 KiB
Go
142 lines
5.2 KiB
Go
package gateway
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver"
|
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
|
"github.com/tech/sendico/gateway/chain/storage/model"
|
|
"github.com/tech/sendico/pkg/merrors"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func (s *Service) launchTransferExecution(transferRef, sourceWalletRef string, network shared.Network) {
|
|
if s.drivers == nil {
|
|
return
|
|
}
|
|
|
|
go func(ref, walletRef string, net shared.Network) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
|
defer cancel()
|
|
|
|
if err := s.executeTransfer(ctx, ref, walletRef, net); err != nil {
|
|
s.logger.Warn("Failed to execute transfer", zap.String("transfer_ref", ref), zap.Error(err))
|
|
}
|
|
}(transferRef, sourceWalletRef, network)
|
|
}
|
|
|
|
func (s *Service) executeTransfer(ctx context.Context, transferRef, sourceWalletRef string, network shared.Network) error {
|
|
transfer, err := s.storage.Transfers().Get(ctx, transferRef)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sourceWallet, err := s.storage.Wallets().Get(ctx, sourceWalletRef)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := s.storage.Transfers().UpdateStatus(ctx, transferRef, model.TransferStatusSigning, "", ""); err != nil {
|
|
s.logger.Warn("Failed to update transfer status to signing", zap.String("transfer_ref", transferRef), zap.Error(err))
|
|
}
|
|
|
|
driverDeps := s.driverDeps()
|
|
chainDriver, err := s.driverForNetwork(network.Name)
|
|
if err != nil {
|
|
_, _ = s.storage.Transfers().UpdateStatus(ctx, transferRef, model.TransferStatusFailed, err.Error(), "")
|
|
return err
|
|
}
|
|
|
|
destinationAddress, err := s.destinationAddress(ctx, chainDriver, transfer.Destination)
|
|
if err != nil {
|
|
_, _ = s.storage.Transfers().UpdateStatus(ctx, transferRef, model.TransferStatusFailed, err.Error(), "")
|
|
return err
|
|
}
|
|
|
|
sourceAddress, err := chainDriver.NormalizeAddress(sourceWallet.DepositAddress)
|
|
if err != nil {
|
|
_, _ = s.storage.Transfers().UpdateStatus(ctx, transferRef, model.TransferStatusFailed, err.Error(), "")
|
|
return err
|
|
}
|
|
if chainDriver.Name() == "tron" && sourceAddress == destinationAddress {
|
|
s.logger.Info("Self transfer detected; skipping submission",
|
|
zap.String("transfer_ref", transferRef),
|
|
zap.String("wallet_ref", sourceWalletRef),
|
|
zap.String("network", network.Name),
|
|
)
|
|
if _, err := s.storage.Transfers().UpdateStatus(ctx, transferRef, model.TransferStatusConfirmed, "", ""); err != nil {
|
|
s.logger.Warn("Failed to update transfer status to confirmed", zap.String("transfer_ref", transferRef), zap.Error(err))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
txHash, err := chainDriver.SubmitTransfer(ctx, driverDeps, network, transfer, sourceWallet, destinationAddress)
|
|
if err != nil {
|
|
_, _ = s.storage.Transfers().UpdateStatus(ctx, transferRef, model.TransferStatusFailed, err.Error(), "")
|
|
return err
|
|
}
|
|
|
|
if _, err := s.storage.Transfers().UpdateStatus(ctx, transferRef, model.TransferStatusSubmitted, "", txHash); err != nil {
|
|
s.logger.Warn("Failed to update transfer status to submitted", zap.String("transfer_ref", transferRef), zap.Error(err))
|
|
}
|
|
|
|
receiptCtx, cancel := context.WithTimeout(ctx, 10*time.Minute)
|
|
defer cancel()
|
|
receipt, err := chainDriver.AwaitConfirmation(receiptCtx, driverDeps, network, txHash)
|
|
if err != nil {
|
|
if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, context.Canceled) {
|
|
s.logger.Warn("Failed to await transfer confirmation", zap.String("transfer_ref", transferRef), zap.Error(err))
|
|
}
|
|
return err
|
|
}
|
|
|
|
if receipt != nil && receipt.Status == types.ReceiptStatusSuccessful {
|
|
if _, err := s.storage.Transfers().UpdateStatus(ctx, transferRef, model.TransferStatusConfirmed, "", txHash); err != nil {
|
|
s.logger.Warn("Failed to update transfer status to confirmed", zap.String("transfer_ref", transferRef), zap.Error(err))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if _, err := s.storage.Transfers().UpdateStatus(ctx, transferRef, model.TransferStatusFailed, "transaction reverted", txHash); err != nil {
|
|
s.logger.Warn("Failed to update transfer status to failed", zap.String("transfer_ref", transferRef), zap.Error(err))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) destinationAddress(ctx context.Context, chainDriver driver.Driver, dest model.TransferDestination) (string, error) {
|
|
if ref := strings.TrimSpace(dest.ManagedWalletRef); ref != "" {
|
|
wallet, err := s.storage.Wallets().Get(ctx, ref)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if strings.TrimSpace(wallet.DepositAddress) == "" {
|
|
return "", merrors.Internal("destination wallet missing deposit address")
|
|
}
|
|
return chainDriver.NormalizeAddress(wallet.DepositAddress)
|
|
}
|
|
if addr := strings.TrimSpace(dest.ExternalAddress); addr != "" {
|
|
return chainDriver.NormalizeAddress(addr)
|
|
}
|
|
return "", merrors.InvalidArgument("transfer destination address not resolved")
|
|
}
|
|
|
|
func (s *Service) driverDeps() driver.Deps {
|
|
return driver.Deps{
|
|
Logger: s.logger.Named("driver"),
|
|
Registry: s.networkRegistry,
|
|
KeyManager: s.keyManager,
|
|
RPCTimeout: s.settings.rpcTimeout(),
|
|
}
|
|
}
|
|
|
|
func (s *Service) driverForNetwork(network string) (driver.Driver, error) {
|
|
if s.drivers == nil {
|
|
return nil, merrors.Internal("chain drivers not configured")
|
|
}
|
|
return s.drivers.Driver(network)
|
|
}
|