> ## Documentation Index
> Fetch the complete documentation index at: https://iqlabs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Developer Docs (Python)

> Core concepts and functions for the IQLabs Python SDK

<Note>
  This document is in progress and will be refined.
</Note>

## Installation

```bash theme={null}
pip install iqlabs-solana-sdk
```

***

## Core Concepts

These are the key concepts to know before using the IQLabs SDK.

***

### Data Storage (Code In)

This is how you store any data (files, text, JSON) on-chain.

#### How is it stored?

Depending on data size, the SDK picks the optimal method:

* **Small data (\< 700 bytes)**: store immediately, fastest
* **Medium data (\< 8.5 KB)**: split into multiple transactions
* **Large data (>= 8.5 KB)**: upload in parallel for speed

#### Key related functions

* [`code_in()`](#code_in): upload data and get a transaction ID
* [`read_code_in()`](#read_code_in): read data back from a transaction ID

***

### User State PDA

An on-chain profile account for a user.

#### What gets stored?

* Profile info (name, profile picture, bio, etc.)
* Number of uploaded files
* Friend request records

<Note>
  Friend requests are not stored as values in the PDA; they are sent as transactions.
</Note>

#### When is it created?

It is created automatically the first time you call [`code_in()`](#code_in). No extra setup is required, but the first user may need to sign twice.

***

### Connection PDA

An on-chain account that manages relationships between two users (friends, messages, etc.).

#### What states can it have?

* **pending**: a friend request was sent but not accepted yet
* **approved**: the request was accepted and the users are connected
* **blocked**: one side blocked the other

<Warning>
  A blocked connection can only be unblocked by the blocker.
</Warning>

#### Key related functions

* [`request_connection()`](#request_connection): send a friend request (creates pending)
* [`manage_connection()`](#manage_connection): approve/reject/block/unblock a request
* [`read_connection()`](#read_connection): check current relationship status
* [`write_connection_row()`](#write_connection_row): exchange messages/data with a connected friend
* [`fetch_user_connections()`](#fetch_user_connections): fetch all connections (sent & received friend requests)

***

### Database Tables

Store JSON data in tables like a database.

#### How are tables created?

Use [`create_table()`](#create_table) to create a table explicitly. If the DbRoot PDA is running low on space, the SDK automatically expands it in the same transaction — no extra steps needed.

<Note>
  A table is uniquely identified by the combination of `db_root_id` and `table_seed`. The `table_seed` is internally hashed (keccak256) to derive the on-chain PDA. A human-readable `table_hint` (e.g. `"users"`, `"chatroom:general"`) is stored in `DbRoot.table_seeds` so that anyone reading the DbRoot can discover tables without a hardcoded lookup.
</Note>

#### Key related functions

* [`create_table()`](#create_table): create a new table (auto-reallocs DbRoot if needed)
* [`write_row()`](#write_row): add a new row to an existing table
* [`read_table_rows()`](#read_table_rows): read rows from a table
* [`get_tablelist_from_root()`](#get_tablelist_from_root): list all tables in a database
* [`fetch_inventory_transactions()`](#fetch_inventory_transactions): list uploaded files

***

### Token & Collection Gating

Tables can be gated so that only users holding a specific token or NFT collection can write data.

#### Gate Types

| Type           | `GateType`            | Description                                                            |
| -------------- | --------------------- | ---------------------------------------------------------------------- |
| **Token**      | `GateType.TOKEN`      | User must hold >= `amount` of the specified SPL token mint             |
| **Collection** | `GateType.COLLECTION` | User must hold any NFT from the specified Metaplex verified collection |

#### How it works

* **Table creator** sets the gate when creating or updating a table
* **Writers** don't need to do anything special — the SDK automatically resolves the required token account (and metadata account for collections) when calling `write_row()` or `manage_row_data()`
* If no gate is set, the table is public (default behavior, no change for existing users)

#### Gate parameter

```python theme={null}
gate = {
    "mint": Pubkey,       # token mint address OR collection address
    "amount": int,        # minimum token amount (default: 1, ignored for collections)
    "gate_type": int,     # GateType.TOKEN (default) or GateType.COLLECTION
}
```

<Note>
  For **collection gates**, the user can present any NFT from that collection. `amount` is ignored since NFTs always have amount=1.
</Note>

***

### Table Creation Permissions

The database owner (DbRoot creator) can control who is allowed to create tables.

#### Two levels of table creation

| Type              | Stored in                            | Visibility                            | Permission field |
| ----------------- | ------------------------------------ | ------------------------------------- | ---------------- |
| **Public table**  | `table_seeds` + `global_table_seeds` | Listed in `get_tablelist_from_root()` | `table_creators` |
| **Private table** | `global_table_seeds` only            | Only accessible if you know the PDA   | `ext_creators`   |

<Note>
  `table_seeds` and `global_table_seeds` store human-readable hints (e.g. `"users"`), not hashed seeds. To derive the table PDA from a hint, hash it with `to_seed_bytes()` first.
</Note>

* If the permission list is **empty**, anyone can create tables (default, backward-compatible)
* If the permission list has wallets, only those wallets + the DbRoot creator can create tables

#### Managing permissions

The DbRoot creator can set both lists in a single call:

```python theme={null}
from iqlabs.contract import manage_table_creators_instruction

ix = manage_table_creators_instruction(
    builder,
    {"signer": wallet.pubkey(), "db_root": db_root_pda, "system_program": SYSTEM_PROGRAM_ID},
    {
        "db_root_id": db_root_id_bytes,
        "table_creators": [admin_wallet_1, admin_wallet_2],
        "ext_creators": [],
    },
)
```

Pass empty lists to make creation public again.

#### Onboarding (private to public)

A private table (exists in `global_table_seeds` only) can be promoted to public (`table_seeds`) by anyone in the `table_creators` list:

```python theme={null}
from iqlabs.contract import onboard_table_instruction

ix = onboard_table_instruction(
    builder,
    {"signer": wallet.pubkey(), "db_root": db_root_pda},
    {"db_root_id": db_root_id_bytes, "table_seed": b"my-board"},
    # table_seed here matches the hint stored in global_table_seeds
)
```

This is useful when you want anyone to create content (e.g. threads) but only admins decide which ones are publicly listed (e.g. boards).

***

### Encryption (Crypto)

The SDK includes a built-in encryption module (`iqlabs.crypto`) for encrypting data before storing it on-chain.

#### Three encryption modes

* **DH Encryption** (single recipient): Ephemeral X25519 ECDH → HKDF-SHA256 → AES-256-GCM. Use when encrypting data for one specific recipient.
* **Password Encryption**: PBKDF2-SHA256 (250k iterations) → AES-256-GCM. Use for password-protected data that anyone with the password can decrypt.
* **Multi-recipient Encryption** (PGP-style hybrid): Generates a random content encryption key (CEK), encrypts data once, then wraps the CEK for each recipient via ECDH. Use when encrypting data for multiple recipients.

#### Key derivation

Users can derive a deterministic X25519 keypair from their wallet signature using [`derive_x25519_keypair()`](#derive_x25519_keypair). This means users don't need to manage separate encryption keys — their wallet is the key.

#### Key related functions

* [`derive_x25519_keypair()`](#derive_x25519_keypair): derive encryption keypair from wallet
* [`dh_encrypt()`](#dh_encrypt) / [`dh_decrypt()`](#dh_decrypt): single-recipient encryption
* [`password_encrypt()`](#password_encrypt) / [`password_decrypt()`](#password_decrypt): password-based encryption
* [`multi_encrypt()`](#multi_encrypt) / [`multi_decrypt()`](#multi_decrypt): multi-recipient encryption

***

## Function Details

### Data Storage and Retrieval

#### `code_in()`

| **Parameters** | `connection`: Solana RPC AsyncClient<br />`signer`: Keypair or WalletSigner<br />`chunks`: data to upload (list\[str])<br />`filename`: optional filename (str or None)<br />`method`: upload method (int, default: 0)<br />`filetype`: file type hint (str, default: '')<br />`on_progress`: optional progress callback (Callable\[\[int], None]) |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Returns**    | Transaction signature (str)                                                                                                                                                                                                                                                                                                                        |

```python theme={null}
from iqlabs import writer
from solana.rpc.async_api import AsyncClient
from solders.keypair import Keypair

# Upload data
signature = await writer.code_in(connection, signer, ['Hello, blockchain!'])

# Upload with filename
signature = await writer.code_in(connection, signer, ['file contents here'], filename='hello.txt')
```

***

#### `read_code_in()`

| **Parameters** | `tx_signature`: transaction signature (str)<br />`speed`: rate limit profile (optional, str)<br />`on_progress`: optional progress callback (Callable\[\[int], None]) |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Returns**    | dict with `metadata` (str) and `data` (str or None)                                                                                                                   |

```python theme={null}
from iqlabs import reader

result = await reader.read_code_in('5Xg7...')
print(result['data'])      # 'Hello, blockchain!'
print(result['metadata'])  # JSON string with file metadata
```

***

### Connection Management

#### `request_connection()`

| **Parameters** | `connection`: AsyncClient<br />`signer`: Keypair or WalletSigner<br />`db_root_id`: database ID (bytes or str)<br />`party_a`: first user pubkey (str)<br />`party_b`: second user pubkey (str)<br />`table_name`: connection table name (str or bytes)<br />`columns`: column list (list\[str or bytes])<br />`id_col`: ID column (str or bytes)<br />`ext_keys`: extension keys (list\[str or bytes]) |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Returns**    | Transaction signature (str)                                                                                                                                                                                                                                                                                                                                                                             |

```python theme={null}
from iqlabs import writer

await writer.request_connection(
    connection, signer, 'my-db',
    my_wallet_address, friend_wallet_address,
    'dm_table', ['message', 'timestamp'], 'message_id', []
)
```

***

#### `manage_connection()`

<Note>
  There is no high-level SDK wrapper for this function. Use the contract-level instruction builder directly.
</Note>

| **Parameters** | `builder`: InstructionBuilder<br />`accounts`: dict with `db_root`, `connection_table`, `signer`<br />`args`: dict with `db_root_id`, `connection_seed`, `new_status` |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Returns**    | Instruction                                                                                                                                                           |

```python theme={null}
from iqlabs import contract

# Create an instruction builder
builder = contract.create_instruction_builder()

# Approve a friend request
approve_ix = contract.manage_connection_instruction(
    builder,
    {"db_root": db_root, "connection_table": connection_table, "signer": my_pubkey},
    {"db_root_id": db_root_id, "connection_seed": connection_seed, "new_status": contract.CONNECTION_STATUS_APPROVED}
)

# Block a user
block_ix = contract.manage_connection_instruction(
    builder,
    {"db_root": db_root, "connection_table": connection_table, "signer": my_pubkey},
    {"db_root_id": db_root_id, "connection_seed": connection_seed, "new_status": contract.CONNECTION_STATUS_BLOCKED}
)
```

***

#### `read_connection()`

| **Parameters** | `db_root_id`: database ID (bytes or str)<br />`party_a`: first wallet (str)<br />`party_b`: second wallet (str) |
| -------------- | --------------------------------------------------------------------------------------------------------------- |
| **Returns**    | dict with `status`, `requester`, `blocker`                                                                      |

```python theme={null}
from iqlabs import reader

conn_info = await reader.read_connection('my-db', party_a, party_b)
print(conn_info['status'])  # 'pending' | 'approved' | 'blocked'
```

***

#### `write_connection_row()`

| **Parameters** | `connection`: AsyncClient<br />`signer`: Keypair or WalletSigner<br />`db_root_id`: database ID (bytes or str)<br />`connection_seed`: connection seed (bytes or str)<br />`row_json`: JSON data (str) |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Returns**    | Transaction signature (str)                                                                                                                                                                            |

```python theme={null}
from iqlabs import writer
import json

await writer.write_connection_row(
    connection, signer, 'my-db', connection_seed,
    json.dumps({"message_id": "123", "message": "Hello friend!", "timestamp": 1234567890})
)
```

***

#### `fetch_user_connections()`

Fetch all connections (friend requests) for a user by analyzing their UserState PDA transaction history. Each connection includes its `db_root_id`, identifying which app the connection belongs to.

| **Parameters** | `user_pubkey`: user public key (str or Pubkey)<br />`limit`: max number of transactions to fetch (optional)<br />`before`: signature to paginate from (optional)<br />`speed`: rate limit profile (optional) |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Returns**    | List of connection dicts with db\_root\_id, connection\_pda, party\_a, party\_b, status, requester, blocker, timestamp                                                                                       |

```python theme={null}
from iqlabs import reader

# Fetch all connections (across all apps!)
connections = await reader.fetch_user_connections(
    my_pubkey,
    speed="light",
    limit=100
)

# Filter by status
pending_requests = [c for c in connections if c['status'] == 'pending']
friends = [c for c in connections if c['status'] == 'approved']
blocked = [c for c in connections if c['status'] == 'blocked']

# Check connection details
for conn in connections:
    print(f"Party A: {conn['party_a']} <-> Party B: {conn['party_b']}, status: {conn['status']}")
```

***

### Table Management

#### `create_table()`

| **Parameters** | `connection`: AsyncClient<br />`signer`: Keypair or WalletSigner<br />`db_root_id`: database ID (bytes or str)<br />`table_seed`: table identifier — hashed internally via `to_seed_bytes()` for PDA derivation<br />`table_name`: display name (bytes or str)<br />`column_names`: column list (list\[bytes or str])<br />`id_col`: ID column (bytes or str)<br />`ext_keys`: extension keys (list\[bytes or str])<br />`gate`: optional access gate (see [Token & Collection Gating](#token--collection-gating))<br />`writers`: optional allowed writers (list\[Pubkey])<br />`table_hint`: human-readable identifier stored in DbRoot for discovery (str) |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Returns**    | Transaction signature (str)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |

The `table_hint` is stored in `DbRoot.table_seeds` so anyone reading the DbRoot can discover tables. The `table_seed` is hashed via `to_seed_bytes()` to derive the on-chain PDA — it is **not** stored. If the DbRoot PDA is running low on space, a realloc instruction is automatically prepended.

```python theme={null}
from iqlabs import writer
from iqlabs.contract import GateType

# Basic table — table_hint "users" is stored in DbRoot for discovery
await writer.create_table(
    connection, signer, 'my-db', 'users',
    'users',
    ['name', 'email', 'age'],
    'name',
    [],
    table_hint='users'
)

# With token gate
await writer.create_table(
    connection, signer, 'my-db', 'vip', 'vip',
    ['name'], 'user_id', [],
    gate={"mint": token_mint_pubkey, "amount": 100, "gate_type": GateType.TOKEN},
    table_hint='vip'
)

# With NFT collection gate
await writer.create_table(
    connection, signer, 'my-db', 'holders', 'holders',
    ['name'], 'user_id', [],
    gate={"mint": collection_pubkey, "gate_type": GateType.COLLECTION},
    table_hint='holders'
)
```

***

#### `write_row()`

| **Parameters** | `connection`: AsyncClient<br />`signer`: Keypair or WalletSigner<br />`db_root_id`: database ID (bytes or str)<br />`table_seed`: table name (bytes or str)<br />`row_json`: JSON row data (str)<br />`skip_confirmation`: skip tx confirmation (default: False)<br />`remaining_accounts`: optional list of Pubkey to append as read-only reference accounts (e.g. feed PDA for indexing) |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Returns**    | Transaction signature (str)                                                                                                                                                                                                                                                                                                                                                                |

```python theme={null}
from iqlabs import writer
import json

# Write the first row to create the table
await writer.write_row(connection, signer, 'my-db', 'users', json.dumps({
    "id": 1, "name": "Alice", "email": "alice@example.com"
}))

# Add another row to the same table
await writer.write_row(connection, signer, 'my-db', 'users', json.dumps({
    "id": 2, "name": "Bob", "email": "bob@example.com"
}))
```

***

#### `read_table_rows()`

| **Parameters** | `account`: table PDA (Pubkey or str)<br />`before`: signature cursor for pagination (optional)<br />`limit`: max number of rows to fetch (optional)<br />`speed`: rate limit profile (optional) |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Returns**    | `list[dict]`                                                                                                                                                                                    |

```python theme={null}
from iqlabs import reader

# Basic usage
rows = await reader.read_table_rows(table_pda, limit=50)

# Cursor-based pagination
older_rows = await reader.read_table_rows(table_pda, limit=50, before="sig...")
```

***

#### `get_tablelist_from_root()`

| **Parameters** | `connection`: AsyncClient<br />`db_root_id`: database ID (bytes or str) |
| -------------- | ----------------------------------------------------------------------- |
| **Returns**    | dict with `root_pda`, `creator`, `table_seeds`, `global_table_seeds`    |

```python theme={null}
from iqlabs import reader

result = await reader.get_tablelist_from_root(connection, 'my-db')
print('Creator:', result['creator'])
print('Table seeds:', result['table_seeds'])
```

***

#### `fetch_inventory_transactions()`

| **Parameters** | `public_key`: user public key (Pubkey)<br />`limit`: max count (int)<br />`before`: pagination cursor (optional, str) |
| -------------- | --------------------------------------------------------------------------------------------------------------------- |
| **Returns**    | Transaction list                                                                                                      |

```python theme={null}
from iqlabs import reader
from solders.pubkey import Pubkey
import json

my_files = await reader.fetch_inventory_transactions(my_pubkey, 20)
for tx in my_files:
    metadata = None
    try:
        metadata = json.loads(tx['metadata'])
    except:
        metadata = None

    if metadata and 'data' in metadata:
        inline_data = metadata['data'] if isinstance(metadata['data'], str) else json.dumps(metadata['data'])
        print(f"Inline data: {inline_data}")
    else:
        print(f"Signature: {tx['signature']}")
```

***

### Encryption

#### `derive_x25519_keypair()`

Derive a deterministic X25519 keypair from a wallet signature. The same wallet always produces the same keypair.

| **Parameters** | `sign_message`: async sign function `Callable[[bytes], Awaitable[bytes]]` |
| -------------- | ------------------------------------------------------------------------- |
| **Returns**    | dict with `priv_key` (bytes) and `pub_key` (bytes)                        |

```python theme={null}
from iqlabs import crypto

keypair = await crypto.derive_x25519_keypair(wallet.sign_message)
pub_hex = keypair['pub_key'].hex()
```

***

#### `dh_encrypt()`

| **Parameters** | `recipient_pub_hex`: recipient's X25519 public key (hex str)<br />`plaintext`: data to encrypt (bytes) |
| -------------- | ------------------------------------------------------------------------------------------------------ |
| **Returns**    | dict with `sender_pub`, `iv`, `ciphertext` (all hex str)                                               |

#### `dh_decrypt()`

| **Parameters** | `priv_key`: recipient's private key (bytes)<br />`sender_pub_hex`: sender's ephemeral public key (hex str)<br />`iv_hex`: IV (hex str)<br />`ciphertext_hex`: ciphertext (hex str) |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Returns**    | bytes (decrypted plaintext)                                                                                                                                                        |

```python theme={null}
from iqlabs import crypto

# Encrypt for a single recipient
encrypted = crypto.dh_encrypt(recipient_pub_hex, b'secret message')

# Decrypt (recipient side)
decrypted = crypto.dh_decrypt(
    recipient_priv_key,
    encrypted['sender_pub'],
    encrypted['iv'],
    encrypted['ciphertext']
)
print(decrypted.decode())  # 'secret message'
```

***

#### `password_encrypt()`

| **Parameters** | `password`: password (str)<br />`plaintext`: data to encrypt (bytes) |
| -------------- | -------------------------------------------------------------------- |
| **Returns**    | dict with `salt`, `iv`, `ciphertext` (all hex str)                   |

#### `password_decrypt()`

| **Parameters** | `password`: password (str)<br />`salt_hex`: salt (hex str)<br />`iv_hex`: IV (hex str)<br />`ciphertext_hex`: ciphertext (hex str) |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| **Returns**    | bytes (decrypted plaintext)                                                                                                        |

```python theme={null}
from iqlabs import crypto

# Encrypt with password
encrypted = crypto.password_encrypt('my-password', b'secret data')

# Decrypt with same password
decrypted = crypto.password_decrypt(
    'my-password',
    encrypted['salt'],
    encrypted['iv'],
    encrypted['ciphertext']
)
```

***

#### `multi_encrypt()`

| **Parameters** | `recipient_pub_hexes`: recipient public keys (list\[str])<br />`plaintext`: data to encrypt (bytes) |
| -------------- | --------------------------------------------------------------------------------------------------- |
| **Returns**    | dict with `recipients` (list\[RecipientEntry]), `iv`, `ciphertext`                                  |

#### `multi_decrypt()`

| **Parameters** | `priv_key`: your private key (bytes)<br />`pub_key_hex`: your public key (hex str)<br />`encrypted`: the MultiEncryptResult dict |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| **Returns**    | bytes (decrypted plaintext)                                                                                                      |

```python theme={null}
from iqlabs import crypto

# Encrypt for multiple recipients
encrypted = crypto.multi_encrypt(
    [alice_pub_hex, bob_pub_hex, carol_pub_hex],
    b'group secret'
)

# Each recipient decrypts with their own key
plaintext = crypto.multi_decrypt(alice_priv_key, alice_pub_hex, encrypted)
```

***

### Environment Settings

#### `set_rpc_url()`

| **Parameters** | `url`: Solana RPC URL (str) |
| -------------- | --------------------------- |
| **Returns**    | None                        |

```python theme={null}
from iqlabs import set_rpc_url

set_rpc_url('https://your-rpc.example.com')
```

***

## Advanced Functions

<Warning>
  These are low-level SDK functions. Not needed for typical usage, but useful when building custom features or debugging.
</Warning>

### Writer Functions

#### `manage_row_data()`

Unified function that handles both table row writes and connection row writes. Auto-detects whether to write to a table or connection based on existing PDAs.

| **Module**     | `writer`                                                                                                                                                                                                                                                                                                                  |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Parameters** | `connection`: AsyncClient<br />`signer`: Keypair or WalletSigner<br />`db_root_id`: database ID (bytes or str)<br />`seed`: table or connection seed (bytes or str)<br />`row_json`: JSON row data (str)<br />`table_name`: required for table edits (optional)<br />`target_tx`: reference tx for table edits (optional) |
| **Returns**    | Transaction signature (str)                                                                                                                                                                                                                                                                                               |
| **Use Case**   | Custom row management, updating existing rows                                                                                                                                                                                                                                                                             |

```python theme={null}
from iqlabs import writer
import json

await writer.manage_row_data(
    connection, signer,
    'my-db',       # db_root_id
    'users',       # seed (table seed)
    json.dumps({"id": 1, "name": "Updated Name"}),
    table_name='users',
    target_tx=original_tx_sig
)
```

***

### Reader Functions

#### `read_user_state()`

Reads the UserState PDA for a given user.

| **Module**     | `reader`                                                             |
| -------------- | -------------------------------------------------------------------- |
| **Parameters** | `user_pubkey`: user public key (str)                                 |
| **Returns**    | dict with `owner`, `metadata`, `total_session_files`, `profile_data` |
| **Use Case**   | Fetching user profile data, checking upload counts                   |

```python theme={null}
from iqlabs import reader

user_state = await reader.read_user_state(user_pubkey)
print('Owner:', user_state['owner'])
print('Session files:', user_state['total_session_files'])
print('Profile data:', user_state['profile_data'])
```

***

#### `read_inventory_metadata()`

Reads metadata associated with a user's inventory transaction.

| **Module**     | `reader`                                             |
| -------------- | ---------------------------------------------------- |
| **Parameters** | `tx_signature`: transaction signature (str)          |
| **Returns**    | dict with metadata                                   |
| **Use Case**   | Extracting file metadata from inventory transactions |

```python theme={null}
from iqlabs import reader

result = await reader.read_inventory_metadata(tx_signature)
print('Metadata:', result)
```

***

#### `get_session_pda_list()`

Retrieves a list of session PDA addresses for a user.

| **Module**     | `reader`                                    |
| -------------- | ------------------------------------------- |
| **Parameters** | `user_pubkey`: user public key (str)        |
| **Returns**    | `list[str]` (session PDA base58 strings)    |
| **Use Case**   | Session management, active session tracking |

```python theme={null}
from iqlabs import reader

sessions = await reader.get_session_pda_list(user_pubkey)
for pda in sessions:
    print(f"Session PDA: {pda}")
```

***

#### Contract PDA Functions

Low-level PDA derivation functions available in the `contract` module.

| **Module**   | `contract`                             |
| ------------ | -------------------------------------- |
| **Use Case** | Custom PDA derivation, account lookups |

```python theme={null}
from iqlabs import contract
from solders.pubkey import Pubkey

program_id = contract.get_program_id()
db_root_pda = contract.get_db_root_pda(db_root_id, program_id)
table_pda = contract.get_table_pda(db_root_pda, table_seed, program_id)
user_pda = contract.get_user_pda(user_pubkey, program_id)
session_pda = contract.get_session_pda(user_pubkey, seq=0, program_id=program_id)
connection_pda = contract.get_connection_table_pda(db_root_pda, connection_seed, program_id)
```

***

### Utility Functions

#### Session speed profiles

Many writer/reader functions accept a `speed` parameter that controls RPS
and concurrency for the call. The SDK ships four presets and lets you
override raw values directly:

```python theme={null}
import iqlabs

# Preset name
await iqlabs.writer.write_row(connection, signer, db_root_id, table_seed, row_json, speed="heavy")

# Raw override — any subset of keys; missing dials inherit the default preset.
await iqlabs.writer.write_row(
    connection, signer, db_root_id, table_seed, row_json,
    speed={"max_rps": 80, "max_concurrency_upload": 30},
)
```

| Preset            | `max_rps` | `max_concurrency` | `max_concurrency_upload` |
| ----------------- | --------- | ----------------- | ------------------------ |
| `light` (default) | 2         | 5                 | 1                        |
| `medium`          | 50        | 50                | 5                        |
| `heavy`           | 100       | 100               | 50                       |
| `extreme`         | 250       | 250               | 100                      |

Profiles are mutable —
`iqlabs.utils.SESSION_SPEED_PROFILES["heavy"]["max_rps"] = 200` affects
every future `speed="heavy"` call.

Exports: `SESSION_SPEED_PROFILES`, `DEFAULT_SESSION_SPEED`,
`resolve_session_speed`, `resolve_session_config`, type
`SessionSpeedOption`.

***

#### `derive_dm_seed()`

Derives a deterministic seed for direct messaging (DM) between two users. Sorts the two pubkeys alphabetically and hashes `"lower:upper"` with Keccak-256.

| **Module**     | `utils`                                                                   |
| -------------- | ------------------------------------------------------------------------- |
| **Parameters** | `user_a`: first user pubkey (str)<br />`user_b`: second user pubkey (str) |
| **Returns**    | bytes (32-byte seed)                                                      |
| **Use Case**   | Creating consistent connection identifiers, DM channel setup              |

```python theme={null}
from iqlabs.sdk.utils.seed import derive_dm_seed

seed1 = derive_dm_seed(wallet_a, wallet_b)
seed2 = derive_dm_seed(wallet_b, wallet_a)
print(seed1 == seed2)  # True — order doesn't matter
```

***

#### `to_seed_bytes()`

Converts a seed identifier to bytes. If the input is a 64-character hex string, it passes through as-is. Otherwise, it applies Keccak-256 hash.

| **Module**     | `utils`                                            |
| -------------- | -------------------------------------------------- |
| **Parameters** | `value`: seed string or bytes                      |
| **Returns**    | bytes                                              |
| **Use Case**   | Custom PDA derivation, low-level seed manipulation |

```python theme={null}
from iqlabs.sdk.utils.seed import to_seed_bytes
from solders.pubkey import Pubkey

seed_string = 'my-custom-seed'
seed_bytes = to_seed_bytes(seed_string)

pda, bump = Pubkey.find_program_address(
    [seed_bytes, other_seed],
    program_id
)
```

***
