Skip to content

Commit a755443

Browse files
committed
refactor(python): modernize SDK with Pydantic v2 and modern Python patterns
- Convert dataclasses to Pydantic v2 models with validation - Use modern type hints (list[X], X | None) for Python 3.10+ - Add environment variable defaults (CASCADE_RPC_URL, CASCADE_PRIVATE_KEY) - Add custom exception hierarchy (CascadeSplitsError, ConfigurationError, etc.) - Add PEP 561 py.typed marker for type checker support - Rename addresses.py to constants.py - Use relative imports throughout - Add Python patterns to .gitignore
1 parent 61a1d87 commit a755443

15 files changed

Lines changed: 2507 additions & 554 deletions

File tree

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ docker-target
1313
.env
1414
tmp
1515

16+
# Python
17+
__pycache__/
18+
*.py[cod]
19+
*$py.class
20+
*.so
21+
.venv/
22+
*.egg-info/
23+
.ruff_cache/
24+
.pytest_cache/
25+
.ty/
26+
1627

1728

1829
.nx/cache

python/splits-sdk-evm/README.md

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# @cascade-fyi/splits-sdk-python
1+
# cascade-splits-evm
22

33
Python SDK for [Cascade Splits](https://github.com/cascade-protocol/splits) on EVM chains (Base).
44

@@ -9,22 +9,24 @@ Split incoming payments to multiple recipients automatically. Built for high-thr
99
## Installation
1010

1111
```bash
12-
pip install cascade-splits
12+
pip install cascade-splits-evm
1313
```
1414

1515
**Requirements:**
16-
- Python 3.9+
17-
- `web3` >= 6.0.0
16+
- Python 3.10+
17+
- `web3` >= 7.0.0
18+
- `pydantic` >= 2.0.0
1819

1920
## Quick Start
2021

2122
### Create a Split
2223

2324
```python
24-
from cascade_splits import CascadeSplitsClient, Recipient
25+
from cascade_splits_evm import CascadeSplitsClient, Recipient
2526
import secrets
2627

2728
# Initialize client (Base mainnet)
29+
# Can also use CASCADE_RPC_URL and CASCADE_PRIVATE_KEY environment variables
2830
client = CascadeSplitsClient(
2931
rpc_url="https://mainnet.base.org",
3032
private_key="0x...",
@@ -91,7 +93,7 @@ Recipient(address="0xBob...", share=40) # 40% of 99% = 39.6%
9193

9294
### Discriminated Union Results
9395

94-
All operations return typed results with `status` discriminant:
96+
All operations return typed Pydantic models with `status` discriminant:
9597

9698
```python
9799
# ensure_split results
@@ -118,13 +120,14 @@ EVM splits are **immutable** — recipients cannot be changed after creation. Cr
118120
### CascadeSplitsClient
119121

120122
```python
121-
from cascade_splits import CascadeSplitsClient
123+
from cascade_splits_evm import CascadeSplitsClient
122124

125+
# All parameters can be set via environment variables
123126
client = CascadeSplitsClient(
124-
rpc_url="https://mainnet.base.org",
125-
private_key="0x...",
126-
chain_id=8453, # Optional, default: 8453 (Base mainnet)
127-
factory_address=None, # Optional, uses default
127+
rpc_url="https://mainnet.base.org", # or CASCADE_RPC_URL env var
128+
private_key="0x...", # or CASCADE_PRIVATE_KEY env var
129+
chain_id=8453, # Optional, default: 8453 (Base mainnet)
130+
factory_address=None, # Optional, uses default
128131
)
129132

130133
# Properties
@@ -145,7 +148,7 @@ predicted = client.predict_split_address(unique_id, recipients, authority, token
145148
### Helper Functions
146149

147150
```python
148-
from cascade_splits import (
151+
from cascade_splits_evm import (
149152
to_evm_recipient,
150153
to_evm_recipients,
151154
is_cascade_split,
@@ -156,7 +159,7 @@ from cascade_splits import (
156159
)
157160

158161
# Convert share (1-100) to basis points
159-
recipient = to_evm_recipient(Recipient("0x...", share=50))
162+
recipient = to_evm_recipient(Recipient(address="0x...", share=50))
160163
# EvmRecipient(addr="0x...", percentage_bps=4950)
161164

162165
# Check if address is a split
@@ -166,10 +169,10 @@ is_split = is_cascade_split(w3, address)
166169
balance = get_split_balance(w3, split_address)
167170
```
168171

169-
### Address Utilities
172+
### Constants
170173

171174
```python
172-
from cascade_splits import (
175+
from cascade_splits_evm import (
173176
get_split_factory_address,
174177
get_usdc_address,
175178
is_supported_chain,
@@ -183,10 +186,24 @@ usdc = get_usdc_address(8453)
183186
supported = is_supported_chain(8453) # True
184187
```
185188

189+
### Exceptions
190+
191+
```python
192+
from cascade_splits_evm import (
193+
CascadeSplitsError, # Base exception
194+
ConfigurationError, # Missing RPC URL, private key, etc.
195+
ChainNotSupportedError, # Unsupported chain ID
196+
TransactionError, # Transaction failed
197+
TransactionRejectedError, # Wallet rejected transaction
198+
TransactionRevertedError, # Transaction reverted on-chain
199+
InsufficientGasError, # Not enough gas
200+
)
201+
```
202+
186203
## Types
187204

188205
```python
189-
from cascade_splits import (
206+
from cascade_splits_evm import (
190207
Recipient, # Input recipient with share (1-100)
191208
EvmRecipient, # On-chain format with basis points
192209
EnsureResult, # Result of ensure_split
@@ -203,19 +220,14 @@ from cascade_splits import (
203220
| Base Mainnet | 8453 | `0x946Cd053514b1Ab7829dD8fEc85E0ade5550dcf7` | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
204221
| Base Sepolia | 84532 | `0x946Cd053514b1Ab7829dD8fEc85E0ade5550dcf7` | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` |
205222

206-
## ubounty.ai Integration
207-
208-
This SDK is designed for easy integration with payment platforms:
223+
## Example Integration
209224

210225
```python
211-
from cascade_splits import CascadeSplitsClient, Recipient
212-
import secrets
226+
from cascade_splits_evm import CascadeSplitsClient, Recipient
227+
import os
213228

214-
# Initialize client
215-
client = CascadeSplitsClient(
216-
rpc_url="https://mainnet.base.org",
217-
private_key=os.environ["PRIVATE_KEY"],
218-
)
229+
# Initialize client (uses environment variables)
230+
client = CascadeSplitsClient()
219231

220232
# Create a split for bounty distribution
221233
result = client.ensure_split(
@@ -227,7 +239,8 @@ result = client.ensure_split(
227239
)
228240

229241
# After PR is merged, execute the split
230-
exec_result = client.execute_split(result.split)
242+
if result.status == "CREATED" or result.status == "NO_CHANGE":
243+
exec_result = client.execute_split(result.split)
231244
```
232245

233246
## Resources

python/splits-sdk-evm/project.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "cascade-splits-evm-python",
3+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
4+
"sourceRoot": "python/splits-sdk-evm/src",
5+
"projectType": "library",
6+
"tags": ["type:sdk", "platform:evm", "lang:python"],
7+
"targets": {
8+
"format": {
9+
"command": "cd python/splits-sdk-evm && uv run ruff format .",
10+
"inputs": ["{projectRoot}/**/*.py"],
11+
"cache": true
12+
},
13+
"lint": {
14+
"command": "cd python/splits-sdk-evm && uv run ruff check",
15+
"inputs": ["{projectRoot}/**/*.py"],
16+
"cache": true
17+
},
18+
"check": {
19+
"dependsOn": ["format", "lint"],
20+
"command": "cd python/splits-sdk-evm && uv run ty check",
21+
"inputs": ["{projectRoot}/**/*.py"],
22+
"cache": true
23+
},
24+
"test": {
25+
"command": "cd python/splits-sdk-evm && uv run pytest",
26+
"inputs": ["{projectRoot}/**/*.py", "{projectRoot}/tests/**/*.py"],
27+
"cache": true
28+
},
29+
"build": {
30+
"command": "cd python/splits-sdk-evm && uv build",
31+
"inputs": ["{projectRoot}/**/*.py", "{projectRoot}/pyproject.toml"],
32+
"outputs": ["{projectRoot}/dist/**"],
33+
"cache": true
34+
}
35+
}
36+
}

python/splits-sdk-evm/pyproject.toml

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,57 @@ requires = ["hatchling"]
33
build-backend = "hatchling.build"
44

55
[project]
6-
name = "cascade-splits"
6+
name = "cascade-splits-evm"
77
version = "0.1.0"
88
description = "Python SDK for Cascade Splits - Non-custodial payment splitting on Base (EVM)"
99
readme = "README.md"
1010
license = "Apache-2.0"
11-
requires-python = ">=3.9"
12-
authors = [
13-
{ name = "Cascade Protocol", email = "hello@cascade.fyi" }
14-
]
15-
keywords = ["ethereum", "base", "payments", "splits", "web3", "usdc"]
11+
requires-python = ">=3.10"
12+
authors = [{ name = "Cascade Protocol", email = "hello@cascade.fyi" }]
13+
keywords = ["ethereum", "evm", "base", "payments", "splits", "web3", "usdc", "cascade"]
1614
classifiers = [
1715
"Development Status :: 4 - Beta",
1816
"Intended Audience :: Developers",
1917
"License :: OSI Approved :: Apache Software License",
2018
"Programming Language :: Python :: 3",
21-
"Programming Language :: Python :: 3.9",
2219
"Programming Language :: Python :: 3.10",
2320
"Programming Language :: Python :: 3.11",
2421
"Programming Language :: Python :: 3.12",
22+
"Programming Language :: Python :: 3.13",
2523
"Topic :: Software Development :: Libraries :: Python Modules",
24+
"Typing :: Typed",
2625
]
2726
dependencies = [
28-
"web3>=6.0.0",
29-
"eth-account>=0.8.0",
30-
]
31-
32-
[project.optional-dependencies]
33-
dev = [
34-
"pytest>=7.0.0",
35-
"pytest-asyncio>=0.21.0",
27+
"web3>=7.0.0",
28+
"pydantic>=2.0.0",
3629
]
3730

3831
[project.urls]
3932
Homepage = "https://github.com/cascade-protocol/splits"
40-
Documentation = "https://github.com/cascade-protocol/splits/tree/main/packages/splits-sdk-python"
33+
Documentation = "https://github.com/cascade-protocol/splits/tree/main/python/splits-sdk-evm"
4134
Repository = "https://github.com/cascade-protocol/splits"
4235

36+
[dependency-groups]
37+
dev = [
38+
"pytest>=8.0",
39+
"pytest-asyncio>=0.24",
40+
"ruff>=0.8",
41+
"ty>=0.0.1a20",
42+
]
43+
4344
[tool.hatch.build.targets.wheel]
44-
packages = ["src/cascade_splits"]
45+
packages = ["src/cascade_splits_evm"]
46+
47+
[tool.ruff]
48+
line-length = 100
49+
target-version = "py310"
50+
51+
[tool.ruff.lint]
52+
select = ["E", "F", "I", "UP", "B", "SIM"]
53+
54+
[tool.ty.environment]
55+
python-version = "3.10"
56+
57+
[tool.pytest.ini_options]
58+
asyncio_mode = "auto"
59+
testpaths = ["tests"]
Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
"""
2-
Cascade Splits Python SDK
2+
Cascade Splits EVM SDK
33
44
Non-custodial payment splitting on Base (EVM).
55
Automatically distribute incoming payments to multiple recipients.
66
77
Usage:
8-
from cascade_splits import CascadeSplitsClient, Recipient
8+
from cascade_splits_evm import CascadeSplitsClient, Recipient
99
1010
client = CascadeSplitsClient(
1111
rpc_url="https://mainnet.base.org",
@@ -21,46 +21,64 @@
2121
)
2222
"""
2323

24-
from cascade_splits.types import (
25-
Recipient,
26-
EvmRecipient,
27-
EnsureResult,
28-
ExecuteResult,
29-
ExecutionPreview,
30-
SplitConfig,
24+
from ._exceptions import (
25+
CascadeSplitsError,
26+
ChainNotSupportedError,
27+
ConfigurationError,
28+
InsufficientGasError,
29+
TransactionError,
30+
TransactionRejectedError,
31+
TransactionRevertedError,
3132
)
32-
from cascade_splits.addresses import (
33+
from ._version import __version__
34+
from .client import CascadeSplitsClient
35+
from .constants import (
3336
SPLIT_FACTORY_ADDRESSES,
34-
USDC_ADDRESSES,
3537
SUPPORTED_CHAIN_IDS,
38+
USDC_ADDRESSES,
3639
get_split_factory_address,
3740
get_usdc_address,
3841
is_supported_chain,
3942
)
40-
from cascade_splits.client import CascadeSplitsClient
41-
from cascade_splits.helpers import (
42-
to_evm_recipient,
43-
to_evm_recipients,
44-
is_cascade_split,
43+
from .helpers import (
4544
get_split_balance,
4645
get_split_config,
4746
has_pending_funds,
47+
is_cascade_split,
4848
preview_execution,
49+
to_evm_recipient,
50+
to_evm_recipients,
51+
)
52+
from .types import (
53+
EnsureResult,
54+
EnsureStatus,
55+
EvmRecipient,
56+
ExecuteResult,
57+
ExecuteStatus,
58+
ExecutionPreview,
59+
FailedReason,
60+
Recipient,
61+
SkippedReason,
62+
SplitConfig,
4963
)
50-
51-
__version__ = "0.1.0"
5264

5365
__all__ = [
66+
# Version
67+
"__version__",
5468
# Client
5569
"CascadeSplitsClient",
5670
# Types
5771
"Recipient",
5872
"EvmRecipient",
73+
"SplitConfig",
5974
"EnsureResult",
6075
"ExecuteResult",
6176
"ExecutionPreview",
62-
"SplitConfig",
63-
# Addresses
77+
"EnsureStatus",
78+
"ExecuteStatus",
79+
"FailedReason",
80+
"SkippedReason",
81+
# Constants
6482
"SPLIT_FACTORY_ADDRESSES",
6583
"USDC_ADDRESSES",
6684
"SUPPORTED_CHAIN_IDS",
@@ -75,4 +93,12 @@
7593
"get_split_config",
7694
"has_pending_funds",
7795
"preview_execution",
96+
# Exceptions
97+
"CascadeSplitsError",
98+
"ConfigurationError",
99+
"ChainNotSupportedError",
100+
"TransactionError",
101+
"TransactionRejectedError",
102+
"TransactionRevertedError",
103+
"InsufficientGasError",
78104
]

0 commit comments

Comments
 (0)