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 }