From 0d6e16711f68ba5e84c71375d86cd33c2341b6d1 Mon Sep 17 00:00:00 2001 From: Stephan D Date: Thu, 15 Jan 2026 20:02:03 +0100 Subject: [PATCH] fixed serialization/deserialization --- api/gateway/chain/client/client.go | 132 +++++---------- .../internal/service/gateway/connector.go | 82 +--------- .../service/gateway/shared/helpers.go | 11 +- .../internal/service/orchestrator/convert.go | 5 +- .../orchestrator/plan_builder_endpoints.go | 34 ---- api/pkg/chain/asset.go | 154 ++++++++++++++++++ api/pkg/chain/asset_test.go | 55 +++++++ 7 files changed, 260 insertions(+), 213 deletions(-) create mode 100644 api/pkg/chain/asset.go create mode 100644 api/pkg/chain/asset_test.go diff --git a/api/gateway/chain/client/client.go b/api/gateway/chain/client/client.go index 1cbd9bc..5d23b85 100644 --- a/api/gateway/chain/client/client.go +++ b/api/gateway/chain/client/client.go @@ -7,11 +7,12 @@ import ( "strings" "time" + chainasset "github.com/tech/sendico/pkg/chain" "github.com/tech/sendico/pkg/merrors" - connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1" describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1" moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1" paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1" + connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1" chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -116,7 +117,7 @@ func (c *chainGatewayClient) CreateManagedWallet(ctx context.Context, req *chain resp, err := c.client.OpenAccount(ctx, &connectorv1.OpenAccountRequest{ IdempotencyKey: strings.TrimSpace(req.GetIdempotencyKey()), Kind: connectorv1.AccountKind_CHAIN_MANAGED_WALLET, - Asset: assetStringFromChainAsset(req.GetAsset()), + Asset: chainasset.AssetString(req.GetAsset()), OwnerRef: strings.TrimSpace(req.GetOwnerRef()), Label: label, Params: params, @@ -152,7 +153,7 @@ func (c *chainGatewayClient) ListManagedWallets(ctx context.Context, req *chainv orgRef := "" var page *paginationv1.CursorPageRequest if req != nil { - assetString = assetStringFromChainAsset(req.GetAsset()) + assetString = chainasset.AssetString(req.GetAsset()) ownerRef = strings.TrimSpace(req.GetOwnerRef()) orgRef = strings.TrimSpace(req.GetOrganizationRef()) page = req.GetPage() @@ -189,10 +190,10 @@ func (c *chainGatewayClient) GetWalletBalance(ctx context.Context, req *chainv1. return nil, merrors.Internal("chain-gateway: balance response missing") } return &chainv1.GetWalletBalanceResponse{Balance: &chainv1.WalletBalance{ - Available: balance.GetAvailable(), - PendingInbound: balance.GetPendingInbound(), + Available: balance.GetAvailable(), + PendingInbound: balance.GetPendingInbound(), PendingOutbound: balance.GetPendingOutbound(), - CalculatedAt: balance.GetCalculatedAt(), + CalculatedAt: balance.GetCalculatedAt(), }}, nil } @@ -332,7 +333,7 @@ func walletParamsFromRequest(req *chainv1.CreateManagedWalletRequest) (*structpb "organization_ref": strings.TrimSpace(req.GetOrganizationRef()), } if asset := req.GetAsset(); asset != nil { - params["network"] = asset.GetChain().String() + params["network"] = chainasset.NetworkName(asset.GetChain()) params["token_symbol"] = strings.TrimSpace(asset.GetTokenSymbol()) params["contract_address"] = strings.TrimSpace(asset.GetContractAddress()) } @@ -370,12 +371,12 @@ func managedWalletFromAccount(account *connectorv1.Account) *chainv1.ManagedWall ownerRef = strings.TrimSpace(account.GetOwnerRef()) } asset := &chainv1.Asset{ - Chain: chainNetworkFromString(stringFromDetails(details, "network")), + Chain: chainasset.NetworkFromString(stringFromDetails(details, "network")), TokenSymbol: strings.TrimSpace(stringFromDetails(details, "token_symbol")), ContractAddress: strings.TrimSpace(stringFromDetails(details, "contract_address")), } if asset.GetTokenSymbol() == "" { - asset.TokenSymbol = strings.TrimSpace(tokenFromAssetString(account.GetAsset())) + asset.TokenSymbol = strings.TrimSpace(chainasset.TokenFromAssetString(account.GetAsset())) } describable := account.GetDescribable() label := strings.TrimSpace(account.GetLabel()) @@ -392,15 +393,15 @@ func managedWalletFromAccount(account *connectorv1.Account) *chainv1.ManagedWall } } return &chainv1.ManagedWallet{ - WalletRef: walletRef, + WalletRef: walletRef, OrganizationRef: organizationRef, - OwnerRef: ownerRef, - Asset: asset, - DepositAddress: stringFromDetails(details, "deposit_address"), - Status: managedWalletStatusFromAccount(account.GetState()), - CreatedAt: account.GetCreatedAt(), - UpdatedAt: account.GetUpdatedAt(), - Describable: describable, + OwnerRef: ownerRef, + Asset: asset, + DepositAddress: stringFromDetails(details, "deposit_address"), + Status: managedWalletStatusFromAccount(account.GetState()), + CreatedAt: account.GetCreatedAt(), + UpdatedAt: account.GetUpdatedAt(), + Describable: describable, } } @@ -438,7 +439,7 @@ func operationFromTransfer(req *chainv1.SubmitTransferRequest) (*connectorv1.Ope op := &connectorv1.Operation{ Type: connectorv1.OperationType_TRANSFER, IdempotencyKey: strings.TrimSpace(req.GetIdempotencyKey()), - From: &connectorv1.OperationParty{Ref: &connectorv1.OperationParty_Account{Account: &connectorv1.AccountRef{ConnectorId: chainConnectorID, AccountId: strings.TrimSpace(req.GetSourceWalletRef())}}}, + From: &connectorv1.OperationParty{Ref: &connectorv1.OperationParty_Account{Account: &connectorv1.AccountRef{ConnectorId: chainConnectorID, AccountId: strings.TrimSpace(req.GetSourceWalletRef())}}}, Money: req.GetAmount(), Params: structFromMap(params), } @@ -487,14 +488,14 @@ func transferFromOperation(op *connectorv1.Operation) *chainv1.Transfer { return nil } transfer := &chainv1.Transfer{ - TransferRef: strings.TrimSpace(op.GetOperationId()), - IdempotencyKey: strings.TrimSpace(op.GetOperationId()), + TransferRef: strings.TrimSpace(op.GetOperationId()), + IdempotencyKey: strings.TrimSpace(op.GetOperationId()), RequestedAmount: op.GetMoney(), - NetAmount: op.GetMoney(), - Status: transferStatusFromOperation(op.GetStatus()), + NetAmount: op.GetMoney(), + Status: transferStatusFromOperation(op.GetStatus()), TransactionHash: strings.TrimSpace(op.GetProviderRef()), - CreatedAt: op.GetCreatedAt(), - UpdatedAt: op.GetUpdatedAt(), + CreatedAt: op.GetCreatedAt(), + UpdatedAt: op.GetUpdatedAt(), } if from := op.GetFrom(); from != nil && from.GetAccount() != nil { transfer.SourceWalletRef = strings.TrimSpace(from.GetAccount().GetAccountId()) @@ -527,7 +528,7 @@ func feeEstimateOperation(req *chainv1.EstimateTransferFeeRequest) (*connectorv1 op := &connectorv1.Operation{ Type: connectorv1.OperationType_FEE_ESTIMATE, IdempotencyKey: feeEstimateKey(req), - From: &connectorv1.OperationParty{Ref: &connectorv1.OperationParty_Account{Account: &connectorv1.AccountRef{ConnectorId: chainConnectorID, AccountId: strings.TrimSpace(req.GetSourceWalletRef())}}}, + From: &connectorv1.OperationParty{Ref: &connectorv1.OperationParty_Account{Account: &connectorv1.AccountRef{ConnectorId: chainConnectorID, AccountId: strings.TrimSpace(req.GetSourceWalletRef())}}}, Money: req.GetAmount(), Params: structFromMap(params), } @@ -567,14 +568,14 @@ func gasTopUpComputeOperation(req *chainv1.ComputeGasTopUpRequest) (*connectorv1 return nil, merrors.InvalidArgument("chain-gateway: estimated_total_fee is required") } params := map[string]interface{}{ - "mode": "compute", + "mode": "compute", "estimated_total_fee": map[string]interface{}{"amount": fee.GetAmount(), "currency": fee.GetCurrency()}, } return &connectorv1.Operation{ Type: connectorv1.OperationType_GAS_TOPUP, IdempotencyKey: fmt.Sprintf("gas_topup_compute:%s:%s", strings.TrimSpace(req.GetWalletRef()), strings.TrimSpace(fee.GetAmount())), - From: &connectorv1.OperationParty{Ref: &connectorv1.OperationParty_Account{Account: &connectorv1.AccountRef{ConnectorId: chainConnectorID, AccountId: strings.TrimSpace(req.GetWalletRef())}}}, - Params: structFromMap(params), + From: &connectorv1.OperationParty{Ref: &connectorv1.OperationParty_Account{Account: &connectorv1.AccountRef{ConnectorId: chainConnectorID, AccountId: strings.TrimSpace(req.GetWalletRef())}}}, + Params: structFromMap(params), }, nil } @@ -596,10 +597,10 @@ func gasTopUpEnsureOperation(req *chainv1.EnsureGasTopUpRequest) (*connectorv1.O return nil, merrors.InvalidArgument("chain-gateway: estimated_total_fee is required") } params := map[string]interface{}{ - "mode": "ensure", - "organization_ref": strings.TrimSpace(req.GetOrganizationRef()), - "target_wallet_ref": strings.TrimSpace(req.GetTargetWalletRef()), - "client_reference": strings.TrimSpace(req.GetClientReference()), + "mode": "ensure", + "organization_ref": strings.TrimSpace(req.GetOrganizationRef()), + "target_wallet_ref": strings.TrimSpace(req.GetTargetWalletRef()), + "client_reference": strings.TrimSpace(req.GetClientReference()), "estimated_total_fee": map[string]interface{}{"amount": fee.GetAmount(), "currency": fee.GetCurrency()}, } if len(req.GetMetadata()) > 0 { @@ -608,7 +609,7 @@ func gasTopUpEnsureOperation(req *chainv1.EnsureGasTopUpRequest) (*connectorv1.O return &connectorv1.Operation{ Type: connectorv1.OperationType_GAS_TOPUP, IdempotencyKey: strings.TrimSpace(req.GetIdempotencyKey()), - From: &connectorv1.OperationParty{Ref: &connectorv1.OperationParty_Account{Account: &connectorv1.AccountRef{ConnectorId: chainConnectorID, AccountId: strings.TrimSpace(req.GetSourceWalletRef())}}}, + From: &connectorv1.OperationParty{Ref: &connectorv1.OperationParty_Account{Account: &connectorv1.AccountRef{ConnectorId: chainConnectorID, AccountId: strings.TrimSpace(req.GetSourceWalletRef())}}}, Params: structFromMap(params), }, nil } @@ -710,10 +711,10 @@ func feesToInterface(fees []*chainv1.ServiceFeeBreakdown) []interface{} { continue } result = append(result, map[string]interface{}{ - "fee_code": strings.TrimSpace(fee.GetFeeCode()), - "description": strings.TrimSpace(fee.GetDescription()), - "amount": strings.TrimSpace(fee.GetAmount().GetAmount()), - "currency": strings.TrimSpace(fee.GetAmount().GetCurrency()), + "fee_code": strings.TrimSpace(fee.GetFeeCode()), + "description": strings.TrimSpace(fee.GetDescription()), + "amount": strings.TrimSpace(fee.GetAmount().GetAmount()), + "currency": strings.TrimSpace(fee.GetAmount().GetCurrency()), }) } if len(result) == 0 { @@ -770,60 +771,3 @@ func operationStatusFromTransfer(status chainv1.TransferStatus) connectorv1.Oper return connectorv1.OperationStatus_OPERATION_STATUS_UNSPECIFIED } } - -func assetStringFromChainAsset(asset *chainv1.Asset) string { - if asset == nil { - return "" - } - symbol := strings.ToUpper(strings.TrimSpace(asset.GetTokenSymbol())) - if symbol == "" { - return "" - } - suffix := chainAssetSuffix(asset.GetChain()) - if suffix == "" { - return symbol - } - return symbol + "-" + suffix -} - -func chainAssetSuffix(chain chainv1.ChainNetwork) string { - switch chain { - case chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET: - return "ETH" - case chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_ONE: - return "ARB" - case chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET: - return "TRC20" - case chainv1.ChainNetwork_CHAIN_NETWORK_TRON_NILE: - return "TRC20" - default: - return "" - } -} - -func tokenFromAssetString(asset string) string { - if asset == "" { - return "" - } - if idx := strings.Index(asset, "-"); idx > 0 { - return asset[:idx] - } - return asset -} - -func chainNetworkFromString(value string) chainv1.ChainNetwork { - value = strings.ToUpper(strings.TrimSpace(value)) - if value == "" { - return chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED - } - if val, ok := chainv1.ChainNetwork_value[value]; ok { - return chainv1.ChainNetwork(val) - } - if !strings.HasPrefix(value, "CHAIN_NETWORK_") { - value = "CHAIN_NETWORK_" + value - } - if val, ok := chainv1.ChainNetwork_value[value]; ok { - return chainv1.ChainNetwork(val) - } - return chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED -} diff --git a/api/gateway/chain/internal/service/gateway/connector.go b/api/gateway/chain/internal/service/gateway/connector.go index 8ddb5e6..0c514f7 100644 --- a/api/gateway/chain/internal/service/gateway/connector.go +++ b/api/gateway/chain/internal/service/gateway/connector.go @@ -8,6 +8,7 @@ import ( "github.com/tech/sendico/gateway/chain/internal/appversion" "github.com/tech/sendico/gateway/chain/internal/service/gateway/shared" + chainasset "github.com/tech/sendico/pkg/chain" "github.com/tech/sendico/pkg/connector/params" "github.com/tech/sendico/pkg/merrors" describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1" @@ -372,7 +373,7 @@ func chainWalletToAccount(wallet *chainv1.ManagedWallet) *connectorv1.Account { AccountId: strings.TrimSpace(wallet.GetWalletRef()), }, Kind: connectorv1.AccountKind_CHAIN_MANAGED_WALLET, - Asset: assetStringFromChainAsset(wallet.GetAsset()), + Asset: chainasset.AssetString(wallet.GetAsset()), State: chainWalletState(wallet.GetStatus()), Label: strings.TrimSpace(wallet.GetDescribable().GetName()), OwnerRef: strings.TrimSpace(wallet.GetOwnerRef()), @@ -568,79 +569,12 @@ func chainStatusFromOperation(status connectorv1.OperationStatus) chainv1.Transf } func parseChainAsset(assetString string, reader params.Reader) (*chainv1.Asset, error) { - network := strings.TrimSpace(reader.String("network")) - token := strings.TrimSpace(reader.String("token_symbol")) - contract := strings.TrimSpace(reader.String("contract_address")) - - if token == "" { - token = tokenFromAssetString(assetString) - } - if network == "" { - network = networkFromAssetString(assetString) - } - if token == "" { - return nil, merrors.InvalidArgument("asset: token_symbol is required") - } - chain := shared.ChainEnumFromName(network) - if chain == chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED { - return nil, merrors.InvalidArgument("asset: network is required") - } - return &chainv1.Asset{ - Chain: chain, - TokenSymbol: strings.ToUpper(token), - ContractAddress: strings.ToLower(contract), - }, nil -} - -func tokenFromAssetString(asset string) string { - if asset == "" { - return "" - } - if idx := strings.Index(asset, "-"); idx > 0 { - return asset[:idx] - } - return asset -} - -func networkFromAssetString(asset string) string { - if asset == "" { - return "" - } - idx := strings.Index(asset, "-") - if idx < 0 { - return "" - } - return strings.TrimSpace(asset[idx+1:]) -} - -func assetStringFromChainAsset(asset *chainv1.Asset) string { - if asset == nil { - return "" - } - symbol := strings.ToUpper(strings.TrimSpace(asset.GetTokenSymbol())) - if symbol == "" { - return "" - } - suffix := chainAssetSuffix(asset.GetChain()) - if suffix == "" { - return symbol - } - return symbol + "-" + suffix -} - -func chainAssetSuffix(chain chainv1.ChainNetwork) string { - switch chain { - case chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET: - return "ETH" - case chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_ONE: - return "ARB" - case chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET: - return "TRC20" - case chainv1.ChainNetwork_CHAIN_NETWORK_TRON_NILE: - return "TRC20" - default: - return "" - } + return chainasset.ParseAsset( + assetString, + reader.String("network"), + reader.String("token_symbol"), + reader.String("contract_address"), + ) } func describableFromLabel(label, desc string) *describablev1.Describable { diff --git a/api/gateway/chain/internal/service/gateway/shared/helpers.go b/api/gateway/chain/internal/service/gateway/shared/helpers.go index 7ff63be..608c572 100644 --- a/api/gateway/chain/internal/service/gateway/shared/helpers.go +++ b/api/gateway/chain/internal/service/gateway/shared/helpers.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/tech/sendico/gateway/chain/storage/model" + chainasset "github.com/tech/sendico/pkg/chain" moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1" chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1" "go.mongodb.org/mongo-driver/bson/primitive" @@ -57,15 +58,7 @@ func ChainKeyFromEnum(chain chainv1.ChainNetwork) (string, chainv1.ChainNetwork) } func ChainEnumFromName(name string) chainv1.ChainNetwork { - if name == "" { - return chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED - } - upper := strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(name, " ", "_"), "-", "_")) - key := "CHAIN_NETWORK_" + upper - if val, ok := chainv1.ChainNetwork_value[key]; ok { - return chainv1.ChainNetwork(val) - } - return chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED + return chainasset.NetworkFromString(name) } func ManagedWalletStatusToProto(status model.ManagedWalletStatus) chainv1.ManagedWalletStatus { diff --git a/api/payments/orchestrator/internal/service/orchestrator/convert.go b/api/payments/orchestrator/internal/service/orchestrator/convert.go index a3ed506..0a45891 100644 --- a/api/payments/orchestrator/internal/service/orchestrator/convert.go +++ b/api/payments/orchestrator/internal/service/orchestrator/convert.go @@ -5,6 +5,7 @@ import ( "time" "github.com/tech/sendico/payments/orchestrator/storage/model" + chainasset "github.com/tech/sendico/pkg/chain" paymenttypes "github.com/tech/sendico/pkg/payments/types" feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1" accountingv1 "github.com/tech/sendico/pkg/proto/common/accounting/v1" @@ -719,7 +720,7 @@ func assetFromProto(asset *chainv1.Asset) *paymenttypes.Asset { return nil } return &paymenttypes.Asset{ - Chain: chainNetworkName(asset.GetChain()), + Chain: chainasset.NetworkAlias(asset.GetChain()), TokenSymbol: asset.GetTokenSymbol(), ContractAddress: asset.GetContractAddress(), } @@ -730,7 +731,7 @@ func assetToProto(asset *paymenttypes.Asset) *chainv1.Asset { return nil } return &chainv1.Asset{ - Chain: chainNetworkFromName(asset.Chain), + Chain: chainasset.NetworkFromString(asset.Chain), TokenSymbol: asset.TokenSymbol, ContractAddress: asset.ContractAddress, } diff --git a/api/payments/orchestrator/internal/service/orchestrator/plan_builder_endpoints.go b/api/payments/orchestrator/internal/service/orchestrator/plan_builder_endpoints.go index 10ab26c..324e903 100644 --- a/api/payments/orchestrator/internal/service/orchestrator/plan_builder_endpoints.go +++ b/api/payments/orchestrator/internal/service/orchestrator/plan_builder_endpoints.go @@ -5,7 +5,6 @@ import ( "github.com/tech/sendico/payments/orchestrator/storage/model" "github.com/tech/sendico/pkg/merrors" - chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1" ) func railFromEndpoint(endpoint model.PaymentEndpoint, attrs map[string]string, isSource bool) (model.Rail, string, error) { @@ -100,36 +99,3 @@ func networkFromEndpoint(endpoint model.PaymentEndpoint) string { } return "" } - -func chainNetworkName(network chainv1.ChainNetwork) string { - switch network { - case chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET: - return "ETH" - case chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET: - return "TRON" - case chainv1.ChainNetwork_CHAIN_NETWORK_TRON_NILE: - return "TRON_NILE" - case chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_ONE: - return "ARBITRUM" - default: - name := strings.TrimSpace(network.String()) - name = strings.TrimPrefix(name, "CHAIN_NETWORK_") - return strings.ToUpper(name) - } -} - -func chainNetworkFromName(network string) chainv1.ChainNetwork { - name := strings.ToUpper(strings.TrimSpace(network)) - switch name { - case "ETH", "ETHEREUM", "ETHEREUM_MAINNET": - return chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET - case "TRON", "TRON_MAINNET": - return chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET - case "TRON_NILE": - return chainv1.ChainNetwork_CHAIN_NETWORK_TRON_NILE - case "ARBITRUM", "ARBITRUM_ONE": - return chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_ONE - default: - return chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED - } -} diff --git a/api/pkg/chain/asset.go b/api/pkg/chain/asset.go new file mode 100644 index 0000000..c6f3a24 --- /dev/null +++ b/api/pkg/chain/asset.go @@ -0,0 +1,154 @@ +package chain + +import ( + "strings" + + "github.com/tech/sendico/pkg/merrors" + chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1" +) + +func AssetString(asset *chainv1.Asset) string { + if asset == nil { + return "" + } + symbol := strings.ToUpper(strings.TrimSpace(asset.GetTokenSymbol())) + if symbol == "" { + return "" + } + suffix := assetSuffix(asset.GetChain()) + if suffix == "" { + return symbol + } + return symbol + "-" + suffix +} + +func ParseAsset(assetString, network, tokenSymbol, contractAddress string) (*chainv1.Asset, error) { + token := strings.TrimSpace(tokenSymbol) + net := strings.TrimSpace(network) + contract := strings.TrimSpace(contractAddress) + + if token == "" { + token = TokenFromAssetString(assetString) + } + if net == "" { + net = NetworkFromAssetString(assetString) + } + if token == "" { + return nil, merrors.InvalidArgument("asset: token_symbol is required") + } + chain := NetworkFromString(net) + if chain == chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED { + return nil, merrors.InvalidArgument("asset: network is required") + } + return &chainv1.Asset{ + Chain: chain, + TokenSymbol: strings.ToUpper(token), + ContractAddress: strings.ToLower(contract), + }, nil +} + +func TokenFromAssetString(asset string) string { + trimmed := strings.TrimSpace(asset) + if trimmed == "" { + return "" + } + if idx := strings.Index(trimmed, "-"); idx > 0 { + return trimmed[:idx] + } + return trimmed +} + +func NetworkFromAssetString(asset string) string { + trimmed := strings.TrimSpace(asset) + if trimmed == "" { + return "" + } + idx := strings.Index(trimmed, "-") + if idx < 0 { + return "" + } + return strings.TrimSpace(trimmed[idx+1:]) +} + +func NetworkName(chain chainv1.ChainNetwork) string { + if chain == chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED { + return "" + } + if name, ok := chainv1.ChainNetwork_name[int32(chain)]; ok { + return strings.TrimPrefix(name, "CHAIN_NETWORK_") + } + return "" +} + +func NetworkAlias(chain chainv1.ChainNetwork) string { + switch chain { + case chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET: + return "ETH" + case chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET: + return "TRON" + case chainv1.ChainNetwork_CHAIN_NETWORK_TRON_NILE: + return "TRON_NILE" + case chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_ONE: + return "ARBITRUM" + default: + name := NetworkName(chain) + if name == "" { + fallback := strings.TrimPrefix(strings.TrimSpace(chain.String()), "CHAIN_NETWORK_") + return strings.ToUpper(fallback) + } + return strings.ToUpper(name) + } +} + +func NetworkFromString(value string) chainv1.ChainNetwork { + normalized := normalizeNetworkString(value) + if normalized == "" { + return chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED + } + if val, ok := chainv1.ChainNetwork_value[normalized]; ok { + return chainv1.ChainNetwork(val) + } + switch normalized { + case "ETH", "ETHEREUM", "ETH_MAINNET": + return chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET + case "TRON": + return chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET + case "TRON_NILE": + return chainv1.ChainNetwork_CHAIN_NETWORK_TRON_NILE + case "ARB", "ARBITRUM": + return chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_ONE + } + if !strings.HasPrefix(normalized, "CHAIN_NETWORK_") { + normalized = "CHAIN_NETWORK_" + normalized + } + if val, ok := chainv1.ChainNetwork_value[normalized]; ok { + return chainv1.ChainNetwork(val) + } + return chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED +} + +func normalizeNetworkString(value string) string { + trimmed := strings.TrimSpace(value) + if trimmed == "" { + return "" + } + normalized := strings.ToUpper(trimmed) + normalized = strings.ReplaceAll(normalized, " ", "_") + normalized = strings.ReplaceAll(normalized, "-", "_") + return normalized +} + +func assetSuffix(chain chainv1.ChainNetwork) string { + switch chain { + case chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET: + return "ETH" + case chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_ONE: + return "ARB" + case chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET: + return "TRC20" + case chainv1.ChainNetwork_CHAIN_NETWORK_TRON_NILE: + return "TRC20" + default: + return "" + } +} diff --git a/api/pkg/chain/asset_test.go b/api/pkg/chain/asset_test.go new file mode 100644 index 0000000..ea4d120 --- /dev/null +++ b/api/pkg/chain/asset_test.go @@ -0,0 +1,55 @@ +package chain + +import ( + "testing" + + "github.com/stretchr/testify/require" + chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1" +) + +func TestNetworkName(t *testing.T) { + require.Equal(t, "TRON_MAINNET", NetworkName(chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET)) + require.Equal(t, "", NetworkName(chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED)) +} + +func TestNetworkAlias(t *testing.T) { + require.Equal(t, "ETH", NetworkAlias(chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET)) + require.Equal(t, "TRON", NetworkAlias(chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET)) + require.Equal(t, "TRON_NILE", NetworkAlias(chainv1.ChainNetwork_CHAIN_NETWORK_TRON_NILE)) + require.Equal(t, "ARBITRUM", NetworkAlias(chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_ONE)) + require.Equal(t, "UNSPECIFIED", NetworkAlias(chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED)) +} + +func TestNetworkFromString(t *testing.T) { + require.Equal(t, chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET, NetworkFromString("TRON_MAINNET")) + require.Equal(t, chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET, NetworkFromString("CHAIN_NETWORK_TRON_MAINNET")) + require.Equal(t, chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET, NetworkFromString("tron mainnet")) + require.Equal(t, chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET, NetworkFromString("tron-mainnet")) + require.Equal(t, chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET, NetworkFromString("TRON")) + require.Equal(t, chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET, NetworkFromString("ETH")) + require.Equal(t, chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_ONE, NetworkFromString("ARBITRUM")) + require.Equal(t, chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED, NetworkFromString("")) +} + +func TestAssetString(t *testing.T) { + asset := &chainv1.Asset{ + Chain: chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET, + TokenSymbol: "usdt", + } + require.Equal(t, "USDT-TRC20", AssetString(asset)) +} + +func TestParseAsset(t *testing.T) { + asset, err := ParseAsset("USDT-TRC20", "TRON_MAINNET", "", "") + require.NoError(t, err) + require.Equal(t, chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET, asset.GetChain()) + require.Equal(t, "USDT", asset.GetTokenSymbol()) + require.Equal(t, "", asset.GetContractAddress()) + + asset, err = ParseAsset("USDT-TRC20", "CHAIN_NETWORK_TRON_MAINNET", "", "") + require.NoError(t, err) + require.Equal(t, chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET, asset.GetChain()) + + _, err = ParseAsset("USDT-TRC20", "", "USDT", "") + require.Error(t, err) +} -- 2.49.1