hippius 0.2.15__tar.gz → 0.2.17__tar.gz
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.
- {hippius-0.2.15 → hippius-0.2.17}/PKG-INFO +3 -1
- {hippius-0.2.15 → hippius-0.2.17}/hippius_sdk/__init__.py +1 -1
- {hippius-0.2.15 → hippius-0.2.17}/hippius_sdk/cli_parser.py +8 -3
- {hippius-0.2.15 → hippius-0.2.17}/hippius_sdk/client.py +23 -1
- {hippius-0.2.15 → hippius-0.2.17}/hippius_sdk/config.py +4 -0
- hippius-0.2.17/hippius_sdk/db/README.md +86 -0
- hippius-0.2.17/hippius_sdk/db/env.db.template +9 -0
- hippius-0.2.17/hippius_sdk/db/migrations/20241201000001_create_key_storage_tables.sql +37 -0
- hippius-0.2.17/hippius_sdk/db/setup_database.sh +84 -0
- hippius-0.2.17/hippius_sdk/db_utils.py +59 -0
- {hippius-0.2.15 → hippius-0.2.17}/hippius_sdk/ipfs.py +189 -0
- hippius-0.2.17/hippius_sdk/key_storage.py +281 -0
- {hippius-0.2.15 → hippius-0.2.17}/hippius_sdk/substrate.py +11 -24
- {hippius-0.2.15 → hippius-0.2.17}/pyproject.toml +7 -1
- {hippius-0.2.15 → hippius-0.2.17}/README.md +0 -0
- {hippius-0.2.15 → hippius-0.2.17}/hippius_sdk/cli.py +0 -0
- {hippius-0.2.15 → hippius-0.2.17}/hippius_sdk/cli_assets.py +0 -0
- {hippius-0.2.15 → hippius-0.2.17}/hippius_sdk/cli_handlers.py +0 -0
- {hippius-0.2.15 → hippius-0.2.17}/hippius_sdk/cli_rich.py +0 -0
- {hippius-0.2.15 → hippius-0.2.17}/hippius_sdk/errors.py +0 -0
- {hippius-0.2.15 → hippius-0.2.17}/hippius_sdk/ipfs_core.py +0 -0
- {hippius-0.2.15 → hippius-0.2.17}/hippius_sdk/utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: hippius
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.17
|
4
4
|
Summary: Python SDK and CLI for Hippius blockchain storage
|
5
5
|
Home-page: https://github.com/thenervelab/hippius-sdk
|
6
6
|
Author: Dubs
|
@@ -15,6 +15,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
16
16
|
Classifier: Programming Language :: Python :: 3.9
|
17
17
|
Provides-Extra: clipboard
|
18
|
+
Provides-Extra: key-storage
|
19
|
+
Requires-Dist: asyncpg (>=0.29.0,<0.30.0) ; extra == "key-storage"
|
18
20
|
Requires-Dist: base58 (>=2.1.1,<3.0.0)
|
19
21
|
Requires-Dist: cryptography (>=44.0.0,<45.0.0)
|
20
22
|
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
@@ -81,6 +81,10 @@ examples:
|
|
81
81
|
|
82
82
|
# Delete an erasure-coded file and all its chunks
|
83
83
|
hippius ec-delete QmMetadataHash
|
84
|
+
|
85
|
+
# Configure PostgreSQL key storage
|
86
|
+
hippius config set key_storage database_url 'postgresql://user:pass@localhost:5432/hippius_keys'
|
87
|
+
hippius config set key_storage enabled true
|
84
88
|
""",
|
85
89
|
)
|
86
90
|
|
@@ -219,7 +223,8 @@ def add_storage_commands(subparsers):
|
|
219
223
|
|
220
224
|
# Add command (alias for store)
|
221
225
|
add_parser = subparsers.add_parser(
|
222
|
-
"add",
|
226
|
+
"add",
|
227
|
+
help="Upload a file to IPFS and store it on Substrate (alias for 'store')",
|
223
228
|
)
|
224
229
|
add_parser.add_argument("file_path", help="Path to file to upload")
|
225
230
|
add_parser.add_argument(
|
@@ -439,7 +444,7 @@ def add_config_commands(subparsers):
|
|
439
444
|
get_parser = config_subparsers.add_parser("get", help="Get a configuration value")
|
440
445
|
get_parser.add_argument(
|
441
446
|
"section",
|
442
|
-
help="Configuration section (ipfs, substrate, encryption, erasure_coding, cli)",
|
447
|
+
help="Configuration section (ipfs, substrate, encryption, erasure_coding, cli, key_storage)",
|
443
448
|
)
|
444
449
|
get_parser.add_argument("key", help="Configuration key")
|
445
450
|
|
@@ -447,7 +452,7 @@ def add_config_commands(subparsers):
|
|
447
452
|
set_parser = config_subparsers.add_parser("set", help="Set a configuration value")
|
448
453
|
set_parser.add_argument(
|
449
454
|
"section",
|
450
|
-
help="Configuration section (ipfs, substrate, encryption, erasure_coding, cli)",
|
455
|
+
help="Configuration section (ipfs, substrate, encryption, erasure_coding, cli, key_storage)",
|
451
456
|
)
|
452
457
|
set_parser.add_argument("key", help="Configuration key")
|
453
458
|
set_parser.add_argument("value", help="Configuration value")
|
@@ -9,7 +9,7 @@ import nacl.secret
|
|
9
9
|
import nacl.utils
|
10
10
|
|
11
11
|
from hippius_sdk.config import get_config_value, get_encryption_key
|
12
|
-
from hippius_sdk.ipfs import IPFSClient
|
12
|
+
from hippius_sdk.ipfs import IPFSClient, S3PublishResult
|
13
13
|
from hippius_sdk.substrate import SubstrateClient
|
14
14
|
|
15
15
|
|
@@ -510,3 +510,25 @@ class HippiusClient:
|
|
510
510
|
parallel_limit,
|
511
511
|
seed_phrase=seed_phrase,
|
512
512
|
)
|
513
|
+
|
514
|
+
async def s3_publish(
|
515
|
+
self, file_path: str, encrypt: bool, seed_phrase: str
|
516
|
+
) -> S3PublishResult:
|
517
|
+
"""
|
518
|
+
Publish a file to IPFS and the Hippius marketplace in one operation.
|
519
|
+
|
520
|
+
Args:
|
521
|
+
file_path: Path to the file to publish
|
522
|
+
encrypt: Whether to encrypt the file before uploading
|
523
|
+
seed_phrase: Seed phrase for blockchain transaction signing
|
524
|
+
|
525
|
+
Returns:
|
526
|
+
S3PublishResult: Object containing CID, file info, and transaction hash
|
527
|
+
|
528
|
+
Raises:
|
529
|
+
HippiusIPFSError: If IPFS operations (add or pin) fail
|
530
|
+
HippiusSubstrateError: If substrate call fails
|
531
|
+
FileNotFoundError: If the file doesn't exist
|
532
|
+
ValueError: If encryption is requested but not available
|
533
|
+
"""
|
534
|
+
return await self.ipfs_client.s3_publish(file_path, encrypt, seed_phrase)
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# Database Migrations for Hippius Key Storage
|
2
|
+
|
3
|
+
This directory contains database migrations for the Hippius SDK key storage feature.
|
4
|
+
|
5
|
+
## Setup for End Users
|
6
|
+
|
7
|
+
If you've installed hippius-sdk as a dependency, you can set up the database files in your project:
|
8
|
+
|
9
|
+
```bash
|
10
|
+
# Copy database files to your project
|
11
|
+
hippius-setup-db
|
12
|
+
|
13
|
+
# Edit the database URL
|
14
|
+
cp env.db.template .env.db
|
15
|
+
# Edit .env.db with your database credentials
|
16
|
+
|
17
|
+
# Run the setup
|
18
|
+
./setup_database.sh
|
19
|
+
```
|
20
|
+
|
21
|
+
## Development Setup
|
22
|
+
|
23
|
+
1. **Install dbmate**:
|
24
|
+
```bash
|
25
|
+
brew install dbmate
|
26
|
+
# or download directly
|
27
|
+
curl -fsSL -o /usr/local/bin/dbmate https://github.com/amacneil/dbmate/releases/latest/download/dbmate-macos-amd64
|
28
|
+
chmod +x /usr/local/bin/dbmate
|
29
|
+
```
|
30
|
+
|
31
|
+
2. **Run the setup script**:
|
32
|
+
```bash
|
33
|
+
./setup_database.sh
|
34
|
+
```
|
35
|
+
|
36
|
+
## Manual Setup
|
37
|
+
|
38
|
+
If you prefer to run migrations manually:
|
39
|
+
|
40
|
+
```bash
|
41
|
+
# Set database URL
|
42
|
+
export DATABASE_URL=postgresql://postgres:postgres@localhost:5432/hippius_keys?sslmode=disable
|
43
|
+
|
44
|
+
# Create database
|
45
|
+
createdb hippius_keys
|
46
|
+
|
47
|
+
# Run migrations
|
48
|
+
dbmate up
|
49
|
+
|
50
|
+
# Configure SDK
|
51
|
+
hippius config set key_storage database_url "postgresql://postgres:postgres@localhost:5432/hippius_keys?sslmode=disable"
|
52
|
+
hippius config set key_storage enabled true
|
53
|
+
```
|
54
|
+
|
55
|
+
## Database Schema
|
56
|
+
|
57
|
+
### Tables
|
58
|
+
|
59
|
+
- **`seed_phrases`**: Stores hashed seed phrases with base64 encoded values
|
60
|
+
- **`encryption_keys`**: Stores versioned encryption keys per seed phrase (never deleted)
|
61
|
+
|
62
|
+
### Key Features
|
63
|
+
|
64
|
+
- **Versioned keys**: New keys create new rows, old keys are never deleted
|
65
|
+
- **Efficient lookups**: Index on `(seed_hash, created_at DESC)` for fast retrieval of latest key
|
66
|
+
- **Secure storage**: Seed phrases are hashed for indexing, stored base64 encoded
|
67
|
+
- **Foreign key constraints**: Ensures data integrity between tables
|
68
|
+
|
69
|
+
## Migration Commands
|
70
|
+
|
71
|
+
```bash
|
72
|
+
# Check migration status
|
73
|
+
dbmate status
|
74
|
+
|
75
|
+
# Create new migration
|
76
|
+
dbmate new migration_name
|
77
|
+
|
78
|
+
# Run migrations
|
79
|
+
dbmate up
|
80
|
+
|
81
|
+
# Rollback last migration
|
82
|
+
dbmate down
|
83
|
+
|
84
|
+
# Reset database (drop and recreate)
|
85
|
+
dbmate drop && dbmate up
|
86
|
+
```
|
@@ -0,0 +1,37 @@
|
|
1
|
+
-- migrate:up
|
2
|
+
|
3
|
+
-- Table to store base64 encoded seed phrases (hashed for indexing)
|
4
|
+
CREATE TABLE seed_phrases (
|
5
|
+
id SERIAL PRIMARY KEY,
|
6
|
+
seed_hash VARCHAR(64) UNIQUE NOT NULL,
|
7
|
+
seed_phrase_b64 TEXT NOT NULL,
|
8
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
9
|
+
);
|
10
|
+
|
11
|
+
-- Table to store encryption keys associated with each seed phrase (versioned, never deleted)
|
12
|
+
CREATE TABLE encryption_keys (
|
13
|
+
id SERIAL PRIMARY KEY,
|
14
|
+
seed_hash VARCHAR(64) NOT NULL,
|
15
|
+
encryption_key_b64 TEXT NOT NULL,
|
16
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
17
|
+
FOREIGN KEY (seed_hash) REFERENCES seed_phrases(seed_hash) ON DELETE CASCADE
|
18
|
+
);
|
19
|
+
|
20
|
+
-- Index for efficient lookups of latest encryption key per seed phrase
|
21
|
+
CREATE INDEX idx_encryption_keys_seed_hash_created
|
22
|
+
ON encryption_keys(seed_hash, created_at DESC);
|
23
|
+
|
24
|
+
-- Comments for documentation
|
25
|
+
COMMENT ON TABLE seed_phrases IS 'Stores hashed seed phrases with base64 encoded values';
|
26
|
+
COMMENT ON TABLE encryption_keys IS 'Stores versioned encryption keys per seed phrase (never deleted, always use most recent)';
|
27
|
+
COMMENT ON COLUMN seed_phrases.seed_hash IS 'SHA-256 hash of the seed phrase for indexing';
|
28
|
+
COMMENT ON COLUMN seed_phrases.seed_phrase_b64 IS 'Base64 encoded seed phrase';
|
29
|
+
COMMENT ON COLUMN encryption_keys.seed_hash IS 'Reference to the seed phrase hash';
|
30
|
+
COMMENT ON COLUMN encryption_keys.encryption_key_b64 IS 'Base64 encoded encryption key';
|
31
|
+
|
32
|
+
-- migrate:down
|
33
|
+
|
34
|
+
-- Drop tables in reverse order due to foreign key constraints
|
35
|
+
DROP INDEX IF EXISTS idx_encryption_keys_seed_hash_created;
|
36
|
+
DROP TABLE IF EXISTS encryption_keys;
|
37
|
+
DROP TABLE IF EXISTS seed_phrases;
|
@@ -0,0 +1,84 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
# Database setup script for Hippius key storage
|
3
|
+
|
4
|
+
set -e
|
5
|
+
|
6
|
+
echo "🗄️ Setting up Hippius key storage database..."
|
7
|
+
|
8
|
+
# Check if .env.db exists, if not create from template
|
9
|
+
if [ ! -f .env.db ]; then
|
10
|
+
if [ -f hippius_sdk/db/env.db.template ]; then
|
11
|
+
echo "📋 Creating .env.db from template..."
|
12
|
+
cp hippius_sdk/db/env.db.template .env.db
|
13
|
+
echo "✏️ Please edit .env.db with your database credentials"
|
14
|
+
elif [ -f env.db.template ]; then
|
15
|
+
cp env.db.template .env.db
|
16
|
+
echo "✏️ Please edit .env.db with your database credentials"
|
17
|
+
else
|
18
|
+
echo "❌ No .env.db file found and no template available"
|
19
|
+
echo "Please create .env.db with your database connection parameters:"
|
20
|
+
echo "DB_HOST=localhost"
|
21
|
+
echo "DB_PORT=5432"
|
22
|
+
echo "DB_USER=postgres"
|
23
|
+
echo "DB_PASSWORD=your_password"
|
24
|
+
echo "DB_NAME=hippius_keys"
|
25
|
+
echo "DB_SSLMODE=disable"
|
26
|
+
exit 1
|
27
|
+
fi
|
28
|
+
fi
|
29
|
+
|
30
|
+
# Load database connection parameters from env file
|
31
|
+
source .env.db
|
32
|
+
|
33
|
+
echo "📊 Database connection details:"
|
34
|
+
echo " Host: $DB_HOST"
|
35
|
+
echo " Port: $DB_PORT"
|
36
|
+
echo " User: $DB_USER"
|
37
|
+
echo " Database: $DB_NAME"
|
38
|
+
echo " SSL Mode: $DB_SSLMODE"
|
39
|
+
|
40
|
+
# Construct DATABASE_URL for dbmate
|
41
|
+
export DATABASE_URL="postgresql://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_NAME?sslmode=$DB_SSLMODE"
|
42
|
+
|
43
|
+
# Create database if it doesn't exist
|
44
|
+
echo "📝 Creating database $DB_NAME..."
|
45
|
+
PGPASSWORD=$DB_PASSWORD createdb -h $DB_HOST -p $DB_PORT -U $DB_USER $DB_NAME 2>/dev/null || echo "Database $DB_NAME already exists"
|
46
|
+
|
47
|
+
# Run migrations with dbmate
|
48
|
+
echo "🚀 Running database migrations..."
|
49
|
+
if command -v dbmate &> /dev/null; then
|
50
|
+
dbmate up
|
51
|
+
echo "✅ Database migrations completed successfully!"
|
52
|
+
else
|
53
|
+
echo "❌ dbmate not found. Please install it first:"
|
54
|
+
echo " brew install dbmate"
|
55
|
+
echo " # or"
|
56
|
+
echo " curl -fsSL -o /usr/local/bin/dbmate https://github.com/amacneil/dbmate/releases/latest/download/dbmate-linux-amd64"
|
57
|
+
echo " chmod +x /usr/local/bin/dbmate"
|
58
|
+
exit 1
|
59
|
+
fi
|
60
|
+
|
61
|
+
# Configure Hippius SDK to use the database
|
62
|
+
echo "⚙️ Configuring Hippius SDK..."
|
63
|
+
if [ -f venv/bin/activate ]; then
|
64
|
+
source venv/bin/activate
|
65
|
+
python -c "
|
66
|
+
from hippius_sdk.config import set_config_value
|
67
|
+
set_config_value('key_storage', 'database_url', '$DATABASE_URL')
|
68
|
+
set_config_value('key_storage', 'enabled', True)
|
69
|
+
print('✅ Hippius SDK configured for key storage')
|
70
|
+
"
|
71
|
+
else
|
72
|
+
echo "⚠️ No virtual environment found. Please configure manually:"
|
73
|
+
echo "hippius config set key_storage database_url '$DATABASE_URL'"
|
74
|
+
echo "hippius config set key_storage enabled true"
|
75
|
+
fi
|
76
|
+
|
77
|
+
echo "🎉 Setup complete! Key storage is ready to use."
|
78
|
+
echo ""
|
79
|
+
echo "📋 What was set up:"
|
80
|
+
echo " - Database: hippius_keys"
|
81
|
+
echo " - Tables: seed_phrases, encryption_keys"
|
82
|
+
echo " - SDK config: key_storage enabled"
|
83
|
+
echo ""
|
84
|
+
echo "🧪 Test with: python test_key_storage.py"
|
@@ -0,0 +1,59 @@
|
|
1
|
+
"""
|
2
|
+
Database utilities for Hippius SDK key storage.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import shutil
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
|
9
|
+
def get_db_path() -> Path:
|
10
|
+
"""Get the path to the database files."""
|
11
|
+
return Path(__file__).parent / "db"
|
12
|
+
|
13
|
+
|
14
|
+
def setup_db_cli() -> None:
|
15
|
+
"""CLI command to set up database files in current directory."""
|
16
|
+
db_path = get_db_path()
|
17
|
+
|
18
|
+
print("🗄️ Setting up Hippius database files...")
|
19
|
+
|
20
|
+
# Copy database files to current directory
|
21
|
+
current_dir = Path.cwd()
|
22
|
+
|
23
|
+
# Copy migrations to hippius_s3/sql/sdk_migrations
|
24
|
+
migrations_dest = current_dir / "hippius_s3" / "sql" / "sdk_migrations"
|
25
|
+
migrations_dest.mkdir(parents=True, exist_ok=True)
|
26
|
+
|
27
|
+
migrations_src = db_path / "migrations"
|
28
|
+
if migrations_src.exists():
|
29
|
+
for migration_file in migrations_src.glob("*.sql"):
|
30
|
+
shutil.copy2(migration_file, migrations_dest)
|
31
|
+
print(f"📁 Copied migrations to {migrations_dest}")
|
32
|
+
|
33
|
+
# Copy setup script
|
34
|
+
setup_script_src = db_path / "setup_database.sh"
|
35
|
+
setup_script_dest = current_dir / "setup_database.sh"
|
36
|
+
if setup_script_src.exists():
|
37
|
+
shutil.copy2(setup_script_src, setup_script_dest)
|
38
|
+
setup_script_dest.chmod(0o755)
|
39
|
+
print(f"🔧 Copied setup script to {setup_script_dest}")
|
40
|
+
|
41
|
+
# Copy env template
|
42
|
+
env_template_src = db_path / "env.db.template"
|
43
|
+
env_template_dest = current_dir / "env.db.template"
|
44
|
+
if env_template_src.exists():
|
45
|
+
shutil.copy2(env_template_src, env_template_dest)
|
46
|
+
print(f"📝 Copied env template to {env_template_dest}")
|
47
|
+
|
48
|
+
print()
|
49
|
+
print("✅ Database setup files copied successfully!")
|
50
|
+
print()
|
51
|
+
print("🚀 Next steps:")
|
52
|
+
print(
|
53
|
+
" 1. Copy env.db.template to .env.db and edit with your database credentials"
|
54
|
+
)
|
55
|
+
print(" 2. Run: ./setup_database.sh")
|
56
|
+
|
57
|
+
|
58
|
+
if __name__ == "__main__":
|
59
|
+
setup_db_cli()
|
@@ -2,8 +2,10 @@
|
|
2
2
|
IPFS operations for the Hippius SDK.
|
3
3
|
"""
|
4
4
|
import asyncio
|
5
|
+
import base64
|
5
6
|
import hashlib
|
6
7
|
import json
|
8
|
+
import logging
|
7
9
|
import os
|
8
10
|
import random
|
9
11
|
import shutil
|
@@ -13,8 +15,10 @@ import uuid
|
|
13
15
|
from typing import Any, Callable, Dict, List, Optional
|
14
16
|
|
15
17
|
import httpx
|
18
|
+
from pydantic import BaseModel
|
16
19
|
|
17
20
|
from hippius_sdk.config import get_config_value, get_encryption_key
|
21
|
+
from hippius_sdk.errors import HippiusIPFSError, HippiusSubstrateError
|
18
22
|
from hippius_sdk.ipfs_core import AsyncIPFSClient
|
19
23
|
from hippius_sdk.substrate import FileInput, SubstrateClient
|
20
24
|
from hippius_sdk.utils import format_cid, format_size
|
@@ -43,6 +47,20 @@ PARALLEL_ORIGINAL_CHUNKS = (
|
|
43
47
|
)
|
44
48
|
|
45
49
|
|
50
|
+
class S3PublishResult(BaseModel):
|
51
|
+
"""Result model for s3_publish method."""
|
52
|
+
|
53
|
+
cid: str
|
54
|
+
file_name: str
|
55
|
+
size_bytes: int
|
56
|
+
encryption_key: Optional[str]
|
57
|
+
tx_hash: str
|
58
|
+
|
59
|
+
|
60
|
+
# Set up logger for this module
|
61
|
+
logger = logging.getLogger(__name__)
|
62
|
+
|
63
|
+
|
46
64
|
class IPFSClient:
|
47
65
|
"""Client for interacting with IPFS."""
|
48
66
|
|
@@ -513,6 +531,9 @@ class IPFSClient:
|
|
513
531
|
raise RuntimeError(f"Failed to download directory: {str(e)}")
|
514
532
|
else:
|
515
533
|
# Regular file download
|
534
|
+
# Create parent directories if they don't exist
|
535
|
+
os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
|
536
|
+
|
516
537
|
retries = 0
|
517
538
|
while retries < max_retries:
|
518
539
|
try:
|
@@ -1890,3 +1911,171 @@ class IPFSClient:
|
|
1890
1911
|
# In either case, we report success
|
1891
1912
|
print("Delete EC file operation completed successfully")
|
1892
1913
|
return True
|
1914
|
+
|
1915
|
+
async def s3_publish(
|
1916
|
+
self, file_path: str, encrypt: bool, seed_phrase: str
|
1917
|
+
) -> S3PublishResult:
|
1918
|
+
"""
|
1919
|
+
Publish a file to IPFS and the Hippius marketplace in one operation.
|
1920
|
+
|
1921
|
+
This method automatically manages encryption keys per seed phrase:
|
1922
|
+
- If encrypt=True, it will get or generate an encryption key for the seed phrase
|
1923
|
+
- Keys are stored in PostgreSQL and versioned (never deleted)
|
1924
|
+
- Always uses the most recent key for a seed phrase
|
1925
|
+
|
1926
|
+
Args:
|
1927
|
+
file_path: Path to the file to publish
|
1928
|
+
encrypt: Whether to encrypt the file before uploading
|
1929
|
+
seed_phrase: Seed phrase for blockchain transaction signing
|
1930
|
+
|
1931
|
+
Returns:
|
1932
|
+
S3PublishResult: Object containing CID, file info, and transaction hash
|
1933
|
+
|
1934
|
+
Raises:
|
1935
|
+
HippiusIPFSError: If IPFS operations (add or pin) fail
|
1936
|
+
HippiusSubstrateError: If substrate call fails
|
1937
|
+
FileNotFoundError: If the file doesn't exist
|
1938
|
+
ValueError: If encryption is requested but not available
|
1939
|
+
"""
|
1940
|
+
# Check if file exists and get initial info
|
1941
|
+
if not os.path.exists(file_path):
|
1942
|
+
raise FileNotFoundError(f"File {file_path} not found")
|
1943
|
+
|
1944
|
+
# Get file info
|
1945
|
+
filename = os.path.basename(file_path)
|
1946
|
+
size_bytes = os.path.getsize(file_path)
|
1947
|
+
|
1948
|
+
# Handle encryption if requested with automatic key management
|
1949
|
+
encryption_key_used = None
|
1950
|
+
if encrypt:
|
1951
|
+
# Check if key storage is enabled and available
|
1952
|
+
key_storage_available = False
|
1953
|
+
try:
|
1954
|
+
from hippius_sdk.key_storage import (
|
1955
|
+
generate_and_store_key_for_seed,
|
1956
|
+
get_key_for_seed,
|
1957
|
+
is_key_storage_enabled,
|
1958
|
+
)
|
1959
|
+
|
1960
|
+
key_storage_available = is_key_storage_enabled()
|
1961
|
+
logger.debug(f"Key storage enabled: {key_storage_available}")
|
1962
|
+
except ImportError:
|
1963
|
+
logger.debug("Key storage module not available")
|
1964
|
+
key_storage_available = False
|
1965
|
+
|
1966
|
+
if key_storage_available:
|
1967
|
+
# Use PostgreSQL-backed key storage
|
1968
|
+
logger.info(f"Using PostgreSQL key storage for seed phrase")
|
1969
|
+
|
1970
|
+
# Try to get existing key for this seed phrase
|
1971
|
+
existing_key_b64 = await get_key_for_seed(seed_phrase)
|
1972
|
+
|
1973
|
+
if existing_key_b64:
|
1974
|
+
# Use existing key
|
1975
|
+
logger.debug("Using existing encryption key for seed phrase")
|
1976
|
+
encryption_key_bytes = base64.b64decode(existing_key_b64)
|
1977
|
+
encryption_key_used = existing_key_b64
|
1978
|
+
else:
|
1979
|
+
# Generate and store new key for this seed phrase
|
1980
|
+
logger.info("Generating new encryption key for seed phrase")
|
1981
|
+
new_key_b64 = await generate_and_store_key_for_seed(seed_phrase)
|
1982
|
+
encryption_key_bytes = base64.b64decode(new_key_b64)
|
1983
|
+
encryption_key_used = new_key_b64
|
1984
|
+
|
1985
|
+
# Read file content into memory
|
1986
|
+
with open(file_path, "rb") as f:
|
1987
|
+
file_data = f.read()
|
1988
|
+
|
1989
|
+
# Encrypt the data using the key from key storage
|
1990
|
+
import nacl.secret
|
1991
|
+
|
1992
|
+
box = nacl.secret.SecretBox(encryption_key_bytes)
|
1993
|
+
encrypted_data = box.encrypt(file_data)
|
1994
|
+
|
1995
|
+
# Overwrite the original file with encrypted data
|
1996
|
+
with open(file_path, "wb") as f:
|
1997
|
+
f.write(encrypted_data)
|
1998
|
+
else:
|
1999
|
+
# Fallback to the original encryption system if key_storage is not available
|
2000
|
+
if not self.encryption_available:
|
2001
|
+
raise ValueError(
|
2002
|
+
"Encryption requested but not available. Either install key storage with 'pip install hippius_sdk[key_storage]' or configure an encryption key with 'hippius keygen --save'"
|
2003
|
+
)
|
2004
|
+
|
2005
|
+
# Read file content into memory
|
2006
|
+
with open(file_path, "rb") as f:
|
2007
|
+
file_data = f.read()
|
2008
|
+
|
2009
|
+
# Encrypt the data using the client's encryption key
|
2010
|
+
encrypted_data = self.encrypt_data(file_data)
|
2011
|
+
|
2012
|
+
# Overwrite the original file with encrypted data
|
2013
|
+
with open(file_path, "wb") as f:
|
2014
|
+
f.write(encrypted_data)
|
2015
|
+
|
2016
|
+
# Store the encryption key for the result
|
2017
|
+
encryption_key_used = (
|
2018
|
+
base64.b64encode(self.encryption_key).decode("utf-8")
|
2019
|
+
if self.encryption_key
|
2020
|
+
else None
|
2021
|
+
)
|
2022
|
+
|
2023
|
+
# Add file to IPFS
|
2024
|
+
try:
|
2025
|
+
result = await self.client.add_file(file_path)
|
2026
|
+
cid = result["Hash"]
|
2027
|
+
except Exception as e:
|
2028
|
+
raise HippiusIPFSError(f"Failed to add file to IPFS: {str(e)}")
|
2029
|
+
|
2030
|
+
# Pin the file to IPFS
|
2031
|
+
try:
|
2032
|
+
pin_result = await self.client.pin(cid)
|
2033
|
+
except Exception as e:
|
2034
|
+
raise HippiusIPFSError(f"Failed to pin file to IPFS: {str(e)}")
|
2035
|
+
|
2036
|
+
# Publish to substrate marketplace
|
2037
|
+
try:
|
2038
|
+
# Pass the seed phrase directly to avoid password prompts for encrypted config
|
2039
|
+
substrate_client = SubstrateClient(seed_phrase=seed_phrase)
|
2040
|
+
logger.info(
|
2041
|
+
f"Submitting storage request to substrate for file: {filename}, CID: {cid}"
|
2042
|
+
)
|
2043
|
+
|
2044
|
+
tx_hash = await substrate_client.storage_request(
|
2045
|
+
files=[
|
2046
|
+
FileInput(
|
2047
|
+
file_hash=cid,
|
2048
|
+
file_name=filename,
|
2049
|
+
)
|
2050
|
+
],
|
2051
|
+
miner_ids=[],
|
2052
|
+
seed_phrase=seed_phrase,
|
2053
|
+
)
|
2054
|
+
|
2055
|
+
logger.debug(f"Substrate call result: {tx_hash}")
|
2056
|
+
|
2057
|
+
# Check if we got a valid transaction hash
|
2058
|
+
if not tx_hash or tx_hash == "0x" or len(tx_hash) < 10:
|
2059
|
+
logger.error(f"Invalid transaction hash received: {tx_hash}")
|
2060
|
+
raise HippiusSubstrateError(
|
2061
|
+
f"Invalid transaction hash received: {tx_hash}. This might indicate insufficient credits or transaction failure."
|
2062
|
+
)
|
2063
|
+
|
2064
|
+
logger.info(
|
2065
|
+
f"Successfully published to substrate with transaction: {tx_hash}"
|
2066
|
+
)
|
2067
|
+
|
2068
|
+
except Exception as e:
|
2069
|
+
logger.error(f"Substrate call failed: {str(e)}")
|
2070
|
+
logger.debug(
|
2071
|
+
"Possible causes: insufficient credits, network issues, invalid seed phrase, or substrate node unavailability"
|
2072
|
+
)
|
2073
|
+
raise HippiusSubstrateError(f"Failed to publish to substrate: {str(e)}")
|
2074
|
+
|
2075
|
+
return S3PublishResult(
|
2076
|
+
cid=cid,
|
2077
|
+
file_name=filename,
|
2078
|
+
size_bytes=size_bytes,
|
2079
|
+
encryption_key=encryption_key_used,
|
2080
|
+
tx_hash=tx_hash,
|
2081
|
+
)
|
@@ -0,0 +1,281 @@
|
|
1
|
+
"""
|
2
|
+
Key storage module for managing encryption keys per seed phrase.
|
3
|
+
|
4
|
+
This module provides PostgreSQL-backed storage for:
|
5
|
+
1. Base64 encoded seed phrases
|
6
|
+
2. Encryption keys associated with each seed phrase (versioned, never deleted)
|
7
|
+
"""
|
8
|
+
|
9
|
+
import base64
|
10
|
+
import hashlib
|
11
|
+
import os
|
12
|
+
from datetime import datetime
|
13
|
+
from typing import Optional
|
14
|
+
|
15
|
+
from hippius_sdk.config import get_config_value
|
16
|
+
|
17
|
+
# Import asyncpg with fallback
|
18
|
+
try:
|
19
|
+
import asyncpg
|
20
|
+
|
21
|
+
ASYNCPG_AVAILABLE = True
|
22
|
+
except ImportError:
|
23
|
+
ASYNCPG_AVAILABLE = False
|
24
|
+
|
25
|
+
|
26
|
+
class KeyStorageError(Exception):
|
27
|
+
"""Base exception for key storage operations."""
|
28
|
+
|
29
|
+
pass
|
30
|
+
|
31
|
+
|
32
|
+
class KeyStorage:
|
33
|
+
"""PostgreSQL-backed key storage for seed phrases and encryption keys."""
|
34
|
+
|
35
|
+
def __init__(self, database_url: Optional[str] = None):
|
36
|
+
"""
|
37
|
+
Initialize key storage with database connection.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
database_url: PostgreSQL connection URL. If None, uses config or defaults to localhost.
|
41
|
+
"""
|
42
|
+
if not ASYNCPG_AVAILABLE:
|
43
|
+
raise KeyStorageError(
|
44
|
+
"asyncpg is required for key storage. Install it with: pip install 'hippius_sdk[key_storage]'"
|
45
|
+
)
|
46
|
+
|
47
|
+
if database_url is None:
|
48
|
+
database_url = get_config_value(
|
49
|
+
"key_storage",
|
50
|
+
"database_url",
|
51
|
+
"postgresql://postgres:password@localhost:5432/hippius_keys",
|
52
|
+
)
|
53
|
+
|
54
|
+
self.database_url = database_url
|
55
|
+
|
56
|
+
async def _get_connection(self):
|
57
|
+
"""Get a database connection."""
|
58
|
+
try:
|
59
|
+
return await asyncpg.connect(self.database_url)
|
60
|
+
except Exception as e:
|
61
|
+
raise KeyStorageError(f"Failed to connect to database: {e}")
|
62
|
+
|
63
|
+
async def _ensure_tables_exist(self):
|
64
|
+
"""Create tables if they don't exist."""
|
65
|
+
create_seed_phrases_table = """
|
66
|
+
CREATE TABLE IF NOT EXISTS seed_phrases (
|
67
|
+
id SERIAL PRIMARY KEY,
|
68
|
+
seed_hash VARCHAR(64) UNIQUE NOT NULL,
|
69
|
+
seed_phrase_b64 TEXT NOT NULL,
|
70
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
71
|
+
);
|
72
|
+
"""
|
73
|
+
|
74
|
+
create_encryption_keys_table = """
|
75
|
+
CREATE TABLE IF NOT EXISTS encryption_keys (
|
76
|
+
id SERIAL PRIMARY KEY,
|
77
|
+
seed_hash VARCHAR(64) NOT NULL,
|
78
|
+
encryption_key_b64 TEXT NOT NULL,
|
79
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
80
|
+
FOREIGN KEY (seed_hash) REFERENCES seed_phrases(seed_hash)
|
81
|
+
);
|
82
|
+
"""
|
83
|
+
|
84
|
+
create_index = """
|
85
|
+
CREATE INDEX IF NOT EXISTS idx_encryption_keys_seed_hash_created
|
86
|
+
ON encryption_keys(seed_hash, created_at DESC);
|
87
|
+
"""
|
88
|
+
|
89
|
+
try:
|
90
|
+
conn = await self._get_connection()
|
91
|
+
try:
|
92
|
+
await conn.execute(create_seed_phrases_table)
|
93
|
+
await conn.execute(create_encryption_keys_table)
|
94
|
+
await conn.execute(create_index)
|
95
|
+
finally:
|
96
|
+
await conn.close()
|
97
|
+
except Exception as e:
|
98
|
+
raise KeyStorageError(f"Failed to create tables: {e}")
|
99
|
+
|
100
|
+
def _hash_seed_phrase(self, seed_phrase: str) -> str:
|
101
|
+
"""Create a SHA-256 hash of the seed phrase for indexing."""
|
102
|
+
return hashlib.sha256(seed_phrase.encode("utf-8")).hexdigest()
|
103
|
+
|
104
|
+
async def _ensure_seed_phrase_exists(self, seed_phrase: str) -> str:
|
105
|
+
"""Ensure seed phrase exists in database and return its hash."""
|
106
|
+
seed_hash = self._hash_seed_phrase(seed_phrase)
|
107
|
+
seed_phrase_b64 = base64.b64encode(seed_phrase.encode("utf-8")).decode("utf-8")
|
108
|
+
|
109
|
+
try:
|
110
|
+
conn = await self._get_connection()
|
111
|
+
try:
|
112
|
+
# Try to insert, ignore if already exists
|
113
|
+
await conn.execute(
|
114
|
+
"""
|
115
|
+
INSERT INTO seed_phrases (seed_hash, seed_phrase_b64)
|
116
|
+
VALUES ($1, $2)
|
117
|
+
ON CONFLICT (seed_hash) DO NOTHING
|
118
|
+
""",
|
119
|
+
seed_hash,
|
120
|
+
seed_phrase_b64,
|
121
|
+
)
|
122
|
+
finally:
|
123
|
+
await conn.close()
|
124
|
+
return seed_hash
|
125
|
+
except Exception as e:
|
126
|
+
raise KeyStorageError(f"Failed to store seed phrase: {e}")
|
127
|
+
|
128
|
+
async def set_key_for_seed(self, seed_phrase: str, encryption_key_b64: str) -> None:
|
129
|
+
"""
|
130
|
+
Store a new encryption key for a seed phrase.
|
131
|
+
|
132
|
+
Creates a new row (doesn't update existing ones) to maintain key history.
|
133
|
+
|
134
|
+
Args:
|
135
|
+
seed_phrase: The seed phrase
|
136
|
+
encryption_key_b64: Base64-encoded encryption key
|
137
|
+
|
138
|
+
Raises:
|
139
|
+
KeyStorageError: If storage fails
|
140
|
+
"""
|
141
|
+
await self._ensure_tables_exist()
|
142
|
+
seed_hash = await self._ensure_seed_phrase_exists(seed_phrase)
|
143
|
+
|
144
|
+
try:
|
145
|
+
conn = await self._get_connection()
|
146
|
+
try:
|
147
|
+
await conn.execute(
|
148
|
+
"""
|
149
|
+
INSERT INTO encryption_keys (seed_hash, encryption_key_b64)
|
150
|
+
VALUES ($1, $2)
|
151
|
+
""",
|
152
|
+
seed_hash,
|
153
|
+
encryption_key_b64,
|
154
|
+
)
|
155
|
+
finally:
|
156
|
+
await conn.close()
|
157
|
+
except Exception as e:
|
158
|
+
raise KeyStorageError(f"Failed to store encryption key: {e}")
|
159
|
+
|
160
|
+
async def get_key_for_seed(self, seed_phrase: str) -> Optional[str]:
|
161
|
+
"""
|
162
|
+
Get the most recent encryption key for a seed phrase.
|
163
|
+
|
164
|
+
Args:
|
165
|
+
seed_phrase: The seed phrase
|
166
|
+
|
167
|
+
Returns:
|
168
|
+
Base64-encoded encryption key or None if not found
|
169
|
+
|
170
|
+
Raises:
|
171
|
+
KeyStorageError: If database operation fails
|
172
|
+
"""
|
173
|
+
await self._ensure_tables_exist()
|
174
|
+
seed_hash = self._hash_seed_phrase(seed_phrase)
|
175
|
+
|
176
|
+
try:
|
177
|
+
conn = await self._get_connection()
|
178
|
+
try:
|
179
|
+
result = await conn.fetchrow(
|
180
|
+
"""
|
181
|
+
SELECT encryption_key_b64
|
182
|
+
FROM encryption_keys
|
183
|
+
WHERE seed_hash = $1
|
184
|
+
ORDER BY created_at DESC
|
185
|
+
LIMIT 1
|
186
|
+
""",
|
187
|
+
seed_hash,
|
188
|
+
)
|
189
|
+
|
190
|
+
return result["encryption_key_b64"] if result else None
|
191
|
+
finally:
|
192
|
+
await conn.close()
|
193
|
+
except Exception as e:
|
194
|
+
raise KeyStorageError(f"Failed to retrieve encryption key: {e}")
|
195
|
+
|
196
|
+
async def generate_and_store_key_for_seed(self, seed_phrase: str) -> str:
|
197
|
+
"""
|
198
|
+
Generate a new encryption key and store it for the seed phrase.
|
199
|
+
|
200
|
+
Args:
|
201
|
+
seed_phrase: The seed phrase
|
202
|
+
|
203
|
+
Returns:
|
204
|
+
Base64-encoded encryption key that was generated and stored
|
205
|
+
|
206
|
+
Raises:
|
207
|
+
KeyStorageError: If generation or storage fails
|
208
|
+
"""
|
209
|
+
# Generate a new encryption key
|
210
|
+
try:
|
211
|
+
import nacl.secret
|
212
|
+
import nacl.utils
|
213
|
+
|
214
|
+
# Generate a random key
|
215
|
+
key = nacl.utils.random(nacl.secret.SecretBox.KEY_SIZE)
|
216
|
+
key_b64 = base64.b64encode(key).decode("utf-8")
|
217
|
+
|
218
|
+
# Store it
|
219
|
+
await self.set_key_for_seed(seed_phrase, key_b64)
|
220
|
+
|
221
|
+
return key_b64
|
222
|
+
except ImportError:
|
223
|
+
raise KeyStorageError(
|
224
|
+
"PyNaCl is required for key generation. Install it with: pip install pynacl"
|
225
|
+
)
|
226
|
+
except Exception as e:
|
227
|
+
raise KeyStorageError(f"Failed to generate encryption key: {e}")
|
228
|
+
|
229
|
+
|
230
|
+
# Module-level convenience functions
|
231
|
+
_default_storage = None
|
232
|
+
|
233
|
+
|
234
|
+
def is_key_storage_enabled() -> bool:
|
235
|
+
"""Check if key storage is enabled in configuration."""
|
236
|
+
return get_config_value("key_storage", "enabled", False)
|
237
|
+
|
238
|
+
|
239
|
+
def get_default_storage() -> KeyStorage:
|
240
|
+
"""Get the default KeyStorage instance."""
|
241
|
+
global _default_storage
|
242
|
+
if _default_storage is None:
|
243
|
+
_default_storage = KeyStorage()
|
244
|
+
return _default_storage
|
245
|
+
|
246
|
+
|
247
|
+
async def get_key_for_seed(seed_phrase: str) -> Optional[str]:
|
248
|
+
"""
|
249
|
+
Get the most recent encryption key for a seed phrase.
|
250
|
+
|
251
|
+
Args:
|
252
|
+
seed_phrase: The seed phrase
|
253
|
+
|
254
|
+
Returns:
|
255
|
+
Base64-encoded encryption key or None if not found
|
256
|
+
"""
|
257
|
+
return await get_default_storage().get_key_for_seed(seed_phrase)
|
258
|
+
|
259
|
+
|
260
|
+
async def set_key_for_seed(seed_phrase: str, encryption_key_b64: str) -> None:
|
261
|
+
"""
|
262
|
+
Store a new encryption key for a seed phrase.
|
263
|
+
|
264
|
+
Args:
|
265
|
+
seed_phrase: The seed phrase
|
266
|
+
encryption_key_b64: Base64-encoded encryption key
|
267
|
+
"""
|
268
|
+
return await get_default_storage().set_key_for_seed(seed_phrase, encryption_key_b64)
|
269
|
+
|
270
|
+
|
271
|
+
async def generate_and_store_key_for_seed(seed_phrase: str) -> str:
|
272
|
+
"""
|
273
|
+
Generate a new encryption key and store it for the seed phrase.
|
274
|
+
|
275
|
+
Args:
|
276
|
+
seed_phrase: The seed phrase
|
277
|
+
|
278
|
+
Returns:
|
279
|
+
Base64-encoded encryption key that was generated and stored
|
280
|
+
"""
|
281
|
+
return await get_default_storage().generate_and_store_key_for_seed(seed_phrase)
|
@@ -69,6 +69,7 @@ class SubstrateClient:
|
|
69
69
|
url: Optional[str] = None,
|
70
70
|
password: Optional[str] = None,
|
71
71
|
account_name: Optional[str] = None,
|
72
|
+
seed_phrase: Optional[str] = None,
|
72
73
|
):
|
73
74
|
"""
|
74
75
|
Initialize the Substrate client.
|
@@ -77,6 +78,7 @@ class SubstrateClient:
|
|
77
78
|
url: WebSocket URL of the Hippius substrate node (from config if None)
|
78
79
|
password: Optional password to decrypt the seed phrase if it's encrypted
|
79
80
|
account_name: Optional name of the account to use (uses active account if None)
|
81
|
+
seed_phrase: Optional unencrypted seed phrase to use directly (bypasses config)
|
80
82
|
"""
|
81
83
|
# Load configuration values if not explicitly provided
|
82
84
|
if url is None:
|
@@ -96,8 +98,7 @@ class SubstrateClient:
|
|
96
98
|
if addr:
|
97
99
|
self._account_address = addr
|
98
100
|
|
99
|
-
|
100
|
-
self._seed_phrase = None
|
101
|
+
self._seed_phrase = seed_phrase # Use passed seed phrase if provided
|
101
102
|
|
102
103
|
# Don't connect immediately to avoid exceptions during initialization
|
103
104
|
# Connection will happen lazily when needed
|
@@ -595,33 +596,19 @@ class SubstrateClient:
|
|
595
596
|
}
|
596
597
|
|
597
598
|
# Create the call to the marketplace
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
)
|
604
|
-
except Exception as e:
|
605
|
-
print(f"Warning: Error composing call: {e}")
|
606
|
-
print("Attempting to use IpfsPallet.storeFile instead...")
|
607
|
-
|
608
|
-
# Try with IpfsPallet.storeFile as an alternative
|
609
|
-
alt_call_params = {
|
610
|
-
"fileHash": files_list_cid,
|
611
|
-
"fileName": f"files_list_{uuid.uuid4()}", # Generate a unique ID
|
612
|
-
}
|
613
|
-
call = self._substrate.compose_call(
|
614
|
-
call_module="IpfsPallet",
|
615
|
-
call_function="storeFile",
|
616
|
-
call_params=alt_call_params,
|
617
|
-
)
|
599
|
+
call = self._substrate.compose_call(
|
600
|
+
call_module="Marketplace",
|
601
|
+
call_function="storage_request",
|
602
|
+
call_params=call_params,
|
603
|
+
)
|
618
604
|
|
619
605
|
# Get payment info to estimate the fee
|
620
606
|
payment_info = self._substrate.get_payment_info(
|
621
|
-
call=call,
|
607
|
+
call=call,
|
608
|
+
keypair=self._keypair,
|
622
609
|
)
|
623
610
|
|
624
|
-
print(f"Payment info: {json.dumps(payment_info, indent=2)}")
|
611
|
+
print(f"]Payment info: {json.dumps(payment_info, indent=2)}")
|
625
612
|
|
626
613
|
# Convert partialFee from Substrate (10^18 units) to a more readable format
|
627
614
|
estimated_fee = payment_info.get("partialFee", 0)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "hippius"
|
3
|
-
version = "0.2.
|
3
|
+
version = "0.2.17"
|
4
4
|
description = "Python SDK and CLI for Hippius blockchain storage"
|
5
5
|
authors = ["Dubs <dubs@dubs.rs>"]
|
6
6
|
readme = "README.md"
|
@@ -16,6 +16,9 @@ classifiers = [
|
|
16
16
|
"Programming Language :: Python :: 3.10",
|
17
17
|
]
|
18
18
|
packages = [{include = "hippius_sdk"}]
|
19
|
+
include = [
|
20
|
+
"hippius_sdk/db/**/*",
|
21
|
+
]
|
19
22
|
|
20
23
|
[tool.poetry.dependencies]
|
21
24
|
python = ">=3.8,<3.9.0 || >3.9.1,<3.14"
|
@@ -28,16 +31,19 @@ base58 = "^2.1.1"
|
|
28
31
|
pynacl = "^1.5.0"
|
29
32
|
cryptography = "^44.0.0"
|
30
33
|
pyperclip = {version = "^1.8.2", optional = true}
|
34
|
+
asyncpg = {version = "^0.29.0", optional = true}
|
31
35
|
zfec = "^1.5.3"
|
32
36
|
mnemonic = "^0.20"
|
33
37
|
rich = "^14.0.0"
|
34
38
|
|
35
39
|
[tool.poetry.extras]
|
36
40
|
clipboard = ["pyperclip"]
|
41
|
+
key_storage = ["asyncpg"]
|
37
42
|
|
38
43
|
[tool.poetry.scripts]
|
39
44
|
hippius = "hippius_sdk.cli:main"
|
40
45
|
hippius-keygen = "hippius_sdk.cli:key_generation_cli"
|
46
|
+
hippius-setup-db = "hippius_sdk.db_utils:setup_db_cli"
|
41
47
|
|
42
48
|
[tool.poetry.group.dev.dependencies]
|
43
49
|
pytest = "^7.0.0"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|