package gateway import ( "context" "errors" "fmt" "strings" "github.com/tech/sendico/gateway/tron/internal/appversion" "github.com/tech/sendico/gateway/tron/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" 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.uber.org/zap" "google.golang.org/protobuf/types/known/structpb" ) const chainConnectorID = "chain" func (s *Service) GetCapabilities(_ context.Context, _ *connectorv1.GetCapabilitiesRequest) (*connectorv1.GetCapabilitiesResponse, error) { return &connectorv1.GetCapabilitiesResponse{ Capabilities: &connectorv1.ConnectorCapabilities{ ConnectorType: chainConnectorID, Version: appversion.Create().Short(), SupportedAccountKinds: []connectorv1.AccountKind{connectorv1.AccountKind_CHAIN_MANAGED_WALLET}, SupportedOperationTypes: []connectorv1.OperationType{ connectorv1.OperationType_TRANSFER, connectorv1.OperationType_FEE_ESTIMATE, connectorv1.OperationType_GAS_TOPUP, }, OpenAccountParams: chainOpenAccountParams(), OperationParams: chainOperationParams(), }, }, nil } func (s *Service) OpenAccount(ctx context.Context, req *connectorv1.OpenAccountRequest) (*connectorv1.OpenAccountResponse, error) { if req == nil { return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "open_account: request is required", nil, "")}, nil } if req.GetKind() != connectorv1.AccountKind_CHAIN_MANAGED_WALLET { return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_UNSUPPORTED_ACCOUNT_KIND, "open_account: unsupported account kind", nil, "")}, nil } reader := params.New(req.GetParams()) orgRef := strings.TrimSpace(reader.String("organization_ref")) if orgRef == "" { return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "open_account: organization_ref is required", nil, "")}, nil } asset, err := parseChainAsset(strings.TrimSpace(req.GetAsset()), reader) if err != nil { return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, err.Error(), nil, "")}, nil } resp, err := s.CreateManagedWallet(ctx, &chainv1.CreateManagedWalletRequest{ IdempotencyKey: strings.TrimSpace(req.GetIdempotencyKey()), OrganizationRef: orgRef, OwnerRef: strings.TrimSpace(req.GetOwnerRef()), Asset: asset, Metadata: shared.CloneMetadata(reader.StringMap("metadata")), Describable: describableFromLabel(req.GetLabel(), reader.String("description")), }) if err != nil { return &connectorv1.OpenAccountResponse{Error: connectorError(mapErrorCode(err), err.Error(), nil, "")}, nil } return &connectorv1.OpenAccountResponse{Account: chainWalletToAccount(resp.GetWallet())}, nil } func (s *Service) GetAccount(ctx context.Context, req *connectorv1.GetAccountRequest) (*connectorv1.GetAccountResponse, error) { if req == nil || req.GetAccountRef() == nil || strings.TrimSpace(req.GetAccountRef().GetAccountId()) == "" { return nil, merrors.InvalidArgument("get_account: account_ref.account_id is required") } resp, err := s.GetManagedWallet(ctx, &chainv1.GetManagedWalletRequest{WalletRef: strings.TrimSpace(req.GetAccountRef().GetAccountId())}) if err != nil { return nil, err } return &connectorv1.GetAccountResponse{Account: chainWalletToAccount(resp.GetWallet())}, nil } func (s *Service) ListAccounts(ctx context.Context, req *connectorv1.ListAccountsRequest) (*connectorv1.ListAccountsResponse, error) { if req == nil { return nil, merrors.InvalidArgument("list_accounts: request is required") } asset := (*chainv1.Asset)(nil) if assetString := strings.TrimSpace(req.GetAsset()); assetString != "" { parsed, err := parseChainAsset(assetString, params.New(nil)) if err != nil { s.logger.Warn("Error listing accounts", zap.Error(err)) return nil, err } asset = parsed } resp, err := s.ListManagedWallets(ctx, &chainv1.ListManagedWalletsRequest{ OrganizationRef: strings.TrimSpace(req.GetOrganizationRef()), OwnerRefFilter: req.GetOwnerRefFilter(), Asset: asset, Page: req.GetPage(), }) if err != nil { return nil, err } accounts := make([]*connectorv1.Account, 0, len(resp.GetWallets())) for _, wallet := range resp.GetWallets() { accounts = append(accounts, chainWalletToAccount(wallet)) } return &connectorv1.ListAccountsResponse{ Accounts: accounts, Page: resp.GetPage(), }, nil } func (s *Service) GetBalance(ctx context.Context, req *connectorv1.GetBalanceRequest) (*connectorv1.GetBalanceResponse, error) { if req == nil || req.GetAccountRef() == nil || strings.TrimSpace(req.GetAccountRef().GetAccountId()) == "" { return nil, merrors.InvalidArgument("get_balance: account_ref.account_id is required") } resp, err := s.GetWalletBalance(ctx, &chainv1.GetWalletBalanceRequest{WalletRef: strings.TrimSpace(req.GetAccountRef().GetAccountId())}) if err != nil { return nil, err } bal := resp.GetBalance() return &connectorv1.GetBalanceResponse{ Balance: &connectorv1.Balance{ AccountRef: req.GetAccountRef(), Available: bal.GetAvailable(), PendingInbound: bal.GetPendingInbound(), PendingOutbound: bal.GetPendingOutbound(), CalculatedAt: bal.GetCalculatedAt(), }, }, nil } func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOperationRequest) (*connectorv1.SubmitOperationResponse, error) { if req == nil || req.GetOperation() == nil { return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "submit_operation: operation is required", nil, "")}}, nil } op := req.GetOperation() if strings.TrimSpace(op.GetIdempotencyKey()) == "" { return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "submit_operation: idempotency_key is required", op, "")}}, nil } reader := params.New(op.GetParams()) orgRef := strings.TrimSpace(reader.String("organization_ref")) source := operationAccountID(op.GetFrom()) if source == "" { return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "operation: from.account is required", op, "")}}, nil } switch op.GetType() { case connectorv1.OperationType_TRANSFER: dest, err := transferDestinationFromOperation(op) if err != nil { return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, err.Error(), op, "")}}, nil } amount := op.GetMoney() if amount == nil { return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "transfer: money is required", op, "")}}, nil } amount = normalizeMoneyForChain(amount) if orgRef == "" { return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "transfer: organization_ref is required", op, "")}}, nil } resp, err := s.SubmitTransfer(ctx, &chainv1.SubmitTransferRequest{ IdempotencyKey: strings.TrimSpace(op.GetIdempotencyKey()), OrganizationRef: orgRef, SourceWalletRef: source, Destination: dest, Amount: amount, Fees: parseChainFees(reader), Metadata: shared.CloneMetadata(reader.StringMap("metadata")), PaymentRef: strings.TrimSpace(reader.String("payment_ref")), IntentRef: strings.TrimSpace(op.GetIntentRef()), OperationRef: strings.TrimSpace(op.GetOperationRef()), }) if err != nil { return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil } transfer := resp.GetTransfer() return &connectorv1.SubmitOperationResponse{ Receipt: &connectorv1.OperationReceipt{ OperationId: strings.TrimSpace(transfer.GetTransferRef()), Status: chainTransferStatusToOperation(transfer.GetStatus()), ProviderRef: strings.TrimSpace(transfer.GetTransactionHash()), }, }, nil case connectorv1.OperationType_FEE_ESTIMATE: dest, err := transferDestinationFromOperation(op) if err != nil { return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, err.Error(), op, "")}}, nil } amount := op.GetMoney() if amount == nil { return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "estimate: money is required", op, "")}}, nil } amount = normalizeMoneyForChain(amount) opID := strings.TrimSpace(op.GetOperationId()) if opID == "" { opID = strings.TrimSpace(op.GetIdempotencyKey()) } resp, err := s.EstimateTransferFee(ctx, &chainv1.EstimateTransferFeeRequest{ SourceWalletRef: source, Destination: dest, Amount: amount, }) if err != nil { return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil } result := feeEstimateResult(resp) return &connectorv1.SubmitOperationResponse{ Receipt: &connectorv1.OperationReceipt{ OperationId: opID, Status: connectorv1.OperationStatus_OPERATION_SUCCESS, Result: result, }, }, nil case connectorv1.OperationType_GAS_TOPUP: fee, err := parseMoneyFromMap(reader.Map("estimated_total_fee")) if err != nil { return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, err.Error(), op, "")}}, nil } fee = normalizeMoneyForChain(fee) mode := strings.ToLower(strings.TrimSpace(reader.String("mode"))) if mode == "" { mode = "compute" } switch mode { case "compute": opID := strings.TrimSpace(op.GetOperationId()) if opID == "" { opID = strings.TrimSpace(op.GetIdempotencyKey()) } resp, err := s.ComputeGasTopUp(ctx, &chainv1.ComputeGasTopUpRequest{ WalletRef: source, EstimatedTotalFee: fee, }) if err != nil { return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil } return &connectorv1.SubmitOperationResponse{ Receipt: &connectorv1.OperationReceipt{ OperationId: opID, Status: connectorv1.OperationStatus_OPERATION_SUCCESS, Result: gasTopUpResult(resp.GetTopupAmount(), resp.GetCapHit(), ""), }, }, nil case "ensure": opID := strings.TrimSpace(op.GetOperationId()) if opID == "" { opID = strings.TrimSpace(op.GetIdempotencyKey()) } if orgRef == "" { return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "gas_topup: organization_ref is required", op, "")}}, nil } target := strings.TrimSpace(reader.String("target_wallet_ref")) if target == "" { return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "gas_topup: target_wallet_ref is required", op, "")}}, nil } 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")), PaymentRef: strings.TrimSpace(reader.String("payment_ref")), }) if err != nil { return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil } transferRef := "" if transfer := resp.GetTransfer(); transfer != nil { transferRef = strings.TrimSpace(transfer.GetTransferRef()) } return &connectorv1.SubmitOperationResponse{ Receipt: &connectorv1.OperationReceipt{ OperationId: opID, Status: shared.ChainTransferStatusToOperation(resp.GetTransfer().GetStatus()), Result: gasTopUpResult(resp.GetTopupAmount(), resp.GetCapHit(), transferRef), }, }, nil default: return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "gas_topup: invalid mode", op, "")}}, nil } default: return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_UNSUPPORTED_OPERATION, "submit_operation: unsupported operation type", op, "")}}, nil } } func (s *Service) GetOperation(ctx context.Context, req *connectorv1.GetOperationRequest) (*connectorv1.GetOperationResponse, error) { if req == nil || strings.TrimSpace(req.GetOperationId()) == "" { return nil, merrors.InvalidArgument("get_operation: operation_id is required") } resp, err := s.GetTransfer(ctx, &chainv1.GetTransferRequest{TransferRef: strings.TrimSpace(req.GetOperationId())}) if err != nil { return nil, err } return &connectorv1.GetOperationResponse{Operation: chainTransferToOperation(resp.GetTransfer())}, nil } func (s *Service) ListOperations(ctx context.Context, req *connectorv1.ListOperationsRequest) (*connectorv1.ListOperationsResponse, error) { if req == nil { return nil, merrors.InvalidArgument("list_operations: request is required") } source := "" if req.GetAccountRef() != nil { source = strings.TrimSpace(req.GetAccountRef().GetAccountId()) } resp, err := s.ListTransfers(ctx, &chainv1.ListTransfersRequest{ SourceWalletRef: source, Status: chainStatusFromOperation(req.GetStatus()), Page: req.GetPage(), }) if err != nil { return nil, err } ops := make([]*connectorv1.Operation, 0, len(resp.GetTransfers())) for _, transfer := range resp.GetTransfers() { ops = append(ops, chainTransferToOperation(transfer)) } return &connectorv1.ListOperationsResponse{Operations: ops, Page: resp.GetPage()}, nil } func chainOpenAccountParams() []*connectorv1.ParamSpec { return []*connectorv1.ParamSpec{ {Key: "organization_ref", Type: connectorv1.ParamType_STRING, Required: true, Description: "Organization reference for the wallet."}, {Key: "network", Type: connectorv1.ParamType_STRING, Required: true, Description: "Blockchain network name."}, {Key: "token_symbol", Type: connectorv1.ParamType_STRING, Required: true, Description: "Token symbol (e.g., USDT)."}, {Key: "contract_address", Type: connectorv1.ParamType_STRING, Required: false, Description: "Token contract address override."}, {Key: "metadata", Type: connectorv1.ParamType_JSON, Required: false, Description: "Additional metadata map."}, {Key: "description", Type: connectorv1.ParamType_STRING, Required: false, Description: "Wallet description."}, } } func chainOperationParams() []*connectorv1.OperationParamSpec { return []*connectorv1.OperationParamSpec{ {OperationType: connectorv1.OperationType_TRANSFER, Params: []*connectorv1.ParamSpec{ {Key: "organization_ref", Type: connectorv1.ParamType_STRING, Required: true, Description: "Organization reference."}, {Key: "destination_memo", Type: connectorv1.ParamType_STRING, Required: false, Description: "Destination memo/tag."}, {Key: "client_reference", Type: connectorv1.ParamType_STRING, Required: false, Description: "Client reference id."}, {Key: "metadata", Type: connectorv1.ParamType_JSON, Required: false, Description: "Transfer metadata."}, {Key: "fees", Type: connectorv1.ParamType_JSON, Required: false, Description: "Service fee breakdowns."}, }}, {OperationType: connectorv1.OperationType_FEE_ESTIMATE, Params: []*connectorv1.ParamSpec{ {Key: "organization_ref", Type: connectorv1.ParamType_STRING, Required: false, Description: "Organization reference."}, {Key: "metadata", Type: connectorv1.ParamType_JSON, Required: false, Description: "Estimate metadata."}, }}, {OperationType: connectorv1.OperationType_GAS_TOPUP, Params: []*connectorv1.ParamSpec{ {Key: "mode", Type: connectorv1.ParamType_STRING, Required: false, Description: "compute | ensure."}, {Key: "organization_ref", Type: connectorv1.ParamType_STRING, Required: false, Description: "Organization reference (required for ensure)."}, {Key: "target_wallet_ref", Type: connectorv1.ParamType_STRING, Required: false, Description: "Target wallet ref (ensure)."}, {Key: "estimated_total_fee", Type: connectorv1.ParamType_JSON, Required: true, Description: "Estimated total fee {amount,currency}."}, {Key: "client_reference", Type: connectorv1.ParamType_STRING, Required: false, Description: "Client reference."}, {Key: "metadata", Type: connectorv1.ParamType_JSON, Required: false, Description: "Top-up metadata."}, }}, } } func chainWalletToAccount(wallet *chainv1.ManagedWallet) *connectorv1.Account { if wallet == nil { return nil } details, _ := structpb.NewStruct(map[string]interface{}{ "deposit_address": wallet.GetDepositAddress(), "organization_ref": wallet.GetOrganizationRef(), "owner_ref": wallet.GetOwnerRef(), "network": wallet.GetAsset().GetChain().String(), "token_symbol": wallet.GetAsset().GetTokenSymbol(), "contract_address": wallet.GetAsset().GetContractAddress(), "wallet_ref": wallet.GetWalletRef(), }) return &connectorv1.Account{ Ref: &connectorv1.AccountRef{ ConnectorId: chainConnectorID, AccountId: strings.TrimSpace(wallet.GetWalletRef()), }, Kind: connectorv1.AccountKind_CHAIN_MANAGED_WALLET, Asset: chainasset.AssetString(wallet.GetAsset()), State: chainWalletState(wallet.GetStatus()), Label: strings.TrimSpace(wallet.GetDescribable().GetName()), OwnerRef: strings.TrimSpace(wallet.GetOwnerRef()), ProviderDetails: details, CreatedAt: wallet.GetCreatedAt(), UpdatedAt: wallet.GetUpdatedAt(), Describable: wallet.GetDescribable(), } } func chainWalletState(status chainv1.ManagedWalletStatus) connectorv1.AccountState { switch status { case chainv1.ManagedWalletStatus_MANAGED_WALLET_ACTIVE: return connectorv1.AccountState_ACCOUNT_ACTIVE case chainv1.ManagedWalletStatus_MANAGED_WALLET_SUSPENDED: return connectorv1.AccountState_ACCOUNT_SUSPENDED case chainv1.ManagedWalletStatus_MANAGED_WALLET_CLOSED: return connectorv1.AccountState_ACCOUNT_CLOSED default: return connectorv1.AccountState_ACCOUNT_STATE_UNSPECIFIED } } func transferDestinationFromOperation(op *connectorv1.Operation) (*chainv1.TransferDestination, error) { if op == nil { return nil, merrors.InvalidArgument("transfer: operation is required") } if to := op.GetTo(); to != nil { if account := to.GetAccount(); account != nil { return &chainv1.TransferDestination{Destination: &chainv1.TransferDestination_ManagedWalletRef{ManagedWalletRef: strings.TrimSpace(account.GetAccountId())}}, nil } if ext := to.GetExternal(); ext != nil { return &chainv1.TransferDestination{Destination: &chainv1.TransferDestination_ExternalAddress{ExternalAddress: strings.TrimSpace(ext.GetExternalRef())}}, nil } } return nil, merrors.InvalidArgument("transfer: to.account or to.external is required") } func normalizeMoneyForChain(m *moneyv1.Money) *moneyv1.Money { if m == nil { return nil } currency := strings.TrimSpace(m.GetCurrency()) if idx := strings.Index(currency, "-"); idx > 0 { currency = currency[:idx] } return &moneyv1.Money{ Amount: strings.TrimSpace(m.GetAmount()), Currency: currency, } } func parseChainFees(reader params.Reader) []*chainv1.ServiceFeeBreakdown { rawFees := reader.List("fees") if len(rawFees) == 0 { return nil } result := make([]*chainv1.ServiceFeeBreakdown, 0, len(rawFees)) for _, item := range rawFees { raw, ok := item.(map[string]interface{}) if !ok { continue } amount := strings.TrimSpace(fmt.Sprint(raw["amount"])) currency := strings.TrimSpace(fmt.Sprint(raw["currency"])) if amount == "" || currency == "" { continue } result = append(result, &chainv1.ServiceFeeBreakdown{ FeeCode: strings.TrimSpace(fmt.Sprint(raw["fee_code"])), Description: strings.TrimSpace(fmt.Sprint(raw["description"])), Amount: &moneyv1.Money{Amount: amount, Currency: currency}, }) } if len(result) == 0 { return nil } return result } func parseMoneyFromMap(raw map[string]interface{}) (*moneyv1.Money, error) { if raw == nil { return nil, merrors.InvalidArgument("money is required") } amount := strings.TrimSpace(fmt.Sprint(raw["amount"])) currency := strings.TrimSpace(fmt.Sprint(raw["currency"])) if amount == "" || currency == "" { return nil, merrors.InvalidArgument("money is required") } return &moneyv1.Money{ Amount: amount, Currency: currency, }, nil } func feeEstimateResult(resp *chainv1.EstimateTransferFeeResponse) *structpb.Struct { if resp == nil { return nil } payload := map[string]interface{}{ "estimation_context": strings.TrimSpace(resp.GetEstimationContext()), } if fee := resp.GetNetworkFee(); fee != nil { payload["network_fee"] = map[string]interface{}{ "amount": strings.TrimSpace(fee.GetAmount()), "currency": strings.TrimSpace(fee.GetCurrency()), } } result, err := structpb.NewStruct(payload) if err != nil { return nil } return result } func gasTopUpResult(amount *moneyv1.Money, capHit bool, transferRef string) *structpb.Struct { payload := map[string]interface{}{ "cap_hit": capHit, } if amount != nil { payload["topup_amount"] = map[string]interface{}{ "amount": strings.TrimSpace(amount.GetAmount()), "currency": strings.TrimSpace(amount.GetCurrency()), } } if strings.TrimSpace(transferRef) != "" { payload["transfer_ref"] = strings.TrimSpace(transferRef) } result, err := structpb.NewStruct(payload) if err != nil { return nil } return result } func chainTransferToOperation(transfer *chainv1.Transfer) *connectorv1.Operation { if transfer == nil { return nil } op := &connectorv1.Operation{ OperationId: strings.TrimSpace(transfer.GetTransferRef()), Type: connectorv1.OperationType_TRANSFER, Status: chainTransferStatusToOperation(transfer.GetStatus()), Money: transfer.GetRequestedAmount(), ProviderRef: strings.TrimSpace(transfer.GetTransactionHash()), CreatedAt: transfer.GetCreatedAt(), UpdatedAt: transfer.GetUpdatedAt(), From: &connectorv1.OperationParty{Ref: &connectorv1.OperationParty_Account{Account: &connectorv1.AccountRef{ ConnectorId: chainConnectorID, AccountId: strings.TrimSpace(transfer.GetSourceWalletRef()), }}}, } if dest := transfer.GetDestination(); dest != nil { switch d := dest.GetDestination().(type) { case *chainv1.TransferDestination_ManagedWalletRef: op.To = &connectorv1.OperationParty{Ref: &connectorv1.OperationParty_Account{Account: &connectorv1.AccountRef{ ConnectorId: chainConnectorID, AccountId: strings.TrimSpace(d.ManagedWalletRef), }}} case *chainv1.TransferDestination_ExternalAddress: op.To = &connectorv1.OperationParty{Ref: &connectorv1.OperationParty_External{External: &connectorv1.ExternalRef{ ExternalRef: strings.TrimSpace(d.ExternalAddress), }}} } } return op } 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 } } func chainStatusFromOperation(status connectorv1.OperationStatus) chainv1.TransferStatus { switch status { 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_OPERATION_CANCELLED: return chainv1.TransferStatus_TRANSFER_CANCELLED default: return chainv1.TransferStatus_TRANSFER_STATUS_UNSPECIFIED } } func parseChainAsset(assetString string, reader params.Reader) (*chainv1.Asset, error) { return chainasset.ParseAsset( assetString, reader.String("network"), reader.String("token_symbol"), reader.String("contract_address"), ) } func describableFromLabel(label, desc string) *describablev1.Describable { label = strings.TrimSpace(label) desc = strings.TrimSpace(desc) if label == "" && desc == "" { return nil } return &describablev1.Describable{ Name: label, Description: &desc, } } func operationAccountID(party *connectorv1.OperationParty) string { if party == nil { return "" } if account := party.GetAccount(); account != nil { return strings.TrimSpace(account.GetAccountId()) } return "" } func connectorError(code connectorv1.ErrorCode, message string, op *connectorv1.Operation, accountID string) *connectorv1.ConnectorError { err := &connectorv1.ConnectorError{ Code: code, Message: strings.TrimSpace(message), AccountId: strings.TrimSpace(accountID), } if op != nil { err.CorrelationId = strings.TrimSpace(op.GetCorrelationId()) err.ParentIntentId = strings.TrimSpace(op.GetParentIntentId()) err.OperationId = strings.TrimSpace(op.GetOperationId()) } return err } func mapErrorCode(err error) connectorv1.ErrorCode { switch { case errors.Is(err, merrors.ErrInvalidArg): return connectorv1.ErrorCode_INVALID_PARAMS case errors.Is(err, merrors.ErrNoData): return connectorv1.ErrorCode_NOT_FOUND case errors.Is(err, merrors.ErrNotImplemented): return connectorv1.ErrorCode_UNSUPPORTED_OPERATION case errors.Is(err, merrors.ErrInternal): return connectorv1.ErrorCode_TEMPORARY_UNAVAILABLE default: return connectorv1.ErrorCode_PROVIDER_ERROR } }