540 lines
17 KiB
Go
540 lines
17 KiB
Go
package quotation
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/tech/sendico/payments/storage/model"
|
|
chainasset "github.com/tech/sendico/pkg/chain"
|
|
payecon "github.com/tech/sendico/pkg/payments/economics"
|
|
paymenttypes "github.com/tech/sendico/pkg/payments/types"
|
|
feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1"
|
|
fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1"
|
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
|
paymentv1 "github.com/tech/sendico/pkg/proto/common/payment/v1"
|
|
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
|
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
|
|
sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1"
|
|
)
|
|
|
|
func intentFromProto(src *sharedv1.PaymentIntent) model.PaymentIntent {
|
|
if src == nil {
|
|
return model.PaymentIntent{}
|
|
}
|
|
attrs := cloneMetadata(src.GetAttributes())
|
|
feeTreatment := feeTreatmentFromAttributes(attrs)
|
|
delete(attrs, "fee_treatment")
|
|
delete(attrs, "feeTreatment")
|
|
delete(attrs, "settlement_mode")
|
|
delete(attrs, "settlementMode")
|
|
|
|
settlementCurrency := derivedSettlementCurrencyFromProtoIntent(src)
|
|
requiresFX := derivedRequiresFXFromProtoIntent(src, settlementCurrency)
|
|
|
|
intent := model.PaymentIntent{
|
|
Ref: src.GetRef(),
|
|
Kind: modelKindFromProto(src.GetKind()),
|
|
Source: endpointFromProto(src.GetSource()),
|
|
Destination: endpointFromProto(src.GetDestination()),
|
|
Amount: moneyFromProto(src.GetAmount()),
|
|
RequiresFX: requiresFX,
|
|
FeePolicy: feePolicyFromProto(src.GetFeePolicy()),
|
|
SettlementMode: settlementModeFromProto(src.GetSettlementMode()),
|
|
FeeTreatment: feeTreatment,
|
|
SettlementCurrency: settlementCurrency,
|
|
Attributes: attrs,
|
|
Customer: customerFromProto(src.GetCustomer()),
|
|
}
|
|
if src.GetFx() != nil {
|
|
intent.FX = fxIntentFromProto(src.GetFx())
|
|
}
|
|
return intent
|
|
}
|
|
|
|
func endpointFromProto(src *sharedv1.PaymentEndpoint) model.PaymentEndpoint {
|
|
if src == nil {
|
|
return model.PaymentEndpoint{Type: model.EndpointTypeUnspecified}
|
|
}
|
|
result := model.PaymentEndpoint{
|
|
Type: model.EndpointTypeUnspecified,
|
|
InstanceID: strings.TrimSpace(src.GetInstanceId()),
|
|
Metadata: cloneMetadata(src.GetMetadata()),
|
|
}
|
|
if ledger := src.GetLedger(); ledger != nil {
|
|
result.Type = model.EndpointTypeLedger
|
|
result.Ledger = &model.LedgerEndpoint{
|
|
LedgerAccountRef: strings.TrimSpace(ledger.GetLedgerAccountRef()),
|
|
ContraLedgerAccountRef: strings.TrimSpace(ledger.GetContraLedgerAccountRef()),
|
|
}
|
|
return result
|
|
}
|
|
if managed := src.GetManagedWallet(); managed != nil {
|
|
result.Type = model.EndpointTypeManagedWallet
|
|
result.ManagedWallet = &model.ManagedWalletEndpoint{
|
|
ManagedWalletRef: strings.TrimSpace(managed.GetManagedWalletRef()),
|
|
Asset: assetFromProto(managed.GetAsset()),
|
|
}
|
|
return result
|
|
}
|
|
if external := src.GetExternalChain(); external != nil {
|
|
result.Type = model.EndpointTypeExternalChain
|
|
result.ExternalChain = &model.ExternalChainEndpoint{
|
|
Asset: assetFromProto(external.GetAsset()),
|
|
Address: strings.TrimSpace(external.GetAddress()),
|
|
Memo: strings.TrimSpace(external.GetMemo()),
|
|
}
|
|
return result
|
|
}
|
|
if card := src.GetCard(); card != nil {
|
|
result.Type = model.EndpointTypeCard
|
|
result.Card = &model.CardEndpoint{
|
|
Pan: strings.TrimSpace(card.GetPan()),
|
|
Token: strings.TrimSpace(card.GetToken()),
|
|
Cardholder: strings.TrimSpace(card.GetCardholderName()),
|
|
CardholderSurname: strings.TrimSpace(card.GetCardholderSurname()),
|
|
ExpMonth: card.GetExpMonth(),
|
|
ExpYear: card.GetExpYear(),
|
|
Country: strings.TrimSpace(card.GetCountry()),
|
|
MaskedPan: strings.TrimSpace(card.GetMaskedPan()),
|
|
}
|
|
return result
|
|
}
|
|
return result
|
|
}
|
|
|
|
func fxIntentFromProto(src *sharedv1.FXIntent) *model.FXIntent {
|
|
if src == nil {
|
|
return nil
|
|
}
|
|
return &model.FXIntent{
|
|
Pair: pairFromProto(src.GetPair()),
|
|
Side: fxSideFromProto(src.GetSide()),
|
|
Firm: src.GetFirm(),
|
|
TTLMillis: src.GetTtlMs(),
|
|
PreferredProvider: strings.TrimSpace(src.GetPreferredProvider()),
|
|
MaxAgeMillis: src.GetMaxAgeMs(),
|
|
}
|
|
}
|
|
|
|
func protoIntentFromModel(src model.PaymentIntent) *sharedv1.PaymentIntent {
|
|
attrs := cloneMetadata(src.Attributes)
|
|
if attrs == nil {
|
|
attrs = map[string]string{}
|
|
}
|
|
if feeTreatment := strings.TrimSpace(string(src.FeeTreatment)); feeTreatment != "" && feeTreatment != string(model.FeeTreatmentUnspecified) {
|
|
attrs["fee_treatment"] = feeTreatment
|
|
}
|
|
if len(attrs) == 0 {
|
|
attrs = nil
|
|
}
|
|
|
|
settlementCurrency := strings.TrimSpace(src.SettlementCurrency)
|
|
if settlementCurrency == "" {
|
|
settlementCurrency = derivedSettlementCurrencyFromModelIntent(src)
|
|
}
|
|
|
|
intent := &sharedv1.PaymentIntent{
|
|
Ref: src.Ref,
|
|
Kind: protoKindFromModel(src.Kind),
|
|
Source: protoEndpointFromModel(src.Source),
|
|
Destination: protoEndpointFromModel(src.Destination),
|
|
Amount: protoMoney(src.Amount),
|
|
RequiresFx: derivedRequiresFXFromModelIntent(src, settlementCurrency),
|
|
FeePolicy: feePolicyToProto(src.FeePolicy),
|
|
SettlementMode: settlementModeToProto(src.SettlementMode),
|
|
SettlementCurrency: settlementCurrency,
|
|
Attributes: attrs,
|
|
Customer: protoCustomerFromModel(src.Customer),
|
|
}
|
|
if src.FX != nil {
|
|
intent.Fx = protoFXIntentFromModel(src.FX)
|
|
}
|
|
return intent
|
|
}
|
|
|
|
func feeTreatmentFromAttributes(attrs map[string]string) model.FeeTreatment {
|
|
if len(attrs) == 0 {
|
|
return model.FeeTreatmentAddToSource
|
|
}
|
|
keys := []string{"fee_treatment", "feeTreatment"}
|
|
for _, key := range keys {
|
|
if value := strings.TrimSpace(attrs[key]); value != "" {
|
|
switch payecon.ResolveFeeTreatmentFromStringOrDefault(value) {
|
|
case quotationv2.FeeTreatment_FEE_TREATMENT_DEDUCT_FROM_DESTINATION:
|
|
return model.FeeTreatmentDeductFromDestination
|
|
default:
|
|
return model.FeeTreatmentAddToSource
|
|
}
|
|
}
|
|
}
|
|
return model.FeeTreatmentAddToSource
|
|
}
|
|
|
|
func derivedSettlementCurrencyFromProtoIntent(src *sharedv1.PaymentIntent) string {
|
|
if src == nil {
|
|
return ""
|
|
}
|
|
if fx := src.GetFx(); fx != nil && fx.GetPair() != nil {
|
|
if currency := settlementCurrencyFromPair(fx.GetPair(), fx.GetSide()); currency != "" {
|
|
return currency
|
|
}
|
|
}
|
|
if amount := src.GetAmount(); amount != nil {
|
|
if currency := strings.ToUpper(strings.TrimSpace(amount.GetCurrency())); currency != "" {
|
|
return currency
|
|
}
|
|
}
|
|
return strings.ToUpper(strings.TrimSpace(src.GetSettlementCurrency()))
|
|
}
|
|
|
|
func derivedSettlementCurrencyFromModelIntent(src model.PaymentIntent) string {
|
|
if src.FX != nil && src.FX.Pair != nil {
|
|
pair := &fxv1.CurrencyPair{
|
|
Base: strings.TrimSpace(src.FX.Pair.Base),
|
|
Quote: strings.TrimSpace(src.FX.Pair.Quote),
|
|
}
|
|
if currency := settlementCurrencyFromPair(pair, fxSideToProto(src.FX.Side)); currency != "" {
|
|
return currency
|
|
}
|
|
}
|
|
if src.Amount != nil {
|
|
if currency := strings.ToUpper(strings.TrimSpace(src.Amount.Currency)); currency != "" {
|
|
return currency
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func settlementCurrencyFromPair(pair *fxv1.CurrencyPair, side fxv1.Side) string {
|
|
if pair == nil {
|
|
return ""
|
|
}
|
|
base := strings.ToUpper(strings.TrimSpace(pair.GetBase()))
|
|
quote := strings.ToUpper(strings.TrimSpace(pair.GetQuote()))
|
|
switch side {
|
|
case fxv1.Side_BUY_BASE_SELL_QUOTE:
|
|
if base != "" {
|
|
return base
|
|
}
|
|
case fxv1.Side_SELL_BASE_BUY_QUOTE, fxv1.Side_SIDE_UNSPECIFIED:
|
|
if quote != "" {
|
|
return quote
|
|
}
|
|
}
|
|
if quote != "" {
|
|
return quote
|
|
}
|
|
return base
|
|
}
|
|
|
|
func derivedRequiresFXFromProtoIntent(src *sharedv1.PaymentIntent, settlementCurrency string) bool {
|
|
if src == nil {
|
|
return false
|
|
}
|
|
if fx := src.GetFx(); fx != nil && fx.GetPair() != nil {
|
|
return true
|
|
}
|
|
amount := src.GetAmount()
|
|
if amount == nil {
|
|
return false
|
|
}
|
|
amountCurrency := strings.ToUpper(strings.TrimSpace(amount.GetCurrency()))
|
|
settlementCurrency = strings.ToUpper(strings.TrimSpace(settlementCurrency))
|
|
return amountCurrency != "" && settlementCurrency != "" && !strings.EqualFold(amountCurrency, settlementCurrency)
|
|
}
|
|
|
|
func derivedRequiresFXFromModelIntent(src model.PaymentIntent, settlementCurrency string) bool {
|
|
if src.FX != nil && src.FX.Pair != nil {
|
|
return true
|
|
}
|
|
if src.Amount == nil {
|
|
return false
|
|
}
|
|
amountCurrency := strings.ToUpper(strings.TrimSpace(src.Amount.Currency))
|
|
settlementCurrency = strings.ToUpper(strings.TrimSpace(settlementCurrency))
|
|
return amountCurrency != "" && settlementCurrency != "" && !strings.EqualFold(amountCurrency, settlementCurrency)
|
|
}
|
|
|
|
func customerFromProto(src *sharedv1.Customer) *model.Customer {
|
|
if src == nil {
|
|
return nil
|
|
}
|
|
return &model.Customer{
|
|
ID: strings.TrimSpace(src.GetId()),
|
|
FirstName: strings.TrimSpace(src.GetFirstName()),
|
|
MiddleName: strings.TrimSpace(src.GetMiddleName()),
|
|
LastName: strings.TrimSpace(src.GetLastName()),
|
|
IP: strings.TrimSpace(src.GetIp()),
|
|
Zip: strings.TrimSpace(src.GetZip()),
|
|
Country: strings.TrimSpace(src.GetCountry()),
|
|
State: strings.TrimSpace(src.GetState()),
|
|
City: strings.TrimSpace(src.GetCity()),
|
|
Address: strings.TrimSpace(src.GetAddress()),
|
|
}
|
|
}
|
|
|
|
func protoCustomerFromModel(src *model.Customer) *sharedv1.Customer {
|
|
if src == nil {
|
|
return nil
|
|
}
|
|
return &sharedv1.Customer{
|
|
Id: strings.TrimSpace(src.ID),
|
|
FirstName: strings.TrimSpace(src.FirstName),
|
|
MiddleName: strings.TrimSpace(src.MiddleName),
|
|
LastName: strings.TrimSpace(src.LastName),
|
|
Ip: strings.TrimSpace(src.IP),
|
|
Zip: strings.TrimSpace(src.Zip),
|
|
Country: strings.TrimSpace(src.Country),
|
|
State: strings.TrimSpace(src.State),
|
|
City: strings.TrimSpace(src.City),
|
|
Address: strings.TrimSpace(src.Address),
|
|
}
|
|
}
|
|
|
|
func protoEndpointFromModel(src model.PaymentEndpoint) *sharedv1.PaymentEndpoint {
|
|
endpoint := &sharedv1.PaymentEndpoint{
|
|
Metadata: cloneMetadata(src.Metadata),
|
|
InstanceId: strings.TrimSpace(src.InstanceID),
|
|
}
|
|
switch src.Type {
|
|
case model.EndpointTypeLedger:
|
|
if src.Ledger != nil {
|
|
endpoint.Endpoint = &sharedv1.PaymentEndpoint_Ledger{
|
|
Ledger: &sharedv1.LedgerEndpoint{
|
|
LedgerAccountRef: src.Ledger.LedgerAccountRef,
|
|
ContraLedgerAccountRef: src.Ledger.ContraLedgerAccountRef,
|
|
},
|
|
}
|
|
}
|
|
case model.EndpointTypeManagedWallet:
|
|
if src.ManagedWallet != nil {
|
|
endpoint.Endpoint = &sharedv1.PaymentEndpoint_ManagedWallet{
|
|
ManagedWallet: &sharedv1.ManagedWalletEndpoint{
|
|
ManagedWalletRef: src.ManagedWallet.ManagedWalletRef,
|
|
Asset: assetToProto(src.ManagedWallet.Asset),
|
|
},
|
|
}
|
|
}
|
|
case model.EndpointTypeExternalChain:
|
|
if src.ExternalChain != nil {
|
|
endpoint.Endpoint = &sharedv1.PaymentEndpoint_ExternalChain{
|
|
ExternalChain: &sharedv1.ExternalChainEndpoint{
|
|
Asset: assetToProto(src.ExternalChain.Asset),
|
|
Address: src.ExternalChain.Address,
|
|
Memo: src.ExternalChain.Memo,
|
|
},
|
|
}
|
|
}
|
|
case model.EndpointTypeCard:
|
|
if src.Card != nil {
|
|
card := &sharedv1.CardEndpoint{
|
|
CardholderName: src.Card.Cardholder,
|
|
CardholderSurname: src.Card.CardholderSurname,
|
|
ExpMonth: src.Card.ExpMonth,
|
|
ExpYear: src.Card.ExpYear,
|
|
Country: src.Card.Country,
|
|
MaskedPan: src.Card.MaskedPan,
|
|
}
|
|
if pan := strings.TrimSpace(src.Card.Pan); pan != "" {
|
|
card.Card = &sharedv1.CardEndpoint_Pan{Pan: pan}
|
|
}
|
|
if token := strings.TrimSpace(src.Card.Token); token != "" {
|
|
card.Card = &sharedv1.CardEndpoint_Token{Token: token}
|
|
}
|
|
endpoint.Endpoint = &sharedv1.PaymentEndpoint_Card{Card: card}
|
|
}
|
|
default:
|
|
// leave unspecified
|
|
}
|
|
return endpoint
|
|
}
|
|
|
|
func protoFXIntentFromModel(src *model.FXIntent) *sharedv1.FXIntent {
|
|
if src == nil {
|
|
return nil
|
|
}
|
|
return &sharedv1.FXIntent{
|
|
Pair: pairToProto(src.Pair),
|
|
Side: fxSideToProto(src.Side),
|
|
Firm: src.Firm,
|
|
TtlMs: src.TTLMillis,
|
|
PreferredProvider: src.PreferredProvider,
|
|
MaxAgeMs: src.MaxAgeMillis,
|
|
}
|
|
}
|
|
|
|
func protoKindFromModel(kind model.PaymentKind) sharedv1.PaymentKind {
|
|
switch kind {
|
|
case model.PaymentKindPayout:
|
|
return sharedv1.PaymentKind_PAYMENT_KIND_PAYOUT
|
|
case model.PaymentKindInternalTransfer:
|
|
return sharedv1.PaymentKind_PAYMENT_KIND_INTERNAL_TRANSFER
|
|
case model.PaymentKindFXConversion:
|
|
return sharedv1.PaymentKind_PAYMENT_KIND_FX_CONVERSION
|
|
default:
|
|
return sharedv1.PaymentKind_PAYMENT_KIND_UNSPECIFIED
|
|
}
|
|
}
|
|
|
|
func modelKindFromProto(kind sharedv1.PaymentKind) model.PaymentKind {
|
|
switch kind {
|
|
case sharedv1.PaymentKind_PAYMENT_KIND_PAYOUT:
|
|
return model.PaymentKindPayout
|
|
case sharedv1.PaymentKind_PAYMENT_KIND_INTERNAL_TRANSFER:
|
|
return model.PaymentKindInternalTransfer
|
|
case sharedv1.PaymentKind_PAYMENT_KIND_FX_CONVERSION:
|
|
return model.PaymentKindFXConversion
|
|
default:
|
|
return model.PaymentKindUnspecified
|
|
}
|
|
}
|
|
|
|
func settlementModeFromProto(mode paymentv1.SettlementMode) model.SettlementMode {
|
|
switch mode {
|
|
case paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE:
|
|
return model.SettlementModeFixSource
|
|
case paymentv1.SettlementMode_SETTLEMENT_FIX_RECEIVED:
|
|
return model.SettlementModeFixReceived
|
|
default:
|
|
return model.SettlementModeUnspecified
|
|
}
|
|
}
|
|
|
|
func settlementModeToProto(mode model.SettlementMode) paymentv1.SettlementMode {
|
|
switch mode {
|
|
case model.SettlementModeFixSource:
|
|
return paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE
|
|
case model.SettlementModeFixReceived:
|
|
return paymentv1.SettlementMode_SETTLEMENT_FIX_RECEIVED
|
|
default:
|
|
return paymentv1.SettlementMode_SETTLEMENT_UNSPECIFIED
|
|
}
|
|
}
|
|
|
|
func moneyFromProto(m *moneyv1.Money) *paymenttypes.Money {
|
|
if m == nil {
|
|
return nil
|
|
}
|
|
return &paymenttypes.Money{
|
|
Currency: m.GetCurrency(),
|
|
Amount: m.GetAmount(),
|
|
}
|
|
}
|
|
|
|
func protoMoney(m *paymenttypes.Money) *moneyv1.Money {
|
|
if m == nil {
|
|
return nil
|
|
}
|
|
return &moneyv1.Money{
|
|
Currency: m.GetCurrency(),
|
|
Amount: m.GetAmount(),
|
|
}
|
|
}
|
|
|
|
func feePolicyFromProto(src *feesv1.PolicyOverrides) *paymenttypes.FeePolicy {
|
|
if src == nil {
|
|
return nil
|
|
}
|
|
return &paymenttypes.FeePolicy{
|
|
InsufficientNet: insufficientPolicyFromProto(src.GetInsufficientNet()),
|
|
}
|
|
}
|
|
|
|
func feePolicyToProto(src *paymenttypes.FeePolicy) *feesv1.PolicyOverrides {
|
|
if src == nil {
|
|
return nil
|
|
}
|
|
return &feesv1.PolicyOverrides{
|
|
InsufficientNet: insufficientPolicyToProto(src.InsufficientNet),
|
|
}
|
|
}
|
|
|
|
func insufficientPolicyFromProto(policy feesv1.InsufficientNetPolicy) paymenttypes.InsufficientNetPolicy {
|
|
switch policy {
|
|
case feesv1.InsufficientNetPolicy_BLOCK_POSTING:
|
|
return paymenttypes.InsufficientNetBlockPosting
|
|
case feesv1.InsufficientNetPolicy_SWEEP_ORG_CASH:
|
|
return paymenttypes.InsufficientNetSweepOrgCash
|
|
case feesv1.InsufficientNetPolicy_INVOICE_LATER:
|
|
return paymenttypes.InsufficientNetInvoiceLater
|
|
default:
|
|
return paymenttypes.InsufficientNetUnspecified
|
|
}
|
|
}
|
|
|
|
func insufficientPolicyToProto(policy paymenttypes.InsufficientNetPolicy) feesv1.InsufficientNetPolicy {
|
|
switch policy {
|
|
case paymenttypes.InsufficientNetBlockPosting:
|
|
return feesv1.InsufficientNetPolicy_BLOCK_POSTING
|
|
case paymenttypes.InsufficientNetSweepOrgCash:
|
|
return feesv1.InsufficientNetPolicy_SWEEP_ORG_CASH
|
|
case paymenttypes.InsufficientNetInvoiceLater:
|
|
return feesv1.InsufficientNetPolicy_INVOICE_LATER
|
|
default:
|
|
return feesv1.InsufficientNetPolicy_INSUFFICIENT_NET_UNSPECIFIED
|
|
}
|
|
}
|
|
|
|
func pairFromProto(pair *fxv1.CurrencyPair) *paymenttypes.CurrencyPair {
|
|
if pair == nil {
|
|
return nil
|
|
}
|
|
return &paymenttypes.CurrencyPair{
|
|
Base: pair.GetBase(),
|
|
Quote: pair.GetQuote(),
|
|
}
|
|
}
|
|
|
|
func pairToProto(pair *paymenttypes.CurrencyPair) *fxv1.CurrencyPair {
|
|
if pair == nil {
|
|
return nil
|
|
}
|
|
return &fxv1.CurrencyPair{
|
|
Base: pair.GetBase(),
|
|
Quote: pair.GetQuote(),
|
|
}
|
|
}
|
|
|
|
func fxSideFromProto(side fxv1.Side) paymenttypes.FXSide {
|
|
switch side {
|
|
case fxv1.Side_BUY_BASE_SELL_QUOTE:
|
|
return paymenttypes.FXSideBuyBaseSellQuote
|
|
case fxv1.Side_SELL_BASE_BUY_QUOTE:
|
|
return paymenttypes.FXSideSellBaseBuyQuote
|
|
default:
|
|
return paymenttypes.FXSideUnspecified
|
|
}
|
|
}
|
|
|
|
func fxSideToProto(side paymenttypes.FXSide) fxv1.Side {
|
|
switch side {
|
|
case paymenttypes.FXSideBuyBaseSellQuote:
|
|
return fxv1.Side_BUY_BASE_SELL_QUOTE
|
|
case paymenttypes.FXSideSellBaseBuyQuote:
|
|
return fxv1.Side_SELL_BASE_BUY_QUOTE
|
|
default:
|
|
return fxv1.Side_SIDE_UNSPECIFIED
|
|
}
|
|
}
|
|
|
|
func assetFromProto(asset *chainv1.Asset) *paymenttypes.Asset {
|
|
if asset == nil {
|
|
return nil
|
|
}
|
|
return &paymenttypes.Asset{
|
|
Chain: chainasset.NetworkAlias(asset.GetChain()),
|
|
TokenSymbol: asset.GetTokenSymbol(),
|
|
ContractAddress: asset.GetContractAddress(),
|
|
}
|
|
}
|
|
|
|
func assetToProto(asset *paymenttypes.Asset) *chainv1.Asset {
|
|
if asset == nil {
|
|
return nil
|
|
}
|
|
return &chainv1.Asset{
|
|
Chain: chainasset.NetworkFromString(asset.Chain),
|
|
TokenSymbol: asset.TokenSymbol,
|
|
ContractAddress: asset.ContractAddress,
|
|
}
|
|
}
|