package lifecycle import ( "errors" "fmt" "regexp" "strings" ) var txHashPattern = regexp.MustCompile(`0x[a-fA-F0-9]{64}`) type RecordedError struct { Err error TxHash string } func (re RecordedError) Error() string { if re.Err == nil { return "" } return re.Err.Error() } func enrichErrorWithTxHash(message string, err error, attrs []interface{}) (error, string, []interface{}) { txHash, attrsWithTx := ensureTxHash(attrs, err) wrapped := fmt.Errorf("%s: %w", message, err) if txHash != "" { wrapped = fmt.Errorf("%s [tx_hash=%s]: %w", message, txHash, err) } return wrapped, txHash, attrsWithTx } func ensureTxHash(attrs []interface{}, err error) (string, []interface{}) { txHash := extractTxHashFromAttrs(attrs) if txHash == "" { txHash = extractTxHashFromError(err) } if txHash == "" { return "", attrs } hasTxAttr := false for i := 0; i+1 < len(attrs); i += 2 { key, ok := attrs[i].(string) if !ok { continue } if key == "tx_hash" || key == "transaction_hash" || key == "tx" { hasTxAttr = true break } } if !hasTxAttr { attrs = append(attrs, "tx_hash", txHash) } return txHash, attrs } func extractTxHashFromAttrs(attrs []interface{}) string { for i := 0; i+1 < len(attrs); i += 2 { key, ok := attrs[i].(string) if !ok { continue } if key == "tx_hash" || key == "transaction_hash" || key == "tx" { if value, ok := attrs[i+1].(string); ok && isValidTxHash(value) { return strings.ToLower(value) } } } return "" } func extractTxHashFromError(err error) string { for err != nil { if match := txHashPattern.FindString(err.Error()); match != "" { return strings.ToLower(match) } err = errors.Unwrap(err) } return "" } func isValidTxHash(value string) bool { if value == "" { return false } if len(value) != 66 { return false } if !strings.HasPrefix(value, "0x") { return false } for _, r := range value[2:] { if !isHexChar(r) { return false } } return true } func isHexChar(r rune) bool { return (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F') }