refactored payment orchestration
This commit is contained in:
@@ -26,8 +26,8 @@ func resolveDestination(ctx context.Context, deps Deps, dest *chainv1.TransferDe
|
||||
deps.Logger.Warn("Destination wallet lookup failed", zap.Error(err), zap.String("managed_wallet_ref", managedRef))
|
||||
return model.TransferDestination{}, err
|
||||
}
|
||||
if !strings.EqualFold(wallet.Network, source.Network) {
|
||||
deps.Logger.Warn("Destination wallet network mismatch", zap.String("source_network", source.Network), zap.String("dest_network", wallet.Network))
|
||||
if wallet.Network != source.Network {
|
||||
deps.Logger.Warn("Destination wallet network mismatch", zap.String("source_network", string(source.Network)), zap.String("dest_network", string(wallet.Network)))
|
||||
return model.TransferDestination{}, merrors.InvalidArgument("destination wallet network mismatch")
|
||||
}
|
||||
if strings.TrimSpace(wallet.DepositAddress) == "" {
|
||||
@@ -44,12 +44,12 @@ func resolveDestination(ctx context.Context, deps Deps, dest *chainv1.TransferDe
|
||||
return model.TransferDestination{}, merrors.InvalidArgument("destination is required")
|
||||
}
|
||||
if deps.Drivers == nil {
|
||||
deps.Logger.Warn("Chain drivers missing", zap.String("network", source.Network))
|
||||
deps.Logger.Warn("Chain drivers missing", zap.String("network", string(source.Network)))
|
||||
return model.TransferDestination{}, merrors.Internal("chain drivers not configured")
|
||||
}
|
||||
chainDriver, err := deps.Drivers.Driver(source.Network)
|
||||
if err != nil {
|
||||
deps.Logger.Warn("Unsupported chain driver", zap.String("network", source.Network), zap.Error(err))
|
||||
deps.Logger.Warn("Unsupported chain driver", zap.String("network", string(source.Network)), zap.Error(err))
|
||||
return model.TransferDestination{}, merrors.InvalidArgument("unsupported chain for wallet")
|
||||
}
|
||||
normalized, err := chainDriver.NormalizeAddress(external)
|
||||
|
||||
@@ -53,19 +53,18 @@ func (c *estimateTransferFeeCommand) Execute(ctx context.Context, req *chainv1.E
|
||||
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)
|
||||
networkCfg, ok := c.deps.Networks.Network(sourceWallet.Network)
|
||||
if !ok {
|
||||
c.deps.Logger.Warn("Unsupported chain", zap.String("network", networkKey))
|
||||
c.deps.Logger.Warn("Unsupported chain", zap.String("network", string(sourceWallet.Network)))
|
||||
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))
|
||||
c.deps.Logger.Warn("Chain drivers missing", zap.String("network", string(sourceWallet.Network)))
|
||||
return gsresponse.Internal[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, merrors.Internal("chain drivers not configured"))
|
||||
}
|
||||
chainDriver, err := c.deps.Drivers.Driver(networkKey)
|
||||
chainDriver, err := c.deps.Drivers.Driver(sourceWallet.Network)
|
||||
if err != nil {
|
||||
c.deps.Logger.Warn("Unsupported chain driver", zap.String("network", networkKey), zap.Error(err))
|
||||
c.deps.Logger.Warn("Unsupported chain driver", zap.String("network", string(sourceWallet.Network)), zap.Error(err))
|
||||
return gsresponse.InvalidArgument[chainv1.EstimateTransferFeeResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported chain for wallet"))
|
||||
}
|
||||
|
||||
|
||||
@@ -125,9 +125,9 @@ func (c *ensureGasTopUpCommand) Execute(ctx context.Context, req *chainv1.Ensure
|
||||
Destination: &chainv1.TransferDestination{
|
||||
Destination: &chainv1.TransferDestination_ManagedWalletRef{ManagedWalletRef: targetWalletRef},
|
||||
},
|
||||
Amount: topUp,
|
||||
Metadata: shared.CloneMetadata(req.GetMetadata()),
|
||||
ClientReference: strings.TrimSpace(req.GetClientReference()),
|
||||
Amount: topUp,
|
||||
Metadata: shared.CloneMetadata(req.GetMetadata()),
|
||||
PaymentRef: strings.TrimSpace(req.GetPaymentRef()),
|
||||
}
|
||||
|
||||
submitResponder := NewSubmitTransfer(c.deps.WithLogger("transfer.submit")).Execute(ctx, submitReq)
|
||||
@@ -152,12 +152,7 @@ func computeGasTopUp(ctx context.Context, deps Deps, walletRef string, estimated
|
||||
return nil, false, nil, nil, err
|
||||
}
|
||||
|
||||
networkKey := strings.ToLower(strings.TrimSpace(walletModel.Network))
|
||||
if strings.HasPrefix(networkKey, "tron") {
|
||||
return nil, false, nil, nil, merrors.InvalidArgument("tron networks must use the tron gateway")
|
||||
}
|
||||
|
||||
networkCfg, ok := deps.Networks.Network(networkKey)
|
||||
networkCfg, ok := deps.Networks.Network(walletModel.Network)
|
||||
if !ok {
|
||||
return nil, false, nil, nil, merrors.InvalidArgument("unsupported chain for wallet")
|
||||
}
|
||||
@@ -248,7 +243,7 @@ func logDecision(logger mlogger.Logger, walletRef string, estimatedFee *moneyv1.
|
||||
zap.Bool("cap_hit", capHit),
|
||||
}
|
||||
if walletModel != nil {
|
||||
fields = append(fields, zap.String("network", strings.TrimSpace(walletModel.Network)))
|
||||
fields = append(fields, zap.String("network", string(walletModel.Network)))
|
||||
}
|
||||
logger.Info("Gas top-up decision", fields...)
|
||||
}
|
||||
|
||||
@@ -41,8 +41,8 @@ func toProtoTransfer(transfer *model.Transfer) *chainv1.Transfer {
|
||||
SourceWalletRef: transfer.SourceWalletRef,
|
||||
Destination: destination,
|
||||
Asset: asset,
|
||||
RequestedAmount: shared.CloneMoney(transfer.RequestedAmount),
|
||||
NetAmount: shared.CloneMoney(transfer.NetAmount),
|
||||
RequestedAmount: shared.MonenyToProto(transfer.RequestedAmount),
|
||||
NetAmount: shared.MonenyToProto(transfer.NetAmount),
|
||||
Fees: protoFees,
|
||||
Status: shared.TransferStatusToProto(transfer.Status),
|
||||
TransactionHash: transfer.TxHash,
|
||||
|
||||
@@ -77,10 +77,9 @@ func (c *submitTransferCommand) Execute(ctx context.Context, req *chainv1.Submit
|
||||
c.deps.Logger.Warn("Organization mismatch", zap.String("wallet_org", sourceWallet.OrganizationRef), zap.String("req_org", organizationRef))
|
||||
return gsresponse.InvalidArgument[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("organization_ref mismatch with wallet"))
|
||||
}
|
||||
networkKey := strings.ToLower(strings.TrimSpace(sourceWallet.Network))
|
||||
networkCfg, ok := c.deps.Networks.Network(networkKey)
|
||||
networkCfg, ok := c.deps.Networks.Network(sourceWallet.Network)
|
||||
if !ok {
|
||||
c.deps.Logger.Warn("Unsupported chain", zap.String("network", networkKey))
|
||||
c.deps.Logger.Warn("Unsupported chain", zap.String("network", string(sourceWallet.Network)))
|
||||
return gsresponse.InvalidArgument[chainv1.SubmitTransferResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported chain for wallet"))
|
||||
}
|
||||
|
||||
@@ -124,17 +123,19 @@ func (c *submitTransferCommand) Execute(ctx context.Context, req *chainv1.Submit
|
||||
transfer := &model.Transfer{
|
||||
IdempotencyKey: idempotencyKey,
|
||||
TransferRef: shared.GenerateTransferRef(),
|
||||
IntentRef: req.IntentRef,
|
||||
OperationRef: req.OperationRef,
|
||||
OrganizationRef: organizationRef,
|
||||
SourceWalletRef: sourceWalletRef,
|
||||
Destination: destination,
|
||||
Network: sourceWallet.Network,
|
||||
TokenSymbol: effectiveTokenSymbol,
|
||||
ContractAddress: effectiveContractAddress,
|
||||
RequestedAmount: shared.CloneMoney(amount),
|
||||
NetAmount: netAmount,
|
||||
RequestedAmount: shared.ProtoToMoney(amount),
|
||||
NetAmount: shared.ProtoToMoney(netAmount),
|
||||
Fees: fees,
|
||||
Status: model.TransferStatusPending,
|
||||
ClientReference: strings.TrimSpace(req.GetClientReference()),
|
||||
Status: model.TransferStatusCreated,
|
||||
PaymentRef: strings.TrimSpace(req.GetPaymentRef()),
|
||||
LastStatusAt: c.deps.Clock.Now().UTC(),
|
||||
}
|
||||
|
||||
|
||||
@@ -51,23 +51,19 @@ func (c *createManagedWalletCommand) Execute(ctx context.Context, req *chainv1.C
|
||||
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"))
|
||||
}
|
||||
chainKey := shared.ChainKeyFromEnum(asset.GetChain())
|
||||
networkCfg, ok := c.deps.Networks.Network(chainKey)
|
||||
if !ok {
|
||||
c.deps.Logger.Warn("Unsupported chain in config", zap.String("chain", chainKey))
|
||||
c.deps.Logger.Warn("Unsupported chain in config", zap.String("chain", string(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))
|
||||
c.deps.Logger.Warn("Chain drivers missing", zap.String("chain", string(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))
|
||||
c.deps.Logger.Warn("Unsupported chain driver", zap.String("chain", string(chainKey)), zap.Error(err))
|
||||
return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported chain"))
|
||||
}
|
||||
|
||||
@@ -81,7 +77,7 @@ func (c *createManagedWalletCommand) Execute(ctx context.Context, req *chainv1.C
|
||||
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))
|
||||
c.deps.Logger.Warn("Unsupported token", zap.String("token", tokenSymbol), zap.String("chain", string(chainKey)))
|
||||
return gsresponse.InvalidArgument[chainv1.CreateManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("unsupported token for chain"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func (c *listManagedWalletsCommand) Execute(ctx context.Context, req *chainv1.Li
|
||||
filter.OwnerRefFilter = &ownerRef
|
||||
}
|
||||
if asset := req.GetAsset(); asset != nil {
|
||||
filter.Network, _ = shared.ChainKeyFromEnum(asset.GetChain())
|
||||
filter.Network = shared.ChainKeyFromEnum(asset.GetChain())
|
||||
filter.TokenSymbol = strings.TrimSpace(asset.GetTokenSymbol())
|
||||
}
|
||||
if page := req.GetPage(); page != nil {
|
||||
|
||||
@@ -3,7 +3,6 @@ package wallet
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver"
|
||||
"github.com/tech/sendico/gateway/chain/storage/model"
|
||||
@@ -24,21 +23,20 @@ func OnChainWalletBalances(ctx context.Context, deps Deps, wallet *model.Managed
|
||||
return nil, nil, merrors.Internal("chain drivers not configured")
|
||||
}
|
||||
|
||||
networkKey := strings.ToLower(strings.TrimSpace(wallet.Network))
|
||||
network, ok := deps.Networks.Network(networkKey)
|
||||
network, ok := deps.Networks.Network(wallet.Network)
|
||||
if !ok {
|
||||
logger.Warn("Requested network is not configured",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", networkKey),
|
||||
zap.String("network", string(wallet.Network)),
|
||||
)
|
||||
return nil, nil, merrors.Internal(fmt.Sprintf("Requested network '%s' is not configured", networkKey))
|
||||
return nil, nil, merrors.Internal(fmt.Sprintf("Requested network '%s' is not configured", wallet.Network))
|
||||
}
|
||||
|
||||
chainDriver, err := deps.Drivers.Driver(networkKey)
|
||||
chainDriver, err := deps.Drivers.Driver(wallet.Network)
|
||||
if err != nil {
|
||||
logger.Warn("Chain driver not configured",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", networkKey),
|
||||
zap.String("network", string(wallet.Network)),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, nil, merrors.InvalidArgument("unsupported chain")
|
||||
|
||||
@@ -165,11 +165,13 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
||||
IdempotencyKey: strings.TrimSpace(op.GetIdempotencyKey()),
|
||||
OrganizationRef: orgRef,
|
||||
SourceWalletRef: source,
|
||||
IntentRef: strings.TrimSpace(op.GetIntentRef()),
|
||||
OperationRef: op.GetOperationRef(),
|
||||
Destination: dest,
|
||||
Amount: amount,
|
||||
Fees: parseChainFees(reader),
|
||||
Metadata: shared.CloneMetadata(reader.StringMap("metadata")),
|
||||
ClientReference: strings.TrimSpace(reader.String("client_reference")),
|
||||
PaymentRef: strings.TrimSpace(reader.String("payment_ref")),
|
||||
})
|
||||
if err != nil {
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil
|
||||
@@ -208,7 +210,7 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
||||
return &connectorv1.SubmitOperationResponse{
|
||||
Receipt: &connectorv1.OperationReceipt{
|
||||
OperationId: opID,
|
||||
Status: connectorv1.OperationStatus_CONFIRMED,
|
||||
Status: connectorv1.OperationStatus_OPERATION_SUCCESS,
|
||||
Result: result,
|
||||
},
|
||||
}, nil
|
||||
@@ -238,7 +240,7 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
||||
return &connectorv1.SubmitOperationResponse{
|
||||
Receipt: &connectorv1.OperationReceipt{
|
||||
OperationId: opID,
|
||||
Status: connectorv1.OperationStatus_CONFIRMED,
|
||||
Status: connectorv1.OperationStatus_OPERATION_SUCCESS,
|
||||
Result: gasTopUpResult(resp.GetTopupAmount(), resp.GetCapHit(), ""),
|
||||
},
|
||||
}, nil
|
||||
@@ -256,12 +258,14 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
||||
}
|
||||
resp, err := s.EnsureGasTopUp(ctx, &chainv1.EnsureGasTopUpRequest{
|
||||
IdempotencyKey: strings.TrimSpace(op.GetIdempotencyKey()),
|
||||
IntentRef: strings.TrimSpace(op.GetIntentRef()),
|
||||
OperationRef: strings.TrimSpace(op.GetOperationRef()),
|
||||
OrganizationRef: orgRef,
|
||||
SourceWalletRef: source,
|
||||
TargetWalletRef: target,
|
||||
EstimatedTotalFee: fee,
|
||||
Metadata: shared.CloneMetadata(reader.StringMap("metadata")),
|
||||
ClientReference: strings.TrimSpace(reader.String("client_reference")),
|
||||
PaymentRef: strings.TrimSpace(reader.String("payment_ref")),
|
||||
})
|
||||
if err != nil {
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil
|
||||
@@ -273,7 +277,7 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
||||
return &connectorv1.SubmitOperationResponse{
|
||||
Receipt: &connectorv1.OperationReceipt{
|
||||
OperationId: opID,
|
||||
Status: connectorv1.OperationStatus_CONFIRMED,
|
||||
Status: shared.СhainTransferStatusToOperation(resp.GetTransfer().GetStatus()),
|
||||
Result: gasTopUpResult(resp.GetTopupAmount(), resp.GetCapHit(), transferRef),
|
||||
},
|
||||
}, nil
|
||||
@@ -544,25 +548,51 @@ func chainTransferToOperation(transfer *chainv1.Transfer) *connectorv1.Operation
|
||||
|
||||
func chainTransferStatusToOperation(status chainv1.TransferStatus) connectorv1.OperationStatus {
|
||||
switch status {
|
||||
case chainv1.TransferStatus_TRANSFER_CONFIRMED:
|
||||
return connectorv1.OperationStatus_CONFIRMED
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_CREATED:
|
||||
return connectorv1.OperationStatus_OPERATION_CREATED
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_PROCESSING:
|
||||
return connectorv1.OperationStatus_OPERATION_PROCESSING
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_WAITING:
|
||||
return connectorv1.OperationStatus_OPERATION_WAITING
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_SUCCESS:
|
||||
return connectorv1.OperationStatus_OPERATION_SUCCESS
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_FAILED:
|
||||
return connectorv1.OperationStatus_FAILED
|
||||
return connectorv1.OperationStatus_OPERATION_FAILED
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_CANCELLED:
|
||||
return connectorv1.OperationStatus_CANCELED
|
||||
return connectorv1.OperationStatus_OPERATION_CANCELLED
|
||||
|
||||
default:
|
||||
return connectorv1.OperationStatus_PENDING
|
||||
return connectorv1.OperationStatus_OPERATION_STATUS_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func chainStatusFromOperation(status connectorv1.OperationStatus) chainv1.TransferStatus {
|
||||
switch status {
|
||||
case connectorv1.OperationStatus_CONFIRMED:
|
||||
return chainv1.TransferStatus_TRANSFER_CONFIRMED
|
||||
case connectorv1.OperationStatus_FAILED:
|
||||
|
||||
case connectorv1.OperationStatus_OPERATION_CREATED:
|
||||
return chainv1.TransferStatus_TRANSFER_CREATED
|
||||
|
||||
case connectorv1.OperationStatus_OPERATION_PROCESSING:
|
||||
return chainv1.TransferStatus_TRANSFER_PROCESSING
|
||||
|
||||
case connectorv1.OperationStatus_OPERATION_WAITING:
|
||||
return chainv1.TransferStatus_TRANSFER_WAITING
|
||||
|
||||
case connectorv1.OperationStatus_OPERATION_SUCCESS:
|
||||
return chainv1.TransferStatus_TRANSFER_SUCCESS
|
||||
|
||||
case connectorv1.OperationStatus_OPERATION_FAILED:
|
||||
return chainv1.TransferStatus_TRANSFER_FAILED
|
||||
case connectorv1.OperationStatus_CANCELED:
|
||||
|
||||
case connectorv1.OperationStatus_OPERATION_CANCELLED:
|
||||
return chainv1.TransferStatus_TRANSFER_CANCELLED
|
||||
|
||||
default:
|
||||
return chainv1.TransferStatus_TRANSFER_STATUS_UNSPECIFIED
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func (d *Driver) NormalizeAddress(address string) (string, error) {
|
||||
func (d *Driver) Balance(ctx context.Context, deps driver.Deps, network shared.Network, wallet *model.ManagedWallet) (*moneyv1.Money, error) {
|
||||
d.logger.Debug("Balance request",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
)
|
||||
driverDeps := deps
|
||||
driverDeps.Logger = d.logger
|
||||
@@ -55,13 +55,13 @@ func (d *Driver) Balance(ctx context.Context, deps driver.Deps, network shared.N
|
||||
if err != nil {
|
||||
d.logger.Warn("Balance failed",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else if result != nil {
|
||||
d.logger.Debug("Balance result",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.String("amount", result.Amount),
|
||||
zap.String("currency", result.Currency),
|
||||
)
|
||||
@@ -72,7 +72,7 @@ func (d *Driver) Balance(ctx context.Context, deps driver.Deps, network shared.N
|
||||
func (d *Driver) NativeBalance(ctx context.Context, deps driver.Deps, network shared.Network, wallet *model.ManagedWallet) (*moneyv1.Money, error) {
|
||||
d.logger.Debug("Native balance request",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
)
|
||||
driverDeps := deps
|
||||
driverDeps.Logger = d.logger
|
||||
@@ -80,13 +80,13 @@ func (d *Driver) NativeBalance(ctx context.Context, deps driver.Deps, network sh
|
||||
if err != nil {
|
||||
d.logger.Warn("Native balance failed",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else if result != nil {
|
||||
d.logger.Debug("Native balance result",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.String("amount", result.Amount),
|
||||
zap.String("currency", result.Currency),
|
||||
)
|
||||
@@ -97,7 +97,7 @@ func (d *Driver) NativeBalance(ctx context.Context, deps driver.Deps, network sh
|
||||
func (d *Driver) EstimateFee(ctx context.Context, deps driver.Deps, network shared.Network, wallet *model.ManagedWallet, destination string, amount *moneyv1.Money) (*moneyv1.Money, error) {
|
||||
d.logger.Debug("Estimate fee request",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.String("destination", destination),
|
||||
)
|
||||
driverDeps := deps
|
||||
@@ -106,13 +106,13 @@ func (d *Driver) EstimateFee(ctx context.Context, deps driver.Deps, network shar
|
||||
if err != nil {
|
||||
d.logger.Warn("Estimate fee failed",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else if result != nil {
|
||||
d.logger.Debug("Estimate fee result",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.String("amount", result.Amount),
|
||||
zap.String("currency", result.Currency),
|
||||
)
|
||||
@@ -123,7 +123,7 @@ func (d *Driver) EstimateFee(ctx context.Context, deps driver.Deps, network shar
|
||||
func (d *Driver) SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Network, transfer *model.Transfer, source *model.ManagedWallet, destination string) (string, error) {
|
||||
d.logger.Debug("Submit transfer request",
|
||||
zap.String("transfer_ref", transfer.TransferRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.String("destination", destination),
|
||||
)
|
||||
driverDeps := deps
|
||||
@@ -132,13 +132,13 @@ func (d *Driver) SubmitTransfer(ctx context.Context, deps driver.Deps, network s
|
||||
if err != nil {
|
||||
d.logger.Warn("Submit transfer failed",
|
||||
zap.String("transfer_ref", transfer.TransferRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
d.logger.Debug("Submit transfer result",
|
||||
zap.String("transfer_ref", transfer.TransferRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.String("tx_hash", txHash),
|
||||
)
|
||||
}
|
||||
@@ -148,7 +148,7 @@ func (d *Driver) SubmitTransfer(ctx context.Context, deps driver.Deps, network s
|
||||
func (d *Driver) AwaitConfirmation(ctx context.Context, deps driver.Deps, network shared.Network, txHash string) (*types.Receipt, error) {
|
||||
d.logger.Debug("Await confirmation",
|
||||
zap.String("tx_hash", txHash),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
)
|
||||
driverDeps := deps
|
||||
driverDeps.Logger = d.logger
|
||||
@@ -156,13 +156,13 @@ func (d *Driver) AwaitConfirmation(ctx context.Context, deps driver.Deps, networ
|
||||
if err != nil {
|
||||
d.logger.Warn("Await confirmation failed",
|
||||
zap.String("tx_hash", txHash),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else if receipt != nil {
|
||||
d.logger.Debug("Await confirmation result",
|
||||
zap.String("tx_hash", txHash),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Uint64("block_number", receipt.BlockNumber.Uint64()),
|
||||
zap.Uint64("status", receipt.Status),
|
||||
)
|
||||
|
||||
@@ -47,7 +47,7 @@ func (d *Driver) NormalizeAddress(address string) (string, error) {
|
||||
func (d *Driver) Balance(ctx context.Context, deps driver.Deps, network shared.Network, wallet *model.ManagedWallet) (*moneyv1.Money, error) {
|
||||
d.logger.Debug("Balance request",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
)
|
||||
driverDeps := deps
|
||||
driverDeps.Logger = d.logger
|
||||
@@ -55,13 +55,13 @@ func (d *Driver) Balance(ctx context.Context, deps driver.Deps, network shared.N
|
||||
if err != nil {
|
||||
d.logger.Warn("Balance failed",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else if result != nil {
|
||||
d.logger.Debug("Balance result",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.String("amount", result.Amount),
|
||||
zap.String("currency", result.Currency),
|
||||
)
|
||||
@@ -72,7 +72,7 @@ func (d *Driver) Balance(ctx context.Context, deps driver.Deps, network shared.N
|
||||
func (d *Driver) NativeBalance(ctx context.Context, deps driver.Deps, network shared.Network, wallet *model.ManagedWallet) (*moneyv1.Money, error) {
|
||||
d.logger.Debug("Native balance request",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
)
|
||||
driverDeps := deps
|
||||
driverDeps.Logger = d.logger
|
||||
@@ -80,13 +80,13 @@ func (d *Driver) NativeBalance(ctx context.Context, deps driver.Deps, network sh
|
||||
if err != nil {
|
||||
d.logger.Warn("Native balance failed",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else if result != nil {
|
||||
d.logger.Debug("Native balance result",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.String("amount", result.Amount),
|
||||
zap.String("currency", result.Currency),
|
||||
)
|
||||
@@ -97,7 +97,7 @@ func (d *Driver) NativeBalance(ctx context.Context, deps driver.Deps, network sh
|
||||
func (d *Driver) EstimateFee(ctx context.Context, deps driver.Deps, network shared.Network, wallet *model.ManagedWallet, destination string, amount *moneyv1.Money) (*moneyv1.Money, error) {
|
||||
d.logger.Debug("Estimate fee request",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.String("destination", destination),
|
||||
)
|
||||
driverDeps := deps
|
||||
@@ -106,13 +106,13 @@ func (d *Driver) EstimateFee(ctx context.Context, deps driver.Deps, network shar
|
||||
if err != nil {
|
||||
d.logger.Warn("Estimate fee failed",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else if result != nil {
|
||||
d.logger.Debug("Estimate fee result",
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.String("amount", result.Amount),
|
||||
zap.String("currency", result.Currency),
|
||||
)
|
||||
@@ -123,7 +123,7 @@ func (d *Driver) EstimateFee(ctx context.Context, deps driver.Deps, network shar
|
||||
func (d *Driver) SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Network, transfer *model.Transfer, source *model.ManagedWallet, destination string) (string, error) {
|
||||
d.logger.Debug("Submit transfer request",
|
||||
zap.String("transfer_ref", transfer.TransferRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.String("destination", destination),
|
||||
)
|
||||
driverDeps := deps
|
||||
@@ -132,13 +132,13 @@ func (d *Driver) SubmitTransfer(ctx context.Context, deps driver.Deps, network s
|
||||
if err != nil {
|
||||
d.logger.Warn("Submit transfer failed",
|
||||
zap.String("transfer_ref", transfer.TransferRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
d.logger.Debug("Submit transfer result",
|
||||
zap.String("transfer_ref", transfer.TransferRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.String("tx_hash", txHash),
|
||||
)
|
||||
}
|
||||
@@ -148,7 +148,7 @@ func (d *Driver) SubmitTransfer(ctx context.Context, deps driver.Deps, network s
|
||||
func (d *Driver) AwaitConfirmation(ctx context.Context, deps driver.Deps, network shared.Network, txHash string) (*types.Receipt, error) {
|
||||
d.logger.Debug("Await confirmation",
|
||||
zap.String("tx_hash", txHash),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
)
|
||||
driverDeps := deps
|
||||
driverDeps.Logger = d.logger
|
||||
@@ -156,13 +156,13 @@ func (d *Driver) AwaitConfirmation(ctx context.Context, deps driver.Deps, networ
|
||||
if err != nil {
|
||||
d.logger.Warn("Await confirmation failed",
|
||||
zap.String("tx_hash", txHash),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else if receipt != nil {
|
||||
d.logger.Debug("Await confirmation result",
|
||||
zap.String("tx_hash", txHash),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Uint64("block_number", receipt.BlockNumber.Uint64()),
|
||||
zap.Uint64("status", receipt.Status),
|
||||
)
|
||||
|
||||
@@ -74,7 +74,7 @@ func NormalizeAddress(address string) (string, error) {
|
||||
func nativeCurrency(network shared.Network) string {
|
||||
currency := strings.ToUpper(strings.TrimSpace(network.NativeToken))
|
||||
if currency == "" {
|
||||
currency = strings.ToUpper(network.Name)
|
||||
currency = strings.ToUpper(string(network.Name))
|
||||
}
|
||||
return currency
|
||||
}
|
||||
@@ -114,7 +114,7 @@ func Balance(ctx context.Context, deps driver.Deps, network shared.Network, wall
|
||||
rpcURL := strings.TrimSpace(network.RPCURL)
|
||||
logFields := []zap.Field{
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", strings.ToLower(strings.TrimSpace(network.Name))),
|
||||
zap.String("network", strings.ToLower(strings.TrimSpace(string(network.Name)))),
|
||||
zap.String("token_symbol", strings.ToUpper(strings.TrimSpace(wallet.TokenSymbol))),
|
||||
zap.String("contract", strings.ToLower(strings.TrimSpace(wallet.ContractAddress))),
|
||||
zap.String("wallet_address", normalizedAddress),
|
||||
@@ -194,7 +194,7 @@ func NativeBalance(ctx context.Context, deps driver.Deps, network shared.Network
|
||||
rpcURL := strings.TrimSpace(network.RPCURL)
|
||||
logFields := []zap.Field{
|
||||
zap.String("wallet_ref", wallet.WalletRef),
|
||||
zap.String("network", strings.ToLower(strings.TrimSpace(network.Name))),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.String("wallet_address", normalizedAddress),
|
||||
}
|
||||
if rpcURL == "" {
|
||||
@@ -260,12 +260,12 @@ func EstimateFee(ctx context.Context, deps driver.Deps, network shared.Network,
|
||||
|
||||
client, err := registry.Client(network.Name)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to resolve client", zap.Error(err), zap.String("network_name", network.Name))
|
||||
logger.Warn("Failed to resolve client", zap.Error(err), zap.String("network_name", string(network.Name)))
|
||||
return nil, err
|
||||
}
|
||||
rpcClient, err := registry.RPCClient(network.Name)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to resolve RPC client", zap.Error(err), zap.String("network_name", network.Name))
|
||||
logger.Warn("Failed to resolve RPC client", zap.Error(err), zap.String("network_name", string(network.Name)))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@ func SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Networ
|
||||
}
|
||||
rpcURL := strings.TrimSpace(network.RPCURL)
|
||||
if rpcURL == "" {
|
||||
logger.Warn("Network rpc url missing", zap.String("network", network.Name))
|
||||
logger.Warn("Network rpc url missing", zap.String("network", string(network.Name)))
|
||||
return "", executorInvalid("network rpc url is not configured")
|
||||
}
|
||||
if source == nil || transfer == nil {
|
||||
@@ -397,18 +397,18 @@ func SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Networ
|
||||
logger.Info("Submitting transfer",
|
||||
zap.String("transfer_ref", transfer.TransferRef),
|
||||
zap.String("source_wallet_ref", source.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.String("destination", strings.ToLower(destination)),
|
||||
)
|
||||
|
||||
client, err := registry.Client(network.Name)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to initialise RPC client", zap.Error(err), zap.String("network", network.Name))
|
||||
logger.Warn("Failed to initialise RPC client", zap.Error(err), zap.String("network", string(network.Name)))
|
||||
return "", err
|
||||
}
|
||||
rpcClient, err := registry.RPCClient(network.Name)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to initialise RPC client", zap.String("network", network.Name))
|
||||
logger.Warn("Failed to initialise RPC client", zap.String("network", string(network.Name)))
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -429,7 +429,7 @@ func SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Networ
|
||||
gasPrice, err := client.SuggestGasPrice(ctx)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to suggest gas price", zap.Error(err),
|
||||
zap.String("transfer_ref", transfer.TransferRef), zap.String("network", network.Name),
|
||||
zap.String("transfer_ref", transfer.TransferRef), zap.String("network", string(network.Name)),
|
||||
)
|
||||
return "", executorInternal("failed to suggest gas price", err)
|
||||
}
|
||||
@@ -532,7 +532,7 @@ func SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Networ
|
||||
|
||||
txHash := signedTx.Hash().Hex()
|
||||
logger.Info("Transaction submitted", zap.String("transfer_ref", transfer.TransferRef),
|
||||
zap.String("tx_hash", txHash), zap.String("network", network.Name),
|
||||
zap.String("tx_hash", txHash), zap.String("network", string(network.Name)),
|
||||
)
|
||||
|
||||
return txHash, nil
|
||||
@@ -544,7 +544,7 @@ func AwaitConfirmation(ctx context.Context, deps driver.Deps, network shared.Net
|
||||
registry := deps.Registry
|
||||
|
||||
if strings.TrimSpace(txHash) == "" {
|
||||
logger.Warn("Missing transaction hash for confirmation", zap.String("network", network.Name))
|
||||
logger.Warn("Missing transaction hash for confirmation", zap.String("network", string(network.Name)))
|
||||
return nil, executorInvalid("tx hash is required")
|
||||
}
|
||||
rpcURL := strings.TrimSpace(network.RPCURL)
|
||||
@@ -572,23 +572,23 @@ func AwaitConfirmation(ctx context.Context, deps driver.Deps, network shared.Net
|
||||
select {
|
||||
case <-ticker.C:
|
||||
logger.Debug("Transaction not yet mined", zap.String("tx_hash", txHash),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
)
|
||||
continue
|
||||
case <-ctx.Done():
|
||||
logger.Warn("Context cancelled while awaiting confirmation", zap.String("tx_hash", txHash),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
)
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
logger.Warn("Failed to fetch transaction receipt", zap.Error(err),
|
||||
zap.String("tx_hash", txHash), zap.String("network", network.Name),
|
||||
zap.String("tx_hash", txHash), zap.String("network", string(network.Name)),
|
||||
)
|
||||
return nil, executorInternal("failed to fetch transaction receipt", err)
|
||||
}
|
||||
logger.Info("Transaction confirmed", zap.String("tx_hash", txHash),
|
||||
zap.String("network", network.Name), zap.Uint64("status", receipt.Status),
|
||||
zap.String("network", string(network.Name)), zap.Uint64("status", receipt.Status),
|
||||
zap.Uint64("block_number", receipt.BlockNumber.Uint64()),
|
||||
)
|
||||
return receipt, nil
|
||||
@@ -654,12 +654,6 @@ type gasEstimator interface {
|
||||
}
|
||||
|
||||
func estimateGas(ctx context.Context, network shared.Network, client gasEstimator, rpcClient *rpc.Client, callMsg ethereum.CallMsg) (uint64, error) {
|
||||
if isTronNetwork(network) {
|
||||
if rpcClient == nil {
|
||||
return 0, merrors.Internal("rpc client not initialised")
|
||||
}
|
||||
return estimateGasTron(ctx, rpcClient, callMsg)
|
||||
}
|
||||
return client.EstimateGas(ctx, callMsg)
|
||||
}
|
||||
|
||||
@@ -702,10 +696,6 @@ func tronEstimateCall(callMsg ethereum.CallMsg) map[string]string {
|
||||
return call
|
||||
}
|
||||
|
||||
func isTronNetwork(network shared.Network) bool {
|
||||
return strings.HasPrefix(strings.ToLower(strings.TrimSpace(network.Name)), "tron")
|
||||
}
|
||||
|
||||
func toBaseUnits(amount string, decimals uint8) (*big.Int, error) {
|
||||
value, err := decimal.NewFromString(strings.TrimSpace(amount))
|
||||
if err != nil {
|
||||
|
||||
@@ -29,9 +29,6 @@ func ComputeGasTopUp(network shared.Network, wallet *model.ManagedWallet, estima
|
||||
}
|
||||
|
||||
nativeCurrency := strings.TrimSpace(network.NativeToken)
|
||||
if nativeCurrency == "" {
|
||||
nativeCurrency = strings.ToUpper(strings.TrimSpace(network.Name))
|
||||
}
|
||||
if !strings.EqualFold(nativeCurrency, estimatedFee.GetCurrency()) {
|
||||
return nil, false, merrors.InvalidArgument(fmt.Sprintf("estimated fee currency mismatch (expected %s)", nativeCurrency))
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package drivers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver"
|
||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver/arbitrum"
|
||||
@@ -10,12 +9,13 @@ import (
|
||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
pmodel "github.com/tech/sendico/pkg/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Registry maps configured network keys to chain drivers.
|
||||
type Registry struct {
|
||||
byNetwork map[string]driver.Driver
|
||||
byNetwork map[pmodel.ChainNetwork]driver.Driver
|
||||
}
|
||||
|
||||
// NewRegistry selects drivers for the configured networks.
|
||||
@@ -23,18 +23,14 @@ func NewRegistry(logger mlogger.Logger, networks []shared.Network) (*Registry, e
|
||||
if logger == nil {
|
||||
return nil, merrors.InvalidArgument("driver registry: logger is required")
|
||||
}
|
||||
result := &Registry{byNetwork: map[string]driver.Driver{}}
|
||||
result := &Registry{byNetwork: map[pmodel.ChainNetwork]driver.Driver{}}
|
||||
for _, network := range networks {
|
||||
name := strings.ToLower(strings.TrimSpace(network.Name))
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
chainDriver, err := resolveDriver(logger, name)
|
||||
chainDriver, err := resolveDriver(logger, network.Name)
|
||||
if err != nil {
|
||||
logger.Error("Unsupported chain driver", zap.String("network", name), zap.Error(err))
|
||||
logger.Error("Unsupported chain driver", zap.String("network", string(network.Name)), zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
result.byNetwork[name] = chainDriver
|
||||
result.byNetwork[network.Name] = chainDriver
|
||||
}
|
||||
if len(result.byNetwork) == 0 {
|
||||
return nil, merrors.InvalidArgument("driver registry: no supported networks configured")
|
||||
@@ -44,30 +40,25 @@ func NewRegistry(logger mlogger.Logger, networks []shared.Network) (*Registry, e
|
||||
}
|
||||
|
||||
// Driver resolves a driver for the provided network key.
|
||||
func (r *Registry) Driver(network string) (driver.Driver, error) {
|
||||
func (r *Registry) Driver(network pmodel.ChainNetwork) (driver.Driver, error) {
|
||||
if r == nil || len(r.byNetwork) == 0 {
|
||||
return nil, merrors.Internal("driver registry is not configured")
|
||||
}
|
||||
key := strings.ToLower(strings.TrimSpace(network))
|
||||
if key == "" {
|
||||
return nil, merrors.InvalidArgument("network is required")
|
||||
}
|
||||
chainDriver, ok := r.byNetwork[key]
|
||||
chainDriver, ok := r.byNetwork[network]
|
||||
if !ok {
|
||||
return nil, merrors.InvalidArgument(fmt.Sprintf("unsupported chain network %s", key))
|
||||
return nil, merrors.InvalidArgument(fmt.Sprintf("unsupported chain network %s", network))
|
||||
}
|
||||
return chainDriver, nil
|
||||
}
|
||||
|
||||
func resolveDriver(logger mlogger.Logger, network string) (driver.Driver, error) {
|
||||
switch {
|
||||
case strings.HasPrefix(network, "tron"):
|
||||
return nil, merrors.InvalidArgument("tron networks must use the tron gateway, not chain gateway")
|
||||
case strings.HasPrefix(network, "arbitrum"):
|
||||
func resolveDriver(logger mlogger.Logger, network pmodel.ChainNetwork) (driver.Driver, error) {
|
||||
switch network {
|
||||
case pmodel.ChainNetworkArbitrumOne:
|
||||
case pmodel.ChainNetworkArbitrumSepolia:
|
||||
return arbitrum.New(logger), nil
|
||||
case strings.HasPrefix(network, "ethereum"):
|
||||
case pmodel.ChainNetworkEthereumMainnet:
|
||||
return ethereum.New(logger), nil
|
||||
default:
|
||||
return nil, merrors.InvalidArgument("unsupported chain network " + network)
|
||||
}
|
||||
return nil, merrors.InvalidArgument("unsupported chain network " + string(network))
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func (o *onChainExecutor) SubmitTransfer(ctx context.Context, transfer *model.Tr
|
||||
}
|
||||
rpcURL := strings.TrimSpace(network.RPCURL)
|
||||
if rpcURL == "" {
|
||||
o.logger.Warn("Network rpc url missing", zap.String("network", network.Name))
|
||||
o.logger.Warn("Network rpc url missing", zap.String("network", string(network.Name)))
|
||||
return "", executorInvalid("network rpc url is not configured")
|
||||
}
|
||||
if source == nil || transfer == nil {
|
||||
@@ -75,19 +75,19 @@ func (o *onChainExecutor) SubmitTransfer(ctx context.Context, transfer *model.Tr
|
||||
o.logger.Info("Submitting transfer",
|
||||
zap.String("transfer_ref", transfer.TransferRef),
|
||||
zap.String("source_wallet_ref", source.WalletRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.String("destination", strings.ToLower(destinationAddress)),
|
||||
)
|
||||
|
||||
client, err := o.clients.Client(network.Name)
|
||||
if err != nil {
|
||||
o.logger.Warn("Failed to initialise RPC client", zap.Error(err), zap.String("network", network.Name))
|
||||
o.logger.Warn("Failed to initialise RPC client", zap.Error(err), zap.String("network", string(network.Name)))
|
||||
return "", err
|
||||
}
|
||||
rpcClient, err := o.clients.RPCClient(network.Name)
|
||||
if err != nil {
|
||||
o.logger.Warn("Failed to initialise RPC client",
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Error(err),
|
||||
)
|
||||
return "", err
|
||||
@@ -112,7 +112,7 @@ func (o *onChainExecutor) SubmitTransfer(ctx context.Context, transfer *model.Tr
|
||||
if err != nil {
|
||||
o.logger.Warn("Failed to suggest gas price",
|
||||
zap.String("transfer_ref", transfer.TransferRef),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Error(err),
|
||||
)
|
||||
return "", executorInternal("failed to suggest gas price", err)
|
||||
@@ -206,7 +206,7 @@ func (o *onChainExecutor) SubmitTransfer(ctx context.Context, transfer *model.Tr
|
||||
o.logger.Info("Transaction submitted",
|
||||
zap.String("transfer_ref", transfer.TransferRef),
|
||||
zap.String("tx_hash", txHash),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
)
|
||||
|
||||
return txHash, nil
|
||||
@@ -214,7 +214,7 @@ func (o *onChainExecutor) SubmitTransfer(ctx context.Context, transfer *model.Tr
|
||||
|
||||
func (o *onChainExecutor) AwaitConfirmation(ctx context.Context, network shared.Network, txHash string) (*types.Receipt, error) {
|
||||
if strings.TrimSpace(txHash) == "" {
|
||||
o.logger.Warn("Missing transaction hash for confirmation", zap.String("network", network.Name))
|
||||
o.logger.Warn("Missing transaction hash for confirmation", zap.String("network", string(network.Name)))
|
||||
return nil, executorInvalid("tx hash is required")
|
||||
}
|
||||
rpcURL := strings.TrimSpace(network.RPCURL)
|
||||
@@ -240,27 +240,27 @@ func (o *onChainExecutor) AwaitConfirmation(ctx context.Context, network shared.
|
||||
case <-ticker.C:
|
||||
o.logger.Debug("Transaction not yet mined",
|
||||
zap.String("tx_hash", txHash),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
)
|
||||
continue
|
||||
case <-ctx.Done():
|
||||
o.logger.Warn("Context cancelled while awaiting confirmation",
|
||||
zap.String("tx_hash", txHash),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
)
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
o.logger.Warn("Failed to fetch transaction receipt",
|
||||
zap.String("tx_hash", txHash),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, executorInternal("failed to fetch transaction receipt", err)
|
||||
}
|
||||
o.logger.Info("Transaction confirmed",
|
||||
zap.String("tx_hash", txHash),
|
||||
zap.String("network", network.Name),
|
||||
zap.String("network", string(network.Name)),
|
||||
zap.Uint64("block_number", receipt.BlockNumber.Uint64()),
|
||||
zap.Uint64("status", receipt.Status),
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/rpcclient"
|
||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
||||
clockpkg "github.com/tech/sendico/pkg/clock"
|
||||
pmodel "github.com/tech/sendico/pkg/model"
|
||||
)
|
||||
|
||||
// Option configures the Service.
|
||||
@@ -34,7 +35,7 @@ func WithNetworks(networks []shared.Network) Option {
|
||||
return
|
||||
}
|
||||
if s.networks == nil {
|
||||
s.networks = make(map[string]shared.Network, len(networks))
|
||||
s.networks = make(map[pmodel.ChainNetwork]shared.Network, len(networks))
|
||||
}
|
||||
for _, network := range networks {
|
||||
if network.Name == "" {
|
||||
@@ -48,7 +49,7 @@ func WithNetworks(networks []shared.Network) Option {
|
||||
clone.TokenConfigs[i].Symbol = strings.ToUpper(strings.TrimSpace(clone.TokenConfigs[i].Symbol))
|
||||
clone.TokenConfigs[i].ContractAddress = strings.ToLower(strings.TrimSpace(clone.TokenConfigs[i].ContractAddress))
|
||||
}
|
||||
clone.Name = strings.ToLower(strings.TrimSpace(clone.Name))
|
||||
clone.Name = clone.Name
|
||||
s.networks[clone.Name] = clone
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,14 @@ import (
|
||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
pmodel "github.com/tech/sendico/pkg/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Clients holds pre-initialised RPC clients keyed by network name.
|
||||
type Clients struct {
|
||||
logger mlogger.Logger
|
||||
clients map[string]clientEntry
|
||||
clients map[pmodel.ChainNetwork]clientEntry
|
||||
}
|
||||
|
||||
type clientEntry struct {
|
||||
@@ -36,25 +37,20 @@ func Prepare(ctx context.Context, logger mlogger.Logger, networks []shared.Netwo
|
||||
clientLogger := logger.Named("rpc_client")
|
||||
result := &Clients{
|
||||
logger: clientLogger,
|
||||
clients: make(map[string]clientEntry),
|
||||
clients: make(map[pmodel.ChainNetwork]clientEntry),
|
||||
}
|
||||
|
||||
for _, network := range networks {
|
||||
name := strings.ToLower(strings.TrimSpace(network.Name))
|
||||
rpcURL := strings.TrimSpace(network.RPCURL)
|
||||
if name == "" {
|
||||
clientLogger.Warn("Skipping network with empty name during rpc client preparation")
|
||||
continue
|
||||
}
|
||||
if rpcURL == "" {
|
||||
result.Close()
|
||||
err := merrors.InvalidArgument(fmt.Sprintf("rpc url not configured for network %s", name))
|
||||
clientLogger.Warn("Rpc url missing", zap.String("network", name))
|
||||
err := merrors.InvalidArgument(fmt.Sprintf("rpc url not configured for network %s", network.Name))
|
||||
clientLogger.Warn("Rpc url missing", zap.String("network", string(network.Name)))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fields := []zap.Field{
|
||||
zap.String("network", name),
|
||||
zap.String("network", string(network.Name)),
|
||||
}
|
||||
clientLogger.Info("Initialising rpc client", fields...)
|
||||
|
||||
@@ -62,7 +58,7 @@ func Prepare(ctx context.Context, logger mlogger.Logger, networks []shared.Netwo
|
||||
httpClient := &http.Client{
|
||||
Transport: &loggingRoundTripper{
|
||||
logger: clientLogger,
|
||||
network: name,
|
||||
network: network.Name,
|
||||
endpoint: rpcURL,
|
||||
base: http.DefaultTransport,
|
||||
},
|
||||
@@ -72,10 +68,10 @@ func Prepare(ctx context.Context, logger mlogger.Logger, networks []shared.Netwo
|
||||
if err != nil {
|
||||
result.Close()
|
||||
clientLogger.Warn("Failed to dial rpc endpoint", append(fields, zap.Error(err))...)
|
||||
return nil, merrors.Internal(fmt.Sprintf("rpc dial failed for %s: %s", name, err.Error()))
|
||||
return nil, merrors.Internal(fmt.Sprintf("rpc dial failed for %s: %s", network.Name, err.Error()))
|
||||
}
|
||||
client := ethclient.NewClient(rpcCli)
|
||||
result.clients[name] = clientEntry{
|
||||
result.clients[network.Name] = clientEntry{
|
||||
eth: client,
|
||||
rpc: rpcCli,
|
||||
}
|
||||
@@ -93,27 +89,25 @@ func Prepare(ctx context.Context, logger mlogger.Logger, networks []shared.Netwo
|
||||
}
|
||||
|
||||
// Client returns a prepared client for the given network name.
|
||||
func (c *Clients) Client(network string) (*ethclient.Client, error) {
|
||||
func (c *Clients) Client(network pmodel.ChainNetwork) (*ethclient.Client, error) {
|
||||
if c == nil {
|
||||
return nil, merrors.Internal("RPC clients not initialised")
|
||||
}
|
||||
name := strings.ToLower(strings.TrimSpace(network))
|
||||
entry, ok := c.clients[name]
|
||||
entry, ok := c.clients[network]
|
||||
if !ok || entry.eth == nil {
|
||||
return nil, merrors.InvalidArgument(fmt.Sprintf("RPC client not configured for network %s", name))
|
||||
return nil, merrors.InvalidArgument(fmt.Sprintf("RPC client not configured for network %s", network))
|
||||
}
|
||||
return entry.eth, nil
|
||||
}
|
||||
|
||||
// RPCClient returns the raw RPC client for low-level calls.
|
||||
func (c *Clients) RPCClient(network string) (*rpc.Client, error) {
|
||||
func (c *Clients) RPCClient(network pmodel.ChainNetwork) (*rpc.Client, error) {
|
||||
if c == nil {
|
||||
return nil, merrors.Internal("rpc clients not initialised")
|
||||
}
|
||||
name := strings.ToLower(strings.TrimSpace(network))
|
||||
entry, ok := c.clients[name]
|
||||
entry, ok := c.clients[network]
|
||||
if !ok || entry.rpc == nil {
|
||||
return nil, merrors.InvalidArgument(fmt.Sprintf("rpc client not configured for network %s", name))
|
||||
return nil, merrors.InvalidArgument(fmt.Sprintf("rpc client not configured for network %s", network))
|
||||
}
|
||||
return entry.rpc, nil
|
||||
}
|
||||
@@ -130,14 +124,14 @@ func (c *Clients) Close() {
|
||||
entry.eth.Close()
|
||||
}
|
||||
if c.logger != nil {
|
||||
c.logger.Info("RPC client closed", zap.String("network", name))
|
||||
c.logger.Info("RPC client closed", zap.String("network", string(name)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type loggingRoundTripper struct {
|
||||
logger mlogger.Logger
|
||||
network string
|
||||
network pmodel.ChainNetwork
|
||||
endpoint string
|
||||
base http.RoundTripper
|
||||
}
|
||||
@@ -155,7 +149,7 @@ func (l *loggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, erro
|
||||
}
|
||||
|
||||
fields := []zap.Field{
|
||||
zap.String("network", l.network),
|
||||
zap.String("network", string(l.network)),
|
||||
}
|
||||
if len(reqBody) > 0 {
|
||||
fields = append(fields, zap.String("rpc_request", truncate(string(reqBody), 2048)))
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
package rpcclient
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
pmodel "github.com/tech/sendico/pkg/model"
|
||||
)
|
||||
|
||||
// Registry binds static network metadata with prepared RPC clients.
|
||||
type Registry struct {
|
||||
networks map[string]shared.Network
|
||||
networks map[pmodel.ChainNetwork]shared.Network
|
||||
clients *Clients
|
||||
}
|
||||
|
||||
// NewRegistry constructs a registry keyed by lower-cased network name.
|
||||
func NewRegistry(networks map[string]shared.Network, clients *Clients) *Registry {
|
||||
func NewRegistry(networks map[pmodel.ChainNetwork]shared.Network, clients *Clients) *Registry {
|
||||
return &Registry{
|
||||
networks: networks,
|
||||
clients: clients,
|
||||
@@ -24,31 +23,31 @@ func NewRegistry(networks map[string]shared.Network, clients *Clients) *Registry
|
||||
}
|
||||
|
||||
// Network fetches network metadata by key (case-insensitive).
|
||||
func (r *Registry) Network(key string) (shared.Network, bool) {
|
||||
func (r *Registry) Network(key pmodel.ChainNetwork) (shared.Network, bool) {
|
||||
if r == nil || len(r.networks) == 0 {
|
||||
return shared.Network{}, false
|
||||
}
|
||||
n, ok := r.networks[strings.ToLower(strings.TrimSpace(key))]
|
||||
n, ok := r.networks[key]
|
||||
return n, ok
|
||||
}
|
||||
|
||||
// Client returns the prepared RPC client for the given network name.
|
||||
func (r *Registry) Client(key string) (*ethclient.Client, error) {
|
||||
func (r *Registry) Client(key pmodel.ChainNetwork) (*ethclient.Client, error) {
|
||||
if r == nil || r.clients == nil {
|
||||
return nil, merrors.Internal("rpc clients not initialised")
|
||||
}
|
||||
return r.clients.Client(strings.ToLower(strings.TrimSpace(key)))
|
||||
return r.clients.Client(key)
|
||||
}
|
||||
|
||||
// RPCClient returns the raw RPC client for low-level calls.
|
||||
func (r *Registry) RPCClient(key string) (*rpc.Client, error) {
|
||||
func (r *Registry) RPCClient(key pmodel.ChainNetwork) (*rpc.Client, error) {
|
||||
if r == nil || r.clients == nil {
|
||||
return nil, merrors.Internal("rpc clients not initialised")
|
||||
}
|
||||
return r.clients.RPCClient(strings.ToLower(strings.TrimSpace(key)))
|
||||
return r.clients.RPCClient(key)
|
||||
}
|
||||
|
||||
// Networks exposes the registry map for iteration when needed.
|
||||
func (r *Registry) Networks() map[string]shared.Network {
|
||||
func (r *Registry) Networks() map[pmodel.ChainNetwork]shared.Network {
|
||||
return r.networks
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/tech/sendico/pkg/discovery"
|
||||
msg "github.com/tech/sendico/pkg/messaging"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
pmodel "github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
|
||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
@@ -43,7 +44,7 @@ type Service struct {
|
||||
|
||||
settings CacheSettings
|
||||
|
||||
networks map[string]shared.Network
|
||||
networks map[pmodel.ChainNetwork]shared.Network
|
||||
serviceWallet shared.ServiceWallet
|
||||
keyManager keymanager.Manager
|
||||
rpcClients *rpcclient.Clients
|
||||
@@ -64,7 +65,7 @@ func NewService(logger mlogger.Logger, repo storage.Repository, producer msg.Pro
|
||||
producer: producer,
|
||||
clock: clockpkg.System{},
|
||||
settings: defaultSettings(),
|
||||
networks: map[string]shared.Network{},
|
||||
networks: map[pmodel.ChainNetwork]shared.Network{},
|
||||
}
|
||||
|
||||
initMetrics()
|
||||
@@ -79,7 +80,7 @@ func NewService(logger mlogger.Logger, repo storage.Repository, producer msg.Pro
|
||||
svc.clock = clockpkg.System{}
|
||||
}
|
||||
if svc.networks == nil {
|
||||
svc.networks = map[string]shared.Network{}
|
||||
svc.networks = map[pmodel.ChainNetwork]shared.Network{}
|
||||
}
|
||||
svc.settings = svc.settings.withDefaults()
|
||||
svc.networkRegistry = rpcclient.NewRegistry(svc.networks, svc.rpcClients)
|
||||
@@ -207,7 +208,7 @@ func (s *Service) startDiscoveryAnnouncers() {
|
||||
announce := discovery.Announcement{
|
||||
Service: "CRYPTO_RAIL_GATEWAY",
|
||||
Rail: "CRYPTO",
|
||||
Network: network.Name,
|
||||
Network: string(network.Name),
|
||||
Operations: []string{"balance.read", "payin.crypto", "payout.crypto", "fee.send", "observe.confirm"},
|
||||
Currencies: currencies,
|
||||
InvokeURI: s.invokeURI,
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
pmodel "github.com/tech/sendico/pkg/model"
|
||||
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
|
||||
ichainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
@@ -170,6 +171,7 @@ func TestSubmitTransfer_ManagedDestination(t *testing.T) {
|
||||
Amount: &moneyv1.Money{Currency: "USDC", Amount: "5"},
|
||||
},
|
||||
},
|
||||
IntentRef: "intent-1",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, transferResp.GetTransfer())
|
||||
@@ -177,7 +179,7 @@ func TestSubmitTransfer_ManagedDestination(t *testing.T) {
|
||||
|
||||
stored := repo.transfers.get(transferResp.GetTransfer().GetTransferRef())
|
||||
require.NotNil(t, stored)
|
||||
require.Equal(t, model.TransferStatusPending, stored.Status)
|
||||
require.Equal(t, model.TransferStatusCreated, stored.Status)
|
||||
|
||||
// GetTransfer
|
||||
getResp, err := svc.GetTransfer(ctx, &ichainv1.GetTransferRequest{TransferRef: stored.TransferRef})
|
||||
@@ -335,7 +337,7 @@ func (w *inMemoryWallets) List(ctx context.Context, filter model.ManagedWalletFi
|
||||
continue
|
||||
}
|
||||
}
|
||||
if filter.Network != "" && !strings.EqualFold(wallet.Network, filter.Network) {
|
||||
if wallet.Network != filter.Network {
|
||||
continue
|
||||
}
|
||||
if filter.TokenSymbol != "" && !strings.EqualFold(wallet.TokenSymbol, filter.TokenSymbol) {
|
||||
@@ -644,9 +646,9 @@ func newTestService(t *testing.T) (*Service, *inMemoryRepository) {
|
||||
|
||||
type fakeKeyManager struct{}
|
||||
|
||||
func (f *fakeKeyManager) CreateManagedWalletKey(ctx context.Context, walletRef string, network string) (*keymanager.ManagedWalletKey, error) {
|
||||
func (f *fakeKeyManager) CreateManagedWalletKey(ctx context.Context, walletRef string, network pmodel.ChainNetwork) (*keymanager.ManagedWalletKey, error) {
|
||||
return &keymanager.ManagedWalletKey{
|
||||
KeyID: fmt.Sprintf("%s/%s", strings.ToLower(network), walletRef),
|
||||
KeyID: fmt.Sprintf("%s/%s", network, walletRef),
|
||||
Address: "0x" + strings.Repeat("a", 40),
|
||||
PublicKey: strings.Repeat("b", 128),
|
||||
}, nil
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package shared
|
||||
|
||||
import "github.com/shopspring/decimal"
|
||||
import (
|
||||
"github.com/shopspring/decimal"
|
||||
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
|
||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
)
|
||||
|
||||
// GasTopUpRule defines buffer, minimum, rounding, and cap behavior for native gas top-ups.
|
||||
type GasTopUpRule struct {
|
||||
@@ -30,3 +34,20 @@ func (p *GasTopUpPolicy) Rule(contractTransfer bool) (GasTopUpRule, bool) {
|
||||
}
|
||||
return p.Default, true
|
||||
}
|
||||
|
||||
func СhainTransferStatusToOperation(ts chainv1.TransferStatus) connectorv1.OperationStatus {
|
||||
switch ts {
|
||||
case chainv1.TransferStatus_TRANSFER_CANCELLED:
|
||||
return connectorv1.OperationStatus_OPERATION_CANCELLED
|
||||
case chainv1.TransferStatus_TRANSFER_CREATED:
|
||||
return connectorv1.OperationStatus_OPERATION_CREATED
|
||||
case chainv1.TransferStatus_TRANSFER_FAILED:
|
||||
return connectorv1.OperationStatus_OPERATION_FAILED
|
||||
case chainv1.TransferStatus_TRANSFER_SUCCESS:
|
||||
return connectorv1.OperationStatus_OPERATION_SUCCESS
|
||||
case chainv1.TransferStatus_TRANSFER_WAITING:
|
||||
return connectorv1.OperationStatus_OPERATION_WAITING
|
||||
default:
|
||||
}
|
||||
return connectorv1.OperationStatus_OPERATION_STATUS_UNSPECIFIED
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/gateway/chain/storage/model"
|
||||
chainasset "github.com/tech/sendico/pkg/chain"
|
||||
pmodel "github.com/tech/sendico/pkg/model"
|
||||
paymenttypes "github.com/tech/sendico/pkg/payments/types"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
|
||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
@@ -49,16 +51,38 @@ func GenerateTransferRef() string {
|
||||
return bson.NewObjectID().Hex()
|
||||
}
|
||||
|
||||
func ChainKeyFromEnum(chain chainv1.ChainNetwork) (string, chainv1.ChainNetwork) {
|
||||
if name, ok := chainv1.ChainNetwork_name[int32(chain)]; ok {
|
||||
key := strings.ToLower(strings.TrimPrefix(name, "CHAIN_NETWORK_"))
|
||||
return key, chain
|
||||
func ChainKeyFromEnum(chain chainv1.ChainNetwork) pmodel.ChainNetwork {
|
||||
switch chain {
|
||||
case chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_ONE:
|
||||
return pmodel.ChainNetworkArbitrumOne
|
||||
case chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_SEPOLIA:
|
||||
return pmodel.ChainNetworkArbitrumSepolia
|
||||
case chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET:
|
||||
return pmodel.ChainNetworkEthereumMainnet
|
||||
case chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET:
|
||||
return pmodel.ChainNetworkTronMainnet
|
||||
case chainv1.ChainNetwork_CHAIN_NETWORK_TRON_NILE:
|
||||
return pmodel.ChainNetworkTronNile
|
||||
default:
|
||||
return pmodel.ChainNetworkUnspecified
|
||||
}
|
||||
return "", chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED
|
||||
}
|
||||
|
||||
func ChainEnumFromName(name string) chainv1.ChainNetwork {
|
||||
return chainasset.NetworkFromString(name)
|
||||
func ChainEnumFromName(name pmodel.ChainNetwork) chainv1.ChainNetwork {
|
||||
switch name {
|
||||
case pmodel.ChainNetworkArbitrumOne:
|
||||
return chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_ONE
|
||||
case pmodel.ChainNetworkArbitrumSepolia:
|
||||
return chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_SEPOLIA
|
||||
case pmodel.ChainNetworkEthereumMainnet:
|
||||
return chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET
|
||||
case pmodel.ChainNetworkTronMainnet:
|
||||
return chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET
|
||||
case pmodel.ChainNetworkTronNile:
|
||||
return chainv1.ChainNetwork_CHAIN_NETWORK_TRON_NILE
|
||||
default:
|
||||
return chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func ManagedWalletStatusToProto(status model.ManagedWalletStatus) chainv1.ManagedWalletStatus {
|
||||
@@ -76,54 +100,94 @@ func ManagedWalletStatusToProto(status model.ManagedWalletStatus) chainv1.Manage
|
||||
|
||||
func TransferStatusToModel(status chainv1.TransferStatus) model.TransferStatus {
|
||||
switch status {
|
||||
case chainv1.TransferStatus_TRANSFER_PENDING:
|
||||
return model.TransferStatusPending
|
||||
case chainv1.TransferStatus_TRANSFER_SIGNING:
|
||||
return model.TransferStatusSigning
|
||||
case chainv1.TransferStatus_TRANSFER_SUBMITTED:
|
||||
return model.TransferStatusSubmitted
|
||||
case chainv1.TransferStatus_TRANSFER_CONFIRMED:
|
||||
return model.TransferStatusConfirmed
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_CREATED:
|
||||
return model.TransferStatusCreated
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_PROCESSING:
|
||||
return model.TransferStatusProcessing
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_WAITING:
|
||||
return model.TransferStatusWaiting
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_SUCCESS:
|
||||
return model.TransferStatusSuccess
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_FAILED:
|
||||
return model.TransferStatusFailed
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_CANCELLED:
|
||||
return model.TransferStatusCancelled
|
||||
|
||||
default:
|
||||
return ""
|
||||
return model.TransferStatus("")
|
||||
}
|
||||
}
|
||||
|
||||
func TransferStatusToProto(status model.TransferStatus) chainv1.TransferStatus {
|
||||
switch status {
|
||||
case model.TransferStatusPending:
|
||||
return chainv1.TransferStatus_TRANSFER_PENDING
|
||||
case model.TransferStatusSigning:
|
||||
return chainv1.TransferStatus_TRANSFER_SIGNING
|
||||
case model.TransferStatusSubmitted:
|
||||
return chainv1.TransferStatus_TRANSFER_SUBMITTED
|
||||
case model.TransferStatusConfirmed:
|
||||
return chainv1.TransferStatus_TRANSFER_CONFIRMED
|
||||
|
||||
case model.TransferStatusCreated:
|
||||
return chainv1.TransferStatus_TRANSFER_CREATED
|
||||
|
||||
case model.TransferStatusProcessing:
|
||||
return chainv1.TransferStatus_TRANSFER_PROCESSING
|
||||
|
||||
case model.TransferStatusWaiting:
|
||||
return chainv1.TransferStatus_TRANSFER_WAITING
|
||||
|
||||
case model.TransferStatusSuccess:
|
||||
return chainv1.TransferStatus_TRANSFER_SUCCESS
|
||||
|
||||
case model.TransferStatusFailed:
|
||||
return chainv1.TransferStatus_TRANSFER_FAILED
|
||||
|
||||
case model.TransferStatusCancelled:
|
||||
return chainv1.TransferStatus_TRANSFER_CANCELLED
|
||||
|
||||
default:
|
||||
return chainv1.TransferStatus_TRANSFER_STATUS_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func ChainTransferStatusToOperation(status chainv1.TransferStatus) connectorv1.OperationStatus {
|
||||
switch status {
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_CREATED:
|
||||
return connectorv1.OperationStatus_OPERATION_CREATED
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_PROCESSING:
|
||||
return connectorv1.OperationStatus_OPERATION_PROCESSING
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_WAITING:
|
||||
return connectorv1.OperationStatus_OPERATION_WAITING
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_SUCCESS:
|
||||
return connectorv1.OperationStatus_OPERATION_SUCCESS
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_FAILED:
|
||||
return connectorv1.OperationStatus_OPERATION_FAILED
|
||||
|
||||
case chainv1.TransferStatus_TRANSFER_CANCELLED:
|
||||
return connectorv1.OperationStatus_OPERATION_CANCELLED
|
||||
|
||||
default:
|
||||
return connectorv1.OperationStatus_OPERATION_STATUS_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
// NativeCurrency returns the canonical native token symbol for a network.
|
||||
func NativeCurrency(network Network) string {
|
||||
currency := strings.ToUpper(strings.TrimSpace(network.NativeToken))
|
||||
if currency == "" {
|
||||
currency = strings.ToUpper(strings.TrimSpace(network.Name))
|
||||
currency = strings.ToUpper(network.Name.String())
|
||||
}
|
||||
return currency
|
||||
}
|
||||
|
||||
// Network describes a supported blockchain network and known token contracts.
|
||||
type Network struct {
|
||||
Name string
|
||||
Name pmodel.ChainNetwork
|
||||
RPCURL string
|
||||
ChainID uint64
|
||||
NativeToken string
|
||||
@@ -139,7 +203,27 @@ type TokenContract struct {
|
||||
|
||||
// ServiceWallet captures the managed service wallet configuration.
|
||||
type ServiceWallet struct {
|
||||
Network string
|
||||
Network pmodel.ChainNetwork
|
||||
Address string
|
||||
PrivateKey string
|
||||
}
|
||||
|
||||
func ProtoToMoney(money *moneyv1.Money) *paymenttypes.Money {
|
||||
if money == nil {
|
||||
return &paymenttypes.Money{}
|
||||
}
|
||||
return &paymenttypes.Money{
|
||||
Amount: money.GetAmount(),
|
||||
Currency: money.GetCurrency(),
|
||||
}
|
||||
}
|
||||
|
||||
func MonenyToProto(money *paymenttypes.Money) *moneyv1.Money {
|
||||
if money == nil {
|
||||
return &moneyv1.Money{}
|
||||
}
|
||||
return &moneyv1.Money{
|
||||
Amount: money.Amount,
|
||||
Currency: money.Currency,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
||||
"github.com/tech/sendico/gateway/chain/storage/model"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
pmodel "github.com/tech/sendico/pkg/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -40,35 +41,35 @@ func (s *Service) executeTransfer(ctx context.Context, transferRef, sourceWallet
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := s.storage.Transfers().UpdateStatus(ctx, transferRef, model.TransferStatusSigning, "", ""); err != nil {
|
||||
if _, err := s.updateTransferStatus(ctx, transferRef, model.TransferStatusProcessing, "", ""); 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(), "")
|
||||
_, _ = s.updateTransferStatus(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(), "")
|
||||
_, _ = s.updateTransferStatus(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(), "")
|
||||
_, _ = s.updateTransferStatus(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),
|
||||
zap.String("network", string(network.Name)),
|
||||
)
|
||||
if _, err := s.storage.Transfers().UpdateStatus(ctx, transferRef, model.TransferStatusConfirmed, "", ""); err != nil {
|
||||
if _, err := s.updateTransferStatus(ctx, transferRef, model.TransferStatusSuccess, "", ""); err != nil {
|
||||
s.logger.Warn("Failed to update transfer status to confirmed", zap.String("transfer_ref", transferRef), zap.Error(err))
|
||||
}
|
||||
return nil
|
||||
@@ -76,11 +77,14 @@ func (s *Service) executeTransfer(ctx context.Context, transferRef, sourceWallet
|
||||
|
||||
txHash, err := chainDriver.SubmitTransfer(ctx, driverDeps, network, transfer, sourceWallet, destinationAddress)
|
||||
if err != nil {
|
||||
_, _ = s.storage.Transfers().UpdateStatus(ctx, transferRef, model.TransferStatusFailed, err.Error(), "")
|
||||
s.logger.Warn("Failed to submit transfer", zap.String("transfer_ref", transferRef), zap.Error(err))
|
||||
if _, e := s.updateTransferStatus(ctx, transferRef, model.TransferStatusFailed, err.Error(), ""); e != nil {
|
||||
s.logger.Warn("Failed to update transfer status to failed", zap.String("transfer_ref", transferRef), zap.Error(e))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := s.storage.Transfers().UpdateStatus(ctx, transferRef, model.TransferStatusSubmitted, "", txHash); err != nil {
|
||||
if _, err := s.updateTransferStatus(ctx, transferRef, model.TransferStatusWaiting, "", txHash); err != nil {
|
||||
s.logger.Warn("Failed to update transfer status to submitted", zap.String("transfer_ref", transferRef), zap.Error(err))
|
||||
}
|
||||
|
||||
@@ -94,15 +98,15 @@ func (s *Service) executeTransfer(ctx context.Context, transferRef, sourceWallet
|
||||
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
|
||||
failureReason := ""
|
||||
pStatus := model.TransferStatusSuccess
|
||||
if receipt != nil && receipt.Status != types.ReceiptStatusSuccessful {
|
||||
failureReason = "transaction reverted"
|
||||
pStatus = model.TransferStatusFailed
|
||||
}
|
||||
|
||||
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))
|
||||
if _, err := s.updateTransferStatus(ctx, transferRef, pStatus, failureReason, txHash); err != nil {
|
||||
s.logger.Warn("Failed to update transfer status", zap.Error(err),
|
||||
zap.String("transfer_ref", transferRef), zap.String("status", string(pStatus)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -133,7 +137,7 @@ func (s *Service) driverDeps() driver.Deps {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) driverForNetwork(network string) (driver.Driver, error) {
|
||||
func (s *Service) driverForNetwork(network pmodel.ChainNetwork) (driver.Driver, error) {
|
||||
if s.drivers == nil {
|
||||
return nil, merrors.Internal("chain drivers not configured")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/tech/sendico/gateway/chain/storage/model"
|
||||
paymentgateway "github.com/tech/sendico/pkg/messaging/notifications/paymentgateway"
|
||||
pmodel "github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
"github.com/tech/sendico/pkg/payments/rail"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func isFinalStatus(t *model.Transfer) bool {
|
||||
switch t.Status {
|
||||
case model.TransferStatusFailed, model.TransferStatusSuccess, model.TransferStatusCancelled:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func toOpStatus(t *model.Transfer) rail.OperationResult {
|
||||
switch t.Status {
|
||||
case model.TransferStatusFailed:
|
||||
return rail.OperationResultFailed
|
||||
case model.TransferStatusSuccess:
|
||||
return rail.OperationResultSuccess
|
||||
case model.TransferStatusCancelled:
|
||||
return rail.OperationResultCancelled
|
||||
default:
|
||||
panic(fmt.Sprintf("toOpStatus: unexpected transfer status: %s", t.Status))
|
||||
}
|
||||
}
|
||||
|
||||
func toError(t *model.Transfer) string {
|
||||
if t == nil {
|
||||
return ""
|
||||
}
|
||||
if t.Status == model.TransferStatusSuccess {
|
||||
return ""
|
||||
}
|
||||
return t.FailureReason
|
||||
}
|
||||
|
||||
func (s *Service) updateTransferStatus(ctx context.Context, transferRef string, status model.TransferStatus, failureReason, txHash string) (*model.Transfer, error) {
|
||||
transfer, err := s.storage.Transfers().UpdateStatus(ctx, transferRef, status, failureReason, txHash)
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to update transfer status", zap.String("transfer_ref", transferRef), zap.String("status", string(status)), zap.Error(err))
|
||||
}
|
||||
if isFinalStatus(transfer) {
|
||||
s.emitTransferStatusEvent(transfer)
|
||||
}
|
||||
return transfer, err
|
||||
}
|
||||
|
||||
func (s *Service) emitTransferStatusEvent(transfer *model.Transfer) {
|
||||
if s == nil || s.producer == nil || transfer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
exec := pmodel.PaymentGatewayExecution{
|
||||
PaymentIntentID: transfer.IntentRef,
|
||||
IdempotencyKey: transfer.IdempotencyKey,
|
||||
ExecutedMoney: transfer.NetAmount,
|
||||
PaymentRef: transfer.PaymentRef,
|
||||
Status: toOpStatus(transfer),
|
||||
OperationRef: transfer.OperationRef,
|
||||
Error: toError(transfer),
|
||||
TransferRef: transfer.TransferRef,
|
||||
}
|
||||
env := paymentgateway.PaymentGatewayExecution(mservice.ChainGateway, &exec)
|
||||
if err := s.producer.SendMessage(env); err != nil {
|
||||
s.logger.Warn("Failed to publish transfer status event", zap.Error(err), zap.String("transfer_ref", transfer.TransferRef))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user