Native bindings for Python via PyO3. No CLI, no server, no subprocess — the Rust core runs in-process.
This document is non-normative reference implementation documentation. Package names and function signatures here do not define the OWS standard.
pip install open-wallet-standardPrebuilt wheels are available for macOS (arm64, x64) and Linux (x64, arm64) on Python 3.9–3.13.
from ows import (
generate_mnemonic,
create_wallet,
list_wallets,
sign_message,
sign_typed_data,
delete_wallet,
)
mnemonic = generate_mnemonic(12)
wallet = create_wallet("my-wallet")
sig = sign_message("my-wallet", "evm", "hello")
print(sig["signature"])All functions return Python dicts. Wallet functions return:
# WalletInfo
{
"id": "3198bc9c-...", # UUID v4
"name": "my-wallet",
"created_at": "2026-03-09T...", # ISO 8601
"accounts": [
{
"chain_id": "eip155:1",
"address": "0xab16...",
"derivation_path": "m/44'/60'/0'/0/0",
},
# ... one per supported chain
],
}
# SignResult
{
"signature": "bea6b4ee...", # Hex-encoded
"recovery_id": 0, # EVM/Tron only (None for others)
}
# SendResult
{
"tx_hash": "0xabc...",
}Generate a new BIP-39 mnemonic phrase.
phrase = generate_mnemonic(12) # or 24
# => "goose puzzle decorate much stable beach ..."Derive an address from a mnemonic without creating a wallet.
addr = derive_address(mnemonic, "evm")
# => "0xCc1e2c3D077b7c0f5301ef400bDE30d0e23dF1C6"
sol_addr = derive_address(mnemonic, "solana")
# => "DzkqyvQrBvLqKSMhCoXoGK65e9PvyWjb6YjS4BqcxN2i"| Param | Type | Default | Description |
|---|---|---|---|
mnemonic |
str |
— | BIP-39 mnemonic phrase |
chain |
str |
— | "evm", "solana", "sui", "bitcoin", "cosmos", "tron", "ton", "spark", "filecoin" |
index |
int |
0 |
Account index in derivation path |
Create a new wallet. Derives addresses for the current auto-derived chain set.
wallet = create_wallet("agent-treasury")
for acct in wallet["accounts"]:
print(f"{acct['chain_id']}: {acct['address']}")List all wallets in the vault.
wallets = list_wallets()
print(len(wallets))Look up a wallet by name or UUID.
wallet = get_wallet("agent-treasury")Delete a wallet from the vault.
delete_wallet("agent-treasury")Rename a wallet.
rename_wallet("old-name", "new-name")Export a wallet's secret.
- Mnemonic wallets return the phrase string.
- Private key wallets return a JSON string with both curve keys.
# Mnemonic wallet
phrase = export_wallet("mn-wallet")
# => "goose puzzle decorate much ..."
# Private key wallet
import json
keys = json.loads(export_wallet("pk-wallet"))
# => {"secp256k1": "4c0883a6...", "ed25519": "9d61b19d..."}Import a wallet from a BIP-39 mnemonic. Derives all 8 chain accounts via HD paths.
wallet = import_wallet_mnemonic("imported", "goose puzzle decorate ...")import_wallet_private_key(name, private_key_hex, chain=None, passphrase=None, vault_path=None, secp256k1_key=None, ed25519_key=None)
Import a wallet from a hex-encoded private key. All 8 chains are supported: the provided key is used for its curve's chains, and a random key is generated for the other curve.
The optional chain parameter specifies which chain the key originates from to determine the curve. Defaults to "evm" (secp256k1).
Alternatively, provide explicit keys for each curve via secp256k1_key and ed25519_key. When both are given, private_key_hex and chain are ignored.
# Import an EVM private key — generates a random Ed25519 key for Solana/Sui/TON
wallet = import_wallet_private_key("from-evm", "4c0883a691...")
print(len(wallet["accounts"])) # => 8
# Import a Solana private key — generates a random secp256k1 key for EVM/BTC/etc.
wallet = import_wallet_private_key(
"from-solana", "9d61b19d...", chain="solana"
)
print(len(wallet["accounts"])) # => 8
# Import explicit keys for both curves
wallet = import_wallet_private_key(
"both-keys", "",
secp256k1_key="4c0883a691...",
ed25519_key="9d61b19d..."
)
print(len(wallet["accounts"])) # => 8| Param | Type | Default | Description |
|---|---|---|---|
name |
str |
— | Wallet name |
private_key_hex |
str |
— | Hex-encoded private key. Ignored when both curve keys are provided. |
chain |
str |
"evm" |
Source chain: "evm", "bitcoin", "cosmos", "tron", "filecoin" (secp256k1) or "solana", "sui", "ton" (Ed25519) |
passphrase |
str |
None |
Encryption passphrase |
vault_path |
str |
None |
Custom vault directory |
secp256k1_key |
str |
None |
Explicit secp256k1 private key (hex) |
ed25519_key |
str |
None |
Explicit Ed25519 private key (hex) |
Sign a message with chain-specific formatting.
result = sign_message("agent-treasury", "evm", "hello world")
print(result["signature"]) # hex string
print(result["recovery_id"]) # 0 or 1Sign EIP-712 typed structured data (EVM only).
Current implementations support typed-data signing for owner-mode credentials. API-token typed-data signing is not yet supported.
import json
typed_data = json.dumps({
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "chainId", "type": "uint256"},
],
"Transfer": [
{"name": "to", "type": "address"},
{"name": "amount", "type": "uint256"},
],
},
"primaryType": "Transfer",
"domain": {"name": "MyDApp", "chainId": "1"},
"message": {"to": "0xabc...", "amount": "1000"},
})
result = sign_typed_data("agent-treasury", "evm", typed_data)
print(result["signature"]) # hex string
print(result["recovery_id"]) # 27 or 28Sign a raw transaction (hex-encoded bytes).
result = sign_transaction("agent-treasury", "evm", "02f8...")
print(result["signature"])Sign and broadcast a transaction.
result = sign_and_send(
"agent-treasury", "evm", "02f8...",
rpc_url="https://mainnet.infura.io/v3/..."
)
print(result["tx_hash"])The Python bindings also expose policy and API-key management directly:
from ows import (
create_policy,
list_policies,
get_policy,
delete_policy,
create_api_key,
list_api_keys,
revoke_api_key,
)| Function | Description |
|---|---|
create_policy(policy_json, vault_path=None) |
Register a policy from a JSON string |
list_policies(vault_path=None) |
List all registered policies |
get_policy(id, vault_path=None) |
Load a single policy by ID |
delete_policy(id, vault_path=None) |
Delete a policy |
create_api_key(name, wallet_ids, policy_ids, passphrase, expires_at=None, vault_path=None) |
Create an API key and return {"token", "id", "name"} |
list_api_keys(vault_path=None) |
List API keys (metadata only, no tokens) |
revoke_api_key(id, vault_path=None) |
Delete an API key file |
Every function accepts an optional vault_path parameter for testing or isolation:
import tempfile
import shutil
vault = tempfile.mkdtemp(prefix="ows-test-")
try:
wallet = create_wallet("test", vault_path=vault)
# ... use wallet ...
finally:
shutil.rmtree(vault)