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
55 changes: 42 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,29 +147,53 @@ visit the [Payment](#payment) section.

## Payment

### Free call
When creating a service client, you can select a payment strategy using the `payment_strategy_type` parameter:

If you want to use the free calls you will need to pass these arguments to the `create_service_client()` method:
```python
from snet.sdk import PaymentStrategyType

```python
free_call_auth_token_bin = "f2548d27ffd319b9c05918eeac15ebab934e5cfcd68e1ec3db2b92765",
free_call_token_expiry_block = 172800,
email = "test@test.com" # which using in AI marketplace account
payment_strategy_type = PaymentStrategyType.<NAME>
```

You can receive these for a given service from the [Dapp](https://beta.singularitynet.io/)
These are four payment strategies:

- `PaymentStrategyType.DEFAULT`
- `PaymentStrategyType.FREE_CALL`
- `PaymentStrategyType.PAID_CALL`
- `PaymentStrategyType.PREPAID_CALL`

The default payment strategy selects one of the other three each time the service is called, depending on the
availability of free calls, as well as the presence of parameters required for concurrent calls. While choosing
a specific payment strategy will not allow you to switch to another. This is especially convenient when you want
to use free calls without accidentally spending money.

> Note: If you don't specify еру `payment_strategy_type` parameter, the default payment strategy will be used.

### Free call

If you want to use the free calls you will need to choose `PaymentStrategyType.FREE_CALL` as the payment strategy type.
Creating a service client with free calls included would look like this:

```python
service_client = snet_sdk.create_service_client(org_id="26072b8b6a0e448180f8c0e702ab6d2f",
service_id="Exampleservice"
free_call_auth_token_bin="f2548d27ffd319b9c05918eeac15ebab934e5cfcd68e1ec3db2b92765",
free_call_token_expiry_block=172800,
email="test@mail.com")
service_id="Exampleservice",
payment_strategy_type = PaymentStrategyType.FREE_CALL)
```

### Paid call

If you want to use regular paid calls you will need to choose `PaymentStrategyType.PAID_CALL` as the payment strategy type.
Creating a service client with paid calls would look like this:

```python
service_client = snet_sdk.create_service_client(org_id="26072b8b6a0e448180f8c0e702ab6d2f",
service_id="Exampleservice",
payment_strategy_type = PaymentStrategyType.PAID_CALL)
```

There is no need to call functions for interacting with payment channels, because they are automatically
managed by the SDK. But anyway you can use them if you want.

#### Open channel with the specified amount of funds and expiration

`open_channel()`[[1]](#1-this-method-uses-a-call-to-a-paid-smart-contract-function) opens a payment channel with the specified amount of AGIX tokens in cogs and expiration time.
Expand Down Expand Up @@ -209,15 +233,20 @@ payment_channel.extend_and_add_funds(amount=123456, expiration=33333)

### Concurrent (Prepaid) call

Concurrent (prepaid) calls allow you to prepay for a batch of service calls in advance. This off-chain strategy is ideal for scenarios requiring high throughput and low latency. Unlike regular paid calls, the payment is done once upfront, and the SDK automatically manages the channel during usage.
Concurrent (prepaid) calls allow you to prepay for a batch of service calls in advance. This off-chain strategy
is ideal for scenarios requiring high throughput and low latency. Unlike regular paid calls, the payment is done
once upfront, and the SDK automatically manages the channel during usage.

To use concurrent prepaid calls, specify the `concurrent_calls` parameter when creating a service client:
If you want to use prepaid calls you will need to choose `PaymentStrategyType.PREPAID_CALL` as the payment strategy type
as well as pass the number of concurrent calls as the `concurrent_calls` parameter. Creating a service client with
prepaid calls would look like this:

```python
service_client = snet_sdk.create_service_client(
org_id="26072b8b6a0e448180f8c0e702ab6d2f",
service_id="Exampleservice",
group_name="default_group",
payment_strategy_type=PaymentStrategyType.PREPAID_CALL,
concurrent_calls=5 # Number of prepaid calls to allocate
)
```
Expand Down
25 changes: 22 additions & 3 deletions docs/main/init.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
[Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/__init__.py) to GitHub

Entities:
1. [SnetSDK](#class-snetsdk)
1. [PaymentStrategyType](#class-paymentstrategytype)
2. [SnetSDK](#class-snetsdk)
- [\_\_init\_\_](#__init__)
- [create_service_client](#create_service_client)
- [get_service_stub](#get_service_stub)
Expand All @@ -15,6 +16,24 @@ Entities:
- [get_organization_list](#get_organization_list)
- [get_services_list](#get_services_list)

### Class `PaymentStrategyType`

extends: `Enum`

is extended by: -

#### description

This is an `enum` that represents the available payment strategies. It is used to determine which payment
strategy to use when initializing the SDK.

#### members

- `DEFAULT`
- `FREE_CALL`
- `PAID_CALL`
- `PREPAID_CALL`

### Class `SnetSDK`

extends: -
Expand Down Expand Up @@ -73,8 +92,8 @@ of the `ServiceClient` class with all the required parameters, which is then ret
- `service_id` (str): The ID of the service.
- `group_name` (str): The name of the payment group. Defaults to _None_.
- `payment_strategy` (PaymentStrategy): The payment channel management strategy. Defaults to _None_.
- `free_call_auth_token_bin` (str): The free call authentication token in binary format. Defaults to _None_.
- `free_call_token_expiry_block` (int): The block number when the free call token expires. Defaults to _None_.
- `payment_strategy_type` (PaymentStrategyType): The type of payment management strategy.
Defaults to `PaymentStrategyType.DEFAULT`.
- `options` (dict): Additional options for the service client. Defaults to _None_.
- `concurrent_calls` (int): The number of concurrent calls allowed. Defaults to 1.

Expand Down
33 changes: 27 additions & 6 deletions docs/payment_strategies/freecall_payment_strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

Entities:
1. [FreeCallPaymentStrategy](#class-freecallpaymentstrategy)
- [is_free_call_available](#is_free_call_available)
- [get_free_calls_available](#get_free_calls_available)
- [get_payment_metadata](#get_payment_metadata)
- [generate_signature](#generate_signature)
- [get_free_call_token_details](#get_free_call_token_details)

### Class `FreeCallPaymentStrategy`

Expand All @@ -17,28 +18,29 @@ is extended by: -
#### description

The `FreeCallPaymentStrategy` class is a concrete implementation of the `PaymentStrategy` interface.
It allows you to use free calls (which can be received from the [Dapp](https://beta.singularitynet.io/)) to
It allows you to use free calls (which can be received from the daemon) to
call services.

#### methods

#### `is_free_call_available`
#### `get_free_calls_available`

Checks if a free call is available for a given service client.
Using grpc calls to the daemon, it gets a free call token, and also gets and returns the number of free calls
available.

###### args:

- `service_client` (ServiceClient): The service client instance.

###### returns:

- True if a free call is available, False otherwise. (bool)
- Amount of free calls available. (int)

###### raises:

- Exception: If an error occurs while checking the free call availability.

_Note_: If any exception occurs during the process, it returns False.
_Note_: If an error occurs specifically during the grpc call to `GetFreeCallsAvailable`, 0 will be returned.

#### `get_payment_metadata`

Expand All @@ -60,6 +62,8 @@ Generates a signature for the given service client using the provided free call
###### args:

- `service_client` (ServiceClient): The service client instance.
- `current_block_number` (int, optional): The current block number. Defaults to _None_.
- `with_token` (bool, optional): Whether to include the free call token in the signature. Defaults to _True_.

###### returns:

Expand All @@ -68,3 +72,20 @@ Generates a signature for the given service client using the provided free call
###### raises:

- Exception: If any of the required parameters for the free call strategy are missing.

#### `get_free_call_token_details`

Sends a request to the daemon and receives a free call token and its details.

###### args:

- `service_client` (ServiceClient): The service client instance.
- `current_block_number` (int, optional): The current block number. Defaults to _None_.

###### returns:

- A tuple containing the free call token and the token expiration block number. (tuple[str, int])

###### raises:

- Exception: If an error occurred while receiving the token.
34 changes: 12 additions & 22 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
protobuf==4.21.6
grpcio-tools==1.59.0
wheel==0.41.2
jsonrpcclient==4.0.3
eth-hash==0.5.2
rlp==3.0.0
eth-rlp==0.3.0
web3==6.11.1
mnemonic==0.20
pycoin==0.92.20230326
pyyaml==6.0.1
ipfshttpclient==0.4.13.2
rfc3986==2.0.0
pymultihash==0.8.2
base58==2.1.1
argcomplete==3.1.2
grpcio-health-checking==1.59.0
jsonschema==4.0.0
eth-account==0.9.0
snet-contracts==1.0.0
lighthouseweb3==0.1.4
zipp>=3.19.1
protobuf==5.*
grpcio-tools>=1.71.0
wheel>=0.45.0
rlp>=4.1.0
web3==7.*
ipfshttpclient==0.4.13
rfc3986>=2.0.0
base58>=2.1.1
grpcio-health-checking>=1.71.0
snet-contracts==1.0.1
lighthouseweb3>=0.1.4
pymultihash==0.*
45 changes: 23 additions & 22 deletions snet/sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os
import sys
import warnings
from enum import Enum

import google.protobuf.internal.api_implementation

Expand All @@ -26,18 +27,28 @@
from snet.sdk.client_lib_generator import ClientLibGenerator
from snet.sdk.mpe.mpe_contract import MPEContract
from snet.sdk.mpe.payment_channel_provider import PaymentChannelProvider
from snet.sdk.payment_strategies.default_payment_strategy import DefaultPaymentStrategy as PaymentStrategy
from snet.sdk.payment_strategies.default_payment_strategy import *
from snet.sdk.service_client import ServiceClient
from snet.sdk.storage_provider.storage_provider import StorageProvider
from snet.sdk.custom_typing import ModuleName, ServiceStub
from snet.sdk.utils.utils import (bytes32_to_str, find_file_by_keyword,
type_converter)
from snet.sdk.utils.utils import (
bytes32_to_str,
find_file_by_keyword,
type_converter
)

google.protobuf.internal.api_implementation.Type = lambda: 'python'
_sym_db = _symbol_database.Default()
_sym_db.RegisterMessage = lambda x: None


class PaymentStrategyType(Enum):
PAID_CALL = PaidCallPaymentStrategy
FREE_CALL = FreeCallPaymentStrategy
PREPAID_CALL = PrePaidPaymentStrategy
DEFAULT = DefaultPaymentStrategy


class SnetSDK:
"""Base Snet SDK"""

Expand Down Expand Up @@ -91,11 +102,9 @@ def __init__(self, sdk_config: Config, metadata_provider=None):
def create_service_client(self,
org_id: str,
service_id: str,
group_name=None,
payment_strategy=None,
free_call_auth_token_bin=None,
free_call_token_expiry_block=None,
email=None,
group_name: str=None,
payment_strategy: PaymentStrategy = None,
payment_strategy_type: PaymentStrategyType=PaymentStrategyType.DEFAULT,
options=None,
concurrent_calls: int = 1):

Expand All @@ -120,22 +129,13 @@ def create_service_client(self,
print("Generating client library...")
self.lib_generator.generate_client_library()

if payment_strategy is None:
payment_strategy = PaymentStrategy(
concurrent_calls=concurrent_calls
)

if options is None:
options = dict()
options['free_call_auth_token-bin'] = (
bytes.fromhex(free_call_auth_token_bin) if
free_call_token_expiry_block else ""
)
options['free-call-token-expiry-block'] = (
free_call_token_expiry_block if free_call_token_expiry_block else 0
)
options['email'] = email if email else ""
options['concurrency'] = self._sdk_config.get("concurrency", True)
options['concurrent_calls'] = concurrent_calls

if payment_strategy is None:
payment_strategy = payment_strategy_type.value()

service_metadata = self._metadata_provider.enhance_service_metadata(
org_id, service_id
Expand All @@ -146,7 +146,8 @@ def create_service_client(self,

pb2_module = self.get_module_by_keyword(keyword="pb2.py")
_service_client = ServiceClient(org_id, service_id, service_metadata,
group, service_stubs, payment_strategy,
group, service_stubs,
payment_strategy,
options, self.mpe_contract,
self.account, self.web3, pb2_module,
self.payment_channel_provider,
Expand Down
2 changes: 1 addition & 1 deletion snet/sdk/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def _send_signed_transaction(self, contract_fn, *args):
signed_txn = self.web3.eth.account.sign_transaction(
transaction, private_key=self.private_key)
return self.web3.to_hex(
self.web3.eth.send_raw_transaction(signed_txn.rawTransaction)
self.web3.eth.send_raw_transaction(signed_txn.raw_transaction)
)

def send_transaction(self, contract_fn, *args):
Expand Down
13 changes: 8 additions & 5 deletions snet/sdk/concurrency_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
import grpc
import web3

from snet.sdk.service_client import ServiceClient
from snet.sdk.utils.utils import RESOURCES_PATH, add_to_path


class ConcurrencyManager:
def __init__(self, concurrent_calls: int):
def __init__(self, concurrent_calls: int=1):
self.__concurrent_calls: int = concurrent_calls
self.__token: str = ''
self.__planned_amount: int = 0
Expand All @@ -18,14 +17,18 @@ def __init__(self, concurrent_calls: int):
def concurrent_calls(self) -> int:
return self.__concurrent_calls

@concurrent_calls.setter
def concurrent_calls(self, concurrent_calls: int):
self.__concurrent_calls = concurrent_calls

def get_token(self, service_client, channel, service_call_price):
if len(self.__token) == 0:
self.__token = self.__get_token(service_client, channel, service_call_price)
elif self.__used_amount >= self.__planned_amount:
self.__token = self.__get_token(service_client, channel, service_call_price, new_token=True)
return self.__token

def __get_token(self, service_client: ServiceClient, channel, service_call_price, new_token=False):
def __get_token(self, service_client, channel, service_call_price, new_token=False):
if not new_token:
amount = channel.state["last_signed_amount"]
if amount != 0:
Expand All @@ -47,13 +50,13 @@ def __get_token(self, service_client: ServiceClient, channel, service_call_price
self.__planned_amount = token_reply.planned_amount
return token_reply.token

def __get_stub_for_get_token(self, service_client: ServiceClient):
def __get_stub_for_get_token(self, service_client):
grpc_channel = service_client.get_grpc_base_channel()
with add_to_path(str(RESOURCES_PATH.joinpath("proto"))):
token_service_pb2_grpc = importlib.import_module("token_service_pb2_grpc")
return token_service_pb2_grpc.TokenServiceStub(grpc_channel)

def __get_token_for_amount(self, service_client: ServiceClient, channel, amount):
def __get_token_for_amount(self, service_client, channel, amount):
nonce = channel.state["nonce"]
stub = self.__get_stub_for_get_token(service_client)
with add_to_path(str(RESOURCES_PATH.joinpath("proto"))):
Expand Down
Loading
Loading