Python SDK for the DataDID developer platform — Data API and DID API clients
Project description
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
pip install datadid-sdk-python
Requirements: Python 3.10+
Quick Start
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
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)
# 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
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
tokens = await client.login_with_email_password(
"alice@example.com",
"mypassword",
)
Reset password
await client.send_email_code("alice@example.com")
await client.reset_password(
"alice@example.com",
"123456", # code from inbox
"newpassword",
)
Login with Telegram
tokens = await client.login_with_telegram(
telegram_init_data, # string from Telegram WebApp.initData
"App",
)
Login with Pi Browser
tokens = await client.login_with_pi(
pi_access_token, # access token from Pi Browser SDK
"App",
)
Login with EVM wallet (MetaMask, etc.)
# 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
new_access_token = await client.refresh_token(tokens.refresh_token)
Get current user info
# 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.
# 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 subscription6— AliveCheck renewal
Error handling
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:
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:
- Ask the server for a message to sign
- Sign that message with your wallet (free, off-chain)
- Submit the signature — the server does the rest
Create a client
from datadid import DIDClient
did_client = DIDClient.production()
# or: did_client = DIDClient.testnet()
Create a DID
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
result = await did_client.get_did_exists("0xYourWalletAddress")
print(result)
Get DID info
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
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 below.
await did_client.upload_file(file_data, "0xYourWalletAddress")
List files
files = await did_client.list_files("0xYourWalletAddress")
Download a file
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.
# 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
file = await did_client.download_mfile(
"did:mfile:bafkrei...", # the mfile DID
"0xYourWalletAddress",
)
Running the tests
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
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file datadid_sdk_python-1.0.7.tar.gz.
File metadata
- Download URL: datadid_sdk_python-1.0.7.tar.gz
- Upload date:
- Size: 15.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f0992aee7d88ee22095d58618ef9b62a9e25a0e2b1ad56caf11492f0ece96858
|
|
| MD5 |
54361fe9f63a7fef1095732c630dbdc5
|
|
| BLAKE2b-256 |
341486acc782902d9f5f0a2d5f6853da85afa26425aca23f8be184be334c770c
|
File details
Details for the file datadid_sdk_python-1.0.7-py3-none-any.whl.
File metadata
- Download URL: datadid_sdk_python-1.0.7-py3-none-any.whl
- Upload date:
- Size: 13.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bdfc0e2f1ed346bc37029fdcbaaebba6ab7e50bab98a0603135bd4f23dc93c8d
|
|
| MD5 |
b551c8d81388c22389f2896c0226ea2a
|
|
| BLAKE2b-256 |
f013cc5ae6eb675d5ede2bdba0d714ba0ffafcb2cbe0f441678a3798a5d050df
|