datadid-sdk-python 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,479 @@
1
+ Metadata-Version: 2.4
2
+ Name: datadid-sdk-python
3
+ Version: 1.0.0
4
+ Summary: Python SDK for the DataDID developer platform — Data API and DID API clients
5
+ License-Expression: ISC
6
+ Keywords: datadid,did,memo
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: httpx>=0.27
10
+
11
+ # Getting Started with datadid-sdk-python
12
+
13
+ DataDID is a developer platform that combines **decentralized identity (DID)** with user authentication and action records. The SDK provides two clients:
14
+
15
+ - **`DataClient`** — authentication, user info, and action records (`data-be.metamemo.one`)
16
+ - **`DIDClient`** — DID creation/deletion and file operations (`prodidapi.memolabs.org`)
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ pip install datadid-sdk-python
24
+ ```
25
+
26
+ > **Requirements**: Python 3.10+
27
+
28
+ ---
29
+
30
+ ## Quick Start
31
+
32
+ ```python
33
+ import asyncio
34
+ from src import DataClient
35
+
36
+ async def main():
37
+ client = DataClient.production()
38
+
39
+ # Log in — the client stores the access token automatically
40
+ tokens = await client.login_with_email_password("you@example.com", "yourpassword")
41
+
42
+ # Fetch your profile
43
+ me = await client.get_me()
44
+ print(me.uid, me.role)
45
+
46
+ asyncio.run(main())
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Servers
52
+
53
+ | Client | Purpose | Production URL | Test URL |
54
+ |---|---|---|---|
55
+ | `DataClient` | Auth, user info, action records | `https://data-be.metamemo.one` | `https://test-data-be-v2.memolabs.net` |
56
+ | `DIDClient` | DID creation, file operations | `https://prodidapi.memolabs.org` | `https://testdidapi.memolabs.org` |
57
+
58
+ ---
59
+
60
+ ## Part 1 — DataClient
61
+
62
+ ### Create a client
63
+
64
+ ```python
65
+ from src import DataClient
66
+ from src.data.types import DataClientOptions
67
+
68
+ client = DataClient.production()
69
+ # or: client = DataClient.testnet()
70
+ # or: client = DataClient(DataClientOptions(base_url="https://data-be.metamemo.one"))
71
+ ```
72
+
73
+ ---
74
+
75
+ ### Login with email (verification code)
76
+
77
+ ```python
78
+ # Step 1: send a code to the user's inbox
79
+ await client.send_email_code("alice@example.com")
80
+
81
+ # Step 2: log in with the code
82
+ tokens = await client.login_with_email(
83
+ "alice@example.com",
84
+ "123456", # code from inbox
85
+ "Web", # source: a string identifying your app — e.g. "Web", "App", "Mobile"
86
+ )
87
+
88
+ print(tokens.access_token)
89
+ print(tokens.refresh_token)
90
+ ```
91
+
92
+ After a successful login the client stores the access token automatically. All subsequent calls that need authentication will use it.
93
+
94
+ ---
95
+
96
+ ### Register a new account
97
+
98
+ ```python
99
+ await client.send_email_code("bob@example.com")
100
+
101
+ tokens = await client.register_with_email(
102
+ "bob@example.com",
103
+ "123456", # code from inbox
104
+ "mypassword", # choose a password
105
+ "Web",
106
+ )
107
+ ```
108
+
109
+ ---
110
+
111
+ ### Login with email + password
112
+
113
+ ```python
114
+ tokens = await client.login_with_email_password(
115
+ "alice@example.com",
116
+ "mypassword",
117
+ )
118
+ ```
119
+
120
+ ---
121
+
122
+ ### Reset password
123
+
124
+ ```python
125
+ await client.send_email_code("alice@example.com")
126
+
127
+ await client.reset_password(
128
+ "alice@example.com",
129
+ "123456", # code from inbox
130
+ "newpassword",
131
+ )
132
+ ```
133
+
134
+ ---
135
+
136
+ ### Login with Telegram
137
+
138
+ ```python
139
+ tokens = await client.login_with_telegram(
140
+ telegram_init_data, # string from Telegram WebApp.initData
141
+ "App",
142
+ )
143
+ ```
144
+
145
+ ---
146
+
147
+ ### Login with Pi Browser
148
+
149
+ ```python
150
+ tokens = await client.login_with_pi(
151
+ pi_access_token, # access token from Pi Browser SDK
152
+ "App",
153
+ )
154
+ ```
155
+
156
+ ---
157
+
158
+ ### Login with EVM wallet (MetaMask, etc.)
159
+
160
+ ```python
161
+ # Step 1: request a challenge message from the server
162
+ message = await client.get_evm_challenge(
163
+ "0xYourWalletAddress",
164
+ chain_id=985, # optional
165
+ )
166
+
167
+ # Step 2: sign the message with the user's wallet
168
+ # (using eth_account or any signing library)
169
+ from eth_account.messages import encode_defunct
170
+ from eth_account import Account
171
+ signed = Account.sign_message(encode_defunct(text=message), private_key=private_key)
172
+ signature = signed.signature.hex()
173
+
174
+ # Step 3: submit the signature to log in
175
+ result = await client.login_with_evm(message, signature, "Web")
176
+
177
+ print(result.access_token)
178
+ print(result.did) # the user's DID string (e.g. "did:memo:...")
179
+ print(result.number) # the user's numeric platform ID
180
+ ```
181
+
182
+ ---
183
+
184
+ ### Refresh an access token
185
+
186
+ ```python
187
+ new_access_token = await client.refresh_token(tokens.refresh_token)
188
+ ```
189
+
190
+ ---
191
+
192
+ ### Get current user info
193
+
194
+ ```python
195
+ # Basic info (uid, email, username, role)
196
+ me = await client.get_me()
197
+ print(me.uid, me.role)
198
+
199
+ # Full profile (avatar, DID, linked social accounts, etc.)
200
+ info = await client.get_user_info()
201
+ print(info.name)
202
+ print(info.address) # numeric platform ID (e.g. "2018955010523533312")
203
+ print(info.did)
204
+ print(info.email)
205
+ print(info.telegram_info) # if linked
206
+ print(info.twitter_info) # if linked
207
+ print(info.discord_info) # if linked
208
+ print(info.pi_info) # if linked
209
+ ```
210
+
211
+ ---
212
+
213
+ ### Action records
214
+
215
+ 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.
216
+
217
+ ```python
218
+ # Check if the user has completed action #61
219
+ record = await client.get_action_record(61)
220
+ if record:
221
+ print(f"Completed at {record.time}, earned {record.points} points")
222
+ else:
223
+ print("Not completed yet")
224
+
225
+ # Mark action #61 as completed
226
+ await client.add_action_record(61)
227
+
228
+ # With extra options (some actions require additional data)
229
+ await client.add_action_record(61, {"some_option": "value"})
230
+ ```
231
+
232
+ **AliveCheck action IDs** (AliveCheck is the platform's liveness/subscription service):
233
+ - `5` — first-time AliveCheck subscription
234
+ - `6` — AliveCheck renewal
235
+
236
+ ---
237
+
238
+ ### Error handling
239
+
240
+ ```python
241
+ from src import DataDIDApiError
242
+
243
+ try:
244
+ await client.login_with_email_password("alice@example.com", "wrongpassword")
245
+ except DataDIDApiError as err:
246
+ print(str(err)) # human-readable message
247
+ print(err.status_code) # HTTP status code (e.g. 401, 500)
248
+ print(err.response_body) # raw JSON from the server
249
+ ```
250
+
251
+ ---
252
+
253
+ ### Manual token management
254
+
255
+ By default, login methods store the access token on the client automatically. You can disable this:
256
+
257
+ ```python
258
+ from src import DataClient
259
+ from src.data.types import DataClientOptions
260
+
261
+ client = DataClient(DataClientOptions(
262
+ base_url="https://data-be.metamemo.one",
263
+ disable_auto_token=True,
264
+ ))
265
+
266
+ tokens = await client.login_with_email("alice@example.com", "123456", "Web")
267
+
268
+ # Token was NOT stored automatically — set it yourself:
269
+ client.set_access_token(tokens.access_token)
270
+
271
+ # Read the current token at any time:
272
+ token = client.get_access_token()
273
+ ```
274
+
275
+ ---
276
+
277
+ ## Part 2 — DIDClient
278
+
279
+ 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.
280
+
281
+ **The pattern for every write operation:**
282
+ 1. Ask the server for a message to sign
283
+ 2. Sign that message with your wallet (free, off-chain)
284
+ 3. Submit the signature — the server does the rest
285
+
286
+ ### Create a client
287
+
288
+ ```python
289
+ from src import DIDClient
290
+
291
+ did_client = DIDClient.production()
292
+ # or: did_client = DIDClient.testnet()
293
+ ```
294
+
295
+ ---
296
+
297
+ ### Create a DID
298
+
299
+ ```python
300
+ address = "0xYourWalletAddress"
301
+
302
+ # Step 1: get the message to sign
303
+ message = await did_client.get_create_message(address)
304
+
305
+ # Step 2: sign it with your wallet (eth_account example)
306
+ from eth_account.messages import encode_defunct
307
+ from eth_account import Account
308
+ signed = Account.sign_message(encode_defunct(text=message), private_key=private_key)
309
+ signature = signed.signature.hex()
310
+
311
+ # Step 3: submit — server creates the DID on-chain
312
+ new_did = await did_client.create_did(signature, address)
313
+
314
+ print(new_did) # "did:memo:abc123..."
315
+ ```
316
+
317
+ ---
318
+
319
+ ### Check if a DID exists
320
+
321
+ ```python
322
+ result = await did_client.get_did_exists("0xYourWalletAddress")
323
+ print(result)
324
+ ```
325
+
326
+ ---
327
+
328
+ ### Get DID info
329
+
330
+ ```python
331
+ info = await did_client.get_did_info("0xYourWalletAddress")
332
+ print(info.did) # the DID string
333
+ print(info.info) # list of DIDChainInfo(address, balance, chain)
334
+ ```
335
+
336
+ ---
337
+
338
+ ### Delete a DID
339
+
340
+ ```python
341
+ my_did = "did:memo:abc123..."
342
+
343
+ # Step 1: get the message to sign
344
+ message = await did_client.get_delete_message(my_did)
345
+
346
+ # Step 2: sign it
347
+ signature = ...
348
+
349
+ # Step 3: submit
350
+ result = await did_client.delete_did(signature, my_did)
351
+ print(result.status)
352
+ ```
353
+
354
+ ---
355
+
356
+ ### Upload a file
357
+
358
+ 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.
359
+
360
+ ```python
361
+ await did_client.upload_file(file_data, "0xYourWalletAddress")
362
+ ```
363
+
364
+ ---
365
+
366
+ ### List files
367
+
368
+ ```python
369
+ files = await did_client.list_files("0xYourWalletAddress")
370
+ ```
371
+
372
+ ---
373
+
374
+ ### Download a file
375
+
376
+ ```python
377
+ file = await did_client.download_file("0xYourWalletAddress")
378
+ ```
379
+
380
+ ---
381
+
382
+ ### Upload an mfile (file with an on-chain DID)
383
+
384
+ 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.
385
+
386
+ ```python
387
+ # Step 1: create the upload request — returns a message to sign
388
+ message = await did_client.create_mfile_upload(file_data, "0xYourWalletAddress")
389
+
390
+ # Step 2: sign it
391
+ signature = ...
392
+
393
+ # Step 3: confirm the upload
394
+ result = await did_client.confirm_mfile_upload(signature, "0xYourWalletAddress")
395
+ ```
396
+
397
+ ---
398
+
399
+ ### Download an mfile
400
+
401
+ ```python
402
+ file = await did_client.download_mfile(
403
+ "did:mfile:bafkrei...", # the mfile DID
404
+ "0xYourWalletAddress",
405
+ )
406
+ ```
407
+
408
+ ---
409
+
410
+ ## Running the tests
411
+
412
+ ```bash
413
+ python tests/run.py
414
+ ```
415
+
416
+ 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.
417
+
418
+ ---
419
+
420
+ ## API Reference
421
+
422
+ ### DataClient
423
+
424
+ | Method | Description |
425
+ |---|---|
426
+ | `DataClient.production()` | Client for production |
427
+ | `DataClient.testnet()` | Client for test server |
428
+ | `set_access_token(token)` | Manually set the auth token |
429
+ | `get_access_token()` | Read the current token |
430
+ | `send_email_code(email)` | Send verification code to email |
431
+ | `login_with_email(email, code, source)` | Login with email + code |
432
+ | `register_with_email(email, code, password, source)` | Register new account |
433
+ | `login_with_email_password(email, password)` | Login with email + password |
434
+ | `reset_password(email, code, new_password)` | Reset password |
435
+ | `login_with_telegram(initdata, source)` | Login with Telegram |
436
+ | `login_with_pi(pi_access_token, source)` | Login with Pi Browser |
437
+ | `get_evm_challenge(address, chain_id?)` | Get EVM sign-in challenge |
438
+ | `login_with_evm(message, signature, source)` | Login with EVM wallet signature |
439
+ | `refresh_token(refresh_token)` | Get a new access token |
440
+ | `get_me()` | Basic user info (uid, email, role) |
441
+ | `get_user_info()` | Full user profile |
442
+ | `get_action_record(action_id)` | Get action completion record (or None) |
443
+ | `add_action_record(action_id, opts?)` | Record an action completion |
444
+
445
+ ### DIDClient
446
+
447
+ | Method | Description |
448
+ |---|---|
449
+ | `DIDClient.production()` | Client for production (`prodidapi.memolabs.org`) |
450
+ | `DIDClient.testnet()` | Client for test server (`testdidapi.memolabs.org`) |
451
+ | `get_create_message(address)` | Get message to sign before creating a DID |
452
+ | `create_did(sig, address)` | Create a DID (submit signature) |
453
+ | `create_did_admin(address)` | Admin: create DID without signature |
454
+ | `create_ton_did(address)` | Create a Ton-network DID |
455
+ | `get_did_exists(address)` | Check if a DID exists |
456
+ | `get_did_info(address)` | Get DID info and chain balances |
457
+ | `get_delete_message(did)` | Get message to sign before deleting a DID |
458
+ | `delete_did(sig, did)` | Delete a DID (submit signature) |
459
+ | `upload_file(data, address)` | Upload a file |
460
+ | `list_files(address)` | List files for an address |
461
+ | `download_file(address)` | Download a file |
462
+ | `create_mfile_upload(data, address)` | Start an mfile upload (returns message to sign) |
463
+ | `confirm_mfile_upload(sig, address)` | Confirm mfile upload (submit signature) |
464
+ | `download_mfile(mdid, address)` | Download a file by its mfile DID |
465
+
466
+ ### Error class
467
+
468
+ | Attribute | Type | Description |
469
+ |---|---|---|
470
+ | `str(err)` | `str` | Human-readable error message |
471
+ | `status_code` | `int` | HTTP status code |
472
+ | `response_body` | `Any` | Raw JSON response from server |
473
+
474
+ ---
475
+
476
+ ## Links
477
+
478
+ - [Data API reference](https://data-be.metamemo.one)
479
+ - [DID API reference (Swagger)](https://prodidapi.memolabs.org/swagger/index.html)
@@ -0,0 +1,12 @@
1
+ src/__init__.py,sha256=Dq6UTi7FrfefwVKL504HhgGLlJEKvLnkyrWOuoqL8vs,1289
2
+ src/errors.py,sha256=OvBRt9LPB8LrOc_wm4imDvhcyZvKIffUG_3WBNzn1ow,551
3
+ src/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ src/data/client.py,sha256=68JMg2puSeEQuwuOEiZ1ei8Z3rB-xhrfkTUr6WWgwRw,15103
5
+ src/data/types.py,sha256=rLBEqN3-givIePcTSR3rK8kQBxoK13fmx8NshUSQIDo,1850
6
+ src/did/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ src/did/client.py,sha256=XK3eEcjd8kh3_jnunO6HuFIpR6SnhDgI5jGiVwpOxXI,7810
8
+ src/did/types.py,sha256=GGuEAicsnm6reI_fHoQFD31RpTmWFP5itZWShoTBY_U,866
9
+ datadid_sdk_python-1.0.0.dist-info/METADATA,sha256=62NlfD_PsEZqTkl-swDrCFb9OwnbuagEo6-i5odwkvw,12801
10
+ datadid_sdk_python-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
11
+ datadid_sdk_python-1.0.0.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
12
+ datadid_sdk_python-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ src
src/__init__.py ADDED
@@ -0,0 +1,50 @@
1
+ # ── Shared ────────────────────────────────────────────────────
2
+ from .errors import DataDIDApiError
3
+
4
+ # ── Data API (data-be.metamemo.one) ───────────────────────────
5
+ from .data.client import DataClient
6
+ from .data.types import (
7
+ DataClientOptions,
8
+ AuthTokens,
9
+ EvmLoginResult,
10
+ AuthMeResponse,
11
+ UserInfo,
12
+ PiInfo,
13
+ TwitterInfo,
14
+ TelegramInfo,
15
+ DiscordInfo,
16
+ ActionRecord,
17
+ )
18
+
19
+ # ── DID API (prodidapi.memolabs.org) ──────────────────────────
20
+ from .did.client import DIDClient
21
+ from .did.types import (
22
+ DIDClientOptions,
23
+ SigMsgResponse,
24
+ CreateDIDResponse,
25
+ DeleteDIDResponse,
26
+ DIDInfoResponse,
27
+ DIDChainInfo,
28
+ )
29
+
30
+ __all__ = [
31
+ "DataDIDApiError",
32
+ "DataClient",
33
+ "DataClientOptions",
34
+ "AuthTokens",
35
+ "EvmLoginResult",
36
+ "AuthMeResponse",
37
+ "UserInfo",
38
+ "PiInfo",
39
+ "TwitterInfo",
40
+ "TelegramInfo",
41
+ "DiscordInfo",
42
+ "ActionRecord",
43
+ "DIDClient",
44
+ "DIDClientOptions",
45
+ "SigMsgResponse",
46
+ "CreateDIDResponse",
47
+ "DeleteDIDResponse",
48
+ "DIDInfoResponse",
49
+ "DIDChainInfo",
50
+ ]
src/data/__init__.py ADDED
File without changes