Metadata-Version: 2.4
Name: datadid-sdk-python
Version: 1.0.7
Summary: Python SDK for the DataDID developer platform — Data API and DID API clients
License-Expression: ISC
Keywords: datadid,did,memo
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.27

# Getting Started with datadid-sdk-python

DataDID is a developer platform that combines **decentralized identity (DID)** with user authentication and action records. The SDK provides two clients:

- **`DataClient`** — authentication, user info, and action records (`data-api.memolabs.net`)
- **`DIDClient`** — DID creation/deletion and file operations (`prodidapi.memolabs.org`)

---

## Installation

```bash
pip install datadid-sdk-python
```

> **Requirements**: Python 3.10+

---

## Quick Start

```python
import asyncio
from datadid import DataClient

async def main():
    client = DataClient.production()

    # Log in — the client stores the access token automatically
    tokens = await client.login_with_email_password("you@example.com", "yourpassword")

    # Fetch your profile
    me = await client.get_me()
    print(me.uid, me.role)

asyncio.run(main())
```

---

## Servers

| Client | Purpose | Production URL | Test URL |
|---|---|---|---|
| `DataClient` | Auth, user info, action records | `https://data-api.memolabs.net` | `https://testdata-api.memolabs.net` |
| `DIDClient` | DID creation, file operations | `https://prodidapi.memolabs.org` | `https://testdidapi.memolabs.org` |

---

## Part 1 — DataClient

### Create a client

```python
from datadid import DataClient
from datadid.data.types import DataClientOptions

client = DataClient.production()
# or: client = DataClient.testnet()
# or: client = DataClient(DataClientOptions(base_url="https://data-api.memolabs.net"))
```

---

### Login with email (verification code)

```python
# Step 1: send a code to the user's inbox
await client.send_email_code("alice@example.com")

# Step 2: log in with the code
tokens = await client.login_with_email(
    "alice@example.com",
    "123456",  # code from inbox
    "Web",     # source: a string identifying your app — e.g. "Web", "App", "Mobile"
)

print(tokens.access_token)
print(tokens.refresh_token)
```

After a successful login the client stores the access token automatically. All subsequent calls that need authentication will use it.

---

### Register a new account

```python
await client.send_email_code("bob@example.com")

tokens = await client.register_with_email(
    "bob@example.com",
    "123456",     # code from inbox
    "mypassword", # choose a password
    "Web",
)
```

---

### Login with email + password

```python
tokens = await client.login_with_email_password(
    "alice@example.com",
    "mypassword",
)
```

---

### Reset password

```python
await client.send_email_code("alice@example.com")

await client.reset_password(
    "alice@example.com",
    "123456",      # code from inbox
    "newpassword",
)
```

---

### Login with Telegram

```python
tokens = await client.login_with_telegram(
    telegram_init_data,  # string from Telegram WebApp.initData
    "App",
)
```

---

### Login with Pi Browser

```python
tokens = await client.login_with_pi(
    pi_access_token,  # access token from Pi Browser SDK
    "App",
)
```

---

### Login with EVM wallet (MetaMask, etc.)

```python
# Step 1: request a challenge message from the server
# origin is required — use your app's own domain
message = await client.get_evm_challenge(
    "0xYourWalletAddress",
    chain_id=985,                  # optional, defaults to 985
    origin="https://myapp.com",    # required for login_with_evm to succeed
)

# Step 2: sign the message with the user's wallet
# (using eth_account or any signing library)
from eth_account.messages import encode_defunct
from eth_account import Account
signed = Account.sign_message(encode_defunct(text=message), private_key=private_key)
signature = signed.signature.hex()

# Step 3: submit the signature to log in
result = await client.login_with_evm(message, signature, "Web")

print(result.access_token)
print(result.did)     # the user's DID string (e.g. "did:memo:...")
print(result.number)  # the user's numeric platform ID
```

---

### Refresh an access token

```python
new_access_token = await client.refresh_token(tokens.refresh_token)
```

---

### Get current user info

```python
# Basic info (uid, email, username, role)
me = await client.get_me()
print(me.uid, me.role)

# Full profile (avatar, DID, linked social accounts, etc.)
info = await client.get_user_info()
print(info.name)
print(info.address)        # numeric platform ID (e.g. "2018955010523533312")
print(info.did)
print(info.email)
print(info.telegram_info)  # if linked
print(info.twitter_info)   # if linked
print(info.discord_info)   # if linked
print(info.pi_info)        # if linked
```

---

### Action records

Action records are the platform's points/achievement system. Each action has a numeric ID. When a user completes an action, a record is stored with the points earned and a Unix timestamp.

```python
# Check if the user has completed action #61
record = await client.get_action_record(61)
if record:
    print(f"Completed at {record.time}, earned {record.points} points")
else:
    print("Not completed yet")

# Mark action #61 as completed
await client.add_action_record(61)

# With extra options (some actions require additional data)
await client.add_action_record(61, {"some_option": "value"})
```

**AliveCheck action IDs** (AliveCheck is the platform's liveness/subscription service):
- `5` — first-time AliveCheck subscription
- `6` — AliveCheck renewal

---

### Error handling

```python
from datadid import DataDIDApiError

try:
    await client.login_with_email_password("alice@example.com", "wrongpassword")
except DataDIDApiError as err:
    print(str(err))          # human-readable message
    print(err.status_code)   # HTTP status code (e.g. 401, 500)
    print(err.response_body) # raw JSON from the server
```

---

### Manual token management

By default, login methods store the access token on the client automatically. You can disable this:

```python
from datadid import DataClient
from datadid.data.types import DataClientOptions

client = DataClient(DataClientOptions(
    base_url="https://data-api.memolabs.net",
    disable_auto_token=True,
))

tokens = await client.login_with_email("alice@example.com", "123456", "Web")

# Token was NOT stored automatically — set it yourself:
client.set_access_token(tokens.access_token)

# Read the current token at any time:
token = client.get_access_token()
```

---

## Part 2 — DIDClient

DID operations use a **sign-then-submit** pattern. You never send your private key to the server — you only sign a message locally, and the server pays the gas fee and submits the transaction on your behalf.

**The pattern for every write operation:**
1. Ask the server for a message to sign
2. Sign that message with your wallet (free, off-chain)
3. Submit the signature — the server does the rest

### Create a client

```python
from datadid import DIDClient

did_client = DIDClient.production()
# or: did_client = DIDClient.testnet()
```

---

### Create a DID

```python
address = "0xYourWalletAddress"

# Step 1: get the message to sign
message = await did_client.get_create_message(address)

# Step 2: sign it — the message is a hex-encoded byte string, sign the bytes
from eth_account.messages import encode_defunct
from eth_account import Account
signed = Account.sign_message(encode_defunct(primitive=bytes.fromhex(message[2:])), private_key=private_key)
signature = signed.signature.hex()

# Step 3: submit — server creates the DID on-chain
new_did = await did_client.create_did(signature, address)

print(new_did)  # "did:memo:abc123..."
```

---

### Check if a DID exists

```python
result = await did_client.get_did_exists("0xYourWalletAddress")
print(result)
```

---

### Get DID info

```python
info = await did_client.get_did_info("0xYourWalletAddress")
print(info.did)     # the DID string (e.g. "did:memo:...")
print(info.number)  # the numeric platform ID
```

---

### Delete a DID

```python
my_did = "did:memo:abc123..."

# Step 1: get the message to sign
message = await did_client.get_delete_message(my_did)

# Step 2: sign it — the message is a hex-encoded byte string, sign the bytes
signed = Account.sign_message(encode_defunct(primitive=bytes.fromhex(message[2:])), private_key=private_key)
signature = signed.signature.hex()

# Step 3: submit
result = await did_client.delete_did(signature, my_did)
print(result.status)
```

---

### Upload a file

Files are stored directly by wallet address. For files that need their own on-chain DID, see [mfile operations](#upload-an-mfile-file-with-an-on-chain-did) below.

```python
await did_client.upload_file(file_data, "0xYourWalletAddress")
```

---

### List files

```python
files = await did_client.list_files("0xYourWalletAddress")
```

---

### Download a file

```python
file = await did_client.download_file("0xYourWalletAddress")
```

---

### Upload an mfile (file with an on-chain DID)

An mfile is a file that gets its own DID minted on-chain, making it permanently addressable and verifiable on the Memo network. This uses the sign-then-submit pattern.

```python
# Step 1: create the upload request — returns a message to sign
message = await did_client.create_mfile_upload(file_data, "0xYourWalletAddress")

# Step 2: sign it
signature = ...  # sign message with your wallet

# Step 3: confirm the upload
result = await did_client.confirm_mfile_upload(signature, "0xYourWalletAddress")
```

---

### Download an mfile

```python
file = await did_client.download_mfile(
    "did:mfile:bafkrei...",  # the mfile DID
    "0xYourWalletAddress",
)
```

---

## Running the tests

```bash
python tests/run.py
```

Tests hit the real production and testnet servers. The `ACCESS_TOKEN` constant in `tests/data_client_test.py` needs to be a valid token; replace it if tests fail with a 401 error.

---

## API Reference

### DataClient

| Method | Description |
|---|---|
| `DataClient.production()` | Client for production |
| `DataClient.testnet()` | Client for test server |
| `set_access_token(token)` | Manually set the auth token |
| `get_access_token()` | Read the current token |
| `send_email_code(email)` | Send verification code to email |
| `login_with_email(email, code, source)` | Login with email + code |
| `register_with_email(email, code, password, source)` | Register new account |
| `login_with_email_password(email, password)` | Login with email + password |
| `reset_password(email, code, new_password)` | Reset password |
| `login_with_telegram(initdata, source)` | Login with Telegram |
| `login_with_pi(pi_access_token, source)` | Login with Pi Browser |
| `get_evm_challenge(address, chain_id?)` | Get EVM sign-in challenge |
| `login_with_evm(message, signature, source)` | Login with EVM wallet signature |
| `refresh_token(refresh_token)` | Get a new access token |
| `get_me()` | Basic user info (uid, email, role) |
| `get_user_info()` | Full user profile |
| `get_action_record(action_id)` | Get action completion record (or None) |
| `add_action_record(action_id, opts?)` | Record an action completion |

### DIDClient

| Method | Description |
|---|---|
| `DIDClient.production()` | Client for production (`prodidapi.memolabs.org`) |
| `DIDClient.testnet()` | Client for test server (`testdidapi.memolabs.org`) |
| `get_create_message(address)` | Get message to sign before creating a DID |
| `create_did(sig, address)` | Create a DID (submit signature) |
| `create_did_admin(address)` | Admin: create DID without signature |
| `create_ton_did(address)` | Create a Ton-network DID |
| `get_did_exists(address)` | Check if a DID exists |
| `get_did_info(address)` | Get DID info and chain balances |
| `get_delete_message(did)` | Get message to sign before deleting a DID |
| `delete_did(sig, did)` | Delete a DID (submit signature) |
| `upload_file(data, address)` | Upload a file |
| `list_files(address)` | List files for an address |
| `download_file(address)` | Download a file |
| `create_mfile_upload(data, address)` | Start an mfile upload (returns message to sign) |
| `confirm_mfile_upload(sig, address)` | Confirm mfile upload (submit signature) |
| `download_mfile(mdid, address)` | Download a file by its mfile DID |

### Error class

| Attribute | Type | Description |
|---|---|---|
| `str(err)` | `str` | Human-readable error message |
| `status_code` | `int` | HTTP status code |
| `response_body` | `Any` | Raw JSON response from server |

---

## Links

- [Data API reference](https://memolabs.gitbook.io/datadid-developer-platform-v2/en/api)
- [DID API reference (Swagger)](https://prodidapi.memolabs.org/swagger/index.html)
