Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Virtual environments
venv/
ENV/
env/
.venv

# IDEs
.vscode/
.idea/
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,60 @@ Before submitting an EIP (Ethereum Improvement Proposal), ensure you have:

---

**Note**: This repository (Darliewithrow/master) is a guide repository. The actual EIPs should be submitted to https://github.com/ethereum/EIPs following the process described above.

---

## Transaction ID Handling

This repository includes a Python utility (`transaction_id.py`) for working with Ethereum transaction IDs (transaction hashes).

### What is an Ethereum Transaction ID?

An Ethereum transaction ID is a 32-byte hash represented as a 66-character hex string, for example:
Comment thread
Darliewithrow marked this conversation as resolved.

```
0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1
```

It starts with `0x` and is followed by exactly 64 hexadecimal digits.

### Usage

```python
from transaction_id import (
is_valid_transaction_id,
normalize_transaction_id,
format_transaction_id,
parse_transaction_ids,
)

tx = "0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1"

# Validate
is_valid_transaction_id(tx) # True

# Normalize (lowercase, ensure 0x prefix)
normalize_transaction_id("0xABC123DEF456ABC123DEF456ABC123DEF456ABC123DEF456ABC123DEF456ABC1") # "0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1"

# Format (full or shortened)
format_transaction_id(tx) # full form
format_transaction_id(tx, short=True) # "0xabc123...56abc1"

# Extract all valid IDs from a block of text
parse_transaction_ids("tx1=0x... tx2=0x...")

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parsing example uses placeholders like "tx1=0x... tx2=0x...", but parse_transaction_ids only extracts full 32-byte transaction hashes (no ellipses/short forms). Update the example to include full-length IDs or clarify the supported input formats.

Suggested change
parse_transaction_ids("tx1=0x... tx2=0x...")
parse_transaction_ids(
"tx1=0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1 "
"tx2=0xdef456abc123def456abc123def456abc123def456abc123def456abc123def4"
)

Copilot uses AI. Check for mistakes.
```

Run the script directly:

```bash
python3 transaction_id.py # Uses built-in example data
python3 transaction_id.py txdata.txt # Reads from a file
```

Run the test suite:

```bash
python3 test_transaction_id.py
```
> **Note**: This repository (`Darliewithrow/master`) is a guide repository. The actual EIPs should be submitted to [https://github.com/ethereum/EIPs](https://github.com/ethereum/EIPs) following the process described above.
139 changes: 139 additions & 0 deletions test_transaction_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#!/usr/bin/env python3
"""
Test suite for Transaction ID Handler
"""

import unittest
from transaction_id import (
is_valid_transaction_id,
normalize_transaction_id,
format_transaction_id,
parse_transaction_ids,
)


class TestIsValidTransactionId(unittest.TestCase):
"""Tests for is_valid_transaction_id."""

def test_valid_lowercase(self):
tx = "0x" + "a" * 64
self.assertTrue(is_valid_transaction_id(tx))

def test_valid_uppercase(self):
tx = "0x" + "A" * 64
self.assertTrue(is_valid_transaction_id(tx))

def test_valid_mixed_case(self):
tx = "0xaAbBcCdDeEfF" + "0" * 52
self.assertTrue(is_valid_transaction_id(tx))

def test_too_short(self):
tx = "0x" + "a" * 63
self.assertFalse(is_valid_transaction_id(tx))

def test_too_long(self):
tx = "0x" + "a" * 65
self.assertFalse(is_valid_transaction_id(tx))

def test_missing_prefix(self):
tx = "a" * 64
self.assertFalse(is_valid_transaction_id(tx))

def test_invalid_hex_chars(self):
tx = "0x" + "g" * 64
self.assertFalse(is_valid_transaction_id(tx))

def test_empty_string(self):
self.assertFalse(is_valid_transaction_id(""))

def test_non_string_input(self):
self.assertFalse(is_valid_transaction_id(12345)) # type: ignore[arg-type]


class TestNormalizeTransactionId(unittest.TestCase):
"""Tests for normalize_transaction_id."""

def test_uppercase_normalized_to_lowercase(self):
tx = "0x" + "A" * 64
self.assertEqual(normalize_transaction_id(tx), "0x" + "a" * 64)

def test_prefix_added_when_missing(self):
tx = "a" * 64
self.assertEqual(normalize_transaction_id(tx), "0x" + "a" * 64)

def test_leading_whitespace_stripped(self):
tx = " 0x" + "b" * 64
self.assertEqual(normalize_transaction_id(tx), "0x" + "b" * 64)

def test_invalid_returns_none(self):
self.assertIsNone(normalize_transaction_id("not_a_tx_id"))

def test_non_string_returns_none(self):
self.assertIsNone(normalize_transaction_id(None)) # type: ignore[arg-type]

def test_already_normalized(self):
tx = "0x" + "1234567890abcdef" * 4
self.assertEqual(normalize_transaction_id(tx), tx)


class TestFormatTransactionId(unittest.TestCase):
"""Tests for format_transaction_id."""

def test_full_format(self):
tx = "0x" + "a" * 64
self.assertEqual(format_transaction_id(tx), "0x" + "a" * 64)

def test_short_format(self):
tx = "0x" + "abcdef" + "0" * 52 + "123456"
result = format_transaction_id(tx, short=True)
self.assertEqual(result, "0xabcdef...123456")

def test_invalid_returns_none(self):
self.assertIsNone(format_transaction_id("invalid"))

def test_normalizes_case(self):
tx = "0x" + "A" * 64
self.assertEqual(format_transaction_id(tx), "0x" + "a" * 64)


class TestParseTransactionIds(unittest.TestCase):
"""Tests for parse_transaction_ids."""

def test_single_tx_id(self):
tx = "0x" + "1" * 64
result = parse_transaction_ids(f"tx={tx}")
self.assertEqual(result, [tx])

def test_multiple_tx_ids(self):
tx1 = "0x" + "1" * 64
tx2 = "0x" + "2" * 64
result = parse_transaction_ids(f"{tx1} {tx2}")
self.assertEqual(result, [tx1, tx2])

def test_duplicates_removed(self):
tx = "0x" + "a" * 64
result = parse_transaction_ids(f"{tx} {tx}")
self.assertEqual(result, [tx])

def test_invalid_entries_skipped(self):
tx = "0x" + "c" * 64
result = parse_transaction_ids(f"garbage {tx} more_garbage")
self.assertEqual(result, [tx])

def test_empty_string(self):
self.assertEqual(parse_transaction_ids(""), [])

def test_no_tx_ids(self):
self.assertEqual(parse_transaction_ids("no transaction ids here"), [])

def test_case_normalization(self):
tx_upper = "0x" + "A" * 64
tx_lower = "0x" + "a" * 64
# Both forms refer to the same ID; only one should appear
result = parse_transaction_ids(f"{tx_upper} {tx_lower}")
Comment on lines +129 to +133

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a test that parse_transaction_ids extracts IDs with an uppercase 0X prefix (and normalizes them), plus a boundary case where a valid 66-char ID is immediately followed by another hex character (to avoid greedy-match failures). This will lock in the intended extraction behavior.

Copilot uses AI. Check for mistakes.
self.assertEqual(len(result), 1)
self.assertEqual(result[0], tx_lower)


if __name__ == "__main__":
unittest.main()
153 changes: 153 additions & 0 deletions transaction_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/usr/bin/env python3
"""
Transaction ID Handler

This module provides functions to validate, normalize, and handle
Ethereum transaction IDs (transaction hashes).

Ethereum transaction IDs are 32-byte values represented as 66-character
hex strings (including the '0x' prefix).
"""

import re
import sys
from typing import List, Optional


# Ethereum transaction ID pattern: '0x' followed by exactly 64 hex characters
TX_ID_PATTERN = re.compile(r'^0x[0-9a-fA-F]{64}$')
TX_ID_LENGTH = 64 # hex characters after '0x'
Comment thread
Darliewithrow marked this conversation as resolved.


def is_valid_transaction_id(tx_id: str) -> bool:
"""
Check whether a string is a valid Ethereum transaction ID.

A valid transaction ID starts with '0x' and is followed by
exactly 64 hexadecimal characters (case-insensitive).

Args:
tx_id: The string to validate.

Returns:
True if tx_id is a valid Ethereum transaction ID, False otherwise.
"""
if not isinstance(tx_id, str):
return False
return bool(TX_ID_PATTERN.match(tx_id))


def normalize_transaction_id(tx_id: str) -> Optional[str]:
"""
Normalize a transaction ID to lowercase hex with '0x' prefix.

Args:
tx_id: A raw transaction ID string. May include or omit the '0x'
prefix and may use upper- or lower-case hex digits.

Returns:
The normalized transaction ID (lowercase, with '0x' prefix), or
None if the input cannot be normalized to a valid ID.
"""
if not isinstance(tx_id, str):
return None

stripped = tx_id.strip()

# Add '0x' prefix if missing
if not stripped.startswith('0x') and not stripped.startswith('0X'):
stripped = '0x' + stripped

normalized = '0x' + stripped[2:].lower()

if not is_valid_transaction_id(normalized):
return None

return normalized


def format_transaction_id(tx_id: str, short: bool = False) -> Optional[str]:
"""
Return a human-readable representation of a transaction ID.

Args:
tx_id: A transaction ID string (with or without '0x' prefix).
short: If True, return a shortened form showing the first and last
six hex characters separated by '...'.

Returns:
The formatted string, or None if tx_id is not valid.
"""
normalized = normalize_transaction_id(tx_id)
if normalized is None:
return None

if short:
# e.g. 0xabcdef...123456
hex_part = normalized[2:]
return f"0x{hex_part[:6]}...{hex_part[-6:]}"

return normalized


def parse_transaction_ids(data: str) -> List[str]:
"""
Extract all valid Ethereum transaction IDs from a block of text.

Args:
data: A string that may contain one or more transaction IDs.

Returns:
A list of unique, normalized transaction IDs found in the text,
in the order they first appear.
"""
if not data:
return []

# Find all candidate '0x...' tokens
candidates = re.findall(r'0x[0-9a-fA-F]+', data)

Comment thread
Darliewithrow marked this conversation as resolved.
seen: set = set()
result: List[str] = []
for candidate in candidates:
normalized = normalize_transaction_id(candidate)
if normalized and normalized not in seen:
seen.add(normalized)
result.append(normalized)

return result


def main() -> None:
if len(sys.argv) > 1:
file_path = sys.argv[1]
try:
with open(file_path, 'r') as f:

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open(file_path, 'r') relies on the platform default encoding. Specify an explicit encoding (e.g., UTF-8) for consistent behavior across environments.

Suggested change
with open(file_path, 'r') as f:
with open(file_path, 'r', encoding='utf-8') as f:

Copilot uses AI. Check for mistakes.
input_data = f.read().strip()
print(f"Reading from file: {file_path}")
except FileNotFoundError:
print(f"Error: File '{file_path}' not found")
sys.exit(1)
else:
# Example Ethereum transaction IDs for demonstration
input_data = (
"0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1 "
"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF "
"invalid_tx "
"0xabc123def456abc123def456abc123def456abc123def456abc123def456abc1"
)

print(f"Input data:\n{input_data}\n")

tx_ids = parse_transaction_ids(input_data)

print("Found transaction IDs:")
for i, tx_id in enumerate(tx_ids, 1):
short = format_transaction_id(tx_id, short=True)
print(f" {i}. {tx_id} (short: {short})")

print(f"\nTotal unique transaction IDs: {len(tx_ids)}")


if __name__ == "__main__":
main()
Loading