github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/protocol/stellar1/extras.go (about)

     1  package stellar1
     2  
     3  import (
     4  	"encoding/hex"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  )
     9  
    10  const (
    11  	KeybaseTransactionIDLen       = 16
    12  	KeybaseTransactionIDSuffix    = 0x30
    13  	KeybaseTransactionIDSuffixHex = "30"
    14  
    15  	KeybaseRequestIDLen       = 16
    16  	KeybaseRequestIDSuffix    = 0x31
    17  	KeybaseRequestIDSuffixHex = "31"
    18  )
    19  
    20  const (
    21  	PushAutoClaim           = "stellar.autoclaim"
    22  	PushPaymentStatus       = "stellar.payment_status"
    23  	PushPaymentNotification = "stellar.payment_notification"
    24  	PushRequestStatus       = "stellar.request_status"
    25  	PushAccountChange       = "stellar.account_change"
    26  )
    27  
    28  const (
    29  	AirdropQualified   = "qualified"
    30  	AirdropUnqualified = "unqualified"
    31  	AirdropAccepted    = "accepted"
    32  )
    33  
    34  func KeybaseTransactionIDFromString(s string) (KeybaseTransactionID, error) {
    35  	if len(s) != hex.EncodedLen(KeybaseTransactionIDLen) {
    36  		return "", fmt.Errorf("bad KeybaseTransactionID %q: must be %d bytes long", s, KeybaseTransactionIDLen)
    37  	}
    38  	suffix := s[len(s)-2:]
    39  	if suffix != KeybaseTransactionIDSuffixHex {
    40  		return "", fmt.Errorf("bad KeybaseTransactionID %q: must end in 0x%x", s, KeybaseTransactionIDSuffix)
    41  	}
    42  	return KeybaseTransactionID(s), nil
    43  }
    44  
    45  func (k KeybaseTransactionID) String() string {
    46  	return string(k)
    47  }
    48  
    49  func (k KeybaseTransactionID) Eq(b KeybaseTransactionID) bool {
    50  	return k == b
    51  }
    52  
    53  func (k KeybaseTransactionID) IsNil() bool {
    54  	return len(k) == 0
    55  }
    56  
    57  func TransactionIDFromPaymentID(p PaymentID) TransactionID {
    58  	return TransactionID(p)
    59  }
    60  
    61  func (t TransactionID) String() string {
    62  	return string(t)
    63  }
    64  
    65  func (t TransactionID) Eq(b TransactionID) bool {
    66  	return t == b
    67  }
    68  
    69  func KeybaseRequestIDFromString(s string) (KeybaseRequestID, error) {
    70  	if len(s) != hex.EncodedLen(KeybaseRequestIDLen) {
    71  		return "", fmt.Errorf("bad KeybaseRequestID %q: must be %d bytes long", s, KeybaseRequestIDLen)
    72  	}
    73  	suffix := s[len(s)-2:]
    74  	if suffix != KeybaseRequestIDSuffixHex {
    75  		return "", fmt.Errorf("bad KeybaseRequestID %q: must end in 0x%x", s, KeybaseRequestIDSuffix)
    76  	}
    77  	return KeybaseRequestID(s), nil
    78  }
    79  
    80  func (k KeybaseRequestID) String() string {
    81  	return string(k)
    82  }
    83  
    84  func (k KeybaseRequestID) Eq(b KeybaseRequestID) bool {
    85  	return k == b
    86  }
    87  
    88  func ToTimeMs(t time.Time) TimeMs {
    89  	// the result of calling UnixNano on the zero Time is undefined.
    90  	// https://golang.org/pkg/time/#Time.UnixNano
    91  	if t.IsZero() {
    92  		return 0
    93  	}
    94  	return TimeMs(t.UnixNano() / 1000000)
    95  }
    96  
    97  func FromTimeMs(t TimeMs) time.Time {
    98  	if t == 0 {
    99  		return time.Time{}
   100  	}
   101  	return time.Unix(0, int64(t)*1000000)
   102  }
   103  
   104  func (t TimeMs) Time() time.Time {
   105  	return FromTimeMs(t)
   106  }
   107  
   108  func (a AccountID) String() string {
   109  	return string(a)
   110  }
   111  
   112  func (a AccountID) Eq(b AccountID) bool {
   113  	return a == b
   114  }
   115  
   116  func (a AccountID) IsNil() bool {
   117  	return len(a) == 0
   118  }
   119  
   120  func (a AccountID) LossyAbbreviation() string {
   121  	if len(a) != 56 {
   122  		return "[invalid account id]"
   123  	}
   124  	return fmt.Sprintf("%v...%v", a[:2], a[len(a)-4:])
   125  }
   126  
   127  func (s SecretKey) String() string {
   128  	return "[secret key redacted]"
   129  }
   130  
   131  func (s SecretKey) SecureNoLogString() string {
   132  	return string(s)
   133  }
   134  
   135  // CheckInvariants checks that the Bundle satisfies
   136  // 1. No duplicate account IDs
   137  // 2. Exactly one primary account
   138  // 3. Non-negative revision numbers
   139  // 4. Account Bundle accountIDs are consistent
   140  // 5. every account in AccountBundles is also in Accounts
   141  func (r Bundle) CheckInvariants() error {
   142  	accountIDs := make(map[AccountID]bool)
   143  	var foundPrimary bool
   144  	for _, entry := range r.Accounts {
   145  		_, found := accountIDs[entry.AccountID]
   146  		if found {
   147  			return fmt.Errorf("duplicate account ID: %v", entry.AccountID)
   148  		}
   149  		accountIDs[entry.AccountID] = true
   150  		if entry.IsPrimary {
   151  			if foundPrimary {
   152  				return errors.New("multiple primary accounts")
   153  			}
   154  			foundPrimary = true
   155  		}
   156  		if entry.Mode == AccountMode_NONE {
   157  			return errors.New("account missing mode")
   158  		}
   159  		if entry.AcctBundleRevision < 1 {
   160  			return fmt.Errorf("account bundle revision %v < 1 for %v", entry.AcctBundleRevision, entry.AccountID)
   161  		}
   162  	}
   163  	if !foundPrimary {
   164  		return errors.New("missing primary account")
   165  	}
   166  	if r.Revision < 1 {
   167  		return fmt.Errorf("revision %v < 1", r.Revision)
   168  	}
   169  	for accID, accBundle := range r.AccountBundles {
   170  		if accID != accBundle.AccountID {
   171  			return fmt.Errorf("account ID mismatch in bundle for %v", accID)
   172  		}
   173  		var AccountBundleInAccounts bool
   174  		for _, accountListAccount := range r.Accounts {
   175  			if accountListAccount.AccountID == accID {
   176  				AccountBundleInAccounts = true
   177  			}
   178  		}
   179  		if !AccountBundleInAccounts {
   180  			return fmt.Errorf("account in AccountBundles not in Accounts %v", accID)
   181  		}
   182  	}
   183  	return nil
   184  }
   185  
   186  func (s Bundle) PrimaryAccount() (BundleEntry, error) {
   187  	for _, entry := range s.Accounts {
   188  		if entry.IsPrimary {
   189  			return entry, nil
   190  		}
   191  	}
   192  	return BundleEntry{}, errors.New("primary stellar account not found")
   193  }
   194  
   195  func (s Bundle) Lookup(acctID AccountID) (BundleEntry, error) {
   196  	for _, entry := range s.Accounts {
   197  		if entry.AccountID == acctID {
   198  			return entry, nil
   199  		}
   200  	}
   201  	return BundleEntry{}, errors.New("stellar account not found")
   202  }
   203  
   204  func (a Asset) SameAsset(b Asset) bool {
   205  	return a.Type == b.Type && a.Code == b.Code && a.Issuer == b.Issuer
   206  }
   207  
   208  func (a *Asset) IsNativeXLM() bool {
   209  	return a.Type == "native"
   210  }
   211  
   212  func (a *Asset) IsEmpty() bool {
   213  	return (a == nil) || (*a == Asset{})
   214  }
   215  
   216  // String returns a display friendly form of the asset, compatible with
   217  // xdr.Asset fomat: type/code/issuer or just "native" if asset is native XLM.
   218  func (a Asset) String() string {
   219  	if a.Type == "native" {
   220  		return a.Type
   221  	}
   222  	return fmt.Sprintf("%s/%s/%s", a.Type, a.Code, a.Issuer)
   223  }
   224  
   225  func AssetNative() Asset {
   226  	return Asset{
   227  		Type:   "native",
   228  		Code:   "",
   229  		Issuer: "",
   230  	}
   231  }
   232  
   233  func CreateNonNativeAssetType(code string) (string, error) {
   234  	if len(code) >= 1 && len(code) <= 4 {
   235  		return "credit_alphanum4", nil
   236  	} else if len(code) >= 5 && len(code) <= 12 {
   237  		return "credit_alphanum12", nil
   238  	} else {
   239  		return "", fmt.Errorf("Invalid asset code: %q", code)
   240  	}
   241  }
   242  
   243  func (t TransactionStatus) ToPaymentStatus() PaymentStatus {
   244  	switch t {
   245  	case TransactionStatus_PENDING:
   246  		return PaymentStatus_PENDING
   247  	case TransactionStatus_SUCCESS:
   248  		return PaymentStatus_COMPLETED
   249  	case TransactionStatus_ERROR_TRANSIENT, TransactionStatus_ERROR_PERMANENT:
   250  		return PaymentStatus_ERROR
   251  	default:
   252  		return PaymentStatus_UNKNOWN
   253  	}
   254  
   255  }
   256  
   257  func (t TransactionStatus) Details(errMsg string) (status, detail string) {
   258  	switch t {
   259  	case TransactionStatus_PENDING:
   260  		status = "pending"
   261  	case TransactionStatus_SUCCESS:
   262  		status = "completed"
   263  	case TransactionStatus_ERROR_TRANSIENT, TransactionStatus_ERROR_PERMANENT:
   264  		status = "error"
   265  		detail = errMsg
   266  	default:
   267  		status = "unknown"
   268  		detail = errMsg
   269  	}
   270  
   271  	return status, detail
   272  }
   273  
   274  func NewPaymentLocal(txid TransactionID, ctime TimeMs) *PaymentLocal {
   275  	return &PaymentLocal{
   276  		Id:   NewPaymentID(txid),
   277  		Time: ctime,
   278  	}
   279  }
   280  
   281  func NewPaymentID(txid TransactionID) PaymentID {
   282  	return PaymentID(txid)
   283  }
   284  
   285  func (p PaymentID) String() string {
   286  	return string(p)
   287  }
   288  
   289  func (p *PaymentSummary) ToDetails() *PaymentDetails {
   290  	return &PaymentDetails{
   291  		Summary: *p,
   292  	}
   293  }
   294  
   295  func (p *PaymentSummary) TransactionID() (TransactionID, error) {
   296  	t, err := p.Typ()
   297  	if err != nil {
   298  		return "", err
   299  	}
   300  
   301  	switch t {
   302  	case PaymentSummaryType_STELLAR:
   303  		s := p.Stellar()
   304  		return s.TxID, nil
   305  	case PaymentSummaryType_DIRECT:
   306  		s := p.Direct()
   307  		return s.TxID, nil
   308  	case PaymentSummaryType_RELAY:
   309  		s := p.Relay()
   310  		return s.TxID, nil
   311  	}
   312  
   313  	return "", errors.New("unknown payment summary type")
   314  }
   315  
   316  func (p *PaymentSummary) TransactionStatus() (TransactionStatus, error) {
   317  	t, err := p.Typ()
   318  	if err != nil {
   319  		return TransactionStatus_NONE, err
   320  	}
   321  
   322  	switch t {
   323  	case PaymentSummaryType_STELLAR:
   324  		return TransactionStatus_SUCCESS, nil
   325  	case PaymentSummaryType_DIRECT:
   326  		return p.Direct().TxStatus, nil
   327  	case PaymentSummaryType_RELAY:
   328  		return p.Relay().TxStatus, nil
   329  	}
   330  
   331  	return TransactionStatus_NONE, errors.New("unknown payment summary type")
   332  }
   333  
   334  func (c *ClaimSummary) ToPaymentStatus() PaymentStatus {
   335  	txStatus := c.TxStatus.ToPaymentStatus()
   336  	switch txStatus {
   337  	case PaymentStatus_COMPLETED:
   338  		if c.Dir == RelayDirection_YANK {
   339  			return PaymentStatus_CANCELED
   340  		}
   341  	}
   342  	return txStatus
   343  }
   344  
   345  func (d *StellarServerDefinitions) GetCurrencyLocal(code OutsideCurrencyCode) (res CurrencyLocal, ok bool) {
   346  	def, found := d.Currencies[code]
   347  	if found {
   348  		res = CurrencyLocal{
   349  			Description: fmt.Sprintf("%s (%s)", string(code), def.Symbol.Symbol),
   350  			Code:        code,
   351  			Symbol:      def.Symbol.Symbol,
   352  			Name:        def.Name,
   353  		}
   354  		ok = true
   355  	} else {
   356  		res = CurrencyLocal{
   357  			Code: code,
   358  		}
   359  		ok = false
   360  	}
   361  	return res, ok
   362  }
   363  
   364  func (c OutsideCurrencyCode) String() string {
   365  	return string(c)
   366  }
   367  
   368  func (b BuildPaymentID) String() string {
   369  	return string(b)
   370  }
   371  
   372  func (b BuildPaymentID) IsNil() bool {
   373  	return len(b) == 0
   374  }
   375  
   376  func (b BuildPaymentID) Eq(other BuildPaymentID) bool {
   377  	return b == other
   378  }
   379  
   380  func NewChatConversationID(b []byte) *ChatConversationID {
   381  	cid := ChatConversationID(hex.EncodeToString(b))
   382  	return &cid
   383  }
   384  
   385  func (a *AccountDetails) SetDefaultDisplayCurrency() {
   386  	if a.DisplayCurrency == "" {
   387  		a.DisplayCurrency = "USD"
   388  	}
   389  }
   390  
   391  func (a AssetCode) String() string {
   392  	return string(a)
   393  }
   394  
   395  func (a AssetCode) Eq(other AssetCode) bool {
   396  	return a == other
   397  }
   398  
   399  func (a AssetCode) GetAssetType() string {
   400  	switch {
   401  	case len(a) >= 1 && len(a) <= 4:
   402  		return "credit_alphanum4"
   403  	case len(a) >= 5 && len(a) <= 12:
   404  		return "credit_alphanum12"
   405  	default:
   406  		// nil or invalid AssetCode.
   407  		return "asset_code_invalid"
   408  	}
   409  }
   410  
   411  // TypeString implements stellarnet.AssetBase.
   412  func (a Asset) TypeString() string {
   413  	return a.Type
   414  }
   415  
   416  // CodeString implements stellarnet.AssetBase.
   417  func (a Asset) CodeString() string {
   418  	return a.Code
   419  }
   420  
   421  // IssuerString implements stellarnet.AssetBase.
   422  func (a Asset) IssuerString() string {
   423  	return a.Issuer
   424  }