fixed serialization/deserialization #259

Merged
tech merged 1 commits from chain-258 into main 2026-01-15 19:03:06 +00:00
7 changed files with 260 additions and 213 deletions

View File

@@ -7,11 +7,12 @@ import (
"strings" "strings"
"time" "time"
chainasset "github.com/tech/sendico/pkg/chain"
"github.com/tech/sendico/pkg/merrors" "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" describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1"
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1" moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/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" chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "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{ resp, err := c.client.OpenAccount(ctx, &connectorv1.OpenAccountRequest{
IdempotencyKey: strings.TrimSpace(req.GetIdempotencyKey()), IdempotencyKey: strings.TrimSpace(req.GetIdempotencyKey()),
Kind: connectorv1.AccountKind_CHAIN_MANAGED_WALLET, Kind: connectorv1.AccountKind_CHAIN_MANAGED_WALLET,
Asset: assetStringFromChainAsset(req.GetAsset()), Asset: chainasset.AssetString(req.GetAsset()),
OwnerRef: strings.TrimSpace(req.GetOwnerRef()), OwnerRef: strings.TrimSpace(req.GetOwnerRef()),
Label: label, Label: label,
Params: params, Params: params,
@@ -152,7 +153,7 @@ func (c *chainGatewayClient) ListManagedWallets(ctx context.Context, req *chainv
orgRef := "" orgRef := ""
var page *paginationv1.CursorPageRequest var page *paginationv1.CursorPageRequest
if req != nil { if req != nil {
assetString = assetStringFromChainAsset(req.GetAsset()) assetString = chainasset.AssetString(req.GetAsset())
ownerRef = strings.TrimSpace(req.GetOwnerRef()) ownerRef = strings.TrimSpace(req.GetOwnerRef())
orgRef = strings.TrimSpace(req.GetOrganizationRef()) orgRef = strings.TrimSpace(req.GetOrganizationRef())
page = req.GetPage() 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 nil, merrors.Internal("chain-gateway: balance response missing")
} }
return &chainv1.GetWalletBalanceResponse{Balance: &chainv1.WalletBalance{ return &chainv1.GetWalletBalanceResponse{Balance: &chainv1.WalletBalance{
Available: balance.GetAvailable(), Available: balance.GetAvailable(),
PendingInbound: balance.GetPendingInbound(), PendingInbound: balance.GetPendingInbound(),
PendingOutbound: balance.GetPendingOutbound(), PendingOutbound: balance.GetPendingOutbound(),
CalculatedAt: balance.GetCalculatedAt(), CalculatedAt: balance.GetCalculatedAt(),
}}, nil }}, nil
} }
@@ -332,7 +333,7 @@ func walletParamsFromRequest(req *chainv1.CreateManagedWalletRequest) (*structpb
"organization_ref": strings.TrimSpace(req.GetOrganizationRef()), "organization_ref": strings.TrimSpace(req.GetOrganizationRef()),
} }
if asset := req.GetAsset(); asset != nil { 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["token_symbol"] = strings.TrimSpace(asset.GetTokenSymbol())
params["contract_address"] = strings.TrimSpace(asset.GetContractAddress()) params["contract_address"] = strings.TrimSpace(asset.GetContractAddress())
} }
@@ -370,12 +371,12 @@ func managedWalletFromAccount(account *connectorv1.Account) *chainv1.ManagedWall
ownerRef = strings.TrimSpace(account.GetOwnerRef()) ownerRef = strings.TrimSpace(account.GetOwnerRef())
} }
asset := &chainv1.Asset{ asset := &chainv1.Asset{
Chain: chainNetworkFromString(stringFromDetails(details, "network")), Chain: chainasset.NetworkFromString(stringFromDetails(details, "network")),
TokenSymbol: strings.TrimSpace(stringFromDetails(details, "token_symbol")), TokenSymbol: strings.TrimSpace(stringFromDetails(details, "token_symbol")),
ContractAddress: strings.TrimSpace(stringFromDetails(details, "contract_address")), ContractAddress: strings.TrimSpace(stringFromDetails(details, "contract_address")),
} }
if asset.GetTokenSymbol() == "" { if asset.GetTokenSymbol() == "" {
asset.TokenSymbol = strings.TrimSpace(tokenFromAssetString(account.GetAsset())) asset.TokenSymbol = strings.TrimSpace(chainasset.TokenFromAssetString(account.GetAsset()))
} }
describable := account.GetDescribable() describable := account.GetDescribable()
label := strings.TrimSpace(account.GetLabel()) label := strings.TrimSpace(account.GetLabel())
@@ -392,15 +393,15 @@ func managedWalletFromAccount(account *connectorv1.Account) *chainv1.ManagedWall
} }
} }
return &chainv1.ManagedWallet{ return &chainv1.ManagedWallet{
WalletRef: walletRef, WalletRef: walletRef,
OrganizationRef: organizationRef, OrganizationRef: organizationRef,
OwnerRef: ownerRef, OwnerRef: ownerRef,
Asset: asset, Asset: asset,
DepositAddress: stringFromDetails(details, "deposit_address"), DepositAddress: stringFromDetails(details, "deposit_address"),
Status: managedWalletStatusFromAccount(account.GetState()), Status: managedWalletStatusFromAccount(account.GetState()),
CreatedAt: account.GetCreatedAt(), CreatedAt: account.GetCreatedAt(),
UpdatedAt: account.GetUpdatedAt(), UpdatedAt: account.GetUpdatedAt(),
Describable: describable, Describable: describable,
} }
} }
@@ -438,7 +439,7 @@ func operationFromTransfer(req *chainv1.SubmitTransferRequest) (*connectorv1.Ope
op := &connectorv1.Operation{ op := &connectorv1.Operation{
Type: connectorv1.OperationType_TRANSFER, Type: connectorv1.OperationType_TRANSFER,
IdempotencyKey: strings.TrimSpace(req.GetIdempotencyKey()), 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(), Money: req.GetAmount(),
Params: structFromMap(params), Params: structFromMap(params),
} }
@@ -487,14 +488,14 @@ func transferFromOperation(op *connectorv1.Operation) *chainv1.Transfer {
return nil return nil
} }
transfer := &chainv1.Transfer{ transfer := &chainv1.Transfer{
TransferRef: strings.TrimSpace(op.GetOperationId()), TransferRef: strings.TrimSpace(op.GetOperationId()),
IdempotencyKey: strings.TrimSpace(op.GetOperationId()), IdempotencyKey: strings.TrimSpace(op.GetOperationId()),
RequestedAmount: op.GetMoney(), RequestedAmount: op.GetMoney(),
NetAmount: op.GetMoney(), NetAmount: op.GetMoney(),
Status: transferStatusFromOperation(op.GetStatus()), Status: transferStatusFromOperation(op.GetStatus()),
TransactionHash: strings.TrimSpace(op.GetProviderRef()), TransactionHash: strings.TrimSpace(op.GetProviderRef()),
CreatedAt: op.GetCreatedAt(), CreatedAt: op.GetCreatedAt(),
UpdatedAt: op.GetUpdatedAt(), UpdatedAt: op.GetUpdatedAt(),
} }
if from := op.GetFrom(); from != nil && from.GetAccount() != nil { if from := op.GetFrom(); from != nil && from.GetAccount() != nil {
transfer.SourceWalletRef = strings.TrimSpace(from.GetAccount().GetAccountId()) transfer.SourceWalletRef = strings.TrimSpace(from.GetAccount().GetAccountId())
@@ -527,7 +528,7 @@ func feeEstimateOperation(req *chainv1.EstimateTransferFeeRequest) (*connectorv1
op := &connectorv1.Operation{ op := &connectorv1.Operation{
Type: connectorv1.OperationType_FEE_ESTIMATE, Type: connectorv1.OperationType_FEE_ESTIMATE,
IdempotencyKey: feeEstimateKey(req), 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(), Money: req.GetAmount(),
Params: structFromMap(params), Params: structFromMap(params),
} }
@@ -567,14 +568,14 @@ func gasTopUpComputeOperation(req *chainv1.ComputeGasTopUpRequest) (*connectorv1
return nil, merrors.InvalidArgument("chain-gateway: estimated_total_fee is required") return nil, merrors.InvalidArgument("chain-gateway: estimated_total_fee is required")
} }
params := map[string]interface{}{ params := map[string]interface{}{
"mode": "compute", "mode": "compute",
"estimated_total_fee": map[string]interface{}{"amount": fee.GetAmount(), "currency": fee.GetCurrency()}, "estimated_total_fee": map[string]interface{}{"amount": fee.GetAmount(), "currency": fee.GetCurrency()},
} }
return &connectorv1.Operation{ return &connectorv1.Operation{
Type: connectorv1.OperationType_GAS_TOPUP, Type: connectorv1.OperationType_GAS_TOPUP,
IdempotencyKey: fmt.Sprintf("gas_topup_compute:%s:%s", strings.TrimSpace(req.GetWalletRef()), strings.TrimSpace(fee.GetAmount())), 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())}}}, From: &connectorv1.OperationParty{Ref: &connectorv1.OperationParty_Account{Account: &connectorv1.AccountRef{ConnectorId: chainConnectorID, AccountId: strings.TrimSpace(req.GetWalletRef())}}},
Params: structFromMap(params), Params: structFromMap(params),
}, nil }, nil
} }
@@ -596,10 +597,10 @@ func gasTopUpEnsureOperation(req *chainv1.EnsureGasTopUpRequest) (*connectorv1.O
return nil, merrors.InvalidArgument("chain-gateway: estimated_total_fee is required") return nil, merrors.InvalidArgument("chain-gateway: estimated_total_fee is required")
} }
params := map[string]interface{}{ params := map[string]interface{}{
"mode": "ensure", "mode": "ensure",
"organization_ref": strings.TrimSpace(req.GetOrganizationRef()), "organization_ref": strings.TrimSpace(req.GetOrganizationRef()),
"target_wallet_ref": strings.TrimSpace(req.GetTargetWalletRef()), "target_wallet_ref": strings.TrimSpace(req.GetTargetWalletRef()),
"client_reference": strings.TrimSpace(req.GetClientReference()), "client_reference": strings.TrimSpace(req.GetClientReference()),
"estimated_total_fee": map[string]interface{}{"amount": fee.GetAmount(), "currency": fee.GetCurrency()}, "estimated_total_fee": map[string]interface{}{"amount": fee.GetAmount(), "currency": fee.GetCurrency()},
} }
if len(req.GetMetadata()) > 0 { if len(req.GetMetadata()) > 0 {
@@ -608,7 +609,7 @@ func gasTopUpEnsureOperation(req *chainv1.EnsureGasTopUpRequest) (*connectorv1.O
return &connectorv1.Operation{ return &connectorv1.Operation{
Type: connectorv1.OperationType_GAS_TOPUP, Type: connectorv1.OperationType_GAS_TOPUP,
IdempotencyKey: strings.TrimSpace(req.GetIdempotencyKey()), 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), Params: structFromMap(params),
}, nil }, nil
} }
@@ -710,10 +711,10 @@ func feesToInterface(fees []*chainv1.ServiceFeeBreakdown) []interface{} {
continue continue
} }
result = append(result, map[string]interface{}{ result = append(result, map[string]interface{}{
"fee_code": strings.TrimSpace(fee.GetFeeCode()), "fee_code": strings.TrimSpace(fee.GetFeeCode()),
"description": strings.TrimSpace(fee.GetDescription()), "description": strings.TrimSpace(fee.GetDescription()),
"amount": strings.TrimSpace(fee.GetAmount().GetAmount()), "amount": strings.TrimSpace(fee.GetAmount().GetAmount()),
"currency": strings.TrimSpace(fee.GetAmount().GetCurrency()), "currency": strings.TrimSpace(fee.GetAmount().GetCurrency()),
}) })
} }
if len(result) == 0 { if len(result) == 0 {
@@ -770,60 +771,3 @@ func operationStatusFromTransfer(status chainv1.TransferStatus) connectorv1.Oper
return connectorv1.OperationStatus_OPERATION_STATUS_UNSPECIFIED 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
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/tech/sendico/gateway/chain/internal/appversion" "github.com/tech/sendico/gateway/chain/internal/appversion"
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared" "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/connector/params"
"github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/merrors"
describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1" 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()), AccountId: strings.TrimSpace(wallet.GetWalletRef()),
}, },
Kind: connectorv1.AccountKind_CHAIN_MANAGED_WALLET, Kind: connectorv1.AccountKind_CHAIN_MANAGED_WALLET,
Asset: assetStringFromChainAsset(wallet.GetAsset()), Asset: chainasset.AssetString(wallet.GetAsset()),
State: chainWalletState(wallet.GetStatus()), State: chainWalletState(wallet.GetStatus()),
Label: strings.TrimSpace(wallet.GetDescribable().GetName()), Label: strings.TrimSpace(wallet.GetDescribable().GetName()),
OwnerRef: strings.TrimSpace(wallet.GetOwnerRef()), 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) { func parseChainAsset(assetString string, reader params.Reader) (*chainv1.Asset, error) {
network := strings.TrimSpace(reader.String("network")) return chainasset.ParseAsset(
token := strings.TrimSpace(reader.String("token_symbol")) assetString,
contract := strings.TrimSpace(reader.String("contract_address")) reader.String("network"),
reader.String("token_symbol"),
if token == "" { reader.String("contract_address"),
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 ""
}
} }
func describableFromLabel(label, desc string) *describablev1.Describable { func describableFromLabel(label, desc string) *describablev1.Describable {

View File

@@ -4,6 +4,7 @@ import (
"strings" "strings"
"github.com/tech/sendico/gateway/chain/storage/model" "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" moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1" chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
"go.mongodb.org/mongo-driver/bson/primitive" "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 { func ChainEnumFromName(name string) chainv1.ChainNetwork {
if name == "" { return chainasset.NetworkFromString(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
} }
func ManagedWalletStatusToProto(status model.ManagedWalletStatus) chainv1.ManagedWalletStatus { func ManagedWalletStatusToProto(status model.ManagedWalletStatus) chainv1.ManagedWalletStatus {

View File

@@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/tech/sendico/payments/orchestrator/storage/model" "github.com/tech/sendico/payments/orchestrator/storage/model"
chainasset "github.com/tech/sendico/pkg/chain"
paymenttypes "github.com/tech/sendico/pkg/payments/types" paymenttypes "github.com/tech/sendico/pkg/payments/types"
feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1" feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1"
accountingv1 "github.com/tech/sendico/pkg/proto/common/accounting/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 nil
} }
return &paymenttypes.Asset{ return &paymenttypes.Asset{
Chain: chainNetworkName(asset.GetChain()), Chain: chainasset.NetworkAlias(asset.GetChain()),
TokenSymbol: asset.GetTokenSymbol(), TokenSymbol: asset.GetTokenSymbol(),
ContractAddress: asset.GetContractAddress(), ContractAddress: asset.GetContractAddress(),
} }
@@ -730,7 +731,7 @@ func assetToProto(asset *paymenttypes.Asset) *chainv1.Asset {
return nil return nil
} }
return &chainv1.Asset{ return &chainv1.Asset{
Chain: chainNetworkFromName(asset.Chain), Chain: chainasset.NetworkFromString(asset.Chain),
TokenSymbol: asset.TokenSymbol, TokenSymbol: asset.TokenSymbol,
ContractAddress: asset.ContractAddress, ContractAddress: asset.ContractAddress,
} }

View File

@@ -5,7 +5,6 @@ import (
"github.com/tech/sendico/payments/orchestrator/storage/model" "github.com/tech/sendico/payments/orchestrator/storage/model"
"github.com/tech/sendico/pkg/merrors" "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) { 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 "" 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
}
}

154
api/pkg/chain/asset.go Normal file
View File

@@ -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 ""
}
}

View File

@@ -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)
}