Python Client for Sui gRPC
Build production-ready Python applications for Sui using gRPC. This guide uses a simple, straightforward approach - no complex package setup needed, just install dependencies and start coding!
Why Python for gRPC?#
- Rapid development - Python's simplicity accelerates prototyping
- Data analysis - Perfect for blockchain analytics and data science
- Strong ecosystem - Excellent libraries for async I/O, data processing
- Type hints - Modern Python supports strong typing with gRPC
- Easy deployment - Simple containerization and serverless deployment
Prerequisites#
- Python 3.8 or higher
- pip or poetry for package management
- Basic understanding of async/await (recommended)
- Dwellir API key (get one here)
Project Setup#
Create Virtual Environment#
# Create project directory
mkdir sui-grpc-python
cd sui-grpc-python
# Create virtual environment
python3 -m venv venv
# Activate virtual environment
source venv/bin/activate # On macOS/Linux
# or
.\venv\Scripts\activate # On Windows
Install Dependencies#
Simple and direct - just install what you need:
pip install grpcio grpcio-tools protobuf python-dotenv
This installs:
grpcio- Core gRPC librarygrpcio-tools- Protocol buffer compilerprotobuf- Protocol Buffers supportpython-dotenv- Environment variable management
Get Proto Files from Sui APIs Repository#
# Clone the Sui APIs repository
git clone https://github.com/MystenLabs/sui-apis.git
# Copy proto files to your project
cp -r sui-apis/proto .
The proto files are located in proto/sui/rpc/v2/:
ledger_service.proto- Ledger service (checkpoints, objects, transactions)state_service.proto- State service (balances, objects)move_package_service.proto- Package servicetransaction_execution_service.proto- Transaction servicesignature_verification_service.proto- Signature verification- And many more supporting proto files
Generate Python Code from Proto Files#
Simple one-liner to generate all Python gRPC code:
# Generate Python code from proto files
python -m grpc_tools.protoc \
-I./proto \
--python_out=. \
--grpc_python_out=. \
./proto/sui/rpc/v2/*.proto \
./proto/google/rpc/*.proto
# Create __init__.py files for sui package only
find sui -type d -exec touch {}/__init__.py \;
This creates:
sui/rpc/v2/*_pb2.py- Message definitionssui/rpc/v2/*_pb2_grpc.py- Service stubsgoogle/rpc/*_pb2.py- Google protobuf definitions
Project Structure#
sui-grpc-python/
├── .env.example # Environment variables template
├── .env # Your config (gitignored)
├── client.py # gRPC client wrapper
├── example_get_checkpoint.py
├── example_get_balance.py
├── proto/ # Protocol buffer definitions
│ ├── sui/rpc/v2/
│ └── google/rpc/
├── sui/ # Generated Python code
│ └── rpc/v2/
└── google/ # Generated Google proto code
└── rpc/
Configuration#
Environment Variables#
Create a .env file from the template:
# Copy the example
cp .env.example .env
# Edit and add your API key
# .env
DWELLIR_ENDPOINT=api-sui-mainnet-full.n.dwellir.com:443
DWELLIR_API_KEY=your_api_key_here
Quick Start: Running Your First Example#
Now that you've set up your project, let's run a simple example to verify everything works!
Complete Setup Checklist#
Before running the example, ensure you have:
- ✓ Created and activated your virtual environment
- ✓ Installed all dependencies
- ✓ Generated proto files from
.protodefinitions - ✓ Created
.envfile with your Dwellir credentials - ✓ Created
client.pyfile
Create the Client#
Create client.py:
"""Simple Sui gRPC Client"""
import grpc
import os
from dotenv import load_dotenv
load_dotenv()
class SuiGrpcClient:
"""Basic Sui gRPC client"""
def __init__(self, endpoint=None, api_key=None):
self.endpoint = endpoint or os.getenv('DWELLIR_ENDPOINT')
self.api_key = api_key or os.getenv('DWELLIR_API_KEY')
if not self.endpoint or not self.api_key:
raise ValueError(
"Missing DWELLIR_ENDPOINT or DWELLIR_API_KEY. "
"Set them in .env file or pass as arguments."
)
self._channel = None
def __enter__(self):
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def connect(self):
"""Establish gRPC connection"""
credentials = grpc.ssl_channel_credentials()
self._channel = grpc.secure_channel(
self.endpoint,
credentials,
options=[
('grpc.max_receive_message_length', 50 * 1024 * 1024), # 50MB
('grpc.max_send_message_length', 50 * 1024 * 1024),
]
)
def close(self):
"""Close gRPC connection"""
if self._channel:
self._channel.close()
self._channel = None
def get_metadata(self):
"""Generate authentication metadata"""
return [('x-api-key', self.api_key)]
@property
def channel(self):
"""Get or create channel"""
if self._channel is None:
self.connect()
return self._channel
Create the Example File#
Create example_get_checkpoint.py:
#!/usr/bin/env python3
"""Example: Fetch the latest checkpoint from Sui"""
import grpc
from client import SuiGrpcClient
from sui.rpc.v2 import ledger_service_pb2
from sui.rpc.v2 import ledger_service_pb2_grpc
def get_current_checkpoint():
"""Fetch the current checkpoint"""
print("Connecting to Sui gRPC API...")
with SuiGrpcClient() as client:
# Create ledger service stub
stub = ledger_service_pb2_grpc.LedgerServiceStub(client.channel)
# Create request - get latest checkpoint
request = ledger_service_pb2.GetCheckpointRequest()
# Execute request with authentication
try:
print("Fetching latest checkpoint...")
response = stub.GetCheckpoint(
request,
metadata=client.get_metadata(),
timeout=30
)
checkpoint = response.checkpoint
print("\n" + "="*50)
print("Successfully fetched checkpoint!")
print("="*50)
print(f"Sequence Number: {checkpoint.summary.sequence_number}")
print(f"Epoch: {checkpoint.summary.epoch}")
print(f"Timestamp: {checkpoint.summary.timestamp}")
if checkpoint.contents and checkpoint.contents.transactions:
print(f"Transactions in checkpoint: {len(checkpoint.contents.transactions)}")
else:
print("No transaction details in response")
print("="*50)
return checkpoint
except grpc.RpcError as e:
print(f"\nError: {e.code()} - {e.details()}")
if e.code() == grpc.StatusCode.UNAUTHENTICATED:
print("\nAuthentication failed. Please check:")
print("1. You have created a .env file (copy from .env.example)")
print("2. Your DWELLIR_API_KEY is correct")
print("3. Your API key is active")
raise
if __name__ == '__main__':
get_current_checkpoint()
Run the Example#
Make sure you're in the project root directory and have your virtual environment activated:
# Activate virtual environment (if not already active)
source venv/bin/activate # On macOS/Linux
# or
.\venv\Scripts\activate # On Windows
# Run the example
python example_get_checkpoint.py
Expected Output#
If everything is set up correctly, you should see:
Connecting to Sui gRPC API...
Fetching latest checkpoint...
==================================================
Successfully fetched checkpoint!
==================================================
Sequence Number: 0
Epoch: 0
Timestamp:
No transaction details in response
==================================================
Practical Examples#
Balance Monitor#
import time
from decimal import Decimal
from typing import Optional
from client import SuiGrpcClient
from sui.rpc.v2 import state_service_pb2, state_service_pb2_grpc
class BalanceMonitor:
"""Monitor balance changes for an address"""
def __init__(self, address: str, coin_type: str):
self.address = address
self.coin_type = coin_type
self.previous_balance: Optional[int] = None
def check_balance(self) -> dict:
"""Check current balance and detect changes"""
with SuiGrpcClient() as client:
stub = state_service_pb2_grpc.StateServiceStub(client.channel)
request = state_service_pb2.GetBalanceRequest(
owner=self.address,
coin_type=self.coin_type
)
response = stub.GetBalance(
request,
metadata=client.get_metadata()
)
# response.balance is a Balance object, access its balance field
if response.balance and response.balance.balance is not None:
current_balance = response.balance.balance
else:
current_balance = 0
result = {
'balance': current_balance,
'balance_sui': Decimal(current_balance) / Decimal(1_000_000_000),
'changed': False,
'change_amount': 0
}
if self.previous_balance is not None:
change = current_balance - self.previous_balance
if change != 0:
result['changed'] = True
result['change_amount'] = change
self.previous_balance = current_balance
return result
def monitor(self, interval: int = 10):
"""Continuously monitor balance changes"""
print(f"Monitoring balance for: {self.address}")
print(f"Coin type: {self.coin_type}")
print("Press Ctrl+C to stop\n")
try:
while True:
result = self.check_balance()
if result['changed']:
change = result['change_amount']
direction = '+' if change > 0 else ''
print(f"[{time.strftime('%H:%M:%S')}] Balance changed: "
f"{direction}{change} (Total: {result['balance_sui']} SUI)")
else:
print(f"[{time.strftime('%H:%M:%S')}] Balance: "
f"{result['balance_sui']} SUI")
time.sleep(interval)
except KeyboardInterrupt:
print("\nMonitoring stopped")
# Usage
if __name__ == '__main__':
monitor = BalanceMonitor(
address='0x935029ca5219502a47ac9b69f556ccf6e2198b5e7815cf50f68846f723739cbd',
coin_type='0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI'
)
monitor.monitor(interval=5)
Portfolio Tracker#
from typing import List, Dict
from dataclasses import dataclass
import grpc
from client import SuiGrpcClient
from sui.rpc.v2 import state_service_pb2, state_service_pb2_grpc
@dataclass
class TokenBalance:
coin_type: str
balance: int
symbol: str
decimals: int
@property
def formatted_balance(self) -> str:
"""Human-readable balance"""
value = self.balance / (10 ** self.decimals)
return f"{value:.{self.decimals}f} {self.symbol}"
class PortfolioTracker:
"""Track all token balances for an address"""
def __init__(self, address: str):
self.address = address
def get_portfolio(self) -> List[TokenBalance]:
"""Fetch all token balances"""
with SuiGrpcClient() as client:
state_stub = state_service_pb2_grpc.StateServiceStub(client.channel)
# Get all balances
balance_request = state_service_pb2.ListBalancesRequest(
owner=self.address
)
balance_response = state_stub.ListBalances(
balance_request,
metadata=client.get_metadata()
)
portfolio = []
# Enrich with coin metadata
for balance_item in balance_response.balances:
# Get coin info
info_request = state_service_pb2.GetCoinInfoRequest(
coin_type=balance_item.coin_type
)
try:
info_response = state_stub.GetCoinInfo(
info_request,
metadata=client.get_metadata()
)
metadata = info_response.metadata
portfolio.append(TokenBalance(
coin_type=balance_item.coin_type,
balance=int(balance_item.balance),
symbol=metadata.symbol,
decimals=metadata.decimals
))
except grpc.RpcError as e:
print(f"Warning: Could not get info for {balance_item.coin_type}")
return portfolio
def print_portfolio(self):
"""Display portfolio in formatted table"""
portfolio = self.get_portfolio()
print(f"\nPortfolio for: {self.address}\n")
print(f"{'Token':<15} {'Balance':<30}")
print("-" * 45)
for token in portfolio:
print(f"{token.symbol:<15} {token.formatted_balance:<30}")
print(f"\nTotal tokens: {len(portfolio)}")
# Usage
if __name__ == '__main__':
tracker = PortfolioTracker('0x935029ca5219502a47ac9b69f556ccf6e2198b5e7815cf50f68846f723739cbd')
tracker.print_portfolio()
Related Resources#
- gRPC Overview - Complete API introduction
- TypeScript Setup - TypeScript client
- Go Setup - Go client implementation
- grpcurl Setup - Command-line testing
Need help? Contact support@dwellir.com