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
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ markers =
unit: marks tests as unit tests
integration: marks tests as integration tests
addopts = -v -ra
filterwarnings =
ignore::DeprecationWarning:websockets.legacy

# ignore directories
norecursedirs = *.egg .git .* _darcs build dist venv
Expand Down
16 changes: 14 additions & 2 deletions src/story_protocol_python_sdk/utils/transaction_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ def build_and_send_transaction(
:param account: The account to use for signing the transaction.
:param client_function: The client function to build the transaction.
:param client_args: Arguments to pass to the client function.
:param tx_options dict: Optional transaction options.
:param tx_options dict: Optional transaction options. Can include 'nonce' to use a custom nonce value.
If not provided, nonce will be fetched from web3.eth.get_transaction_count().
:return dict: A dictionary with the transaction hash and receipt, or encoded data if encodedTxDataOnly is True.
:raises Exception: If there is an error during the transaction process.
"""
Expand All @@ -28,9 +29,20 @@ def build_and_send_transaction(

transaction_options = {
"from": account.address,
"nonce": web3.eth.get_transaction_count(account.address),
}

if "nonce" in tx_options:
nonce = tx_options["nonce"]
if not isinstance(nonce, int) or nonce < 0:
raise ValueError(
f"Invalid nonce value: {nonce}. Nonce must be a non-negative integer."
)
transaction_options["nonce"] = nonce
else:
transaction_options["nonce"] = web3.eth.get_transaction_count(
account.address
)

# Add value if it exists in tx_options
if "value" in tx_options:
transaction_options["value"] = tx_options["value"]
Expand Down
124 changes: 124 additions & 0 deletions tests/integration/test_integration_transaction_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# tests/integration/test_integration_transaction_utils.py

import pytest

from story_protocol_python_sdk.utils.transaction_utils import build_and_send_transaction

from .setup_for_integration import MockERC20, account, web3


class TestTransactionUtils:
"""Integration tests for transaction utilities with custom nonce support."""

def test_custom_nonce_with_sequential_transactions(self):
"""Test custom nonce works correctly with sequential transactions."""
current_nonce = web3.eth.get_transaction_count(account.address, "pending")

def create_transfer_tx(to_address, value):
def build_tx(tx_options):
return {
"to": to_address,
"value": value,
"data": "0x",
"gas": 21000,
"gasPrice": web3.eth.gas_price,
"chainId": 1315,
**tx_options,
}

return build_tx

tx_func = create_transfer_tx(account.address, 0)
result = build_and_send_transaction(
web3, account, tx_func, tx_options={"nonce": current_nonce}
)

assert result["tx_receipt"]["status"] == 1
tx = web3.eth.get_transaction(result["tx_hash"])
assert tx["nonce"] == current_nonce

def test_automatic_nonce_fallback(self):
"""Test backward compatibility - automatic nonce when not specified."""

def create_transfer_tx(to_address, value):
def build_tx(tx_options):
return {
"to": to_address,
"value": value,
"data": "0x",
"gas": 21000,
"gasPrice": web3.eth.gas_price,
"chainId": 1315,
**tx_options,
}

return build_tx

tx_func = create_transfer_tx(account.address, 0)
result = build_and_send_transaction(web3, account, tx_func, tx_options={})

assert result["tx_receipt"]["status"] == 1
tx = web3.eth.get_transaction(result["tx_hash"])
assert tx["nonce"] >= 0

def test_invalid_nonce_validation(self):
"""Test that invalid nonce values are properly rejected."""

def dummy_func(tx_options):
return {
"to": account.address,
"value": 0,
"data": "0x",
"gas": 21000,
"gasPrice": web3.eth.gas_price,
"chainId": 1315,
**tx_options,
}

invalid_nonces = [
(-1, "negative"),
("10", "string"),
(10.5, "float"),
(None, "None"),
]

for nonce_value, nonce_type in invalid_nonces:
with pytest.raises(ValueError) as exc_info:
build_and_send_transaction(
web3, account, dummy_func, tx_options={"nonce": nonce_value}
)
assert "Invalid nonce value" in str(exc_info.value)
assert "must be a non-negative integer" in str(exc_info.value)

def test_nonce_with_contract_interaction(self):
"""Test custom nonce works with actual contract calls."""
erc20_contract = web3.eth.contract(
address=MockERC20,
abi=[
{
"inputs": [
{"name": "spender", "type": "address"},
{"name": "amount", "type": "uint256"},
],
"name": "approve",
"outputs": [{"name": "", "type": "bool"}],
"stateMutability": "nonpayable",
"type": "function",
}
],
)

current_nonce = web3.eth.get_transaction_count(account.address)

def approve_func(tx_options):
return erc20_contract.functions.approve(
account.address, 100
).build_transaction(tx_options)

result = build_and_send_transaction(
web3, account, approve_func, tx_options={"nonce": current_nonce}
)

assert result["tx_receipt"]["status"] == 1
tx = web3.eth.get_transaction(result["tx_hash"])
assert tx["nonce"] == current_nonce
1 change: 1 addition & 0 deletions tests/unit/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Unit tests for utils module
Loading
Loading