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
14 changes: 4 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,23 +149,17 @@ visit the [Payment](#payment) section.

### Free call

If you want to use the free calls you will need to pass these arguments to the `create_service_client()` method:
If you want to use the free calls you will need to pass this argument to the `create_service_client()` method:

```python
free_call_auth_token_bin = "f2548d27ffd319b9c05918eeac15ebab934e5cfcd68e1ec3db2b92765",
free_call_token_expiry_block = 172800,
email = "test@test.com" # which using in AI marketplace account
address = "YOUR_ETHEREUM_ADDRESS"
```

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

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",
address="0xtest")
```

### Paid call
Expand Down
3 changes: 1 addition & 2 deletions docs/main/init.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ 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_.
- `address` (str): Wallet address to use free calls. Defaults to _None_.
- `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
32 changes: 31 additions & 1 deletion docs/payment_strategies/freecall_payment_strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Entities:
- [is_free_call_available](#is_free_call_available)
- [get_payment_metadata](#get_payment_metadata)
- [generate_signature](#generate_signature)
- [get_free_call_token_details](#get_free_call_token_details)
- [select_channel](#select_channel)

### Class `FreeCallPaymentStrategy`

Expand All @@ -17,7 +19,7 @@ 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
Expand Down Expand Up @@ -68,3 +70,31 @@ 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.

###### args:

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

###### 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.

#### `select_channel`

Creates a channel to the daemon.

###### args:

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

###### returns:

- The channel for the service calling.
17 changes: 4 additions & 13 deletions snet/sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
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 DefaultPaymentStrategy
from snet.sdk.service_client import ServiceClient
from snet.sdk.storage_provider.storage_provider import StorageProvider
from snet.sdk.custom_typing import ModuleName, ServiceStub
Expand Down Expand Up @@ -93,9 +93,7 @@ def create_service_client(self,
service_id: str,
group_name=None,
payment_strategy=None,
free_call_auth_token_bin=None,
free_call_token_expiry_block=None,
email=None,
address=None,
options=None,
concurrent_calls: int = 1):

Expand All @@ -121,20 +119,13 @@ def create_service_client(self,
self.lib_generator.generate_client_library()

if payment_strategy is None:
payment_strategy = PaymentStrategy(
payment_strategy = DefaultPaymentStrategy(
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['user_address'] = address if address else ""
options['concurrency'] = self._sdk_config.get("concurrency", True)

service_metadata = self._metadata_provider.enhance_service_metadata(
Expand Down
100 changes: 61 additions & 39 deletions snet/sdk/payment_strategies/freecall_payment_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,87 +2,109 @@
from urllib.parse import urlparse

import grpc
from grpc import _channel
import web3
from snet.sdk.resources.root_certificate import certificate
from snet.sdk.utils.utils import RESOURCES_PATH, add_to_path

from snet.sdk.payment_strategies.payment_strategy import PaymentStrategy

from snet.sdk.resources.root_certificate import certificate
from snet.sdk.utils.utils import RESOURCES_PATH, add_to_path

class FreeCallPaymentStrategy(PaymentStrategy):

def is_free_call_available(self, service_client):
def is_free_call_available(self, service_client) -> bool:
try:
org_id, service_id, group_id, daemon_endpoint = service_client.get_service_details()
email, token_for_free_call, token_expiry_date_block = service_client.get_free_call_config()
self._user_address = service_client.options["user_address"]
self._free_call_token, self._token_expiry_date_block = self.get_free_call_token_details(service_client)

if not token_for_free_call:
if not self._free_call_token:
return False

signature, current_block_number = self.generate_signature(service_client)
with add_to_path(str(RESOURCES_PATH.joinpath("proto"))):
state_service_pb2 = importlib.import_module("state_service_pb2")

with add_to_path(str(RESOURCES_PATH.joinpath("proto"))):
state_service_pb2_grpc = importlib.import_module("state_service_pb2_grpc")

signature, current_block_number = self.generate_signature(service_client)

request = state_service_pb2.FreeCallStateRequest()
request.user_id = email
request.token_for_free_call = token_for_free_call
request.token_expiry_date_block = token_expiry_date_block
request.user_address = self._user_address
request.token_for_free_call = self._free_call_token
request.token_expiry_date_block = self._token_expiry_date_block
request.signature = signature
request.current_block = current_block_number

endpoint_object = urlparse(daemon_endpoint)
if endpoint_object.port is not None:
channel_endpoint = endpoint_object.hostname + ":" + str(endpoint_object.port)
else:
channel_endpoint = endpoint_object.hostname

if endpoint_object.scheme == "http":
channel = grpc.insecure_channel(channel_endpoint)
elif endpoint_object.scheme == "https":
channel = grpc.secure_channel(channel_endpoint, grpc.ssl_channel_credentials(root_certificates=certificate))
else:
raise ValueError('Unsupported scheme in service metadata ("{}")'.format(endpoint_object.scheme))
channel = self.select_channel(service_client)

stub = state_service_pb2_grpc.FreeCallStateServiceStub(channel)
response = stub.GetFreeCallsAvailable(request)
if response.free_calls_available > 0:
return True
return False
except grpc.RpcError as e:
if self._user_address:
print(f"Warning: {e.details()}")
return False
except Exception as e:
return False

def get_payment_metadata(self, service_client):
email, token_for_free_call, token_expiry_date_block = service_client.get_free_call_config()
def get_payment_metadata(self, service_client) -> list:
signature, current_block_number = self.generate_signature(service_client)
metadata = [("snet-free-call-auth-token-bin", token_for_free_call),
("snet-free-call-token-expiry-block", str(token_expiry_date_block)),
metadata = [("snet-free-call-auth-token-bin", self._free_call_token),
("snet-free-call-token-expiry-block", str(self._token_expiry_date_block)),
("snet-payment-type", "free-call"),
("snet-free-call-user-id", email),
("snet-free-call-user-id", self._user_address),
("snet-current-block-number", str(current_block_number)),
("snet-payment-channel-signature-bin", signature)]

return metadata

def select_channel(self, service_client):
pass

def generate_signature(self, service_client):
org_id, service_id, group_id, daemon_endpoint = service_client.get_service_details()
email, token_for_free_call, token_expiry_date_block = service_client.get_free_call_config()

if token_expiry_date_block == 0 or len(email) == 0 or len(token_for_free_call) == 0:
def select_channel(self, service_client) -> _channel.Channel:
_, _, _, daemon_endpoint = service_client.get_service_details()
endpoint_object = urlparse(daemon_endpoint)
if endpoint_object.port is not None:
channel_endpoint = endpoint_object.hostname + ":" + str(endpoint_object.port)
else:
channel_endpoint = endpoint_object.hostname

if endpoint_object.scheme == "http":
channel = grpc.insecure_channel(channel_endpoint)
elif endpoint_object.scheme == "https":
channel = grpc.secure_channel(channel_endpoint, grpc.ssl_channel_credentials(root_certificates=certificate))
else:
raise ValueError('Unsupported scheme in service metadata ("{}")'.format(endpoint_object.scheme))
return channel

def generate_signature(self, service_client) -> tuple[bytes, int]:
org_id, service_id, group_id, _ = service_client.get_service_details()

if self._token_expiry_date_block == 0 or len(self._user_address) == 0 or len(self._free_call_token) == 0:
raise Exception(
"You are using default 'FreeCallPaymentStrategy' to use this strategy you need to pass "
"'free_call_auth_token-bin','email','free-call-token-expiry-block' in config")
"'free_call_auth_token-bin','user_address','free-call-token-expiry-block' in config")

current_block_number = service_client.get_current_block_number()

message = web3.Web3.solidity_keccak(
["string", "string", "string", "string", "string", "uint256", "bytes32"],
["__prefix_free_trial", email, org_id, service_id, group_id, current_block_number,
token_for_free_call]
["__prefix_free_trial", self._user_address, org_id, service_id, group_id, current_block_number,
self._free_call_token]
)
return service_client.generate_signature(message), current_block_number

def get_free_call_token_details(self, service_client) -> tuple[bytes, int]:
with add_to_path(str(RESOURCES_PATH.joinpath("proto"))):
state_service_pb2 = importlib.import_module("state_service_pb2")

request = state_service_pb2.GetFreeCallTokenRequest()
request.address = self._user_address

with add_to_path(str(RESOURCES_PATH.joinpath("proto"))):
state_service_pb2_grpc = importlib.import_module("state_service_pb2_grpc")

channel = self.select_channel(service_client)
stub = state_service_pb2_grpc.FreeCallStateServiceStub(channel)
response = stub.GetFreeCallToken(request)

return response.token, response.token_expiration_block

Loading
Loading