from collections.abc import Callable, Sequence
from typing import TypeVar
from .. import Account, Asset, Keypair, MuxedAccount, scval
from .. import xdr as stellar_xdr
from ..base_soroban_server import ResourceLeeway
from ..client.base_sync_client import BaseSyncClient
from ..soroban_rpc import AuthMode
from ..soroban_server import SorobanServer
from ..transaction_builder import TransactionBuilder
from .assembled_transaction import AssembledTransaction
NULL_ACCOUNT = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"
__all__ = ["ContractClient"]
T = TypeVar("T")
[docs]
class ContractClient:
"""A client to interact with Soroban smart contracts.
This client is a wrapper for :py:class:`TransactionBuilder <stellar_sdk.TransactionBuilder>` and :py:class:`SorobanServer <stellar_sdk.SorobanServer>`.
If you need more fine-grained control, please consider using them directly.
I strongly recommend that you do not use this client directly, but instead use `stellar-contract-bindings <https://github.com/lightsail-network/stellar-contract-bindings>`_ to
generate contract binding code, which will make calling the contract much simpler.
:param contract_id: The ID of the Soroban contract.
:param rpc_url: The URL of the RPC server.
:param network_passphrase: The network passphrase.
:param request_client: The request client used to send the request.
"""
def __init__(
self,
contract_id: str,
rpc_url: str,
network_passphrase: str,
request_client: BaseSyncClient | None = None,
):
self.contract_id = contract_id
self.rpc_url = rpc_url
self.network_passphrase = network_passphrase
self.server = SorobanServer(rpc_url, request_client)
[docs]
def invoke(
self,
function_name: str,
parameters: Sequence[stellar_xdr.SCVal] | None = None,
source: str | MuxedAccount = NULL_ACCOUNT,
signer: Keypair | None = None,
parse_result_xdr_fn: Callable[[stellar_xdr.SCVal], T] | None = None,
base_fee: int = 100,
transaction_timeout: int = 300,
submit_timeout: int = 30,
simulate: bool = True,
restore: bool = True,
addl_resources: ResourceLeeway | None = None,
auth_mode: AuthMode | None = None,
) -> AssembledTransaction[T]:
"""Build an :py:class:`AssembledTransaction <stellar_sdk.contract.AssembledTransaction>` to invoke a contract function.
:param function_name: The name of the function to invoke.
:param parameters: The parameters to pass to the function.
:param source: The source account for the transaction.
:param signer: The signer for the transaction.
:param parse_result_xdr_fn: The function to parse the result XDR returned by the contract function, keep the result as :py:class:`SCVal <stellar_sdk.xdr.SCVal>` if not provided.
:param base_fee: The base fee for the transaction.
:param transaction_timeout: The timeout for the transaction.
:param submit_timeout: The timeout for submitting the transaction.
:param simulate: Whether to simulate the transaction.
:param restore: Whether to restore the transaction, only valid when simulate is True, and the signer is provided.
:param addl_resources: Additional resource leeway forwarded to every simulation performed by the returned :class:`AssembledTransaction <stellar_sdk.contract.AssembledTransaction>`.
:param auth_mode: Authorization mode forwarded to every simulation performed by the returned :class:`AssembledTransaction <stellar_sdk.contract.AssembledTransaction>`.
:return:
"""
builder = (
TransactionBuilder(
Account(source, 0), self.network_passphrase, base_fee=base_fee
)
.append_invoke_contract_function_op(
self.contract_id, function_name, parameters
)
.set_timeout(transaction_timeout)
)
assembled = AssembledTransaction(
builder,
self.server,
signer,
parse_result_xdr_fn,
submit_timeout,
addl_resources=addl_resources,
auth_mode=auth_mode,
)
if simulate:
assembled = assembled.simulate(restore)
return assembled
[docs]
@staticmethod
def upload_contract_wasm(
contract: bytes | str,
source: str | MuxedAccount,
signer: Keypair,
soroban_server: SorobanServer,
network_passphrase: str | None = None,
base_fee: int = 100,
transaction_timeout: int = 300,
submit_timeout: int = 120,
) -> bytes:
"""Upload a contract wasm.
:param contract: The contract wasm.
:param source: The source account for the transaction.
:param signer: The signer for the transaction.
:param soroban_server: The Soroban server.
:param network_passphrase: The network passphrase, default to the network of the Soroban server.
:param base_fee: The base fee for the transaction.
:param transaction_timeout: The timeout for the transaction.
:param submit_timeout: The timeout for submitting the transaction.
:return: The wasm ID.
"""
if network_passphrase is None:
network_passphrase = soroban_server.get_network().passphrase
builder = (
TransactionBuilder(
Account(source, 0), network_passphrase, base_fee=base_fee
)
.append_upload_contract_wasm_op(contract)
.set_timeout(transaction_timeout)
)
wasm_id = (
AssembledTransaction[bytes](
builder,
soroban_server,
signer,
lambda v: scval.from_bytes(v),
submit_timeout,
)
.simulate()
.sign_and_submit(force=True)
)
assert isinstance(wasm_id, bytes)
return wasm_id
[docs]
@staticmethod
def create_contract(
wasm_id: bytes | str,
source: str | MuxedAccount,
signer: Keypair,
soroban_server: SorobanServer,
constructor_args: Sequence[stellar_xdr.SCVal] | None = None,
salt: bytes | None = None,
network_passphrase: str | None = None,
base_fee: int = 100,
transaction_timeout: int = 300,
submit_timeout: int = 120,
restore: bool = True,
) -> str:
"""Create a contract.
:param wasm_id: The wasm ID.
:param source: The source account for the transaction.
:param signer: The signer for the transaction.
:param soroban_server: The Soroban server.
:param constructor_args: The constructor arguments.
:param salt: The salt.
:param network_passphrase: The network passphrase, default to the network of the Soroban server.
:param base_fee: The base fee for the transaction.
:param transaction_timeout: The timeout for the transaction.
:param submit_timeout: The timeout for submitting the transaction.
:param restore: Whether to restore the transaction.
:return: The contract ID.
"""
if network_passphrase is None:
network_passphrase = soroban_server.get_network().passphrase
address = source if isinstance(source, str) else source.account_id
builder = (
TransactionBuilder(
Account(source, 0), network_passphrase, base_fee=base_fee
)
.append_create_contract_op(wasm_id, address, constructor_args, salt)
.set_timeout(transaction_timeout)
)
contract_id = (
AssembledTransaction[str](
builder,
soroban_server,
signer,
lambda v: scval.from_address(v).address,
submit_timeout,
)
.simulate(restore)
.sign_and_submit(force=True)
)
assert isinstance(contract_id, str)
return contract_id
[docs]
@staticmethod
def create_stellar_asset_contract_from_asset(
asset: Asset,
source: str | MuxedAccount,
signer: Keypair,
soroban_server: SorobanServer,
network_passphrase: str | None = None,
base_fee: int = 100,
submit_timeout: int = 120,
) -> str:
"""Create a Stellar asset contract from an asset.
:param asset: The asset.
:param source: The source account for the transaction.
:param signer: The signer for the transaction.
:param soroban_server: The Soroban server.
:param network_passphrase: The network passphrase, default to the network of the Soroban server.
:param base_fee: The base fee for the transaction.
:param submit_timeout: The timeout for submitting the transaction.
:return: The contract ID.
"""
if network_passphrase is None:
network_passphrase = soroban_server.get_network().passphrase
builder = (
TransactionBuilder(
Account(source, 0), network_passphrase, base_fee=base_fee
)
.append_create_stellar_asset_contract_from_asset_op(asset, source)
.add_time_bounds(0, 0)
)
contract_id = (
AssembledTransaction[str](
builder,
soroban_server,
signer,
lambda v: scval.from_address(v).address,
submit_timeout,
)
.simulate()
.sign_and_submit(force=True)
)
assert isinstance(contract_id, str)
return contract_id
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.server.close()