Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
293 changes: 63 additions & 230 deletions Cargo.lock

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ ahash = "0.8.12"
bincode = "1.3.3"
faster-hex = "0.9.0"
futures = "0.3.31"
kaspa-addresses = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" }
kaspa-bip32 = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" }
kaspa-consensus-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" }
kaspa-consensus-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" }
kaspa-hashes = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" }
kaspa-notify = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" }
kaspa-rpc-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" }
kaspa-txscript = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179", features = ["wasm32-sdk"]}
kaspa-utils = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" }
kaspa-wallet-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" }
kaspa-wallet-keys = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" }
kaspa-wrpc-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "d290179" }
kaspa-addresses = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" }
kaspa-bip32 = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" }
kaspa-consensus-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" }
kaspa-consensus-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" }
kaspa-hashes = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" }
kaspa-notify = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" }
kaspa-rpc-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" }
kaspa-txscript = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07", features = ["wasm32-sdk"]}
kaspa-utils = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" }
kaspa-wallet-core = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" }
kaspa-wallet-keys = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" }
kaspa-wrpc-client = { git = "https://github.com/kaspanet/rusty-kaspa.git", rev = "90dbf07" }
paste = "1.0"
pyo3 = { version = "0.27.1", features = ['multiple-pymethods'] }
pyo3-async-runtimes = { version = "0.27.0", features = ['tokio-runtime'] }
Expand Down
5 changes: 5 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@ search:
- Examples under `examples/wallet/` demonstrating wallet usage
- Pytest options `--network-id` and `--rpc-url` for targeting integration tests at a specific network / node.
- TypedDicts for `RpcClient` subscription event payloads — `BlockAddedEvent`, `VirtualChainChangedEvent`, `FinalityConflictEvent`, `FinalityConflictResolvedEvent`, `UtxosChangedEvent`, `SinkBlueScoreChangedEvent`, `VirtualDaaScoreChangedEvent`, `PruningPointUtxoSetOverrideEvent`, `NewBlockTemplateEvent`, `ConnectEvent`, `DisconnectEvent` — and their notification body TypedDicts (`RpcBlockAddedNotification`, etc.) for typing event-listener callbacks.
- `RpcClient.get_block_reward_info(request)` — new RPC returning a block's reward info (header, block color, confirmation count, merging chain block hash, reward amount).
- `ScriptBuilder` covenant/engine flags: optional `covenants_enabled` and `sigop_script_units` arguments on `ScriptBuilder(...)` and `ScriptBuilder.from_script(...)`, plus read-only `covenants_enabled` / `sigop_script_units` properties (mirrors the WASM SDK's `ScriptBuilderOptions`).
- `Transaction.storage_mass` property and a `storageMass` key in the `Transaction` dict output (alongside the existing `mass`), mirroring the WASM SDK.

### Changed
- Bumped the pinned `rusty-kaspa` dependency from `d290179` to `90dbf07` (Toccata hardfork). The transaction mass field is now tracked internally as `storage_mass`; the Python-facing `mass` property, constructor argument, and dict key are retained as aliases.
- The network minimum relay fee was raised upstream to 100 sompi/gram (Toccata). Fees produced by the wallet and `Generator` reflect the new floor; explicit `fee_rate` values must meet it.
- `py_error_map!` macro extended to register wallet exception variants into the `kaspa.exceptions` submodule.
- Integration tests now default to `mainnet` (overridable via `--network-id` / `--rpc-url`).
- `build-dev` script builds with `--strip` for smaller artifacts.
Expand Down
2 changes: 1 addition & 1 deletion docs/learn/rpc/calls.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ flow and `allowOrphan` semantics.
fee = await client.get_fee_estimate()
# {
# "estimate": {
# "priorityBucket": {"feerate": 1.0, "estimatedSeconds": 1.0},
# "priorityBucket": {"feerate": 100.0, "estimatedSeconds": 1.0},
# "normalBuckets": [...],
# "lowBuckets": [...],
# }
Expand Down
5 changes: 5 additions & 0 deletions docs/learn/transactions/mass-and-fees.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ network's current rate, or `None` if the transaction's mass exceeds
[`maximum_standard_transaction_mass()`](../../reference/Functions/maximum_standard_transaction_mass.md). Split the inputs across
multiple transactions in that case.

Since the Toccata hardfork, the network minimum relay fee is **100 sompi per gram
of mass**. `calculate_transaction_fee` and the fee-estimate buckets are floored at
this rate, and any explicit `fee_rate` you supply must meet it or the node rejects
the transaction.

## Querying the fee rate

The network exposes a fee estimator over RPC — see
Expand Down
2 changes: 1 addition & 1 deletion docs/learn/wallet-sdk/tx-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ gen = Generator(
priority_entries=priority, # UTXOs to consume first
sig_op_count=1, # signature ops per input
minimum_signatures=1, # for multisig mass estimation
fee_rate=2.0, # explicit sompi/gram override
fee_rate=120.0, # explicit sompi/gram override (>= network minimum)
)
```

Expand Down
14 changes: 14 additions & 0 deletions kaspa_rpc.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,11 @@ class GetBlockTemplateRequest(TypedDict):
extraData: str


class GetBlockRewardInfoRequest(TypedDict):
"""Request for get_block_reward_info."""
hash: str


class GetCurrentBlockColorRequest(TypedDict):
"""Request for get_current_block_color."""
hash: str
Expand Down Expand Up @@ -886,6 +891,15 @@ class GetBlockTemplateResponse(TypedDict):
isSynced: bool


class GetBlockRewardInfoResponse(TypedDict):
"""Response from get_block_reward_info."""
header: RpcBlockHeader
blockColor: Literal["unknown", "blue", "red"]
confirmationCount: int | None
mergingChainBlockHash: str | None
rewardAmount: int | None


class GetCurrentBlockColorResponse(TypedDict):
"""Response from get_current_block_color."""
blue: bool
Expand Down
105 changes: 82 additions & 23 deletions python/kaspa/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2025,6 +2025,7 @@ class RpcClient:
def get_block(self, request: GetBlockRequest) -> GetBlockResponse: ...
def get_blocks(self, request: GetBlocksRequest) -> GetBlocksResponse: ...
def get_block_template(self, request: GetBlockTemplateRequest) -> GetBlockTemplateResponse: ...
def get_block_reward_info(self, request: GetBlockRewardInfoRequest) -> GetBlockRewardInfoResponse: ...
def get_current_block_color(self, request: GetCurrentBlockColorRequest) -> GetCurrentBlockColorResponse: ...
def get_daa_score_timestamp_estimate(self, request: GetDaaScoreTimestampEstimateRequest) -> GetDaaScoreTimestampEstimateResponse: ...
def get_fee_estimate_experimental(self, request: GetFeeEstimateExperimentalRequest) -> GetFeeEstimateExperimentalResponse: ...
Expand Down Expand Up @@ -2052,20 +2053,46 @@ class ScriptBuilder:
Used for creating complex spending conditions like multi-signature or time-locked
transactions.
"""
def __new__(cls) -> ScriptBuilder:
@property
def covenants_enabled(self) -> builtins.bool:
r"""
Whether covenant opcodes and post-Toccata script limits are enabled.

Returns:
bool: True if covenants are enabled for this builder.
"""
@property
def sigop_script_units(self) -> builtins.int:
r"""
Script units charged for each signature operation.

Returns:
int: The configured sigop script units.
"""
def __new__(cls, covenants_enabled: builtins.bool = False, sigop_script_units: typing.Optional[builtins.int] = None) -> ScriptBuilder:
r"""
Create a new empty script builder.

Args:
covenants_enabled: Enable covenant opcodes and post-Toccata script
limits (default: False).
sigop_script_units: Script units charged per signature operation.
Defaults to the native engine default when omitted.

Returns:
ScriptBuilder: A new empty ScriptBuilder instance.
"""
@staticmethod
def from_script(script: Binary) -> ScriptBuilder:
def from_script(script: Binary, covenants_enabled: builtins.bool = False, sigop_script_units: typing.Optional[builtins.int] = None) -> ScriptBuilder:
r"""
Create a script builder from an existing script.

Args:
script: Existing script bytes as hex, bytes, or list.
covenants_enabled: Enable covenant opcodes and post-Toccata script
limits (default: False).
sigop_script_units: Script units charged per signature operation.
Defaults to the native engine default when omitted.

Returns:
ScriptBuilder: A new ScriptBuilder initialized with the script.
Expand Down Expand Up @@ -2363,15 +2390,33 @@ class Transaction:
def mass(self) -> builtins.int:
r"""
The transaction mass used for fee calculation.

Alias of `storage_mass`, retained for compatibility with the WASM SDK
and earlier releases of this package.
"""
@mass.setter
def mass(self, value: builtins.int) -> None:
r"""
Set the transaction mass.

Alias of `storage_mass`.

Args:
value: The transaction mass value.
"""
@property
def storage_mass(self) -> builtins.int:
r"""
The transaction storage mass used for fee calculation.
"""
@storage_mass.setter
def storage_mass(self, value: builtins.int) -> None:
r"""
Set the transaction storage mass.

Args:
value: The transaction storage mass value.
"""
@subnetwork_id.setter
def subnetwork_id(self, value: builtins.str) -> None:
r"""
Expand Down Expand Up @@ -2832,27 +2877,6 @@ class UtxoContext:
str: The UtxoContext as a repr string.
"""

@typing.final
class UtxoEntries:
r"""
UTXO entries collection for flexible input handling.

This type is not intended to be instantiated directly from Python.
It serves as a helper type that allows Rust functions to accept a list
of UTXO entries in multiple convenient forms.

Accepts:
list[UtxoEntryReference]: A list of UtxoEntryReference objects.
list[dict]: A list of dicts with UtxoEntryReference-compatible keys.
"""
def __repr__(self) -> builtins.str:
r"""
The detailed string representation.

Returns:
str: The UtxoEntries as a repr string.
"""

@typing.final
class UtxoEntries:
r"""
Expand Down Expand Up @@ -2910,6 +2934,27 @@ class UtxoEntries:
str: The UtxoEntries as a repr string.
"""

@typing.final
class UtxoEntries:
r"""
UTXO entries collection for flexible input handling.

This type is not intended to be instantiated directly from Python.
It serves as a helper type that allows Rust functions to accept a list
of UTXO entries in multiple convenient forms.

Accepts:
list[UtxoEntryReference]: A list of UtxoEntryReference objects.
list[dict]: A list of dicts with UtxoEntryReference-compatible keys.
"""
def __repr__(self) -> builtins.str:
r"""
The detailed string representation.

Returns:
str: The UtxoEntries as a repr string.
"""

@typing.final
class UtxoEntry:
r"""
Expand Down Expand Up @@ -5591,6 +5636,11 @@ class GetBlockTemplateRequest(TypedDict):
extraData: str


class GetBlockRewardInfoRequest(TypedDict):
"""Request for get_block_reward_info."""
hash: str


class GetCurrentBlockColorRequest(TypedDict):
"""Request for get_current_block_color."""
hash: str
Expand Down Expand Up @@ -5867,6 +5917,15 @@ class GetBlockTemplateResponse(TypedDict):
isSynced: bool


class GetBlockRewardInfoResponse(TypedDict):
"""Response from get_block_reward_info."""
header: RpcBlockHeader
blockColor: Literal["unknown", "blue", "red"]
confirmationCount: int | None
mergingChainBlockHash: str | None
rewardAmount: int | None


class GetCurrentBlockColorResponse(TypedDict):
"""Response from get_current_block_color."""
blue: bool
Expand Down
39 changes: 32 additions & 7 deletions src/consensus/client/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,18 +275,38 @@ impl PyTransaction {
}

/// The transaction mass used for fee calculation.
///
/// Alias of `storage_mass`, retained for compatibility with the WASM SDK
/// and earlier releases of this package.
#[getter]
pub fn get_mass(&self) -> u64 {
self.0.inner().mass
self.0.inner().storage_mass
}

/// Set the transaction mass.
///
/// Alias of `storage_mass`.
///
/// Args:
/// value: The transaction mass value.
#[setter]
pub fn set_mass(&mut self, value: u64) {
self.0.inner().mass = value;
self.0.inner().storage_mass = value;
}

/// The transaction storage mass used for fee calculation.
#[getter]
pub fn get_storage_mass(&self) -> u64 {
self.0.inner().storage_mass
}

/// Set the transaction storage mass.
///
/// Args:
/// value: The transaction storage mass value.
#[setter]
pub fn set_storage_mass(&mut self, value: u64) {
self.0.inner().storage_mass = value;
}

pub fn populate_genesis_covenants(&self, groups: Vec<PyGenesisCovenantGroup>) -> PyResult<()> {
Expand Down Expand Up @@ -433,11 +453,16 @@ impl TryFrom<&Bound<'_, PyDict>> for PyTransaction {
Vec::from_hex(&payload_str).map_err(|err| PyException::new_err(err.to_string()))?
};

// Parse mass
let mass: u64 = dict
.get_item("mass")?
.ok_or_else(|| PyKeyError::new_err("Key `mass` not present"))?
.extract()?;
// Parse mass. Accept `storageMass` (WASM SDK) or the legacy/alias `mass`,
// preferring `storageMass` when both are present.
let mass: u64 = match (dict.get_item("storageMass")?, dict.get_item("mass")?) {
(Some(value), _) | (None, Some(value)) => value.extract()?,
(None, None) => {
return Err(PyKeyError::new_err(
"Key `mass` or `storageMass` not present",
));
}
};

// Parse inputs
let inputs_list = dict
Expand Down
5 changes: 3 additions & 2 deletions src/consensus/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,9 @@ impl TryToPyDict for Transaction {
// Set `payload` key
dict.set_item("payload", inner.payload.to_hex())?;

// Set `mass`
dict.set_item("mass", inner.mass)?;
// Set `mass` (alias) and `storageMass` (WASM SDK parity)
dict.set_item("mass", inner.storage_mass)?;
dict.set_item("storageMass", inner.storage_mass)?;

Ok(dict)
}
Expand Down
Loading
Loading