Skip to main content

🐍 ICP-PY-CORE

ICP-PY-CORE Logo

PyPI version License: MIT Internet Computer


📖 About This Project

ICP-PY-CORE is a maintained and extended fork of ic-py.
This version introduces a modular architecture, protocol upgrades, and new APIs while preserving compatibility with the IC ecosystem.

Highlights:

  • ✅ Modular structure under src/ (icp_agent, icp_identity, icp_candid, etc.)
  • ✅ Updated boundary node endpoints (v3/v4: /api/v3/canister/.../query, /api/v4/canister/.../call)
  • ✅ Certificate verification enabled by default via blst (BLS12-381 signatures)
  • ✅ Type-safe Candid encoding/decoding with Rust-based parser (multiple times faster)
  • ✅ Pythonic high-level Agent.update() and Agent.query() methods
  • ✅ HTTP/2 support in async methods for improved performance
  • ✅ Comprehensive structured error handling hierarchy (11 error classes)
  • ✅ High-level wrappers for Ledger, Governance, Cycles Wallet, and Management canisters

🙏 Special thanks to the original ic-py author for their foundational work.

🤝 Community & Contribution


🔧 Installation

pip install icp-py-core

The Candid parser uses a Rust extension with pre-built binary wheels for all platforms.
No Rust compiler is required for installation.
For optional certificate verification, see the blst section below.


🚀 Key Improvements

✳️ Modular Codebase

Each component is isolated for clarity and extensibility:

src/
├── icp_agent/         # Agent & HTTP Client
├── icp_identity/      # ed25519 / secp256k1 identities
├── icp_candid/        # Candid encoder/decoder
├── icp_principal/     # Principal utilities
├── icp_certificate/   # Certificate validation
├── icp_core/          # Unified facade (one-line import)

🔗 Unified Facade (icp_core)

Import everything from a single entrypoint:

from icp_core import (
    Agent, Client,
    Identity, DelegateIdentity,
    Principal, Certificate,
    Canister, Ledger, Governance, Management, CyclesWallet,
    encode, decode, Types,
)

⚡ Endpoint Upgrade

All endpoints now target the latest Boundary Node versions:

  • Query: /api/v3/canister/<canister_id>/query
  • Call: /api/v4/canister/<canister_id>/call
  • Read State: /api/v3/canister/<canister_id>/read_state
  • Read Subnet State: /api/v3/subnet/<subnet_id>/read_state

🔒 Certificate Verification

Certificate verification is enabled by default for security. Verifies responses via BLS12-381 signatures with blst:

With Agent directly:

# Default: verification enabled
agent.update("canister-id", "method_name", [{'type': Types.Nat, 'value': 2}])

# To disable (for compatibility/testing):
agent.update("canister-id", "method_name", [{'type': Types.Nat, 'value': 2}], verify_certificate=False)

With Canister wrapper:

# Default: verification enabled (matches Agent.update() behavior)
canister.set_value(42)

# Explicitly enable (same as default)
canister.set_value(42, verify_certificate=True)

# Disable verification (when blst is not installed)
canister.set_value(42, verify_certificate=False)

Note: Both Agent.update() and Canister methods default to verify_certificate=True for security. If blst is not installed, you must explicitly pass verify_certificate=False to avoid errors.


🧩 Example Usage

Identity

from icp_core import Identity
# Example: well-known Ed25519 test vector (RFC 8032); use Identity() or from_seed() for real keys
iden = Identity(privkey="833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42")
print(iden.sender().to_str())

Client & Agent

from icp_core import Agent, Client, Identity

iden = Identity()
client = Client("https://ic0.app")
agent = Agent(iden, client)

Update (auto-encode)

from icp_core import Types
result = agent.update(
    "wcrzb-2qaaa-aaaap-qhpgq-cai",
    "set",
    [{'type': Types.Nat, 'value': 2}],
    return_type=[Types.Nat],
)

Query (auto-encode empty args)

reply = agent.query("wcrzb-2qaaa-aaaap-qhpgq-cai", "get", [])
print(reply)

Canister Wrapper (Type-Safe Method Calls)

The Canister class provides a high-level, type-safe interface for interacting with canisters. It automatically parses Candid DID files and creates Python methods that match your canister's interface.

Creating a Canister instance:

from icp_core import Agent, Client, Identity, Canister

# Setup agent
client = Client("https://ic0.app")
identity = Identity()
agent = Agent(identity, client)

# Define Candid interface
COUNTER_DID = """
service : {
  get : () -> (nat) query;
  set : (nat) -> (nat)
}
"""

# Create Canister wrapper
counter = Canister(agent, "wcrzb-2qaaa-aaaap-qhpgq-cai", COUNTER_DID)

Calling canister methods:

# Query call (no arguments)
value = counter.get()
print(f"Current value: {value[0]['value']}")

# Update call (with positional argument)
result = counter.set(42)
print(f"Set to: {result[0]['value']}")

# Update call with keyword arguments (for record types)
# If your method takes a single record parameter, you can use kwargs:
# result = counter.update_profile(name="Alice", age=30)

Certificate Verification with Canister: By default, Canister methods enable certificate verification (verify_certificate=True) to match Agent.update() behavior for security. You can control this per method call:

# Default: certificate verification enabled (requires blst)
result = counter.set(42)  # Uses verify_certificate=True by default

# Explicitly enable verification (same as default)
result = counter.set(42, verify_certificate=True)

# Disable verification (useful when blst is not installed)
result = counter.set(42, verify_certificate=False)

Important Notes:

  • verify_certificate is a control parameter, not a method argument. It's extracted from kwargs before processing method arguments.
  • Default value is True to match Agent.update() default behavior for security.
  • If blst is not installed and you don't pass verify_certificate=False, update calls will fail.
  • For query calls, certificate verification is not applicable (queries don't return certificates).

Example: Complete Canister Usage

from icp_core import Agent, Client, Identity, Canister

# Setup
client = Client("https://ic0.app")
identity = Identity(anonymous=True)
agent = Agent(identity, client)

# Define interface
DID = """
service : {
  get : () -> (nat) query;
  set : (nat) -> (nat);
  increment : () -> (nat)
}
"""

# Create canister wrapper
counter = Canister(agent, "wcrzb-2qaaa-aaaap-qhpgq-cai", DID)

# Query (no verification needed)
current = counter.get()
print(f"Current: {current[0]['value']}")

# Update with verification enabled (default, requires blst)
try:
    result = counter.set(100)
    print(f"Set to: {result[0]['value']}")
except Exception as e:
    if "blst" in str(e).lower():
        # Fallback: disable verification if blst not available
        result = counter.set(100, verify_certificate=False)
        print(f"Set to: {result[0]['value']} (verification disabled)")
    else:
        raise

# Update with verification explicitly disabled
result = counter.increment(verify_certificate=False)
print(f"Incremented: {result[0]['value']}")

⚠️ Error Handling

ICP-PY-CORE provides a structured error hierarchy for better error handling and debugging. All errors inherit from ICError and are categorized by type.

Error Classes

from icp_core import (
    ICError,                    # Base class for all errors
    TransportError,             # HTTP/network errors
    SecurityError,              # Base class for security errors
    SignatureVerificationFailed,
    CertificateVerificationError,
    ReplicaReject,              # Canister rejection
    PayloadEncodingError,
    IngressExpiryError,
)

Common Error Scenarios

Transport Errors (Network Issues):

from icp_core import Client, TransportError

client = Client()
try:
    data = client.query("canister-id", b"data")
except TransportError as e:
    print(f"Failed to connect to {e.url}")
    print(f"Error: {e.original_error}")

Replica Rejections (Canister Errors):

from icp_core import Agent, Client, Identity, ReplicaReject

agent = Agent(identity, client)
try:
    result = agent.update("canister-id", "method", args)
except ReplicaReject as e:
    print(f"Rejected with code {e.reject_code}")
    print(f"Message: {e.reject_message}")
    if e.error_code:
        print(f"Error code: {e.error_code}")

Security Errors (Certificate Verification):

from icp_core import (
    CertificateVerificationError,
    SignatureVerificationFailed,
)

try:
    certificate.assert_certificate_valid(canister_id)
except CertificateVerificationError as e:
    print(f"Certificate verification failed: {e.reason}")
except SignatureVerificationFailed:
    print("BLS signature verification failed")

Error Hierarchy

ICError (base class)
├── TransportError (HTTP/network errors)
├── SecurityError (security errors)
│   ├── SignatureVerificationFailed
│   ├── CertificateVerificationError
│   ├── LookupPathMissing
│   ├── NodeKeyNotFoundError
│   └── ReplicaSignatureVerificationFailed
├── ReplicaReject (canister rejections)
├── PayloadEncodingError (CBOR encoding errors)
└── IngressExpiryError (expiry validation errors)

Best Practices

  1. Catch specific errors for better error handling:

    try:
        result = agent.update("canister-id", "method", args)
    except ReplicaReject as e:
        # Handle canister rejection
        handle_rejection(e)
    except TransportError as e:
        # Handle network issues
        handle_network_error(e)
    except SecurityError as e:
        # Handle security issues
        handle_security_error(e)
    
  2. Check error attributes for detailed information:

    except ReplicaReject as e:
        if e.reject_code == 3:
            # Canister trapped
            retry_with_different_args()
        elif e.reject_code == 4:
            # Canister did not reply
            check_canister_status()
    
  3. Preserve error context when re-raising:

    try:
        result = agent.update("canister-id", "method", args)
    except TransportError as e:
        logger.error(f"Network error: {e.url}", exc_info=True)
        raise  # Re-raise to preserve stack trace
    

🔑 Installing blst (optional)

blst is required for certificate verification (enabled by default). If blst is not installed, you can disable verification with verify_certificate=False.

Prerequisites

macOS:

# Install Xcode Command Line Tools
xcode-select --install

# Install SWIG (required for Python bindings)
brew install swig

Linux (Ubuntu/Debian):

sudo apt-get update
sudo apt-get install build-essential swig python3-dev

Linux (Fedora/RHEL):

sudo dnf install gcc gcc-c++ make swig python3-devel

macOS / Linux Installation

Method 1: Build and add to PYTHONPATH (recommended for development)

git clone https://github.com/supranational/blst
cd blst/bindings/python

# For Apple Silicon (M1/M2/M3) if you encounter ABI issues:
# export BLST_PORTABLE=1

python3 run.me

# Temporary (current session only):
export PYTHONPATH="$PWD:$PYTHONPATH"

# Permanent (add to ~/.bashrc or ~/.zshrc):
echo 'export PYTHONPATH="/path/to/blst/bindings/python:$PYTHONPATH"' >> ~/.bashrc
source ~/.bashrc

Method 2: Install to site-packages (recommended for production)

git clone https://github.com/supranational/blst
cd blst/bindings/python

# For Apple Silicon (M1/M2/M3) if needed:
# export BLST_PORTABLE=1

python3 run.me

# Copy to site-packages
BLST_SRC="$PWD"
PYBIN="python3"

SITE_PURE="$($PYBIN -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')"
SITE_PLAT="$($PYBIN -c 'import sysconfig; print(sysconfig.get_paths()["platlib"])')"

cp "$BLST_SRC/blst.py" "$SITE_PURE"/
cp "$BLST_SRC"/_blst*.so "$SITE_PLAT"/

Method 3: Install in virtual environment

# Activate your virtual environment first
source venv/bin/activate  # or: source .venv/bin/activate

git clone https://github.com/supranational/blst
cd blst/bindings/python

# For Apple Silicon if needed:
# export BLST_PORTABLE=1

python3 run.me

# Copy to virtual environment's site-packages
BLST_SRC="$PWD"
SITE_PURE="$(python3 -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')"
SITE_PLAT="$(python3 -c 'import sysconfig; print(sysconfig.get_paths()["platlib"])')"

cp "$BLST_SRC/blst.py" "$SITE_PURE"/
cp "$BLST_SRC"/_blst*.so "$SITE_PLAT"/

Windows Installation

Option 1: WSL2 (Ubuntu) - Recommended

  1. Install WSL2 and Ubuntu from Microsoft Store
  2. Follow the Linux installation instructions above in WSL2

Option 2: Native Windows (Advanced)

  1. Install Visual Studio Build Tools with C++ support
  2. Install SWIG for Windows from swig.org
  3. Install Python 3.8+ with development headers
  4. Follow the Linux build steps in PowerShell or Command Prompt
  5. Note: Windows support is experimental; WSL2 is recommended

Verify Installation

Test if blst is correctly installed:

try:
    import blst
    assert all(hasattr(blst, n) for n in ("P1_Affine", "P2_Affine", "Pairing", "BLST_SUCCESS"))
    print("✓ blst is installed and working correctly")
except (ModuleNotFoundError, AssertionError):
    print("✗ blst is not available or incomplete")

Or test with icp-py-core:

from icp_certificate.certificate import ensure_blst_available
try:
    ensure_blst_available()
    print("✓ blst is available for certificate verification")
except RuntimeError as e:
    print(f"✗ {e}")

Troubleshooting

Issue: "No module named 'blst'"

  • Ensure blst.py and _blst*.so are in your Python path
  • Check python3 -c "import sys; print(sys.path)" to see search paths
  • If using virtual environment, ensure it's activated

Issue: "ABI mismatch" on Apple Silicon

  • Set export BLST_PORTABLE=1 before running python3 run.me
  • This builds a portable version compatible with all architectures

Issue: "SWIG not found"

  • Install SWIG: brew install swig (macOS) or sudo apt-get install swig (Linux)
  • Ensure SWIG is in your PATH: which swig

Issue: Import succeeds but API is incomplete

  • Ensure you're using the official supranational/blst repository
  • Rebuild: cd blst/bindings/python && python3 run.me
  • Check that all required symbols exist: P1_Affine, P2_Affine, Pairing, BLST_SUCCESS

🧠 Features

  1. 🧩 Candid encode & decode (Rust-based parser for high performance)
  2. 🔐 ed25519 & secp256k1 identities
  3. 🧾 Principal utilities (strict DER mode)
  4. ⚙️ High-level canister calls via Agent (update(), query())
  5. 🪙 Support for Ledger / Governance / Management / Cycles Wallet
  6. 🔁 Sync & async APIs (low-level methods)
  7. 🔒 Certificate verification enabled by default (BLS12-381)
  8. ⚡ HTTP/2 support in async methods
  9. 🛡️ Structured error handling (11 error classes)
  10. 📦 Comprehensive example code library

🧰 Example — End-to-End

from icp_core import Agent, Client, Identity, Types

client = Client("https://ic0.app")
iden = Identity()
agent = Agent(iden, client)

# Update (auto-encode [42], certificate verification enabled by default)
agent.update("wcrzb-2qaaa-aaaap-qhpgq-cai", "set_value", [42])

# Query (auto-encode empty args)
res = agent.query("wcrzb-2qaaa-aaaap-qhpgq-cai", "get_value", None, return_type=[Types.Nat])
print(res)

🔄 Migration

Migrating from ic-py? See MIGRATION.md for:

  • New package layout (icp_* subpackages and the icp_core facade)
  • Endpoint changes (v3 call)
  • Argument auto-encoding in Agent.update() / Agent.query()
  • Certificate verification flag

📝 Changelog

We maintain release notes on GitHub Releases:
https://github.com/eliezhao/icp-py-core/releases


🗺 Roadmap

See ROADMAP.md

✅ Milestone 1: v3/v4 endpoint migration, timeouts & error classification
✅ Milestone 2: Certificate verification with blst (enabled by default)
✅ Milestone 3: Candid type-system enhancements (Rust parser, DIDLoader, VarT support)
✅ Milestone 4: Expanded API surface (Ledger, Governance, Cycles Wallet, Management), code optimization, HTTP/2 support, structured error handling
✅ Milestone 5: Auto-fetch DID files, high-level async API methods (update_async, query_async), Canister async method support, replica-signed queries


🔖 Version

  • Current release: v2.3.0

🙌 Acknowledgments

Special thanks to the IC community and contributors to the original ic-py.
icp-py-core continues this legacy with modern Python standards and long-term maintenance.


📚 Additional Resources

Metadata

Release files for icp-py-core 2.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for icp-py-core 2.3.1
File Size Uploaded Provenance Actions
icp_py_core-2.3.1.tar.gz 86.2 kB With provenance Details for icp_py_core-2.3.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for icp-py-core 2.3.1
File Interpreter ABI Platform Actions
icp_py_core-2.3.1-py3-none-any.whl Python 3 none any Details for icp_py_core-2.3.1-py3-none-any.whl

Total release size: 163.8 kB

Release files / icp_py_core-2.3.1.tar.gz

Download URL icp_py_core-2.3.1.tar.gz
Size 86.2 kB
Tags Source
SHA-256 checksum
How to use checksums
b9148fb5f23923eeacfee638426d6baf207b32225b0dbfac2b01893f1ca87dfd
BLAKE2b-256 checksum
How to use checksums
b62072338a8f33984c305178faf47edbfd2726f2f4d0e6dec90ca2d6845f76f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 13, 2026.

Transparency log

Release files / icp_py_core-2.3.1-py3-none-any.whl

Download URL icp_py_core-2.3.1-py3-none-any.whl
Size 77.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a669700acaf3ac5cc628e37454c5badc495b88ac05f03ee5c4afaf3c6f2c5e8a
BLAKE2b-256 checksum
How to use checksums
ad29a6f06eddecdf21a9aaf7d243632eeea725e9dd4bb1e49a4420bbc820111c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 13, 2026.

Transparency log

Security for icp-py-core 2.3.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for icp_py_core-2.3.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 13, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page