@@ -25,6 +25,23 @@ This file provides context for AI coding tools (GitHub Copilot, Cursor, Aider, G
2525- ✅ ` client.current_signer ` → track current account name for switching back
2626- ✅ ` NamedAccount.create(name) ` → for local dev (not Account.create())
2727
28+ ** Time conversion (methods, not imports):**
29+ - ✅ ` client.arkiv.to_seconds(days=7) ` → method on arkiv module
30+ - ✅ ` client.arkiv.to_blocks(days=1) ` → method on arkiv module
31+ - ❌ ` from arkiv import to_seconds ` → WRONG, not exported
32+
33+ ** Account and entity access:**
34+ - ✅ ` client.eth.default_account ` → current account address (string)
35+ - ✅ ` entity.attributes ` → dict of custom attributes (or None)
36+ - ✅ ` entity.payload ` → bytes data (or None, always check!)
37+ - ✅ ` account.address ` → when you have NamedAccount object
38+
39+ ** Testing:**
40+ - ✅ Use fixture name ` arkiv_client ` (not ` client ` )
41+ - ✅ Use fixture name ` arkiv_node ` for node access
42+ - ✅ Session-scoped fixtures = shared blockchain state
43+ - ✅ Use unique identifiers (timestamps/UUIDs) for test isolation
44+
2845** Run examples:** ` uv run python -m arkiv_starter.01_clients ` (etc., 01-05)
2946
3047---
@@ -441,7 +458,7 @@ account = Account.create() # LocalAccount won't work with node.fund_account()
441458### Account Attributes
442459
443460``` python
444- # ✅ CORRECT
461+ # ✅ CORRECT - When you have an account object
445462print (account.address) # Ethereum address
446463print (account.private_key) # Private key for signing
447464print (account.name) # Account name (NamedAccount only)
@@ -450,6 +467,22 @@ print(account.name) # Account name (NamedAccount only)
450467print (account.key) # Use account.private_key instead
451468```
452469
470+ ### Accessing the Current Account from Client
471+
472+ ``` python
473+ # ✅ CORRECT - Get current account address from client
474+ current_address = client.eth.default_account # Returns address string
475+
476+ # ✅ CORRECT - Check if entity belongs to current user
477+ entity = client.arkiv.get_entity(entity_key)
478+ if entity.owner != client.eth.default_account:
479+ print (" You don't own this entity" )
480+
481+ # ❌ WRONG - These don't exist
482+ client.account.address # AttributeError
483+ client.default_account.address # AttributeError
484+ ```
485+
453486### Managing Multiple Accounts
454487
455488The Arkiv client can manage multiple accounts and switch between them:
@@ -521,21 +554,149 @@ cd src && python -m arkiv_starter.02_entity_crud # Wrong directory
521554
522555## 🧪 Testing Patterns
523556
524- ### Use Fixtures from conftest.py
557+ ### Available Test Fixtures
558+
559+ The project's ` conftest.py ` provides these fixtures:
525560
526561``` python
527- # Tests have access to these fixtures:
528- def test_something (client , account , arkiv_node ):
529- # client: Arkiv instance
530- # account: NamedAccount instance
531- # arkiv_node: ArkivNode instance (already started and funded)
562+ # ✅ CORRECT - Use these exact fixture names
563+ def test_something (arkiv_client , arkiv_node ):
564+ # arkiv_client: Arkiv instance (session-scoped, shared across tests)
565+ # arkiv_node: ArkivNode instance (session-scoped, already started)
532566
533- entity_key, receipt = client .arkiv.create_entity(
567+ entity_key, receipt = arkiv_client .arkiv.create_entity(
534568 payload = b " test data" ,
535569 expires_in = 3600 ,
536570 content_type = " text/plain"
537571 )
538572 assert entity_key is not None
573+
574+ # ❌ WRONG - These fixture names don't exist
575+ def test_something (client , account ): # NameError: fixture 'client' not found
576+ ```
577+
578+ ### Test Isolation with Session-Scoped Fixtures
579+
580+ ** ⚠️ CRITICAL:** Session-scoped fixtures mean:
581+ - All tests share the SAME node and blockchain
582+ - Data created in one test is visible in other tests
583+ - Tests run in order but share state
584+
585+ ** Solutions for test isolation:**
586+
587+ ``` python
588+ # ✅ Pattern 1: Unique identifiers per test
589+ import pytest
590+ import time
591+
592+ @pytest.fixture
593+ def unique_channel ():
594+ """ Generate unique channel name for test isolation."""
595+ return f " test- { int (time.time() * 1000000 )} "
596+
597+ def test_messages (arkiv_client , unique_channel ):
598+ chat = ChatClient(arkiv_client, channel = unique_channel)
599+ # Now isolated from other tests
600+
601+ # ✅ Pattern 2: Function-scoped client for full isolation
602+ from typing import cast
603+ from web3.providers.base import BaseProvider
604+ from arkiv import Arkiv, NamedAccount
605+ from arkiv.provider import ProviderBuilder
606+
607+ @pytest.fixture
608+ def isolated_client (arkiv_node ):
609+ """ Create a fresh client with unique account per test."""
610+ provider = cast(BaseProvider, ProviderBuilder().node(arkiv_node).build())
611+ account = NamedAccount.create(f " test- { int (time.time() * 1000000 )} " )
612+ arkiv_node.fund_account(account)
613+ client = Arkiv(provider, account = account)
614+ yield client
615+ client.arkiv.cleanup_filters()
616+
617+ def test_with_isolation (isolated_client ):
618+ # Fresh client with unique account
619+ pass
620+
621+ # ✅ Pattern 3: Query with test-specific filters
622+ def test_user_messages (arkiv_client , unique_channel ):
623+ alice = ChatClient(arkiv_client, username = " Alice" , channel = unique_channel)
624+ alice.send_message(" Test message" )
625+
626+ # Query may return messages from other tests too
627+ all_alice_msgs = alice.get_user_messages(" Alice" )
628+
629+ # Filter for messages from THIS test
630+ test_msgs = [m for m in all_alice_msgs if " Test message" in m[" text" ]]
631+ assert len (test_msgs) == 1
632+ ```
633+
634+ ### Testing Event Watchers
635+
636+ Event watchers require special timing considerations:
637+
638+ ``` python
639+ import time
640+
641+ def test_event_watching (arkiv_client ):
642+ received_events = []
643+
644+ def on_event (event , tx_hash ):
645+ received_events.append(event)
646+
647+ # ✅ CORRECT - Set up watcher BEFORE creating entities
648+ watcher = arkiv_client.arkiv.watch_entity_created(on_event)
649+
650+ # Give watcher time to initialize
651+ time.sleep(0.2 )
652+
653+ # Create entity
654+ entity_key, receipt = arkiv_client.arkiv.create_entity(
655+ payload = b " test" ,
656+ expires_in = 3600 ,
657+ content_type = " text/plain"
658+ )
659+
660+ # Wait for event processing (local node needs time)
661+ time.sleep(1.0 )
662+
663+ # Now check events
664+ assert len (received_events) >= 1
665+
666+ # Cleanup
667+ watcher.uninstall()
668+
669+ # ❌ WRONG - Common pitfalls
670+ def test_event_watching_wrong (arkiv_client ):
671+ # Create entity FIRST → miss the event!
672+ entity_key, _ = arkiv_client.arkiv.create_entity(... )
673+
674+ # Set up watcher AFTER → too late!
675+ watcher = arkiv_client.arkiv.watch_entity_created(on_event)
676+
677+ # No sleep → event not processed yet
678+ assert len (received_events) >= 1 # Fails!
679+ ```
680+
681+ ### Type Hints for Arkiv Code
682+
683+ ``` python
684+ # ✅ CORRECT - Standard type hint imports
685+ from typing import Any, Callable, Optional, cast
686+ from web3.providers.base import BaseProvider
687+ from arkiv import Arkiv, NamedAccount
688+ from arkiv.types import CreateEvent, DeleteEvent, TxHash
689+
690+ # Callback type hints
691+ def setup_watcher (callback : Callable[[str , int ], None ]) -> None :
692+ pass
693+
694+ # Provider casting (avoids IDE errors)
695+ provider = cast(BaseProvider, ProviderBuilder().node(node).build())
696+
697+ # ❌ WRONG - Old-style or incorrect
698+ def setup_watcher (callback : callable ) -> None : # Use Callable, not callable
699+ pass
539700```
540701
541702### Local Node Doesn't Support All Queries Yet
@@ -589,6 +750,27 @@ except ValueError:
589750 print (" Entity not found" )
590751```
591752
753+ ### Accessing Entity Attributes
754+
755+ ``` python
756+ # ✅ CORRECT - Attributes are directly on the entity object
757+ entity = client.arkiv.get_entity(entity_key)
758+ attr_dict = entity.attributes # Returns dict or None
759+
760+ # Example: Get channel from attributes
761+ if entity.attributes:
762+ channel = entity.attributes.get(" channel" , " default" )
763+ else :
764+ channel = " default"
765+
766+ # Example: Access payload safely
767+ if entity.payload:
768+ data = entity.payload.decode(' utf-8' )
769+
770+ # ❌ WRONG - No separate method exists
771+ attributes = client.arkiv.get_entity_attributes(entity_key) # Doesn't exist!
772+ ```
773+
592774### Querying Entities
593775
594776``` python
@@ -663,14 +845,15 @@ for event in event_filter.get_new_entries():
663845### Time Conversions
664846
665847``` python
666- from arkiv import to_seconds, to_blocks
667-
668- # Convert time to seconds
669- expires_in = to_seconds(days = 7 )
670- expires_in = to_seconds(hours = 2 , minutes = 30 )
848+ # ✅ CORRECT - to_seconds and to_blocks are METHODS on the client
849+ expires_in = client.arkiv.to_seconds(days = 7 )
850+ expires_in = client.arkiv.to_seconds(hours = 2 , minutes = 30 )
671851
672852# Convert time to blocks (assuming 2s block time)
673- expires_in_blocks = to_blocks(days = 1 , block_time = 2 )
853+ expires_in_blocks = client.arkiv.to_blocks(days = 1 , block_time = 2 )
854+
855+ # ❌ WRONG - Don't try to import them
856+ from arkiv import to_seconds, to_blocks # ImportError!
674857```
675858
676859---
@@ -961,6 +1144,12 @@ client.arkiv.watch_entity_created(on_entity_created)
961114411 . ** Prefer high-level event watchers** - Use ` watch_entity_*() ` methods over raw contract filters
962114512 . ** Note the watch_entity_deleted type bug** - Always add ` # type: ignore[arg-type] ` when using it
963114613 . ** Use ` entity_exists() ` to check existence** - Cleaner than try-except with ` get_entity() `
1147+ 14 . ** Test fixture names matter** - Use ` arkiv_client ` and ` arkiv_node ` , not ` client ` or ` account `
1148+ 15 . ** Session-scoped fixtures share state** - Use unique identifiers (timestamps, UUIDs) for test isolation
1149+ 16 . ** Event watchers need timing** - Set up BEFORE entity creation, add sleeps for processing
1150+ 17 . ** to_seconds is a method** - Use ` client.arkiv.to_seconds() ` , not ` from arkiv import to_seconds `
1151+ 18 . ** Current account access** - Use ` client.eth.default_account ` , not ` client.account.address `
1152+ 19 . ** Entity attributes are direct** - Use ` entity.attributes ` , not a separate method call
9641153
9651154---
9661155
0 commit comments