Skip to main content

Dynamic MPC Wallet SDK for Python — create and manage multi-party computation wallets for EVM and Solana

Project description

Dynamic Wallet SDK for Python

A Python SDK for Dynamic's MPC (Multi-Party Computation) wallet infrastructure. Create, sign with, and manage non-custodial wallets from Python — no browser required.

Guides: ARCHITECTURE.md — internals deep-dive | DEMO_GUIDE.md — build a working demo in minutes

The SDK is split into two layers:

  • dynamic_wallet_mpc — A native Rust extension (via PyO3) wrapping Dynamic's MPC library. Handles all low-level cryptographic MPC operations: key generation, signing, key refresh, import/export.
  • dynamic_wallet_sdk — Pure Python package providing API client orchestration, key backup encryption (AES-256-GCM), SSE event parsing, and chain-specific logic for EVM and SVM.

Prerequisites

  • Python 3.10+
  • Rust toolchain (for building the native extension) — install via rustup
  • MPC registry access — the native MPC crate is hosted on a private Cargo registry
  • Dynamic environment ID + API key — from the Dynamic dashboard

Setup

1. Clone and navigate

cd python/

2. Create a virtual environment

python3 -m venv .venv
source .venv/bin/activate

3. Configure the private Cargo registry

The native extension depends on an internal MPC crate hosted on a private Cargo registry.

.cargo/config.toml (already present in the repo) configures the registry URL.

Add credentials to your global Cargo config:

cat >> ~/.cargo/credentials.toml << 'EOF'
[registries.dynamic-mpc]
token = "Bearer <YOUR_MPC_REGISTRY_TOKEN>"
EOF

The registry token is stored in 1Password. Ask a team member if you don't have access.

4. Install Python dependencies

pip install -e ".[dev]"

This installs:

Dependency Purpose
httpx Async HTTP client for Dynamic API + SSE streaming
cryptography AES-256-GCM encryption, PBKDF2 key derivation, RSA-OAEP for delegated signing
eth-account EIP-712 typed data encoding
eth-hash[pycryptodome] Keccak-256 hashing for EVM address derivation and EIP-191
base58 Solana address encoding
solders Solana primitives
pytest, pytest-asyncio Testing
respx HTTP mocking for tests
maturin Rust-to-Python build tool

5. Build the native extension

maturin develop

This compiles the Rust code in src/ and installs the resulting dynamic_wallet_mpc native module into your venv. The first build downloads and compiles dependencies, which takes a few minutes. Subsequent builds are incremental (~2-5s).

To verify the build succeeded:

python -c "from dynamic_wallet_mpc import PyEcdsa, PyEd25519; print('Native module loaded')"

Running Tests

pytest

All tests run in ~2 seconds. The test suite covers:

  • test_encryption.py — AES-256-GCM encrypt/decrypt round-trips, v1/v2 compatibility, wrong-password detection
  • test_evm_utils.py — EVM address derivation from public keys, EIP-191 message formatting, EIP-55 checksum encoding, ECDSA signature serialization
  • test_svm_utils.py — Solana address derivation (base58), hex round-trips, message formatting
  • test_sse.py — SSE event stream parsing, error events, edge cases
  • test_wallet_client.py — Wallet client initialization, authentication flow (mocked), wallet map operations, address normalization

Tests don't call any live relay or Dynamic API — they exercise the pure Python logic. The native module is imported only for type checks.

Project Structure

python/
├── .cargo/config.toml              # Private MPC registry URL
├── Cargo.toml                      # Rust crate config: MPC library + pyo3 + tokio
├── pyproject.toml                  # Python package config (maturin build backend)
│
├── src/                            # Rust native extension (PyO3)
│   ├── lib.rs                      # #[pymodule] entry point, tokio runtime init
│   ├── ecdsa.rs                    # PyEcdsa — ECDSA MPC operations
│   ├── ed25519.rs                  # PyEd25519 — standard Ed25519 MPC operations
│   ├── exportable_ed25519.rs       # PyExportableEd25519 — ExportableEd25519 (used for SVM)
│   ├── types.rs                    # Python-visible types (PyInitKeygenResult, PyMessageHash, etc.)
│   ├── error.rs                    # MPCError Python exception
│   └── unsafesend.rs               # SendFuture wrapper for !Send FFI futures
│
├── dynamic_wallet_sdk/             # Pure Python SDK
│   ├── __init__.py                 # Public API re-exports
│   ├── constants.py                # URLs, headers, enums (Environment, ChainType, BackupLocation)
│   ├── mpc_config.py               # TSS schemes, chain configs, derivation paths
│   ├── exceptions.py               # Error hierarchy (DynamicSDKError, AuthenticationError, etc.)
│   │
│   ├── api/
│   │   ├── client.py               # BaseClient: dual httpx clients (API + Evervault relay)
│   │   ├── dynamic_api.py          # All Dynamic API endpoints (SSE + REST)
│   │   └── sse.py                  # SSE event stream parser
│   │
│   ├── crypto/
│   │   └── encryption.py           # AES-256-GCM + PBKDF2 key share backup encryption
│   │
│   ├── mpc/
│   │   ├── signer.py               # get_mpc_signer() factory — returns PyEcdsa or PyExportableEd25519
│   │   └── types.py                # ServerKeyShare, WalletProperties dataclasses
│   │
│   ├── wallet_client.py            # DynamicWalletClient — orchestrates keygen, sign, refresh, export, import, backup
│   │
│   ├── evm/
│   │   ├── client.py               # DynamicEvmWalletClient — EIP-191/712 signing, tx signing
│   │   └── utils.py                # EVM address derivation, message formatting, signature serialization
│   │
│   ├── svm/
│   │   ├── client.py               # DynamicSvmWalletClient — Solana message/tx signing
│   │   └── utils.py                # Solana address derivation, message formatting
│   │
│   └── delegated/
│       ├── client.py               # Delegated signing — factory + sign with delegated key shares
│       └── decrypt.py              # RSA-OAEP + AES-GCM webhook decryption
│
└── tests/
    ├── conftest.py                 # Shared fixtures
    ├── test_encryption.py
    ├── test_evm_utils.py
    ├── test_svm_utils.py
    ├── test_sse.py
    └── test_wallet_client.py

Architecture

Native Extension (dynamic_wallet_mpc)

The Rust layer exposes these classes to Python:

Class Description
PyEcdsa ECDSA MPC operations (EVM, BTC segwit)
PyEd25519 Ed25519 MPC operations
PyExportableEd25519 ExportableEd25519 MPC operations (Solana)
PyInitKeygenResult Returned by init_keygen() — contains keygen_id (str) and keygen_secret (bytes)
PyEcdsaKeygenResult ECDSA keygen output — pubkey_uncompressed, pubkey_compressed, secret_share
PyEd25519KeygenResult Ed25519 keygen output — pubkey (32 bytes), secret_share
PyEcdsaSignature ECDSA signature — r, s, v, der
PyMessageHash 32-byte message hash with sha256(), keccak256() static constructors

Key API details:

  • init_keygen(), derive_pubkey(), and export_id() are synchronous
  • keygen(), sign(), refresh(), export_full_private_key(), import_*, reshare_* are async (return Python awaitables)
  • All async operations share a single tokio runtime initialized at module load
  • The SendFuture wrapper in unsafesend.rs makes the MPC library's FFI futures compatible with pyo3_async_runtimes::tokio::future_into_py

Python SDK (dynamic_wallet_sdk)

The Python layer handles everything above the MPC primitives:

  • Authentication — Exchange API tokens for JWTs via Dynamic's API
  • Keygen orchestrationinit_keygen() → SSE call to Dynamic API → MPC keygen() → derive address
  • Sign orchestration — SSE call for signing room → MPC sign() → format chain-specific signature
  • Key backup — Encrypt key shares with AES-256-GCM (PBKDF2, 1M iterations) and store via Evervault relay
  • Key recovery — Retrieve and decrypt backed-up key shares
  • Key export/import — Full private key export via MPC, or import existing keys into MPC
  • Delegated signing — Sign using server-held delegated key shares, decrypt webhook payloads

Chain Support

Chain Algorithm Client Class Derivation Path
EVM ECDSA DynamicEvmWalletClient m/44'/60'/0'/0/0
SVM (Solana) Ed25519 DynamicSvmWalletClient m/44'/501'/0'/0/0
Cosmos Ed25519 m/44'/118'/0'/0/0
Sui Ed25519 m/44'/784'/0'/0/0
TON Ed25519 m/44'/607'/0'/0/0
Stellar Ed25519 m/44'/148'/0'/0/0
BTC (taproot) BIP340 m/86'/0'/0'/0/0
BTC (segwit) ECDSA m/84'/0'/0'/0/0

EVM and SVM have full client implementations. Other chains have config entries but need client classes.

Usage

EVM Wallet

import asyncio
from dynamic_wallet_sdk.evm.client import DynamicEvmWalletClient

async def main():
    async with DynamicEvmWalletClient("your-environment-id") as client:
        # Authenticate
        await client.authenticate_api_token("your-api-token")

        # Create wallet (keygen)
        address = await client.create_wallet_account(password="backup-password")
        print(f"Created EVM wallet: {address}")

        # Sign a message (EIP-191)
        signature = await client.sign_message(
            message="Hello, Dynamic!",
            address=address,
            password="backup-password",
        )
        print(f"Signature: {signature}")

        # Sign EIP-712 typed data
        typed_data = {
            "types": {...},
            "primaryType": "...",
            "domain": {...},
            "message": {...},
        }
        sig = await client.sign_typed_data(address, typed_data, password="backup-password")

asyncio.run(main())

SVM (Solana) Wallet

import asyncio
from dynamic_wallet_sdk.svm.client import DynamicSvmWalletClient

async def main():
    async with DynamicSvmWalletClient("your-environment-id") as client:
        await client.authenticate_api_token("your-api-token")

        address = await client.create_wallet_account(password="backup-password")
        print(f"Created Solana wallet: {address}")

        # Sign a message
        signature = await client.sign_message(
            message="Hello from Solana!",
            address=address,
            password="backup-password",
        )
        print(f"Signature (base58): {signature}")

asyncio.run(main())

Delegated Signing

For server-side signing where key shares are held by the customer's server:

from dynamic_wallet_sdk.delegated.client import (
    create_delegated_evm_client,
    delegated_sign_message,
)
from dynamic_wallet_sdk.delegated.decrypt import decrypt_delegated_webhook_data

# 1. Decrypt the webhook data you received
decrypted = decrypt_delegated_webhook_data(
    private_key_pem=rsa_private_key,
    encrypted_delegated_key_share=webhook["encryptedDelegatedShare"],
    encrypted_wallet_api_key=webhook["encryptedWalletApiKey"],
)

# 2. Create a client and sign
client = await create_delegated_evm_client("env-id", "api-key")
sig = await delegated_sign_message(
    client,
    wallet_id="wallet-id",
    wallet_api_key=decrypted.decrypted_wallet_api_key,
    key_share=decrypted.decrypted_delegated_share["secretShare"],
    message="0x...",
    chain_name="EVM",
    is_formatted=True,
)

Using the Native Module Directly

If you want to use the MPC primitives without the SDK orchestration:

from dynamic_wallet_mpc import PyEcdsa, PyEd25519, PyMessageHash

# ECDSA
ecdsa = PyEcdsa("https://relay.dynamicauth.com")
init = ecdsa.init_keygen()  # synchronous
print(init.keygen_id, len(init.keygen_secret))  # str, 32 bytes

# Ed25519
ed = PyEd25519("https://relay.dynamicauth.com")
init = ed.init_keygen()

# Hashing
h = PyMessageHash.keccak256(b"hello world")
print(h.to_hex())

h2 = PyMessageHash.sha256(b"hello world")
h3 = PyMessageHash("ab" * 32)  # from 64-char hex string

Key Concepts

Threshold Signature Schemes

The SDK supports three TSS configurations:

Scheme Parties Threshold Client Shares Server Shares
TWO_OF_TWO 2 2 1 1
TWO_OF_THREE 3 2 2 1
THREE_OF_FIVE 5 3 3 2

Wallet Map

The DynamicWalletClient maintains an in-memory _wallet_map keyed by normalized address. Always use the accessor methods:

wp = client.get_wallet_from_map(address)       # raises WalletNotFoundError if missing
client.update_wallet_in_map(address, wallet_id="new-id")

EVM addresses are normalized to lowercase. Solana addresses are stored as-is.

Key Share Backup

Key shares are encrypted with AES-256-GCM using a password-derived key (PBKDF2, SHA-256, 1M iterations). Two versions exist:

  • v1 — 100K PBKDF2 iterations (legacy)
  • v2 — 1M PBKDF2 iterations (current default)

Decryption auto-detects the version. Encrypted backups are stored through the Evervault keyshares relay.

SSE Communication

Most mutating API calls (create wallet, sign, refresh, export, import) use Server-Sent Events:

  1. Client POSTs to the API endpoint with Accept: text/event-stream
  2. Server creates an MPC room and streams events
  3. Client waits for a success event (keygen_complete or room_created)
  4. Client uses the room ID to perform the MPC operation via the Dynamic relay

Timeout is 30 seconds by default. See api/sse.py.

Build Commands

Command Description
maturin develop Build native extension in dev mode and install into venv
maturin develop --release Build with optimizations
maturin build --release Build a distributable wheel in target/wheels/
pytest Run all tests
pytest -v Run tests with verbose output
pytest tests/test_encryption.py Run a specific test file

Troubleshooting

cargo not found

Install the Rust toolchain:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env

Registry authentication failure

If you see authenticated registries require a credential-provider to be available:

  1. Verify ~/.cargo/credentials.toml has the [registries.dynamic-mpc] section with your registry token
  2. Verify .cargo/config.toml has global-credential-providers = ["cargo:token"]

maturin develop fails with compilation errors

Make sure you're using a compatible Rust version (1.75+). Update with:

rustup update stable

ImportError for dynamic_wallet_mpc

The native module wasn't built or the venv isn't activated. Run:

source .venv/bin/activate
maturin develop

Slow encryption tests

PBKDF2 with 1M iterations takes ~1-2 seconds per encrypt/decrypt. This is by design.

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

dynamic_wallet_sdk-0.1.1.tar.gz (87.3 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

dynamic_wallet_sdk-0.1.1-cp311-abi3-manylinux_2_28_x86_64.whl (4.6 MB view details)

Uploaded CPython 3.11+manylinux: glibc 2.28+ x86-64

dynamic_wallet_sdk-0.1.1-cp311-abi3-manylinux_2_28_aarch64.whl (4.6 MB view details)

Uploaded CPython 3.11+manylinux: glibc 2.28+ ARM64

dynamic_wallet_sdk-0.1.1-cp311-abi3-macosx_11_0_arm64.whl (4.8 MB view details)

Uploaded CPython 3.11+macOS 11.0+ ARM64

dynamic_wallet_sdk-0.1.1-cp311-abi3-macosx_10_12_x86_64.whl (5.0 MB view details)

Uploaded CPython 3.11+macOS 10.12+ x86-64

File details

Details for the file dynamic_wallet_sdk-0.1.1.tar.gz.

File metadata

  • Download URL: dynamic_wallet_sdk-0.1.1.tar.gz
  • Upload date:
  • Size: 87.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for dynamic_wallet_sdk-0.1.1.tar.gz
Algorithm Hash digest
SHA256 c6cc12a42f505cd8f0c4ee50ec34b750e963fae4106add3e39a48b5c767a9b5d
MD5 261a1cad2433f42c32038a1c2f21ffb6
BLAKE2b-256 91005b1fe6a62bbff0f91810dca65e1b6e09eead56038207b00f451d8ae8970f

See more details on using hashes here.

Provenance

The following attestation bundles were made for dynamic_wallet_sdk-0.1.1.tar.gz:

Publisher: python-publish.yml on dynamic-labs/dynamic-waas-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dynamic_wallet_sdk-0.1.1-cp311-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for dynamic_wallet_sdk-0.1.1-cp311-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 fd88f99b3b1e2196c8f48d341e3d378bd4a6d213d6ab84bb706d795554ed1dbd
MD5 06d94dd242525290935fbed1c9600875
BLAKE2b-256 7e265cd28caf118b62dffbec192e3f4963f4afba16a7451328f7ef6e87d48b34

See more details on using hashes here.

Provenance

The following attestation bundles were made for dynamic_wallet_sdk-0.1.1-cp311-abi3-manylinux_2_28_x86_64.whl:

Publisher: python-publish.yml on dynamic-labs/dynamic-waas-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dynamic_wallet_sdk-0.1.1-cp311-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for dynamic_wallet_sdk-0.1.1-cp311-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 e09b5d88a9a36d1b472b98c981ec74366533ad0f1111f3e248a1b5f208b21d78
MD5 c20cf8cc60aed4f03ac26cff2b955127
BLAKE2b-256 79fb8e49c191c5ebe9468cac4a8d53000296caf0af780d9a5cc6a27d837dbeba

See more details on using hashes here.

Provenance

The following attestation bundles were made for dynamic_wallet_sdk-0.1.1-cp311-abi3-manylinux_2_28_aarch64.whl:

Publisher: python-publish.yml on dynamic-labs/dynamic-waas-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dynamic_wallet_sdk-0.1.1-cp311-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for dynamic_wallet_sdk-0.1.1-cp311-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 85a65530ef86822edaa8208279a74a56a00ca71eeaaeaeaa5604410dd2cfb974
MD5 e81028ebe12b8ba32cec32228c0b9dcf
BLAKE2b-256 626fac406a8caf624a53b69a8719d4bf11d9273e36eb5ce297c2a0ae2fec5f16

See more details on using hashes here.

Provenance

The following attestation bundles were made for dynamic_wallet_sdk-0.1.1-cp311-abi3-macosx_11_0_arm64.whl:

Publisher: python-publish.yml on dynamic-labs/dynamic-waas-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dynamic_wallet_sdk-0.1.1-cp311-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for dynamic_wallet_sdk-0.1.1-cp311-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 26f6ee8f74c90500ef870c7dd5f113e4a08d412b040031e495b7849bb3bee538
MD5 7109fa34a36fd9bddd44338d2436b2cb
BLAKE2b-256 b5946c0629e5c7a55c7b7a5ddea32ddcc20a4778bac8958724211ad3659664ef

See more details on using hashes here.

Provenance

The following attestation bundles were made for dynamic_wallet_sdk-0.1.1-cp311-abi3-macosx_10_12_x86_64.whl:

Publisher: python-publish.yml on dynamic-labs/dynamic-waas-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page