github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/stellar/format.go (about)

     1  package stellar
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/keybase/client/go/libkb"
     7  	"github.com/keybase/client/go/protocol/stellar1"
     8  	"github.com/keybase/stellarnet"
     9  )
    10  
    11  func FormatCurrency(mctx libkb.MetaContext, amount string, code stellar1.OutsideCurrencyCode, rounding stellarnet.FmtRoundingBehavior) (string, error) {
    12  	conf, err := mctx.G().GetStellar().GetServerDefinitions(mctx.Ctx())
    13  	if err != nil {
    14  		return "", err
    15  	}
    16  	currency, ok := conf.Currencies[code]
    17  	if !ok {
    18  		return "", fmt.Errorf("FormatCurrency error: cannot find curency code %q", code)
    19  	}
    20  
    21  	return stellarnet.FmtCurrency(amount, rounding, currency.Symbol.Symbol, currency.Symbol.Postfix)
    22  }
    23  
    24  // FormatCurrencyWithCodeSuffix will return a fiat currency amount formatted with
    25  // its currency code suffix at the end, like "$123.12 CLP"
    26  func FormatCurrencyWithCodeSuffix(mctx libkb.MetaContext, amount string, code stellar1.OutsideCurrencyCode, rounding stellarnet.FmtRoundingBehavior) (string, error) {
    27  	conf, err := mctx.G().GetStellar().GetServerDefinitions(mctx.Ctx())
    28  	if err != nil {
    29  		return "", err
    30  	}
    31  	currency, ok := conf.Currencies[code]
    32  	if !ok {
    33  		return "", fmt.Errorf("FormatCurrencyWithCodeSuffix error: cannot find curency code %q", code)
    34  	}
    35  	return stellarnet.FmtCurrencyWithCodeSuffix(amount, rounding, string(code), currency.Symbol.Symbol, currency.Symbol.Postfix)
    36  }
    37  
    38  // Return an error if asset is completely outside of what we understand, like
    39  // asset unknown types or unexpected length.
    40  func assertAssetIsSane(asset stellar1.Asset) error {
    41  	switch asset.Type {
    42  	case "credit_alphanum4", "credit_alphanum12":
    43  	case "alphanum4", "alphanum12": // These prefixes that are missing "credit_" shouldn't show up, but just to be on the safe side.
    44  	default:
    45  		return fmt.Errorf("unrecognized asset type: %v", asset.Type)
    46  	}
    47  	// Sanity check asset code very loosely. We know tighter bounds but there's no need to fail here.
    48  	if len(asset.Code) == 0 || len(asset.Code) >= 20 {
    49  		return fmt.Errorf("invalid asset code: %v", asset.Code)
    50  	}
    51  	return nil
    52  }
    53  
    54  // Example: "157.5000000 XLM"
    55  // Example: "12.9000000 USD"
    56  //
    57  //	(where USD is a non-native asset issued by someone).
    58  //
    59  // User interfaces should be careful to never give user just amount + asset
    60  // code, but annotate when it's a non-native asset and make Issuer ID and
    61  // Verified Domain visible.
    62  // If you are coming from CLI, FormatAmountDescriptionAssetEx might be a better
    63  // choice which is more verbose about non-native assets.
    64  func FormatAmountDescriptionAsset(mctx libkb.MetaContext, amount string, asset stellar1.Asset) (string, error) {
    65  	if asset.IsNativeXLM() {
    66  		return FormatAmountDescriptionXLM(mctx, amount)
    67  	}
    68  	if err := assertAssetIsSane(asset); err != nil {
    69  		return "", err
    70  	}
    71  	// Sanity check asset issuer.
    72  	if _, err := libkb.ParseStellarAccountID(asset.Issuer); err != nil {
    73  		return "", fmt.Errorf("asset issuer is not account ID: %v", asset.Issuer)
    74  	}
    75  	return FormatAmountWithSuffix(mctx, amount, false /* precisionTwo */, false /* simplify */, asset.Code)
    76  }
    77  
    78  // FormatAmountDescriptionAssetEx is a more verbose version of FormatAmountDescriptionAsset.
    79  // In case of non-native asset, it includes issuer domain (or "Unknown") and issuer ID.
    80  // Example: "157.5000000 XLM"
    81  // Example: "1,000.15 CATS/catmoney.example.com (GDWVJEG7CMYKRYGB2MWSRZNSPCWIGGA4FRNFTQBIR6RAEPNEGGEH4XYZ)"
    82  // Example: "1,000.15 BTC/Unknown (GBPEHURSE52GCBRPDWNV2VL3HRLCI42367OGRPBOO3AW6VAYEW5EO5PM)"
    83  func FormatAmountDescriptionAssetEx(mctx libkb.MetaContext, amount string, asset stellar1.Asset) (string, error) {
    84  	if asset.IsNativeXLM() {
    85  		return FormatAmountDescriptionXLM(mctx, amount)
    86  	}
    87  	if err := assertAssetIsSane(asset); err != nil {
    88  		return "", err
    89  	}
    90  	// Sanity check asset issuer.
    91  	issuerAccountID, err := libkb.ParseStellarAccountID(asset.Issuer)
    92  	if err != nil {
    93  		return "", fmt.Errorf("asset issuer is not account ID: %v", asset.Issuer)
    94  	}
    95  	amountFormatted, err := FormatAmount(mctx, amount, false /* precisionTwo */, stellarnet.Round)
    96  	if err != nil {
    97  		return "", err
    98  	}
    99  	var issuerDesc string
   100  	if asset.VerifiedDomain != "" {
   101  		issuerDesc = asset.VerifiedDomain
   102  	} else {
   103  		issuerDesc = "Unknown"
   104  	}
   105  	return fmt.Sprintf("%s %s/%s (%s)", amountFormatted, asset.Code, issuerDesc, issuerAccountID.String()), nil
   106  }
   107  
   108  // FormatAmountDescriptionAssetEx2 is like FormatAmountDescriptionAssetEx,
   109  // except that it only shows one of issuer domain and issuer account ID. When
   110  // issuer domain is available, the domain is shown. Otherwise account ID is
   111  // used.
   112  // Example: "157.5000000 XLM"
   113  // Example: "1,000.15 CATS/catmoney.example.com
   114  // Example: "1,000.15 BTC/GBPEHURSE52GCBRPDWNV2VL3HRLCI42367OGRPBOO3AW6VAYEW5EO5PM"
   115  func FormatAmountDescriptionAssetEx2(mctx libkb.MetaContext, amount string, asset stellar1.Asset) (string, error) {
   116  	if asset.IsNativeXLM() {
   117  		return FormatAmountDescriptionXLM(mctx, amount)
   118  	}
   119  	if err := assertAssetIsSane(asset); err != nil {
   120  		return "", err
   121  	}
   122  	// Sanity check asset issuer.
   123  	issuerAccountID, err := libkb.ParseStellarAccountID(asset.Issuer)
   124  	if err != nil {
   125  		return "", fmt.Errorf("asset issuer is not account ID: %v", asset.Issuer)
   126  	}
   127  	amountFormatted, err := FormatAmount(mctx, amount, false /* precisionTwo */, stellarnet.Round)
   128  	if err != nil {
   129  		return "", err
   130  	}
   131  	var issuerDesc string
   132  	if asset.VerifiedDomain != "" {
   133  		issuerDesc = asset.VerifiedDomain
   134  	} else {
   135  		issuerDesc = issuerAccountID.String()
   136  	}
   137  	return fmt.Sprintf("%s %s/%s", amountFormatted, asset.Code, issuerDesc), nil
   138  }
   139  
   140  // FormatAssetIssuerString returns "Unknown issuer" if asset does not have a
   141  // verified domain, or returns asset verified domain if it does (e.g.
   142  // "example.com").
   143  func FormatAssetIssuerString(asset stellar1.Asset) string {
   144  	if asset.VerifiedDomain != "" {
   145  		return asset.VerifiedDomain
   146  	}
   147  	iaid := asset.IssuerString()
   148  	iaidLen := len(iaid)
   149  	switch {
   150  	case iaidLen > 16:
   151  		return iaid[:8] + "..." + iaid[iaidLen-8:]
   152  	case iaidLen > 0:
   153  		return iaid
   154  	default:
   155  		return "Unknown issuer"
   156  	}
   157  }
   158  
   159  // Example: "157.5000000 XLM"
   160  func FormatAmountDescriptionXLM(mctx libkb.MetaContext, amount string) (string, error) {
   161  	// Do not simplify XLM amounts, all zeroes are important because
   162  	// that's the exact number of digits that Stellar protocol
   163  	// supports.
   164  	return FormatAmountWithSuffix(mctx, amount, false /* precisionTwo */, false /* simplify */, "XLM")
   165  }
   166  
   167  func FormatAmountWithSuffix(mctx libkb.MetaContext, amount string, precisionTwo bool, simplify bool, suffix string) (string, error) {
   168  	formatted, err := FormatAmount(mctx, amount, precisionTwo, stellarnet.Round)
   169  	if err != nil {
   170  		return "", err
   171  	}
   172  	if simplify {
   173  		formatted = libkb.StellarSimplifyAmount(formatted)
   174  	}
   175  	return fmt.Sprintf("%s %s", formatted, suffix), nil
   176  }
   177  
   178  func FormatAmount(mctx libkb.MetaContext, amount string, precisionTwo bool, rounding stellarnet.FmtRoundingBehavior) (string, error) {
   179  	if amount == "" {
   180  		EmptyAmountStack(mctx)
   181  		return "", fmt.Errorf("empty amount")
   182  	}
   183  	return stellarnet.FmtAmount(amount, precisionTwo, rounding)
   184  }