├── .github └── workflows │ ├── dev.yml │ └── master.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── main │ ├── account.md │ ├── client_lib_generator.md │ ├── concurrency_manager.md │ ├── config.md │ ├── init.md │ └── service_client.md ├── mpe │ ├── mpe_contract.md │ ├── payment_channel.md │ └── payment_channel_provider.md ├── payment_strategies │ ├── default_payment_strategy.md │ ├── freecall_payment_strategy.md │ ├── paidcall_payment_strategy.md │ ├── payment_strategy.md │ ├── prepaid_payment_strategy.md │ └── training_payment_strategy.md ├── snet-sdk-python-documentation.md ├── storage_provider │ ├── service_metadata.md │ └── storage_provider.md ├── training │ ├── exceptions.md │ ├── responses.md │ └── training.md └── utils │ ├── call_utils.md │ ├── ipfs_utils.md │ └── utils.md ├── examples ├── calculator.py ├── console_app.py └── examples_docs │ ├── calculator.md │ └── console_app.md ├── requirements.txt ├── scripts └── package-pip ├── setup.py ├── snet └── sdk │ ├── __init__.py │ ├── account.py │ ├── client_lib_generator.py │ ├── concurrency_manager.py │ ├── config.py │ ├── custom_typing.py │ ├── generic_client_interceptor.py │ ├── mpe │ ├── __init__.py │ ├── mpe_contract.py │ ├── payment_channel.py │ └── payment_channel_provider.py │ ├── payment_strategies │ ├── __init__.py │ ├── default_payment_strategy.py │ ├── freecall_payment_strategy.py │ ├── paidcall_payment_strategy.py │ ├── payment_strategy.py │ ├── prepaid_payment_strategy.py │ └── training_payment_strategy.py │ ├── resources │ ├── package.json │ ├── proto │ │ ├── control_service.proto │ │ ├── control_service_pb2.py │ │ ├── control_service_pb2_grpc.py │ │ ├── state_service.proto │ │ ├── state_service_pb2.py │ │ ├── state_service_pb2_grpc.py │ │ ├── token_service.proto │ │ ├── token_service_pb2.py │ │ ├── token_service_pb2_grpc.py │ │ ├── training.proto │ │ ├── training │ │ │ └── training.proto │ │ ├── training_daemon.proto │ │ ├── training_daemon_pb2.py │ │ ├── training_daemon_pb2_grpc.py │ │ ├── training_pb2.py │ │ └── training_pb2_grpc.py │ └── root_certificate.py │ ├── service_client.py │ ├── storage_provider │ ├── __init__.py │ ├── service_metadata.py │ └── storage_provider.py │ ├── training │ ├── exceptions.py │ ├── responses.py │ └── training.py │ └── utils │ ├── __init__.py │ ├── call_utils.py │ ├── ipfs_utils.py │ └── utils.py ├── testcases ├── __init__.py ├── functional_tests │ ├── __init__.py │ ├── snetd.config.json │ ├── test_prepaid_payment.py │ └── test_sdk_client.py └── utils │ ├── reset_environment.sh │ └── run_all_functional.sh ├── tests └── unit_tests │ ├── test_account.py │ ├── test_config.py │ ├── test_files.zip │ ├── test_lib_generator.py │ ├── test_service_client.py │ └── test_training_v2.py └── version.py /.github/workflows/dev.yml: -------------------------------------------------------------------------------- 1 | name: tests_development 2 | on: 3 | # push: 4 | # branches: [ "development" ] 5 | pull_request: 6 | branches: [ "development" ] 7 | workflow_dispatch: 8 | 9 | jobs: 10 | run_tests_development: 11 | runs-on: ubuntu-latest 12 | container: node:20-bookworm 13 | steps: 14 | 15 | - name: install packs 16 | run: | 17 | apt update 18 | apt install -y libudev-dev libusb-1.0-0-dev curl jq 19 | apt install -y python3-pip python3.11-venv 20 | echo DEV 21 | 22 | - name: install ipfs 23 | run: | 24 | wget https://dist.ipfs.io/go-ipfs/v0.9.0/go-ipfs_v0.9.0_linux-amd64.tar.gz 25 | tar -xvzf go-ipfs_v0.9.0_linux-amd64.tar.gz 26 | bash go-ipfs/install.sh 27 | ipfs --version 28 | node --version 29 | 30 | - name: clone repo 31 | uses: actions/checkout@v3 32 | 33 | # - name: install platform-contracts 34 | # run: | 35 | # cd .. 36 | # git clone https://github.com/singnet/platform-contracts.git 37 | # cd platform-contracts 38 | # npm install 39 | # npm install ganache-cli 40 | # npm run-script compile 41 | 42 | # - name: build example service 43 | # run: | 44 | # git clone https://github.com/singnet/example-service.git 45 | # cd example-service 46 | # pip3 install -r requirements.txt --break-system-packages 47 | # sh buildproto.sh 48 | 49 | - name: Set up for snet daemon 50 | run: | 51 | cd .. 52 | mkdir snet-daemon 53 | cd snet-daemon 54 | wget https://github.com/singnet/snet-daemon/releases/download/v5.0.1/snet-daemon-v5.0.1-linux-amd64.tar.gz 55 | tar -xvf snet-daemon-v5.0.1-linux-amd64.tar.gz 56 | cd snet-daemon-v5.0.1-linux-amd64 57 | cp ../../snet-sdk-python/testcases/functional_tests/snetd.config.json . 58 | 59 | - name: install pip packages 60 | run: | 61 | pip3 install -r requirements.txt --break-system-packages 62 | # mkdir ~/.snet 63 | # cp ./testcases/utils/config ~/.snet/ 64 | # cat ~/.snet/config 65 | 66 | - name: install snet-sdk 67 | run: pip3 install -e . --break-system-packages 68 | 69 | - name: functional tests for sdk 70 | run: | 71 | export SNET_TEST_WALLET_PRIVATE_KEY=${{ secrets.PRIV_KEY }} 72 | export SNET_TEST_INFURA_KEY=${{ secrets.INF_KEY }} 73 | export FORMER_SNET_TEST_INFURA_KEY=${{ secrets.FORM_INF_KEY }} 74 | export PIP_BREAK_SYSTEM_PACKAGES=1 75 | export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python 76 | sh -ex ./testcases/utils/run_all_functional.sh 77 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: tests_master 2 | on: 3 | # push: 4 | # branches: [ "master" ] 5 | pull_request: 6 | branches: [ "master" ] 7 | workflow_dispatch: 8 | 9 | jobs: 10 | run_tests_master: 11 | runs-on: ubuntu-latest 12 | container: node:20-bookworm 13 | steps: 14 | 15 | - name: install packs 16 | run: | 17 | apt update 18 | apt install -y libudev-dev libusb-1.0-0-dev curl jq 19 | apt install -y python3-pip python3.11-venv 20 | echo MASTER 21 | 22 | - name: install ipfs 23 | run: | 24 | wget https://dist.ipfs.io/go-ipfs/v0.9.0/go-ipfs_v0.9.0_linux-amd64.tar.gz 25 | tar -xvzf go-ipfs_v0.9.0_linux-amd64.tar.gz 26 | bash go-ipfs/install.sh 27 | ipfs --version 28 | node --version 29 | 30 | - name: clone repo 31 | uses: actions/checkout@v3 32 | 33 | # - name: install platform-contracts 34 | # run: | 35 | # cd .. 36 | # git clone https://github.com/singnet/platform-contracts.git 37 | # cd platform-contracts 38 | # npm install 39 | # npm install ganache-cli 40 | # npm run-script compile 41 | 42 | # - name: build example service 43 | # run: | 44 | # git clone https://github.com/singnet/example-service.git 45 | # cd example-service 46 | # pip3 install -r requirements.txt --break-system-packages 47 | # sh buildproto.sh 48 | 49 | 50 | - name: Set up for snet daemon 51 | run: | 52 | cd .. 53 | mkdir snet-daemon 54 | cd snet-daemon 55 | wget https://github.com/singnet/snet-daemon/releases/download/v5.0.1/snet-daemon-v5.0.1-linux-amd64.tar.gz 56 | tar -xvf snet-daemon-v5.0.1-linux-amd64.tar.gz 57 | cd snet-daemon-v5.0.1-linux-amd64 58 | cp ../../snet-sdk-python/testcases/functional_tests/snetd.config.json . 59 | 60 | - name: install pip packages 61 | run: | 62 | pip3 install -r requirements.txt --break-system-packages 63 | # mkdir ~/.snet 64 | # cp ./testcases/utils/config ~/.snet/ 65 | # cat ~/.snet/config 66 | 67 | - name: install snet-sdk 68 | run: pip3 install -e . --break-system-packages 69 | 70 | - name: functional tests for sdk 71 | run: | 72 | export SNET_TEST_WALLET_PRIVATE_KEY=${{ secrets.PRIV_KEY }} 73 | export SNET_TEST_INFURA_KEY=${{ secrets.INF_KEY }} 74 | export FORMER_SNET_TEST_INFURA_KEY=${{ secrets.FORM_INF_KEY }} 75 | export PIP_BREAK_SYSTEM_PACKAGES=1 76 | export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python 77 | sh -ex ./testcases/utils/run_all_functional.sh 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .venv 3 | env/ 4 | venv/ 5 | .idea 6 | snet.sdk.egg-info/ 7 | blockchain/node_modules/ 8 | __pycache__ 9 | snet_sdk/resources/contracts/abi 10 | snet_sdk/resources/contracts/networks 11 | .pyi 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 SingularityNET 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include snet/sdk/resources * 2 | 3 | recursive-exclude * .git 4 | recursive-exclude * node_modules 5 | recursive-exclude * __pycache__ 6 | recursive-exclude * *.py[co] -------------------------------------------------------------------------------- /docs/main/account.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.account 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/account.py) to GitHub 4 | 5 | Entities: 6 | 1. [TransactionError](#class-transactionerror) 7 | - [\_\_init\_\_](#__init__) 8 | - [\_\_str\_\_](#__str__) 9 | 2. [Account](#class-account) 10 | - [\_\_init\_\_](#__init__-1) 11 | - [_get_nonce](#_get_nonce) 12 | - [_get_gas_price](#_get_gas_price) 13 | - [_send_signed_transaction](#_send_signed_transaction) 14 | - [send_transaction](#send_transaction) 15 | - [_parse_receipt](#_parse_receipt) 16 | - [escrow_balance](#escrow_balance) 17 | - [deposit_to_escrow_account](#deposit_to_escrow_account) 18 | - [approve_transfer](#approve_transfer) 19 | - [allowance](#allowance) 20 | 21 | ### Class `TransactionError` 22 | 23 | extends: `Exception` 24 | 25 | is extended by: - 26 | 27 | #### description 28 | 29 | `TransactionError` is a custom exception class that is raised when an Ethereum transaction receipt has a status of 0. 30 | This indicates that the transaction failed. Can provide a custom message. Optionally includes receipt 31 | 32 | #### attributes 33 | 34 | - `message` (str): The exception message. 35 | - `receipt` (dict): The transaction receipt. 36 | 37 | #### methods 38 | 39 | #### `__init__` 40 | 41 | Initializes the exception with the provided message and receipt. 42 | 43 | ###### args: 44 | 45 | - `message` (str): The exception message. 46 | - `receipt` (dict): The transaction receipt. Defaults to _None_. 47 | 48 | ###### returns: 49 | 50 | - _None_ 51 | 52 | #### `__str__` 53 | 54 | Returns a string representation of the `TransactionError` object. 55 | 56 | ###### returns: 57 | 58 | - A string containing the `message` attribute of the TransactionError object. (str) 59 | 60 | ### Class `Account` 61 | 62 | extends: - 63 | 64 | is extended by: - 65 | 66 | #### description 67 | 68 | `Account` is responsible for managing the Ethereum account associated with the SingularityNET platform. 69 | It provides methods for interacting with the MultiPartyEscrow contract, the SingularityNetToken contract, and 70 | the Ethereum blockchain. 71 | 72 | #### attributes 73 | 74 | - `config` (dict): The configuration settings for the account. _Note_: In fact, this is the same config 75 | from `SnetSDK`. 76 | - `web3` (Web3): An instance of the `Web3` class for interacting with the Ethereum blockchain. 77 | - `mpe_contract` (MPEContract): An instance of the `MPEContract` class for interacting with 78 | the MultiPartyEscrow contract. 79 | - `token_contract` (Contract): An instance of the `Contract` class from the `web3` library for interacting 80 | with the SingularityNET AGIX Token contract. 81 | - `private_key` (str): The private key associated with the account. 82 | - `signer_private_key` (str): The private key used for signing transactions. 83 | - `address` (str): The Ethereum address associated with the account. 84 | - `signer_address` (str): The Ethereum address used for signing transactions. 85 | - `nonce` (int): The nonce value for the account. 86 | 87 | #### methods 88 | 89 | #### `__init__` 90 | 91 | Initializes a new instance of the `Account` class. 92 | 93 | ###### args: 94 | 95 | - `w3` (Web3): An instance of the `Web3` class. 96 | - `config` (dict): A dictionary containing the configuration settings. 97 | - `mpe_contract` (MPEContract): An instance of the `MPEContract` class. 98 | 99 | ###### returns: 100 | 101 | - _None_ 102 | 103 | #### `_get_nonce` 104 | 105 | Returns the next nonce for a transaction. 106 | 107 | ###### returns: 108 | 109 | - The next nonce for a transaction. (int) 110 | 111 | #### `_get_gas_price` 112 | 113 | Calculates the gas price for a transaction. 114 | 115 | Retrieves the current gas price from the Ethereum network using the web3 and increases it 116 | according to a certain algorithm so that the transaction goes faster. 117 | 118 | ###### returns: 119 | 120 | - The calculated gas price. (int) 121 | 122 | #### `_send_signed_transaction` 123 | 124 | Sends a signed transaction to the Ethereum blockchain. 125 | 126 | Builds a transaction using the given contract function and arguments, signs it with the private key of the account, 127 | and sends it to the Ethereum blockchain. 128 | 129 | ###### args: 130 | 131 | - `contract_fn`: The contract function to be called. 132 | - `*args`: The arguments to pass to the contract function. 133 | 134 | ###### returns: 135 | 136 | - Hash of the sent transaction. (HexStr | str) 137 | 138 | #### `send_transaction` 139 | 140 | Sends a transaction by calling the given contract function with the provided arguments. 141 | 142 | ###### args: 143 | 144 | - `contract_fn`: The contract function to be called. 145 | - `*args`: The arguments to pass to the contract function. 146 | 147 | ###### returns: 148 | 149 | - The transaction receipt indicating the success or failure of the transaction. (TxReceipt) 150 | 151 | #### `_parse_receipt` 152 | 153 | Parses the receipt of a transaction and returns the result as a JSON string. 154 | 155 | ###### args: 156 | 157 | - `receipt` (TxReceipt): The receipt of the transaction. 158 | - `event` (Event): The event to process the receipt with. 159 | - `encoder` (JSONEncoder): The JSON encoder to use. Defaults to json.JSONEncoder. 160 | 161 | ###### returns: 162 | 163 | - The result of processing the receipt as a JSON string. (str) 164 | 165 | ###### raises: 166 | 167 | - TransactionError: If the transaction status is 0, indicating a failed transaction. 168 | 169 | #### `escrow_balance` 170 | 171 | Retrieves the escrow balance for the current account. 172 | 173 | ###### returns: 174 | 175 | - The escrow balance in cogs. (int) 176 | 177 | #### `deposit_to_escrow_account` 178 | 179 | Deposit the specified amount of AGIX tokens in cogs into the MPE account. 180 | 181 | ###### args: 182 | 183 | - `amount_in_cogs` (int): The amount of AGIX tokens in cogs to deposit. 184 | 185 | ###### returns: 186 | 187 | - The transaction receipt of the transaction. (TxReceipt) 188 | 189 | #### `approve_transfer` 190 | 191 | Approves a transfer of a specified amount of AGIX tokens in cogs to the MPE contract. 192 | 193 | ###### args: 194 | 195 | - `amount_in_cogs` (int): The amount of AGIX tokens in cogs to approve for transfer. 196 | 197 | ###### returns: 198 | 199 | - The transaction receipt of the transaction. (TxReceipt) 200 | 201 | #### `allowance` 202 | 203 | Retrieves the allowance of the current account for the MPE contract. 204 | 205 | ###### returns: 206 | 207 | - The allowance in cogs. (int) 208 | 209 | -------------------------------------------------------------------------------- /docs/main/client_lib_generator.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.client_lib_generator 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/client_lib_generator.py) to GitHub 4 | 5 | Entities: 6 | 1. [ClientLibGenerator](#class-clientlibgenerator) 7 | - [\_\_init\_\_](#__init__) 8 | - [generate_client_library](#generate_client_library) 9 | - [generate_directories_by_params](#generate_directories_by_params) 10 | - [create_service_client_libraries_path](#create_service_client_libraries_path) 11 | - [receive_proto_files](#receive_proto_files) 12 | - [training_added](#training_added) 13 | 14 | ### Class `ClientLibGenerator` 15 | 16 | extends: - 17 | 18 | is extended by: - 19 | 20 | #### description 21 | 22 | This class is used to generate client library files for a given service. 23 | 24 | #### attributes 25 | 26 | - `_metadata_provider` (StorageProvider): An instance of the `StorageProvider` class. 27 | - `org_id` (str): The organization ID of the service. 28 | - `service_id` (str): The service ID. 29 | - `language` (str): The language of the client library. Default is `python`. 30 | - `protodir` (str): The directory where the .proto files are located. Default is `~/.snet`. 31 | 32 | #### methods 33 | 34 | #### `__init__` 35 | 36 | Initializes a new instance of the class. Initializes the attributes by arguments values. 37 | 38 | ###### args: 39 | 40 | - `metadata_provider` (StorageProvider): An instance of the `StorageProvider` class. 41 | - `org_id` (str): The organization ID of the service. 42 | - `service_id` (str): The service ID. 43 | - `protodir` (Path | None): The directory where the .proto files are located. Default is _None_. 44 | 45 | ###### returns: 46 | 47 | - _None_ 48 | 49 | #### `generate_client_library` 50 | 51 | Generates client library stub files based on specified organization and service ids, including: 52 | - getting service metadata from Registry 53 | - getting archived proto files from IPFS or FileCoin and extracting them 54 | - compiling .proto file to stub file and saving it in a given directory 55 | 56 | ###### returns: 57 | 58 | - _None_ 59 | 60 | #### `generate_directories_by_params` 61 | 62 | Generates directories for client library in the `~/.snet` directory based on organization and 63 | service ids using the `create_service_client_libraries_path` method. 64 | 65 | ###### returns: 66 | 67 | - _None_ 68 | 69 | #### `create_service_client_libraries_path` 70 | 71 | Creates a directory for client library in the `~/.snet` directory based on organization and 72 | service ids. 73 | 74 | ###### returns: 75 | 76 | - _None_ 77 | 78 | #### `receive_proto_files` 79 | 80 | Receives .proto files from IPFS or FileCoin based on service metadata and extracts them to a 81 | given directory. 82 | 83 | ###### returns: 84 | 85 | - _None_ 86 | 87 | ###### raises: 88 | 89 | - Exception: if the directory for storing proto files is not found. 90 | 91 | #### `training_added` 92 | 93 | Checks whether training is used in the service .proto file. 94 | 95 | ###### returns: 96 | 97 | - _True_ if training is used in the service .proto file, _False_ otherwise. -------------------------------------------------------------------------------- /docs/main/concurrency_manager.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.concurrency_manager 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/concurrency_manager.py) to GitHub 4 | 5 | Entities: 6 | 1. [ConcurrencyManager](#class-concurrencymanager) 7 | - [\_\_init\_\_](#__init__) 8 | - [concurrent_calls](#concurrent_calls) 9 | - [get_token](#get_token) 10 | - [__get_token](#__get_token) 11 | - [__get_stub_for_get_token](#__get_stub_for_get_token) 12 | - [__get_token_for_amount](#__get_token_for_amount) 13 | - [record_successful_call](#record_successful_call) 14 | 15 | ### Class `ConcurrencyManager` 16 | 17 | extends: - 18 | 19 | is extended by: - 20 | 21 | #### description 22 | 23 | `ConcurrencyManager` provides a mechanism for managing the concurrency of service calls in the SDK. 24 | It ensures that only a certain number of concurrent calls are made and handles the retrieval and management 25 | of tokens for making service calls. 26 | 27 | #### attributes 28 | 29 | - `__concurrent_calls` (int): The number of concurrent calls allowed. 30 | - `__token` (str): The token used for concurrent calls. 31 | - `__planned_amount` (int): The planned amount for the payment. 32 | - `__used_amount` (int): The amount used for the payment. 33 | 34 | #### methods 35 | 36 | #### `__init__` 37 | 38 | Initializes a new instance of the class. 39 | 40 | ###### args: 41 | 42 | - concurrent_calls (int): The number of concurrent calls allowed. 43 | 44 | ###### returns: 45 | 46 | - _None_ 47 | 48 | #### `concurrent_calls` 49 | 50 | decorator: `@property` 51 | 52 | Returns the number of concurrent calls allowed. 53 | 54 | ###### returns: 55 | 56 | - The number of concurrent calls allowed. (int) 57 | 58 | #### `get_token` 59 | 60 | Retrieves a token for making service calls. 61 | 62 | ###### args: 63 | 64 | - `service_client` (ServiceClient): The service client instance. 65 | - `channel` (PaymentChannel): The payment channel instance. 66 | - `service_call_price` (int): The price of a service call. 67 | 68 | ###### returns: 69 | 70 | - The token for making service calls. (str) 71 | 72 | #### `__get_token` 73 | 74 | Retrieves a token for a service call. 75 | 76 | ###### args: 77 | 78 | - `service_client` (ServiceClient): The service client instance. 79 | - `channel` (PaymentChannel): The payment channel instance. 80 | - `service_call_price` (int): The price of a service call. 81 | - `new_token` (bool): Whether is needed a new token. Defaults to `False`. 82 | 83 | ###### returns: 84 | 85 | - The token for the service call. (str) 86 | 87 | ###### raises: 88 | 89 | - grpc.RpcError: If an error occurs while retrieving the token. 90 | 91 | #### `__get_stub_for_get_token` 92 | 93 | Retrieves the gRPC service stub for the TokenServiceStub. 94 | 95 | ###### args: 96 | 97 | - `service_client` (ServiceClient): The service client instance. 98 | 99 | ###### returns: 100 | 101 | - The gRPC service stub for the TokenServiceStub. (ServiceStub) 102 | 103 | #### `__get_token_for_amount` 104 | 105 | Retrieves a token for a given amount from the token service. 106 | 107 | This function retrieves a token for a given amount from the token service. It first retrieves the nonce 108 | from the channel state, then it creates a stub for the token service using the `get_stub_for_get_token` 109 | method. It then imports the `token_service_pb2` module and retrieves the current block number from the 110 | service client's SDK web3 instance. It generates a message using the `solidity_keccak` method from the 111 | `web3.Web3` class and generates signatures using the `generate_signature` method from the service client. 112 | It creates a `TokenRequest` object with the necessary parameters and sends it to the token service using 113 | the `GetToken` method of the stub. Finally, it returns the token reply object containing the token. 114 | 115 | ###### args: 116 | 117 | - `service_client` (ServiceClient): The service client instance. 118 | - `channel` (PaymentChannel): The payment channel instance. 119 | - `amount` (int): The amount for which the token is requested. 120 | 121 | ###### returns: 122 | 123 | - The token reply object containing the token. (Any) 124 | 125 | #### `record_successful_call` 126 | 127 | Increments the `__used_amount` attribute by 1 to record a successful call. 128 | 129 | ###### returns: 130 | 131 | - _None_ 132 | 133 | -------------------------------------------------------------------------------- /docs/main/config.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.config 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/config.py) to GitHub 4 | 5 | Entities: 6 | 1. [Config](#class-config) 7 | - [\_\_init\_\_](#__init__) 8 | - [\_\_getitem\_\_](#__getitem__) 9 | - [get](#get) 10 | - [get_ipfs_endpoint](#get_ipfs_endpoint) 11 | 12 | 13 | ### Class `Config` 14 | 15 | extends: - 16 | 17 | is extended by: - 18 | 19 | #### description 20 | 21 | This is a configuration manager for the SDK. It is responsible for handling configuration settings for the SDK. 22 | 23 | #### attributes 24 | 25 | - `__config` (dict): The dictionary containing: 26 | - `private_key` (str): Your wallet's private key that will be used to pay for calls. Is **required** in config. 27 | - `eth_rpc_endpoint` (str): RPC endpoint that is used to access the Ethereum network. Is **required** in config. 28 | - `wallet_index` (int): The index of the wallet that will be used to pay for calls. 29 | - `ipfs_endpoint` (str): IPFS endpoint that is used to access IPFS. Defaults to _"/dns/ipfs.singularitynet.io/tcp/80/"_. 30 | - `concurrency` (bool): If set to True, will enable concurrency for the SDK. 31 | - `force_update` (bool): If set to False, will reuse the existing gRPC stubs (if any) instead of downloading proto 32 | and regenerating them every time. 33 | - `mpe_contract_address` (str): The address of the Multi-Party Escrow smart contract. 34 | - `token_contract_address` (str): The address of the SingularityNET token smart contract. 35 | - `registry_contract_address` (str): The address of the Registry smart contract. 36 | - `signer_private_key` (str): The private key of the signer. Used to sign the service call. Equals to `private_key` 37 | by default. 38 | - `lighthouse_token` (str): The Lighthouse token used to access the Lighthouse storage provider. Defaults to " ". 39 | Currently, it can't be changed. 40 | 41 | #### methods 42 | 43 | #### `__init__` 44 | 45 | Initializes a new instance of the class. Sets `__config` fields from passed arguments. 46 | 47 | ###### args: 48 | 49 | - `private_key` (str): Your wallet's private key that will be used to pay for calls. Is **required** in config. 50 | - `eth_rpc_endpoint` (str): RPC endpoint that is used to access the Ethereum network. Is **required** in config. 51 | - `wallet_index` (int): The index of the wallet that will be used to pay for calls. Defaults to _0_. 52 | - `ipfs_endpoint` (str): IPFS endpoint that is used to access IPFS. Defaults to _None_. 53 | - `concurrency` (bool): If set to True, will enable concurrency for the SDK. Defaults to _True_. 54 | - `force_update` (bool): If set to False, will reuse the existing gRPC stubs (if any) instead of downloading proto 55 | and regenerating them every time. Defaults to _False_. 56 | - `mpe_contract_address` (str): The address of the Multi-Party Escrow smart contract. Defaults to _None_. 57 | - `token_contract_address` (str): The address of the SingularityNET token smart contract. Defaults to _None_. 58 | - `registry_contract_address` (str): The address of the Registry smart contract. Defaults to _None_. 59 | - `signer_private_key` (str): The private key of the signer. Used to sign the service call. Equals to `private_key` 60 | by default. 61 | 62 | ###### returns: 63 | 64 | - _None_ 65 | 66 | #### `__getitem__` 67 | 68 | Overrides the `__getitem__` Python special method. Returns the value associated with the given key from 69 | the `__config` dict. 70 | 71 | ###### args: 72 | 73 | - `key` (str): The key of the value to retrieve. 74 | 75 | ###### returns: 76 | 77 | - The value associated with the given key. (Any) 78 | 79 | #### `get` 80 | 81 | Returns the value associated with the given key from the `__config` dict or the default value if the key is not found. 82 | 83 | ###### args: 84 | 85 | - `key` (str): The key of the value to retrieve. 86 | - `default` (Any): The default value to return if the key is not found. Defaults to _None_. 87 | 88 | ###### returns: 89 | 90 | - The value associated with the given key or the default value if the key is not found. (Any) 91 | 92 | #### `get_ipfs_endpoint` 93 | 94 | Returns the `ipfs_endpoint` field value from the `__config` dict. 95 | 96 | ###### returns: 97 | 98 | - The IPFS endpoint. (str) 99 | 100 | -------------------------------------------------------------------------------- /docs/mpe/mpe_contract.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.mpe.mpe_contract 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/mpe/mpe_contract.py) to GitHub 4 | 5 | Entities: 6 | 1. [MPEContract](#class-mpecontract) 7 | - [\_\_init\_\_](#__init__) 8 | - [balance](#balance) 9 | - [deposit](#deposit) 10 | - [open_channel](#open_channel) 11 | - [deposit_and_open_channel](#deposit_and_open_channel) 12 | - [channel_add_funds](#channel_add_funds) 13 | - [channel_extend](#channel_extend) 14 | - [channel_extend_and_add_funds](#channel_extend_and_add_funds) 15 | - [_fund_escrow_account](#_fund_escrow_account) 16 | 17 | ### Class `MPEContract` 18 | 19 | extends: - 20 | 21 | is extended by: - 22 | 23 | #### description 24 | 25 | The `MPEContract` class is responsible for interacting with the MultiPartyEscrow 26 | contract on the Ethereum blockchain. It provides methods for retrieving the balance of an address, depositing 27 | funds into the contract, opening a channel, adding funds to a channel, extending the expiration of a channel, and more. 28 | 29 | #### attributes 30 | 31 | - `web3` (Web3): An instance of the Web3 class for interacting with the Ethereum blockchain. 32 | - `contract` (Contract): An instance of the `Contract` class from the `web3` library for interacting 33 | with the MultiPartyEscrow contract. 34 | 35 | #### methods 36 | 37 | #### `__init__` 38 | 39 | Initializes a new instance of the class. The class is initialized with a Web3 object and an optional contract address. 40 | If no contract address is provided, it uses the default MultiPartyEscrow contract. 41 | 42 | ###### args: 43 | 44 | - `w3` (Web3): An instance of the `Web3` class. 45 | - `address` (str): The address of the MultiPartyEscrow contract. Defaults to _None_. 46 | 47 | ###### returns: 48 | 49 | - _None_ 50 | 51 | #### `balance` 52 | 53 | Returns the balance of the given address in the MPE contract in cogs. 54 | 55 | ###### args: 56 | 57 | - `address` (str): The address to retrieve the balance for. 58 | 59 | ###### returns: 60 | 61 | - The balance in cogs. (int) 62 | 63 | #### `deposit` 64 | 65 | Deposit the specified amount of AGIX tokens in cogs into the MultiPartyEscrow contract. 66 | 67 | ###### args: 68 | 69 | - `account` (Account): The account instance used to send the transaction. 70 | - `amount_in_cogs` (int): The amount of AGIX tokens in cogs to deposit. 71 | 72 | 73 | ###### returns: 74 | 75 | - The transaction receipt of the deposit transaction. (TxReceipt) 76 | 77 | #### `open_channel` 78 | 79 | Opens a payment channel with the specified amount of AGIX tokens in cogs (taken from MPE) and expiration time. 80 | 81 | ###### args: 82 | 83 | - `account` (Account): The account object used to send the transaction. 84 | - `payment_address` (str): The address of the payment recipient. 85 | - `group_id` (str): The ID of the payment group. 86 | - `amount` (int): The amount of AGIX tokens in cogs to deposit into the channel. 87 | - `expiration` (int): The expiration time of the payment channel in blocks. 88 | 89 | ###### returns: 90 | 91 | - The transaction receipt of the transaction. (TxReceipt) 92 | 93 | #### `deposit_and_open_channel` 94 | 95 | Opens a payment channel with the specified amount of AGIX tokens in cogs (which are previously deposited on MPE) 96 | and expiration time. The account must have sufficient allowance to perform the deposit, otherwise the account 97 | first approves the transfer. 98 | 99 | ###### args: 100 | 101 | - `account` (Account): The account object used to send the transaction. 102 | - `payment_address` (str): The address of the payment recipient. 103 | - `group_id` (str): The ID of the payment group. 104 | - `amount` (int): The amount of AGIX tokens in cogs first for deposit on MPE, then for deposit on the channel. 105 | - `expiration` (int): The expiration time of the payment channel in blocks. 106 | 107 | ###### returns: 108 | 109 | - The transaction receipt of the transaction. (TxReceipt) 110 | 111 | #### `channel_add_funds` 112 | 113 | Adds funds to an existing payment channel. 114 | 115 | ###### args: 116 | 117 | - `account` (Account): The account object used to sign the transaction. 118 | - `channel_id` (int): The ID of the payment channel. 119 | - `amount` (int): The amount of funds to add to the channel. 120 | 121 | ###### returns: 122 | 123 | - The transaction receipt of the transaction. (TxReceipt) 124 | 125 | #### `channel_extend` 126 | 127 | Extends the expiration time of a payment channel. 128 | 129 | ###### args: 130 | 131 | - `account` (Account): The account object used to send the transaction. 132 | - `channel_id` (int): The ID of the payment channel. 133 | - `expiration` (int): The new expiration time of the payment channel in blocks. 134 | 135 | ###### returns: 136 | 137 | - The transaction receipt of the transaction. (TxReceipt) 138 | 139 | #### `channel_extend_and_add_funds` 140 | 141 | Extends the expiration time of a payment channel and adds funds to it. 142 | 143 | ###### args: 144 | 145 | - `account` (Account): The account object used to send the transaction. 146 | - `channel_id` (int): The ID of the payment channel. 147 | - `expiration` (int): The new expiration time of the payment channel in blocks. 148 | - `amount` (int): The amount of funds to add to the channel. 149 | 150 | ###### returns: 151 | 152 | - The transaction receipt of the transaction. (TxReceipt) 153 | 154 | #### `_fund_escrow_account` 155 | 156 | Funds the escrow account for the given account with the specified amount. 157 | 158 | ###### args: 159 | 160 | - `account` (Account): The account object used to send the transaction and for which 161 | the escrow account needs to be funded. 162 | - `amount` (int): The amount to be funded into the escrow account. 163 | 164 | ###### returns: 165 | 166 | - The transaction receipt of the transaction. (TxReceipt | _None_) 167 | 168 | -------------------------------------------------------------------------------- /docs/mpe/payment_channel.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.mpe.payment_channel 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/mpe/payment_channel.py) to GitHub 4 | 5 | Entities: 6 | 1. [PaymentChannel](#class-paymentchannel) 7 | - [\_\_init\_\_](#__init__) 8 | - [add_funds](#add_funds) 9 | - [extend_expiration](#extend_expiration) 10 | - [extend_and_add_funds](#extend_and_add_funds) 11 | - [sync_state](#sync_state) 12 | - [_get_current_channel_state](#_get_current_channel_state) 13 | 14 | ### Class `PaymentChannel` 15 | 16 | extends: - 17 | 18 | is extended by: - 19 | 20 | #### description 21 | 22 | The PaymentChannel (payment_channel.py:8-65:135) class is responsible for managing a payment channel 23 | in the SingularityNET platform. 24 | 25 | #### attributes 26 | 27 | - `channel_id` (int): The ID of the payment channel. 28 | - `web3` (Web3): An instance of the `Web3` class for interacting with the Ethereum blockchain. 29 | - `account` (Account): An instance of the `Account` class for interacting with the MultiPartyEscrow and SingularityNetToken contracts. 30 | - `mpe_contract` (MPEContract): An instance of the `MPEContract` class for interacting with the MultiPartyEscrow contract. 31 | - `payment_channel_state_service_client` (ServiceStub): A stub for interacting with PaymentChannelStateService via gRPC. 32 | - `state` (dict): The current state of the payment channel. It contains the following keys: 33 | - `nonce` (int): The current nonce of the payment channel. 34 | - `last_signed_amount` (int): The last signed amount of the payment channel. 35 | 36 | #### methods 37 | 38 | #### `__init__` 39 | 40 | Initializes a new instance of the class. 41 | 42 | ###### args: 43 | 44 | - `channel_id` (str): The ID of the payment channel. 45 | - `w3` (Web3): An instance of the `Web3` class for interacting with the Ethereum blockchain. 46 | - `account` (Account): An instance of the `Account` class for managing Ethereum accounts. 47 | - `payment_channel_state_service_client` (ServiceStub): A stub for interacting with PaymentChannelStateService via gRPC. 48 | - `mpe_contract` (MPEContract): An instance of the `MPEContract` class for interacting with the MultiPartyEscrow contract. 49 | 50 | ###### returns: 51 | 52 | - _None_ 53 | 54 | #### `add_funds` 55 | 56 | Adds funds to the payment channel. 57 | 58 | ###### args: 59 | 60 | - `amount` (int): The amount of funds to add to the payment channel. 61 | 62 | ###### returns: 63 | 64 | - The transaction receipt of the transaction. (TxReceipt) 65 | 66 | #### `extend_expiration` 67 | 68 | Extends the expiration time of the payment channel. 69 | 70 | ###### args: 71 | 72 | - `expiration` (int): The new expiration time of the payment channel in blocks. 73 | 74 | ###### returns: 75 | 76 | - The transaction receipt of the transaction. (TxReceipt) 77 | 78 | #### `extend_and_add_funds` 79 | 80 | Extends the expiration time of a payment channel and adds funds to it. 81 | 82 | ###### args: 83 | 84 | - `expiration` (int): The new expiration time of the payment channel in blocks. 85 | - `amount` (int): The amount of funds to add to the channel. 86 | 87 | ###### returns: 88 | 89 | - The transaction receipt of the transaction. (TxReceipt) 90 | 91 | #### `sync_state` 92 | 93 | This method gets the channel state data from the MPE and the daemon and updates all values of the state field. 94 | 95 | ###### returns: 96 | 97 | - _None_ 98 | 99 | #### `_get_current_channel_state` 100 | 101 | Receives channel state data from the daemon via gRPC using PaymentChannelStateService and returns it. 102 | 103 | ###### returns: 104 | 105 | - A tuple containing the current nonce and the current signed amount of funds. (tuple[int, int]) 106 | 107 | -------------------------------------------------------------------------------- /docs/payment_strategies/default_payment_strategy.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.payment_strategies.default_payment_strategy 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/payment_strategies/default_payment_strategy.py) to GitHub 4 | 5 | Entities: 6 | 1. [DefaultPaymentStrategy](#class-defaultpaymentstrategy) 7 | - [\_\_init\_\_](#__init__) 8 | - [set_concurrency_token](#set_concurrency_token) 9 | - [set_channel](#set_channel) 10 | - [get_payment_metadata](#get_payment_metadata) 11 | - [get_concurrency_token_and_channel](#get_concurrency_token_and_channel) 12 | 13 | ### Class `DefaultPaymentStrategy` 14 | 15 | extends: `PaymentStrategy` 16 | 17 | is extended by: - 18 | 19 | #### description 20 | 21 | The `DefaultPaymentStrategy` class is an implementation of the `PaymentStrategy`. It defines and returns 22 | payment metadata for a given service client, taking into account free calls, concurrency, and various 23 | payment strategies. In fact, it is not an implementation of a payment strategy on its own, this class is used 24 | by default and selects a payment strategy from `FreeCallPaymentStrategy`, `PaidCallPaymentStrategy` 25 | and `PrePaidPaymentStrategy`. 26 | 27 | #### attributes 28 | 29 | - `concurrent_calls` (int): The number of concurrent calls allowed. 30 | - `concurrencyManager` (ConcurrencyManager): An instance of the `ConcurrencyManager` class for managing concurrency. 31 | - `channel` (PaymentChannel): The payment channel used for a specific service call. 32 | 33 | #### methods 34 | 35 | #### `__init__` 36 | 37 | Initializes a new instance of the class. 38 | 39 | ###### args: 40 | 41 | - `concurrent_calls` (int): The number of concurrent calls allowed. Defaults to 1. 42 | 43 | ###### returns: 44 | 45 | - _None_ 46 | 47 | #### `set_concurrency_token` 48 | 49 | Sets the concurrency token for the concurrency manager. 50 | 51 | ###### args: 52 | 53 | - `token` (str): The token to be set. 54 | 55 | ###### returns: 56 | 57 | - _None_ 58 | 59 | #### `set_channel` 60 | 61 | Sets a new channel object. 62 | 63 | ###### args: 64 | 65 | - `channel` (PaymentChannel): The channel to set for the `DefaultPaymentStrategy` object. 66 | 67 | ###### returns: 68 | 69 | - _None_ 70 | 71 | #### `get_payment_metadata` 72 | 73 | Retrieves payment metadata for the specified service client. Depending on several conditions, creates 74 | an instance of one of the `FreeCallPaymentStrategy`, `PaidCallPaymentStrategy` and `PrePaidPaymentStrategy` 75 | classes and calls the method of the same name in it. 76 | 77 | ###### args: 78 | 79 | - `service_client` (ServiceClient): The service client object. 80 | 81 | ###### returns: 82 | 83 | - The payment metadata. (list[tuple[str, Any]]) 84 | 85 | #### `get_concurrency_token_and_channel` 86 | 87 | Retrieves the concurrency token and channel for a given service client. 88 | 89 | ###### args: 90 | 91 | - `service_client` (ServiceClient): The service client instance. 92 | 93 | ###### returns: 94 | 95 | - The concurrency token and channel. (tuple[str, PaymentChannel]) 96 | 97 | -------------------------------------------------------------------------------- /docs/payment_strategies/freecall_payment_strategy.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.payment_strategies.freecall_payment_strategy 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/payment_strategies/freecall_payment_strategy.py) to GitHub 4 | 5 | Entities: 6 | 1. [FreeCallPaymentStrategy](#class-freecallpaymentstrategy) 7 | - [is_free_call_available](#is_free_call_available) 8 | - [get_payment_metadata](#get_payment_metadata) 9 | - [generate_signature](#generate_signature) 10 | 11 | ### Class `FreeCallPaymentStrategy` 12 | 13 | extends: `PaymentStrategy` 14 | 15 | is extended by: - 16 | 17 | #### description 18 | 19 | The `FreeCallPaymentStrategy` class is a concrete implementation of the `PaymentStrategy` interface. 20 | It allows you to use free calls (which can be received from the [Dapp](https://beta.singularitynet.io/)) to 21 | call services. 22 | 23 | #### methods 24 | 25 | #### `is_free_call_available` 26 | 27 | Checks if a free call is available for a given service client. 28 | 29 | ###### args: 30 | 31 | - `service_client` (ServiceClient): The service client instance. 32 | 33 | ###### returns: 34 | 35 | - True if a free call is available, False otherwise. (bool) 36 | 37 | ###### raises: 38 | 39 | - Exception: If an error occurs while checking the free call availability. 40 | 41 | _Note_: If any exception occurs during the process, it returns False. 42 | 43 | #### `get_payment_metadata` 44 | 45 | Retrieves the payment metadata for a service client with the field `snet-payment-type` equals to `free-call` 46 | using the provided free call configuration. 47 | 48 | ###### args: 49 | 50 | - `service_client` (ServiceClient): The service client instance. 51 | 52 | ###### returns: 53 | 54 | - The payment metadata. (list[tuple[str, Any]]) 55 | 56 | #### `generate_signature` 57 | 58 | Generates a signature for the given service client using the provided free call configuration. 59 | 60 | ###### args: 61 | 62 | - `service_client` (ServiceClient): The service client instance. 63 | 64 | ###### returns: 65 | 66 | - A tuple containing the generated signature and the current block number. (tuple[bytes, int]) 67 | 68 | ###### raises: 69 | 70 | - Exception: If any of the required parameters for the free call strategy are missing. 71 | -------------------------------------------------------------------------------- /docs/payment_strategies/paidcall_payment_strategy.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.payment_strategies.paidcall_payment_strategy 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/payment_strategies/paidcall_payment_strategy.py) to GitHub 4 | 5 | Entities: 6 | 1. [PaidCallPaymentStrategy](#class-paidcallpaymentstrategy) 7 | - [\_\_init\_\_](#__init__) 8 | - [get_price](#get_price) 9 | - [get_payment_metadata](#get_payment_metadata) 10 | - [select_channel](#select_channel) 11 | - [_has_sufficient_funds](#static-_has_sufficient_funds) 12 | - [_is_valid](#static-_is_valid) 13 | 14 | ### Class `PaidCallPaymentStrategy` 15 | 16 | extends: `PaymentStrategy` 17 | 18 | is extended by: `TrainingPaymentStrategy` 19 | 20 | #### description 21 | 22 | The `PaidCallPaymentStrategy` class is a concrete implementation of the `PaymentStrategy` interface. 23 | This is the simplest payment strategy among those presented. In it availability of channel, funds and 24 | expiration are checked before each call and the payment itself is made each call. 25 | 26 | #### attributes 27 | 28 | - `block_offset` (int): Block offset. 29 | - `call_allowance` (int): The amount of allowed calls. 30 | 31 | #### methods 32 | 33 | #### `__init__` 34 | 35 | Initializes a new instance of the class. 36 | 37 | ###### args: 38 | 39 | - `block_offset` (int): Block offset. 40 | - `call_allowance` (int): The amount of allowed calls. Defaults to 1. 41 | 42 | ###### returns: 43 | 44 | - _None_ 45 | 46 | #### `get_price` 47 | 48 | Returns the price of the service call using service client. 49 | 50 | ###### args: 51 | 52 | - `service_client` (ServiceClient): The service client object. 53 | 54 | ###### returns: 55 | 56 | - The price of the service call. (int) 57 | 58 | #### `get_payment_metadata` 59 | 60 | Creates and returns the payment metadata for a service client with the field `snet-payment-type` equals to `escrow`. 61 | 62 | ###### args: 63 | 64 | - `service_client` (ServiceClient): The service client object. 65 | 66 | ###### returns: 67 | 68 | - The payment metadata. (list[tuple[str, Any]]) 69 | 70 | #### `select_channel` 71 | 72 | Retrieves the suitable payment channel from the MPE. Opens the channel, extends expiration 73 | and adds funds id it is necessary. 74 | 75 | ###### args: 76 | 77 | - `service_client` (ServiceClient): The service client object. 78 | 79 | ###### returns: 80 | 81 | - The payment channel for the service calling. (PaymentChannel) 82 | 83 | #### static `_has_sufficient_funds` 84 | 85 | Checks whether the payment channel has the required amount of funds. 86 | 87 | ###### args: 88 | 89 | - `channel` (PaymentChannel): The payment channel to check. 90 | - `amount` (int): The amount of funds in cogs to check. 91 | 92 | ###### returns: 93 | 94 | - True if the channel has enough funds, False otherwise. (bool) 95 | 96 | #### static `_is_valid` 97 | 98 | Checks if the payment channel expires later than it should. 99 | 100 | ###### args: 101 | 102 | - `channel` (PaymentChannel): The payment channel to check. 103 | - `expiry` (int): The expiration time in blocks to check. 104 | 105 | ###### returns: 106 | 107 | - True if the channel has enough expiration, False otherwise. (bool) 108 | 109 | -------------------------------------------------------------------------------- /docs/payment_strategies/payment_strategy.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.payment_strategies.payment_strategy 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/payment_strategies/payment_strategy.py) to GitHub 4 | 5 | Entities: 6 | 1. [PaymentStrategy](#class-paymentstrategy) 7 | - [get_payment_metadata](#get_payment_metadata) 8 | - [get_price](#get_price) 9 | 10 | ### Abstract Class `PaymentStrategy` 11 | 12 | extends: `object` 13 | 14 | is extended by: `DefaultPaymentStrategy`, `DefaultPaymentStrategy`, `PaidCallPaymentStrategy`, `PrePaidPaymentStrategy` 15 | 16 | #### description 17 | 18 | Abstract base class for payment strategies. Defines the interface for organizing payment strategy. 19 | 20 | #### methods 21 | 22 | #### abstract `get_payment_metadata` 23 | 24 | Determines and returns payment metadata for a specified service client. 25 | 26 | ###### args: 27 | 28 | - `service_client` (ServiceClient): The service client object. 29 | 30 | ###### returns: 31 | 32 | - Payment metadata. (list[tuple[str, Any]]) 33 | 34 | #### abstract `get_price` 35 | 36 | Returns the price for calling a service using the provided client service. 37 | 38 | ###### args: 39 | 40 | - `service_client` (ServiceClient): The service client object. 41 | 42 | ###### returns: 43 | 44 | - Price of calling service in cogs. (int) 45 | 46 | -------------------------------------------------------------------------------- /docs/payment_strategies/prepaid_payment_strategy.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.payment_strategies.prepaid_payment_strategy 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/payment_strategies/prepaid_payment_strategy.py) to GitHub 4 | 5 | Entities: 6 | 1. [PrePaidPaymentStrategy](#class-prepaidpaymentstrategy) 7 | - [\_\_init\_\_](#__init__) 8 | - [get_price](#get_price) 9 | - [get_payment_metadata](#get_payment_metadata) 10 | - [get_concurrency_token_and_channel](#get_concurrency_token_and_channel) 11 | - [select_channel](#select_channel) 12 | - [_has_sufficient_funds](#static-_has_sufficient_funds) 13 | - [_is_valid](#static-_is_valid) 14 | 15 | 16 | ### Class `PrePaidPaymentStrategy` 17 | 18 | extends: `PaymentStrategy` 19 | 20 | is extended by: - 21 | 22 | #### description 23 | 24 | The `PrePaidPaymentStrategy` class is a concrete implementation of the `PaymentStrategy` interface. 25 | The prepaid payment strategy is similar to the paid call strategy, but allows you to pay for several calls at once 26 | and then make them. 27 | 28 | #### attributes 29 | 30 | - `concurrency_manager`: The `ConcurrencyManager` instance. 31 | - `block_offset` (int): Block offset. 32 | - `call_allowance` (int): The amount of allowed calls. 33 | 34 | #### methods 35 | 36 | #### `__init__` 37 | 38 | Initializes a new instance of the class. 39 | 40 | ###### args: 41 | 42 | - `concurrency_manager`: The `ConcurrencyManager` instance. 43 | - `block_offset` (int): Block offset. 44 | - `call_allowance` (int): The amount of allowed calls. Defaults to 1. 45 | 46 | ###### returns: 47 | 48 | - _None_ 49 | 50 | #### `get_price` 51 | 52 | Returns the price of the service calls using service client. 53 | 54 | ###### args: 55 | 56 | - `service_client` (ServiceClient): The service client object. 57 | 58 | ###### returns: 59 | 60 | - The price of the service call. (int) 61 | 62 | #### `get_payment_metadata` 63 | 64 | Creates and returns the payment metadata for a service client with the field `snet-payment-type` equals 65 | to `prepaid-call`. 66 | 67 | ###### args: 68 | 69 | - `service_client` (ServiceClient): The service client object. 70 | - `channel` (PaymentChannel): The payment channel to make service call. 71 | 72 | ###### returns: 73 | 74 | - The payment metadata. (list[tuple[str, Any]]) 75 | 76 | #### `get_concurrency_token_and_channel` 77 | 78 | Retrieves the concurrency token and channel from the payment strategy. 79 | 80 | ###### args: 81 | 82 | - `service_client` (ServiceClient): The service client object. 83 | 84 | ###### returns: 85 | 86 | - The concurrency token and channel. (tuple[str, PaymentChannel]) 87 | 88 | #### `select_channel` 89 | 90 | Retrieves the suitable payment channel from the MPE. Opens the channel, extends expiration 91 | and adds funds if it is necessary. 92 | 93 | ###### args: 94 | 95 | - `service_client` (ServiceClient): The service client object. 96 | 97 | ###### returns: 98 | 99 | - The payment channel for the service calling. (PaymentChannel) 100 | 101 | #### static `_has_sufficient_funds` 102 | 103 | Checks whether the payment channel has the required amount of funds. 104 | 105 | ###### args: 106 | 107 | - `channel` (PaymentChannel): The payment channel to check. 108 | - `amount` (int): The amount of funds in cogs to check. 109 | 110 | ###### returns: 111 | 112 | - True if the channel has enough funds, False otherwise. (bool) 113 | 114 | #### static `_is_valid` 115 | 116 | Checks if the payment channel expires later than it should. 117 | 118 | ###### args: 119 | 120 | - `channel` (PaymentChannel): The payment channel to check. 121 | - `expiry` (int): The expiration time in blocks to check. 122 | 123 | ###### returns: 124 | 125 | - True if the channel has enough expiration, False otherwise. (bool) 126 | 127 | -------------------------------------------------------------------------------- /docs/payment_strategies/training_payment_strategy.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.payment_strategies.training_payment_strategy 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/payment_strategies/training_payment_strategy.py) to GitHub 4 | 5 | Entities: 6 | 1. [TrainingPaymentStrategy](#class-trainingpaymentstrategy) 7 | - [\_\_init\_\_](#__init__) 8 | - [get_price](#get_price) 9 | - [set_price](#set_price) 10 | - [get_model_id](#get_model_id) 11 | - [set_model_id](#set_model_id) 12 | - [get_payment_metadata](#get_payment_metadata) 13 | 14 | ### Class `TrainingPaymentStrategy` 15 | 16 | extends: `PaidCallPaymentStrategy` 17 | 18 | is extended by: - 19 | 20 | #### description 21 | 22 | The `TrainingPaymentStrategy` class extends `PaidCallPaymentStrategy` class. The difference from the 23 | parent class is that for training, the call price is a dynamic value, and can be set using the appropriate 24 | setter before the call. 25 | 26 | #### attributes 27 | 28 | - `_call_price` (int): The call price. Defaults to -1 (means that no price has been set). 29 | - `_train_model_id` (str): The training model id. Defaults to empty string. 30 | 31 | #### methods 32 | 33 | #### `__init__` 34 | 35 | Initializes a new instance of the class. 36 | 37 | ###### returns: 38 | 39 | - _None_ 40 | 41 | #### `get_price` 42 | 43 | Returns the price of the service call - `_call_price` value. 44 | 45 | ###### returns: 46 | 47 | - The price of the service call. (int) 48 | 49 | ###### raises: 50 | 51 | - `Exception`: If no price has been set. 52 | 53 | #### `set_price` 54 | 55 | Sets the price of the service call. 56 | 57 | ###### args: 58 | 59 | - `call_price` (int): The price of the service call. 60 | 61 | ###### returns: 62 | 63 | - _None_ 64 | 65 | #### `get_model_id` 66 | 67 | Returns the training model id - `_train_model_id` value. 68 | 69 | ###### returns: 70 | 71 | - The training model id. (str) 72 | 73 | #### `set_model_id` 74 | 75 | Sets the training model id. 76 | 77 | ###### args: 78 | 79 | - `model_id` (str): The training model id. 80 | 81 | ###### returns: 82 | 83 | - _None_ 84 | 85 | #### `get_payment_metadata` 86 | 87 | Creates and returns the payment metadata for a service client with the field `snet-payment-type` equals to `train-call`. 88 | 89 | ###### args: 90 | 91 | - `service_client` (ServiceClient): The service client object. 92 | 93 | ###### returns: 94 | 95 | - The payment metadata. (list[tuple[str, str]]) 96 | -------------------------------------------------------------------------------- /docs/snet-sdk-python-documentation.md: -------------------------------------------------------------------------------- 1 | # Welcome to snet-sdk-python's documentation! 2 | 3 | SingularityNET SDK for Python 4 | 5 | ### Core concepts 6 | 7 | The SingularityNET SDK allows you to make calls to SingularityNET services programmatically from your application. 8 | To communicate between clients and services, SingularityNET uses [gRPC](https://grpc.io/). 9 | To handle payment of services, SingularityNET uses [Ethereum state channels](https://dev.singularitynet.io/docs/concepts/multi-party-escrow/). 10 | The SingularityNET SDK abstracts and manages state channels with service providers on behalf of the user and handles authentication with the SingularityNET services. 11 | 12 | A getting started guide for the SNET SDK for Python is available [here](https://github.com/singnet/snet-sdk-python/blob/master/README.md). 13 | 14 | ### Modules 15 | 16 | 1. [\_\_init\_\_](main/init.md) 17 | 2. [account](main/account.md) 18 | 3. [service_client](main/service_client.md) 19 | 4. [concurrency_manager](main/concurrency_manager.md) 20 | 5. [config](main/config.md) 21 | 6. [client_lib_generator](main/client_lib_generator.md) 22 | 7. storage_provider 23 | 1. [storage_provider](storage_provider/storage_provider.md) 24 | 2. [service_metadata](storage_provider/service_metadata.md) 25 | 8. mpe 26 | 1. [mpe_contract](mpe/mpe_contract.md) 27 | 2. [payment_channel](mpe/payment_channel.md) 28 | 3. [payment_channel_provider](mpe/payment_channel_provider.md) 29 | 9. payment_strategies 30 | 1. [payment_strategy](payment_strategies/payment_strategy.md) 31 | 2. [default_payment_strategy](payment_strategies/default_payment_strategy.md) 32 | 3. [freecall_payment_strategy](payment_strategies/freecall_payment_strategy.md) 33 | 4. [paidcall_payment_strategy](payment_strategies/paidcall_payment_strategy.md) 34 | 5. [prepaid_payment_strategy](payment_strategies/prepaid_payment_strategy.md) 35 | 6. [training_payment_strategy](payment_strategies/training_payment_strategy.md) 36 | 10. utils 37 | 1. [utils](utils/utils.md) 38 | 2. [ipfs_utils](utils/ipfs_utils.md) 39 | 3. [call_utils](utils/call_utils.md) 40 | 11. training 41 | 1. [training](training/training.md) 42 | 2. [responses](training/responses.md) 43 | 3. [exceptions](training/exceptions.md) 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/storage_provider/storage_provider.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.storage_provider.storage_provider 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/storage_provider/storage_provider.py) to GitHub 4 | 5 | Entities: 6 | 1. [StorageProvider](#class-storageprovider) 7 | - [\_\_init\_\_](#__init__) 8 | - [fetch_org_metadata](#fetch_org_metadata) 9 | - [fetch_service_metadata](#fetch_service_metadata) 10 | - [enhance_service_metadata](#enhance_service_metadata) 11 | - [fetch_and_extract_proto](#fetch_and_extract_proto) 12 | 13 | ### Class `StorageProvider` 14 | 15 | extends: `object` 16 | 17 | is extended by: - 18 | 19 | #### description 20 | 21 | Class for extracting metadata from Storage Provider. It can be Interplanetary File System (IPFS) and 22 | FileCoin (Lighthouse). 23 | 24 | #### attributes 25 | 26 | - `registry_contract` (Contract): An instance of the `Contract` class for interacting with the Registry contract. 27 | - `ipfs_client` (ipfshttpclient.Client): An instance of the `ipfshttpclient.Client` class for interacting with the 28 | InterPlanetary File System. 29 | - `lighthouse_client` (Lighthouse): An instance of the `Lighthouse` class for interacting with the Lighthouse (FileCoin) storage provider. 30 | 31 | #### methods 32 | 33 | #### `__init__` 34 | 35 | Initializes a new instance of the class. 36 | 37 | ###### args: 38 | 39 | - `config` (Config): An instance of the `Config` class. 40 | - `registry_contract` (Contract): The contract instance of the registry. 41 | 42 | ###### returns: 43 | 44 | - _None_ 45 | 46 | #### `fetch_org_metadata` 47 | 48 | Retrieves metadata for the specified organization ID from IPFS or FileCoin depends on the `metadataURI` prefix. 49 | 50 | ###### args: 51 | 52 | - org_id (str): The ID of the organization. 53 | 54 | ###### returns: 55 | 56 | - Metadata of a specified organization. (dict) 57 | 58 | #### `fetch_service_metadata` 59 | 60 | Retrieves metadata for the specified service from IPFS or FileCoin depends on the `metadataURI` prefix. 61 | 62 | ###### args: 63 | 64 | - org_id (str): The ID of the organization. 65 | - service_id (str): The ID of the service. 66 | 67 | ###### returns: 68 | 69 | - Metadata of a specified service. (MPEServiceMetadata) 70 | 71 | #### `enhance_service_metadata` 72 | 73 | Enhances the service group details by merging them with the organization group details. 74 | 75 | ###### args: 76 | 77 | - org_id (str): The ID of the organization. 78 | - service_id (str): The ID of the service. 79 | 80 | ###### returns: 81 | 82 | - Enhanced metadata of a specified service. (MPEServiceMetadata) 83 | 84 | #### `fetch_and_extract_proto` 85 | 86 | Retrieves archive with .proto files from IPFS or FileCoin depends on the `service_api_source` prefix and extracts 87 | it using `safe_extract_proto` function from `utils` module. 88 | 89 | ###### args: 90 | 91 | - `service_api_source` (str): The hash link to the .proto files od the service with the prefix ('filecoin://' or 'ipfs://'). 92 | - `protodir` (str): The directory where the .proto files are saved 93 | 94 | ###### returns: 95 | 96 | - _None_ 97 | 98 | -------------------------------------------------------------------------------- /docs/training/exceptions.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.training.exceptions 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/training/exceptions.py) to GitHub 4 | 5 | Entities: 6 | 1. [WrongDatasetException](#class-wrongdatasetexception) 7 | 2. [WrongMethodException](#class-wrongmethodexception) 8 | 3. [NoTrainingException](#class-notrainingexception) 9 | 4. [GRPCException](#class-grpcexception) 10 | 5. [NoSuchModelException](#class-nosuchmodelexception) 11 | 12 | ### Class `WrongDatasetException` 13 | 14 | extends: `Exception` 15 | 16 | is extended by: - 17 | 18 | #### description 19 | 20 | This exception can be thrown when `_check_dataset` method of the `Training` class is called. 21 | If the dataset is not compliant, this exception will be thrown, detailing what incompatibilities the dataset has. 22 | 23 | ### Class `WrongMethodException` 24 | 25 | extends: `Exception` 26 | 27 | is extended by: - 28 | 29 | #### description 30 | 31 | This exception is thrown when you try to call `Training` methods with the wrong grpc method name. 32 | 33 | ### Class `NoTrainingException` 34 | 35 | This exception is thrown while calling the `training` field of the `ServiceClient` class and the training is not 36 | enabled in the service. 37 | 38 | ### Class `GRPCException` 39 | 40 | extends: `RpcError` 41 | 42 | is extended by: - 43 | 44 | #### description 45 | 46 | This exception is thrown when there is an error in the grpc call. 47 | 48 | ### Class `NoSuchModelException` 49 | 50 | extends: `Exception` 51 | 52 | is extended by: - 53 | 54 | #### description 55 | 56 | This exception is thrown when you try to call `Training` methods with the non-existent model id. 57 | -------------------------------------------------------------------------------- /docs/training/responses.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.training.responses 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/training/responses.py) to GitHub 4 | 5 | Entities: 6 | 1. [ModelMethodMessage](#class-modelmethodmessage) 7 | 2. [ModelStatus](#class-modelstatus) 8 | 3. [Model](#class-model) 9 | 4. [TrainingMetadata](#class-trainingmetadata) 10 | 5. [MethodMetadata](#class-methodmetadata) 11 | 6. [to_string](#function-to_string) 12 | 13 | ### Class `ModelMethodMessage` 14 | 15 | extends: `Enum` 16 | 17 | is extended by: - 18 | 19 | #### description 20 | 21 | This is an `enum` that represents the available methods that can be called in the training grpc service. 22 | It is used in the authorization messages. 23 | 24 | #### members 25 | 26 | - `CreateModel` 27 | - `ValidateModelPrice` 28 | - `TrainModelPrice` 29 | - `DeleteModel` 30 | - `GetTrainingMetadata` 31 | - `GetAllModels` 32 | - `GetModel` 33 | - `UpdateModel` 34 | - `GetMethodMetadata` 35 | - `UploadAndValidate` 36 | - `ValidateModel` 37 | - `TrainModel` 38 | 39 | ### Class `ModelStatus` 40 | 41 | extends: `Enum` 42 | 43 | is extended by: - 44 | 45 | #### description 46 | 47 | This is an `enum` that represents the status of a model. It is used to convert status object in the grpc call 48 | response to a readable object. 49 | 50 | #### members 51 | 52 | - `CREATED` 53 | - `VALIDATING` 54 | - `VALIDATED` 55 | - `TRAINING` 56 | - `READY_TO_USE` 57 | - `ERRORED` 58 | - `DELETED` 59 | 60 | ### Class `Model` 61 | 62 | extends: - 63 | 64 | is extended by: - 65 | 66 | #### description 67 | 68 | It is a data class that represents a model. It is used to convert model object in the grpc call response to a 69 | readable object. 70 | 71 | #### attributes 72 | 73 | - `model_id` (str): The id of the model. 74 | - `status` (ModelStatus): The status of the model. 75 | - `created_date` (str): The date when the model was created. 76 | - `updated_date` (str): The date when the model was updated. 77 | - `name` (str): The name of the model. 78 | - `description` (str): The description of the model. 79 | - `grpc_method_name` (str): The name of the gRPC method for which the model was created. 80 | - `grpc_service_name` (str): The name of the gRPC service. 81 | - `address_list` (list[str]): A list of addresses with the access of the model. 82 | - `is_public` (bool): Whether the model is publicly accessible. 83 | - `training_data_link` (str): The link to the training data (not used in SDK). 84 | - `created_by_address` (str): The address of the user wallet who created the model. 85 | - `updated_by_address` (str): The address of the user wallet who updated the model. 86 | 87 | ### Class `TrainingMetadata` 88 | 89 | extends: - 90 | 91 | is extended by: - 92 | 93 | #### description 94 | 95 | It is a data class that represents the training metadata. It is used to convert training metadata object in the 96 | grpc call response to a readable object. 97 | 98 | #### attributes 99 | 100 | - `training_enabled` (bool): Whether training is enabled on the service. 101 | - `training_in_proto` (bool): Whether training is in proto format. 102 | - `training_methods` (dict[str, list[str]]): Dictionary of the following form: rpc service name - list of rpc methods. 103 | 104 | ### Class `MethodMetadata` 105 | 106 | extends: - 107 | 108 | is extended by: - 109 | 110 | #### description 111 | 112 | It is a data class that represents the method metadata. It is used to convert method metadata object in the 113 | grpc call response to a readable object. 114 | 115 | #### attributes 116 | 117 | - `default_model_id` (str): The default model id. 118 | - `max_models_per_user` (int): The maximum number of models per user. 119 | - `dataset_max_size_mb` (int): The maximum size of the dataset in MB. 120 | - `dataset_max_count_files` (int): The maximum number of files in the dataset. 121 | - `dataset_max_size_single_file_mb` (int): The maximum size of a single file in the dataset in MB. 122 | - `dataset_files_type` (str): Allowed types of files in the dataset. (example: "jpg,png,mp3") 123 | - `dataset_type` (str): The type of the dataset. (example: "zip,tar") 124 | - `dataset_description` (str): Additional free-form requirements. 125 | 126 | ### Function `to_string` 127 | 128 | Converts a data object to a string where each attribute is on a separate line with a name. 129 | 130 | #### args: 131 | 132 | - `obj` (Any): The data object to convert to a string. 133 | 134 | #### returns: 135 | 136 | - The string representation of the data object. (str) 137 | -------------------------------------------------------------------------------- /docs/utils/call_utils.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.utils.call_utils 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/utils/call_utils.py) to GitHub 4 | 5 | Entities: 6 | 1. [_ClientCallDetails](#class-_clientcalldetails) 7 | 2. [create_intercept_call_func](#function-create_intercept_call_func) 8 | 9 | ### Class `_ClientCallDetails` 10 | 11 | extends `grpc.ClientCallDetails`, `namedtuple` 12 | 13 | is extended by: - 14 | 15 | ### Function `create_intercept_call_func` 16 | 17 | ###### args: 18 | 19 | - `get_metadata_func` (callable): The function to get metadata for the call. 20 | - `service_client` (ServiceClient): The service client to use for the call. 21 | 22 | ###### returns: 23 | 24 | - The function to intercept the call. (callable) -------------------------------------------------------------------------------- /docs/utils/ipfs_utils.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.utils.ipfs_utils 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/utils/ipfs_utils.py) to GitHub 4 | 5 | Entities: 6 | 1. [get_from_ipfs_and_checkhash](#function-get_from_ipfs_and_checkhash) 7 | 2. [get_ipfs_client](#function-get_ipfs_client) 8 | 9 | ### Function `get_from_ipfs_and_checkhash` 10 | 11 | This function retrieves a file from IPFS and optionally verifies its integrity by 12 | checking the hash (if `validate` is True). If the hash does not match, it raises an exception. If `validate` is False, 13 | it simply retrieves the file data. 14 | 15 | ###### args: 16 | 17 | - `ipfs_client` (ipfshttpclient.client.Client): The IPFS client instance. 18 | - `ipfs_hash_base58` (Any): The base58-encoded IPFS hash of the file. 19 | - `validate` (bool): A boolean indicating whether to validate the hash (default is True). 20 | 21 | ###### returns: 22 | 23 | - The contents of the file retrieved from IPFS. (bytes) 24 | 25 | ###### raises: 26 | 27 | - `Exception`: If the hash validation fails or if the IPFS hash is not a file. 28 | 29 | ### Function `get_ipfs_client` 30 | 31 | Returns an IPFS client instance based on the provided configuration. 32 | 33 | ###### args: 34 | 35 | - config (Config): The configuration object containing the IPFS endpoint. 36 | 37 | ###### returns: 38 | 39 | - An IPFS client instance connected to the specified endpoint. (ipfshttpclient.client.Client) 40 | 41 | -------------------------------------------------------------------------------- /docs/utils/utils.md: -------------------------------------------------------------------------------- 1 | ## module: sdk.utils.utils 2 | 3 | [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/utils/utils.py) to GitHub 4 | 5 | Entities: 6 | 1. [safe_address_converter](#function-safe_address_converter) 7 | 2. [type_converter](#function-type_converter) 8 | 3. [bytes32_to_str](#function-bytes32_to_str) 9 | 4. [compile_proto](#function-compile_proto) 10 | 5. [is_valid_endpoint](#function-is_valid_endpoint) 11 | 6. [normalize_private_key](#function-normalize_private_key) 12 | 7. [get_address_from_private](#function-get_address_from_private) 13 | 8. [get_current_block_number](#function-get_current_block_number) 14 | 9. [add_to_path](#class-add_to_path) 15 | 10. [find_file_by_keyword](#function-find_file_by_keyword) 16 | 11. [bytesuri_to_hash](#function-bytesuri_to_hash) 17 | 12. [safe_extract_proto](#function-safe_extract_proto) 18 | 19 | 20 | ### Function `safe_address_converter` 21 | 22 | Checks if the address is a valid checksum address and returns it, otherwise raises an exception. 23 | 24 | ###### args: 25 | 26 | - `a` (Any): The address to check. 27 | 28 | ###### returns: 29 | 30 | - The address if it is valid. (Any) 31 | 32 | ###### raises: 33 | 34 | - `Exception`: If the address isn't a valid checksum address. 35 | 36 | ### Function `type_converter 37 | 38 | Creates a function that converts a value to the specified type. 39 | 40 | ###### args: 41 | 42 | - `t` (Any): The type to convert the value to. 43 | 44 | ###### returns: 45 | 46 | - A function that converts the value to the specified type. (Any -> Any) 47 | 48 | ### Function `bytes32_to_str` 49 | 50 | Converts a bytes32 value to a string. 51 | 52 | ###### args: 53 | 54 | - `b` (bytes): The bytes32 value to convert. 55 | 56 | ###### returns: 57 | 58 | - The string representation of the bytes32 value. (str) 59 | 60 | ### Function `compile_proto` 61 | 62 | Compiles Protocol Buffer (protobuf) files into code for a specific target language. 63 | Generated files as well as .proto files are stored in the `~/.snet` directory. 64 | 65 | ###### args: 66 | 67 | - `entry_path` (Path): The path to the .proto file. 68 | - `codegen_dir` (PurePath): The directory where the compiled code will be generated. 69 | - `proto_file` (str): The name of the .proto file to compile. Defaults to `None`. 70 | - `target_language` (str, optional): The target language for the compiled code. Defaults to "python". 71 | - `add_training` (bool): Whether to include training.proto in the compilation. Defaults to False. 72 | 73 | ###### returns: 74 | 75 | - True if the compilation is successful, False otherwise. (bool) 76 | 77 | ###### raises: 78 | 79 | - `Exception`: If the error occurs while performing the function. 80 | 81 | ### Function `is_valid_endpoint` 82 | 83 | Checks if the given endpoint is valid. 84 | 85 | ###### args: 86 | 87 | - `url` (str): The endpoint to check. 88 | 89 | ###### returns: 90 | 91 | - True if the endpoint is valid, False otherwise. (bool) 92 | 93 | ###### raises: 94 | 95 | - `ValueError`: If the error occurs while performing the function. 96 | 97 | ### Function `normalize_private_key` 98 | 99 | Returns the normalized private key. 100 | 101 | ###### args: 102 | 103 | - `private_key` (str): The private key in hex format to normalize. 104 | 105 | ###### returns: 106 | 107 | - The normalized private key. (bytes) 108 | 109 | ### Function `get_address_from_private` 110 | 111 | Returns the wallet address from the private key. 112 | 113 | ###### args: 114 | 115 | - `private_key` (Any): The private key. 116 | 117 | ###### returns: 118 | 119 | - The wallet address. (ChecksumAddress) 120 | 121 | ### Function `get_current_block_number` 122 | 123 | Returns the current block number in Ethereum. 124 | 125 | ###### returns: 126 | 127 | - The current block number. (BlockNumber (int)) 128 | 129 | ### Class `add_to_path` 130 | 131 | `add_to_path` class is a _**context manager**_ that temporarily adds a given path to the system's `sys.path` list. 132 | This allows for the import of modules or packages from that path. The `__enter__` method is called when the context 133 | manager is entered, and it inserts the path at the beginning of sys.path. The `__exit__` method is called when the 134 | context manager is exited, and it removes the path from sys.path. 135 | 136 | ###### args: 137 | 138 | - `path` (str): The path to add to sys.path. 139 | 140 | ### Function `find_file_by_keyword` 141 | 142 | Finds a file by keyword in the current directory and subdirectories. 143 | 144 | ###### args: 145 | 146 | - `directory` (AnyStr | PathLike[AnyStr]): The directory to search in. 147 | - `keyword` (AnyStr): The keyword to search for. 148 | - `exclude` (List[AnyStr], optional): A list of strings to exclude from the search. Defaults to _None_. 149 | 150 | ###### returns: 151 | 152 | - The name of the file that contains the keyword, or `None` if no file is found. (AnyStr | None) 153 | 154 | ### Function `bytesuri_to_hash` 155 | 156 | Decodes a bytes URI and splits it into prefix and hash. 157 | 158 | ###### args: 159 | 160 | - `s` (bytes): The bytes URI to convert. 161 | - `to_decode` (bool): Whether to decode the bytes URI. Defaults to `True`. 162 | 163 | ###### returns: 164 | 165 | - The prefix extracted from the URI. (str) 166 | - The hash extracted from the URI. (str) 167 | 168 | ###### raises: 169 | 170 | - `Exception`: If the input is not an IPFS or Filecoin URI. 171 | 172 | ### Function `safe_extract_proto` 173 | 174 | This function safely extracts a tar file to a specified directory. It checks for potential security risks by: 175 | - Ensuring the tar file does not contain directories 176 | - Ensuring all contents are files 177 | - Removing any existing files with the same name before extraction 178 | 179 | If any of these checks fail, it raises an exception. Otherwise, it extracts the tar file to the specified directory. 180 | 181 | ###### args: 182 | 183 | - `spec_tar` (bytes): The tar file to extract. 184 | - `protodir` (str): The directory to extract the tar file to. 185 | 186 | ###### returns: 187 | 188 | - _None_ 189 | 190 | ###### raises: 191 | 192 | - `Exception`: If the tarball contains directories or non-file entries. 193 | -------------------------------------------------------------------------------- /examples/calculator.py: -------------------------------------------------------------------------------- 1 | from snet import sdk 2 | 3 | config = sdk.config.Config(private_key="YOUR_PRIVATE_KEY", 4 | eth_rpc_endpoint=f"https://sepolia.infura.io/v3/YOUR_INFURA_KEY", 5 | concurrency=False, 6 | force_update=False) 7 | 8 | operators = { 9 | "+": "add", 10 | "-": "sub", 11 | "*": "mul", 12 | "/": "div" 13 | } 14 | 15 | snet_sdk = sdk.SnetSDK(config) 16 | calc_client = snet_sdk.create_service_client(org_id="26072b8b6a0e448180f8c0e702ab6d2f", 17 | service_id="Exampleservice", group_name="default_group") 18 | 19 | 20 | def parse_expression(expression): 21 | elements = list(expression.split()) 22 | if len(elements) != 3: 23 | raise Exception(f"Invalid expression '{expression}'. Three items required.") 24 | 25 | if elements[1] not in ["+", "-", "*", "/"]: 26 | raise Exception(f"Invalid expression '{expression}'. Operation must be '+' or '-' or '*' or '/'.") 27 | try: 28 | a = float(elements[0]) 29 | b = float(elements[2]) 30 | except ValueError: 31 | raise Exception(f"Invalid expression '{expression}'. Operands must be integers or floating point numbers.") 32 | op = elements[1] 33 | 34 | return a, b, op 35 | 36 | 37 | def main(): 38 | print(""" 39 | Welcome to the calculator powered by SingularityNET platform! 40 | Please type the expression you want to calculate, e.g. 2 + 3. 41 | Type 'exit' to exit the program.""") 42 | while True: 43 | expression = input("Calculator> ") 44 | if expression == "exit": 45 | break 46 | try: 47 | a, b, op = parse_expression(expression) 48 | print(f"Calculating {a} {op} {b}...") 49 | result = calc_client.call_rpc(operators[op], "Numbers", a=a, b=b) 50 | print(f"{a} {op} {b} = {result}") 51 | except Exception as e: 52 | print(e) 53 | 54 | 55 | if __name__ == "__main__": 56 | main() 57 | 58 | -------------------------------------------------------------------------------- /examples/examples_docs/calculator.md: -------------------------------------------------------------------------------- 1 | ## Tutorial on developing a console calculator 2 | 3 | This is an example of how to use SingularityNET Python SDK to create a console calculator that works using a service 4 | on the SingularityNET platform. 5 | 6 | ### Description 7 | 8 | It is assumed that there is an application provider (developer), who pays for all the 9 | transactions and service calls. 10 | 11 | So, the application must have the next console interface: 12 | 13 | ``` 14 | Welcome to the calculator powered by SingularityNET platform! 15 | Please type the expression you want to calculate, e.g. 2 + 3. 16 | Type 'exit' to exit the program. 17 | Calculator> 34 * 4 18 | Calculating 34 * 4... 19 | 34 * 4 = 134 20 | Calculator> 103 - 82 21 | Calculating 103 - 82... 22 | 103 - 82 = 21 23 | Calculator> exit 24 | ``` 25 | 26 | ### Development 27 | 28 | #### Install package 29 | 30 | Before the beginning we need to install `snet.sdk` package: 31 | 32 | ```sh 33 | pip install snet.sdk 34 | ``` 35 | 36 | #### Configuration 37 | 38 | Firstly, we need to configure sdk and service client. So we create a config dict and then an sdk instance with 39 | that config. 40 | 41 | _Note:_ don't forget to import `snet.sdk` package. 42 | 43 | ```python 44 | from snet import sdk 45 | 46 | config = sdk.config.Config(private_key="YOUR_PRIVATE_KEY", 47 | eth_rpc_endpoint=f"https://sepolia.infura.io/v3/YOUR_INFURA_KEY", 48 | concurrency=False, 49 | force_update=False) 50 | 51 | snet_sdk = sdk.SnetSDK(config) 52 | ``` 53 | 54 | Here you need to set private values: `private_key`, `eth_rpc_endpoint`and possibly change some others. 55 | 56 | Calculator service is deployed on the sepolia network. To create a client of this service we need to pass `org_id`, 57 | `service_id` and `group_name` to `create_service_client` method: 58 | 59 | ```python 60 | calc_client = snet_sdk.create_service_client(org_id="26072b8b6a0e448180f8c0e702ab6d2f", 61 | service_id="Exampleservice", group_name="default_group") 62 | ``` 63 | 64 | #### User input parsing 65 | 66 | Secondly, we need to write a function that will process and parse user input. 67 | 68 | ```python 69 | def parse_expression(expression): 70 | elements = list(expression.split()) 71 | if len(elements) != 3: 72 | raise Exception(f"Invalid expression '{expression}'. Three items required.") 73 | 74 | a = int(elements[0]) 75 | b = int(elements[2]) 76 | if elements[1] not in ["+", "-", "*", "/"]: 77 | raise Exception(f"Invalid expression '{expression}'. Operation must be '+' or '-' or '*' or '/'") 78 | elif not isinstance(a, (float, int)) or not isinstance(b, (float, int)): 79 | raise Exception(f"Invalid expression '{expression}'. Operands must be integers or floating point numbers.") 80 | op = elements[1] 81 | 82 | return a, b, op 83 | ``` 84 | 85 | This function splits the passed expression entered by the user into separate elements and checks their correctness. 86 | In case of invalid input, exceptions are thrown, otherwise three elements of the required types are returned. 87 | 88 | #### Main cycle 89 | 90 | The calculator service accepts the name of the method on which the arithmetic operation depends. Therefore, we will 91 | add a dictionary to match the operation symbol and the method name in the service. 92 | 93 | ```python 94 | operators = { 95 | "+": "add", 96 | "-": "sub", 97 | "*": "mul", 98 | "/": "div" 99 | } 100 | ``` 101 | 102 | Now we can write the main function: 103 | 104 | ```python 105 | def main(): 106 | print(""" 107 | Welcome to the calculator powered by SingularityNET platform! 108 | Please type the expression you want to calculate, e.g. 2 + 3. 109 | Type 'exit' to exit the program.""") 110 | while True: 111 | expression = input("Calculator> ") 112 | if expression == "exit": 113 | break 114 | try: 115 | a, b, op = parse_expression(expression) 116 | print(f"Calculating {a} {op} {b}...") 117 | result = calc_client.call_rpc(operators[op], "Numbers", a=a, b=b) 118 | print(f"{a} {op} {b} = {result}") 119 | except Exception as e: 120 | print(e) 121 | 122 | 123 | if __name__ == "__main__": 124 | main() 125 | ``` 126 | 127 | In an "infinite" loop, user input is read. If `exit` is entered, the program terminates. Otherwise, the expression 128 | is parsed using the `parse_expression` function and, if no errors occur, its result is calculated using the previously 129 | created instance of the ServiceClient class - `calc_client`, using `call_rpc` method. The result is then displayed 130 | on the screen. 131 | 132 | The entire application code can be viewed at the 133 | [link](https://github.com/singnet/snet-sdk-python/blob/master/examples/calculator.py) to GitHub. 134 | 135 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | protobuf==4.21.6 2 | grpcio-tools==1.59.0 3 | wheel==0.41.2 4 | jsonrpcclient==4.0.3 5 | eth-hash==0.5.2 6 | rlp==3.0.0 7 | eth-rlp==0.3.0 8 | web3==6.11.1 9 | mnemonic==0.20 10 | pycoin==0.92.20230326 11 | pyyaml==6.0.1 12 | ipfshttpclient==0.4.13.2 13 | rfc3986==2.0.0 14 | pymultihash==0.8.2 15 | base58==2.1.1 16 | argcomplete==3.1.2 17 | grpcio-health-checking==1.59.0 18 | jsonschema==4.0.0 19 | eth-account==0.9.0 20 | snet-contracts==1.0.0 21 | lighthouseweb3==0.1.4 22 | zipp>=3.19.1 23 | -------------------------------------------------------------------------------- /scripts/package-pip: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | python3.11 setup.py sdist bdist_wheel -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import find_namespace_packages, setup 3 | 4 | from version import __version__ 5 | 6 | PACKAGE_NAME = 'snet-sdk' 7 | 8 | 9 | this_directory = os.path.abspath(os.path.dirname(__file__)) 10 | with open(os.path.join(this_directory, 'README.md'), encoding='utf-8') as f: 11 | long_description = f.read() 12 | 13 | 14 | with open("./requirements.txt") as f: 15 | requirements_str = f.read() 16 | requirements = requirements_str.split("\n") 17 | 18 | 19 | setup( 20 | name=PACKAGE_NAME, 21 | version=__version__, 22 | packages=find_namespace_packages(include=['snet*']), 23 | namespace_packages=['snet'], 24 | url='https://github.com/singnet/snet-sdk-python', 25 | author='SingularityNET Foundation', 26 | author_email='info@singularitynet.io', 27 | description='SingularityNET Python SDK', 28 | long_description=long_description, 29 | long_description_content_type='text/markdown', 30 | license='MIT', 31 | python_requires='>=3.10', 32 | install_requires=requirements, 33 | include_package_data=True 34 | ) 35 | -------------------------------------------------------------------------------- /snet/sdk/account.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import web3 4 | 5 | from snet.contracts import get_contract_object 6 | from snet.sdk.config import Config 7 | from snet.sdk.mpe.mpe_contract import MPEContract 8 | from snet.sdk.utils.utils import (get_address_from_private, 9 | normalize_private_key) 10 | 11 | DEFAULT_GAS = 300000 12 | TRANSACTION_TIMEOUT = 500 13 | 14 | 15 | class TransactionError(Exception): 16 | """ 17 | Raised when an Ethereum transaction receipt has a status of 0. 18 | Can provide a custom message. Optionally includes receipt 19 | """ 20 | 21 | def __init__(self, message, receipt=None): 22 | super().__init__(message) 23 | self.message = message 24 | self.receipt = receipt 25 | 26 | def __str__(self): 27 | return self.message 28 | 29 | 30 | class Account: 31 | def __init__(self, w3: web3.Web3, config: Config, 32 | mpe_contract: MPEContract): 33 | self.config: Config = config 34 | self.web3: web3.Web3 = w3 35 | self.mpe_contract: MPEContract = mpe_contract 36 | _token_contract_address: str | None = self.config.get( 37 | "token_contract_address", None 38 | ) 39 | if _token_contract_address is None: 40 | self.token_contract = get_contract_object( 41 | self.web3, "FetchToken") 42 | else: 43 | self.token_contract = get_contract_object( 44 | self.web3, "FetchToken", _token_contract_address) 45 | 46 | if config.get("private_key") is not None: 47 | self.private_key = normalize_private_key(config.get("private_key")) 48 | if config.get("signer_private_key") is not None: 49 | self.signer_private_key = normalize_private_key( 50 | config.get("signer_private_key") 51 | ) 52 | else: 53 | self.signer_private_key = self.private_key 54 | self.address = get_address_from_private(self.private_key) 55 | self.signer_address = get_address_from_private(self.signer_private_key) 56 | self.nonce = 0 57 | 58 | def _get_nonce(self): 59 | nonce = self.web3.eth.get_transaction_count(self.address) 60 | if self.nonce >= nonce: 61 | nonce = self.nonce + 1 62 | self.nonce = nonce 63 | return nonce 64 | 65 | def _get_gas_price(self): 66 | gas_price = self.web3.eth.gas_price 67 | if gas_price <= 15000000000: 68 | gas_price += gas_price * 1 / 3 69 | elif gas_price > 15000000000 and gas_price <= 50000000000: 70 | gas_price += gas_price * 1 / 5 71 | elif gas_price > 50000000000 and gas_price <= 150000000000: 72 | gas_price += 7000000000 73 | elif gas_price > 150000000000: 74 | gas_price += gas_price * 1 / 10 75 | return int(gas_price) 76 | 77 | def _send_signed_transaction(self, contract_fn, *args): 78 | transaction = contract_fn(*args).build_transaction({ 79 | "chainId": int(self.web3.net.version), 80 | "gas": DEFAULT_GAS, 81 | "gasPrice": self._get_gas_price(), 82 | "nonce": self._get_nonce() 83 | }) 84 | signed_txn = self.web3.eth.account.sign_transaction( 85 | transaction, private_key=self.private_key) 86 | return self.web3.to_hex( 87 | self.web3.eth.send_raw_transaction(signed_txn.rawTransaction) 88 | ) 89 | 90 | def send_transaction(self, contract_fn, *args): 91 | txn_hash = self._send_signed_transaction(contract_fn, *args) 92 | return self.web3.eth.wait_for_transaction_receipt(txn_hash, 93 | TRANSACTION_TIMEOUT) 94 | 95 | def _parse_receipt(self, receipt, event, encoder=json.JSONEncoder): 96 | if receipt.status == 0: 97 | raise TransactionError("Transaction failed", receipt) 98 | else: 99 | return json.dumps(dict(event().processReceipt(receipt)[0]["args"]), 100 | cls=encoder) 101 | 102 | def escrow_balance(self): 103 | return self.mpe_contract.balance(self.address) 104 | 105 | def deposit_to_escrow_account(self, amount_in_cogs): 106 | already_approved = self.allowance() 107 | if amount_in_cogs > already_approved: 108 | self.approve_transfer(amount_in_cogs) 109 | return self.mpe_contract.deposit(self, amount_in_cogs) 110 | 111 | def approve_transfer(self, amount_in_cogs): 112 | return self.send_transaction(self.token_contract.functions.approve, 113 | self.mpe_contract.contract.address, 114 | amount_in_cogs) 115 | 116 | def allowance(self): 117 | return self.token_contract.functions.allowance( 118 | self.address, 119 | self.mpe_contract.contract.address 120 | ).call() 121 | -------------------------------------------------------------------------------- /snet/sdk/client_lib_generator.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | from snet.sdk.storage_provider.storage_provider import StorageProvider 5 | from snet.sdk.utils.utils import compile_proto 6 | 7 | 8 | class ClientLibGenerator: 9 | def __init__(self, metadata_provider: StorageProvider, org_id: str, 10 | service_id: str, protodir: Path | None = None): 11 | self._metadata_provider: StorageProvider = metadata_provider 12 | self.org_id: str = org_id 13 | self.service_id: str = service_id 14 | self.language: str = "python" 15 | self.protodir: Path = (protodir if protodir else 16 | Path.home().joinpath(".snet")) 17 | self.generate_directories_by_params() 18 | 19 | def generate_client_library(self) -> None: 20 | try: 21 | self.receive_proto_files() 22 | compilation_result = compile_proto(entry_path=self.protodir, 23 | codegen_dir=self.protodir, 24 | target_language=self.language, 25 | add_training=self.training_added()) 26 | if compilation_result: 27 | print(f'client libraries for service with id "{self.service_id}" ' 28 | f'in org with id "{self.org_id}" ' 29 | f'generated at {self.protodir}') 30 | except Exception as e: 31 | print(str(e)) 32 | 33 | def generate_directories_by_params(self) -> None: 34 | if not self.protodir.is_absolute(): 35 | self.protodir = Path.cwd().joinpath(self.protodir) 36 | self.create_service_client_libraries_path() 37 | 38 | def create_service_client_libraries_path(self) -> None: 39 | self.protodir = self.protodir.joinpath(self.org_id, 40 | self.service_id, 41 | self.language) 42 | self.protodir.mkdir(parents=True, exist_ok=True) 43 | 44 | def receive_proto_files(self) -> None: 45 | metadata = self._metadata_provider.fetch_service_metadata( 46 | org_id=self.org_id, 47 | service_id=self.service_id 48 | ) 49 | service_api_source = (metadata.get("service_api_source") or 50 | metadata.get("model_ipfs_hash")) 51 | 52 | # Receive proto files 53 | if self.protodir.exists(): 54 | self._metadata_provider.fetch_and_extract_proto( 55 | service_api_source, 56 | self.protodir 57 | ) 58 | else: 59 | raise Exception("Directory for storing proto files is not found") 60 | 61 | def training_added(self) -> bool: 62 | files = os.listdir(self.protodir) 63 | for file in files: 64 | if ".proto" not in file: 65 | continue 66 | with open(self.protodir.joinpath(file), "r") as f: 67 | proto_text = f.read() 68 | if 'import "training.proto";' in proto_text: 69 | return True 70 | return False 71 | -------------------------------------------------------------------------------- /snet/sdk/concurrency_manager.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | import grpc 4 | import web3 5 | 6 | from snet.sdk.service_client import ServiceClient 7 | from snet.sdk.utils.utils import RESOURCES_PATH, add_to_path 8 | 9 | 10 | class ConcurrencyManager: 11 | def __init__(self, concurrent_calls: int): 12 | self.__concurrent_calls: int = concurrent_calls 13 | self.__token: str = '' 14 | self.__planned_amount: int = 0 15 | self.__used_amount: int = 0 16 | 17 | @property 18 | def concurrent_calls(self) -> int: 19 | return self.__concurrent_calls 20 | 21 | def get_token(self, service_client, channel, service_call_price): 22 | if len(self.__token) == 0: 23 | self.__token = self.__get_token(service_client, channel, service_call_price) 24 | elif self.__used_amount >= self.__planned_amount: 25 | self.__token = self.__get_token(service_client, channel, service_call_price, new_token=True) 26 | return self.__token 27 | 28 | def __get_token(self, service_client: ServiceClient, channel, service_call_price, new_token=False): 29 | if not new_token: 30 | amount = channel.state["last_signed_amount"] 31 | if amount != 0: 32 | try: 33 | token_reply = self.__get_token_for_amount(service_client, channel, amount) 34 | planned_amount = token_reply.planned_amount 35 | used_amount = token_reply.used_amount 36 | if planned_amount - used_amount > 0: 37 | self.__used_amount = used_amount 38 | self.__planned_amount = planned_amount 39 | return token_reply.token 40 | except grpc.RpcError as e: 41 | if e.details() != "Unable to retrieve planned Amount ": 42 | raise 43 | 44 | amount = channel.state["last_signed_amount"] + service_call_price 45 | token_reply = self.__get_token_for_amount(service_client, channel, amount) 46 | self.__used_amount = token_reply.used_amount 47 | self.__planned_amount = token_reply.planned_amount 48 | return token_reply.token 49 | 50 | def __get_stub_for_get_token(self, service_client: ServiceClient): 51 | grpc_channel = service_client.get_grpc_base_channel() 52 | with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): 53 | token_service_pb2_grpc = importlib.import_module("token_service_pb2_grpc") 54 | return token_service_pb2_grpc.TokenServiceStub(grpc_channel) 55 | 56 | def __get_token_for_amount(self, service_client: ServiceClient, channel, amount): 57 | nonce = channel.state["nonce"] 58 | stub = self.__get_stub_for_get_token(service_client) 59 | with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): 60 | token_service_pb2 = importlib.import_module("token_service_pb2") 61 | current_block_number = service_client.sdk_web3.eth.get_block("latest").number 62 | message = web3.Web3.solidity_keccak( 63 | ["string", "address", "uint256", "uint256", "uint256"], 64 | ["__MPE_claim_message", service_client.mpe_address, channel.channel_id, nonce, amount] 65 | ) 66 | mpe_signature = service_client.generate_signature(message) 67 | message = web3.Web3.solidity_keccak( 68 | ["bytes", "uint256"], 69 | [mpe_signature, current_block_number] 70 | ) 71 | sign_mpe_signature = service_client.generate_signature(message) 72 | 73 | request = token_service_pb2.TokenRequest( 74 | channel_id=channel.channel_id, current_nonce=nonce, signed_amount=amount, 75 | signature=bytes(sign_mpe_signature), claim_signature=bytes(mpe_signature), 76 | current_block=current_block_number) 77 | token_reply = stub.GetToken(request) 78 | return token_reply 79 | 80 | def record_successful_call(self): 81 | self.__used_amount += 1 82 | -------------------------------------------------------------------------------- /snet/sdk/config.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Config: 4 | def __init__(self, 5 | private_key, 6 | eth_rpc_endpoint, 7 | wallet_index=0, 8 | ipfs_endpoint=None, 9 | concurrency=True, 10 | force_update=False, 11 | mpe_contract_address=None, 12 | token_contract_address=None, 13 | registry_contract_address=None, 14 | signer_private_key=None): 15 | self.__config = { 16 | "private_key": private_key, 17 | "eth_rpc_endpoint": eth_rpc_endpoint, 18 | "wallet_index": wallet_index, 19 | "ipfs_endpoint": (ipfs_endpoint if ipfs_endpoint 20 | else "/dns/ipfs.singularitynet.io/tcp/80/"), 21 | "concurrency": concurrency, 22 | "force_update": force_update, 23 | "mpe_contract_address": mpe_contract_address, 24 | "token_contract_address": token_contract_address, 25 | "registry_contract_address": registry_contract_address, 26 | "signer_private_key": signer_private_key, 27 | "lighthouse_token": " " 28 | } 29 | 30 | def __getitem__(self, key): 31 | return self.__config[key] 32 | 33 | def get(self, key, default=None): 34 | return self.__config.get(key, default) 35 | 36 | def get_ipfs_endpoint(self): 37 | return self["ipfs_endpoint"] 38 | -------------------------------------------------------------------------------- /snet/sdk/custom_typing.py: -------------------------------------------------------------------------------- 1 | from typing import Any, NewType 2 | 3 | 4 | ModuleName = NewType('ModuleName', str) 5 | ServiceStub = NewType('ServiceStub', Any) 6 | -------------------------------------------------------------------------------- /snet/sdk/generic_client_interceptor.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 gRPC authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """Base class for interceptors that operate on all RPC types.""" 15 | 16 | import grpc 17 | 18 | 19 | class _GenericClientInterceptor( 20 | grpc.UnaryUnaryClientInterceptor, grpc.UnaryStreamClientInterceptor, 21 | grpc.StreamUnaryClientInterceptor, grpc.StreamStreamClientInterceptor): 22 | 23 | def __init__(self, interceptor_function): 24 | self._fn = interceptor_function 25 | 26 | def intercept_unary_unary(self, continuation, client_call_details, request): 27 | new_details, new_request_iterator, postprocess = self._fn( 28 | client_call_details, iter((request,)), False, False) 29 | response = continuation(new_details, next(new_request_iterator)) 30 | return postprocess(response) if postprocess else response 31 | 32 | def intercept_unary_stream(self, continuation, client_call_details, 33 | request): 34 | new_details, new_request_iterator, postprocess = self._fn( 35 | client_call_details, iter((request,)), False, True) 36 | response_it = continuation(new_details, next(new_request_iterator)) 37 | return postprocess(response_it) if postprocess else response_it 38 | 39 | def intercept_stream_unary(self, continuation, client_call_details, 40 | request_iterator): 41 | new_details, new_request_iterator, postprocess = self._fn( 42 | client_call_details, request_iterator, True, False) 43 | response = continuation(new_details, new_request_iterator) 44 | return postprocess(response) if postprocess else response 45 | 46 | def intercept_stream_stream(self, continuation, client_call_details, 47 | request_iterator): 48 | new_details, new_request_iterator, postprocess = self._fn( 49 | client_call_details, request_iterator, True, True) 50 | response_it = continuation(new_details, new_request_iterator) 51 | return postprocess(response_it) if postprocess else response_it 52 | 53 | 54 | def create(intercept_call): 55 | return _GenericClientInterceptor(intercept_call) 56 | -------------------------------------------------------------------------------- /snet/sdk/mpe/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singnet/snet-sdk-python/00d73f37e33f12e5d85cd3a4162f2a4a658c1337/snet/sdk/mpe/__init__.py -------------------------------------------------------------------------------- /snet/sdk/mpe/mpe_contract.py: -------------------------------------------------------------------------------- 1 | from snet.contracts import get_contract_deployment_block, get_contract_object 2 | 3 | 4 | class MPEContract: 5 | def __init__(self, w3, address=None): 6 | self.web3 = w3 7 | if address is None: 8 | self.contract = get_contract_object(self.web3, "MultiPartyEscrow") 9 | else: 10 | self.contract = get_contract_object(self.web3, "MultiPartyEscrow", address) 11 | 12 | def balance(self, address): 13 | return self.contract.functions.balances(address).call() 14 | 15 | def deposit(self, account, amount_in_cogs): 16 | return account.send_transaction(self.contract.functions.deposit, amount_in_cogs) 17 | 18 | def open_channel(self, account, payment_address, group_id, amount, expiration): 19 | return account.send_transaction(self.contract.functions.openChannel, account.signer_address, payment_address, 20 | group_id, amount, expiration) 21 | 22 | def deposit_and_open_channel(self, account, payment_address, group_id, amount, expiration): 23 | already_approved_amount = account.allowance() 24 | if amount > already_approved_amount: 25 | account.approve_transfer(amount) 26 | return account.send_transaction(self.contract.functions.depositAndOpenChannel, account.signer_address, 27 | payment_address, group_id, amount, expiration) 28 | 29 | def channel_add_funds(self, account, channel_id, amount): 30 | self._fund_escrow_account(account, amount) 31 | return account.send_transaction(self.contract.functions.channelAddFunds, channel_id, amount) 32 | 33 | def channel_extend(self, account, channel_id, expiration): 34 | return account.send_transaction(self.contract.functions.channelExtend, channel_id, expiration) 35 | 36 | def channel_extend_and_add_funds(self, account, channel_id, expiration, amount): 37 | self._fund_escrow_account(account, amount) 38 | return account.send_transaction(self.contract.functions.channelExtendAndAddFunds, channel_id, expiration, 39 | amount) 40 | 41 | def _fund_escrow_account(self, account, amount): 42 | current_escrow_balance = self.balance(account.address) 43 | if amount > current_escrow_balance: 44 | account.deposit_to_escrow_account(amount - current_escrow_balance) 45 | -------------------------------------------------------------------------------- /snet/sdk/mpe/payment_channel.py: -------------------------------------------------------------------------------- 1 | import web3 2 | import importlib 3 | from eth_account.messages import defunct_hash_message 4 | 5 | from snet.sdk.utils.utils import RESOURCES_PATH, add_to_path 6 | 7 | class PaymentChannel: 8 | def __init__(self, channel_id, w3, account, payment_channel_state_service_client, mpe_contract): 9 | self.channel_id = channel_id 10 | self.web3 = w3 11 | self.account = account 12 | self.mpe_contract = mpe_contract 13 | self.payment_channel_state_service_client = payment_channel_state_service_client 14 | self.state = { 15 | "nonce": 0, 16 | "last_signed_amount": 0 17 | } 18 | 19 | def add_funds(self, amount): 20 | return self.mpe_contract.channel_add_funds(self.account, self.channel_id, amount) 21 | 22 | def extend_expiration(self, expiration): 23 | return self.mpe_contract.channel_extend(self.account, self.channel_id, expiration) 24 | 25 | def extend_and_add_funds(self, expiration, amount): 26 | return self.mpe_contract.channel_extend_and_add_funds(self.account, self.channel_id, expiration, amount) 27 | 28 | def sync_state(self): 29 | channel_blockchain_data = self.mpe_contract.contract.functions.channels(self.channel_id).call() 30 | (current_nonce, last_signed_amount) = self._get_current_channel_state() 31 | nonce = channel_blockchain_data[0] 32 | total_amount = channel_blockchain_data[5] 33 | expiration = channel_blockchain_data[6] 34 | available_amount = total_amount - last_signed_amount 35 | self.state = { 36 | "current_nonce": current_nonce, 37 | "last_signed_amount": last_signed_amount, 38 | "nonce": nonce, 39 | "total_amount": total_amount, 40 | "expiration": expiration, 41 | "available_amount": available_amount 42 | } 43 | 44 | def _get_current_channel_state(self): 45 | stub = self.payment_channel_state_service_client 46 | current_block_number = self.web3.eth.get_block("latest").number 47 | message = web3.Web3.solidity_keccak( 48 | ["string", "address", "uint256", "uint256"], 49 | [ 50 | "__get_channel_state", 51 | web3.Web3.to_checksum_address(self.mpe_contract.contract.address), 52 | self.channel_id,current_block_number 53 | ] 54 | ) 55 | signature = self.web3.eth.account.signHash(defunct_hash_message(message), self.account.signer_private_key).signature 56 | with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): 57 | state_service_pb2 = importlib.import_module("state_service_pb2") 58 | request = state_service_pb2.ChannelStateRequest( 59 | channel_id=web3.Web3.to_bytes(self.channel_id), 60 | signature=bytes(signature), 61 | current_block=current_block_number 62 | ) 63 | response = stub.GetChannelState(request) 64 | return int.from_bytes(response.current_nonce, byteorder="big"),int.from_bytes(response.current_signed_amount, byteorder="big") 65 | -------------------------------------------------------------------------------- /snet/sdk/mpe/payment_channel_provider.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from web3._utils.events import get_event_data 4 | from eth_abi.codec import ABICodec 5 | import pickle 6 | 7 | from snet.sdk.mpe.payment_channel import PaymentChannel 8 | from snet.contracts import get_contract_deployment_block 9 | 10 | 11 | BLOCKS_PER_BATCH = 5000 12 | CHANNELS_DIR = Path.home().joinpath(".snet", "cache", "mpe") 13 | 14 | 15 | class PaymentChannelProvider(object): 16 | def __init__(self, w3, mpe_contract): 17 | self.web3 = w3 18 | 19 | self.mpe_contract = mpe_contract 20 | self.event_topics = [self.web3.keccak( 21 | text="ChannelOpen(uint256,uint256,address,address,address,bytes32,uint256,uint256)").hex()] 22 | self.deployment_block = get_contract_deployment_block(self.web3, "MultiPartyEscrow") 23 | self.mpe_address = mpe_contract.contract.address 24 | self.channels_file = CHANNELS_DIR.joinpath(str(self.mpe_address), "channels.pickle") 25 | 26 | def update_cache(self): 27 | channels = [] 28 | last_read_block = self.deployment_block - 1 29 | 30 | if not self.channels_file.exists(): 31 | print(f"Channels cache is empty. Caching may take some time when first accessing channels.\nCaching in progress...") 32 | self.channels_file.parent.mkdir(parents=True, exist_ok=True) 33 | with open(self.channels_file, "wb") as f: 34 | empty_dict = { 35 | "last_read_block": last_read_block, 36 | "channels": channels 37 | } 38 | pickle.dump(empty_dict, f) 39 | else: 40 | with open(self.channels_file, "rb") as f: 41 | load_dict = pickle.load(f) 42 | last_read_block = load_dict["last_read_block"] 43 | channels = load_dict["channels"] 44 | 45 | current_block_number = self.web3.eth.block_number 46 | 47 | if last_read_block < current_block_number: 48 | new_channels = self._get_all_channels_from_blockchain_logs_to_dicts(last_read_block + 1, current_block_number) 49 | channels = channels + new_channels 50 | last_read_block = current_block_number 51 | 52 | with open(self.channels_file, "wb") as f: 53 | dict_to_save = { 54 | "last_read_block": last_read_block, 55 | "channels": channels 56 | } 57 | pickle.dump(dict_to_save, f) 58 | 59 | def _event_data_args_to_dict(self, event_data): 60 | return { 61 | "channel_id": event_data["channelId"], 62 | "sender": event_data["sender"], 63 | "signer": event_data["signer"], 64 | "recipient": event_data["recipient"], 65 | "group_id": event_data["groupId"], 66 | } 67 | 68 | def _get_all_channels_from_blockchain_logs_to_dicts(self, starting_block_number, to_block_number): 69 | codec: ABICodec = self.web3.codec 70 | 71 | logs = [] 72 | from_block = starting_block_number 73 | while from_block <= to_block_number: 74 | to_block = min(from_block + BLOCKS_PER_BATCH, to_block_number) 75 | logs = logs + self.web3.eth.get_logs({"fromBlock": from_block, 76 | "toBlock": to_block, 77 | "address": self.mpe_address, 78 | "topics": self.event_topics}) 79 | from_block = to_block + 1 80 | 81 | event_abi = self.mpe_contract.contract._find_matching_event_abi(event_name="ChannelOpen") 82 | 83 | event_data_list = [get_event_data(codec, event_abi, l)["args"] for l in logs] 84 | channels_opened = list(map(self._event_data_args_to_dict, event_data_list)) 85 | 86 | return channels_opened 87 | 88 | def _get_channels_from_cache(self): 89 | self.update_cache() 90 | with open(self.channels_file, "rb") as f: 91 | load_dict = pickle.load(f) 92 | return load_dict["channels"] 93 | 94 | def get_past_open_channels(self, account, payment_address, group_id, payment_channel_state_service_client): 95 | 96 | dict_channels = self._get_channels_from_cache() 97 | 98 | channels_opened = list(filter(lambda channel: (channel["sender"] == account.address 99 | or channel["signer"] == account.signer_address) 100 | and channel["recipient"] == payment_address 101 | and channel["group_id"] == group_id, 102 | dict_channels)) 103 | 104 | return list(map(lambda channel: PaymentChannel(channel["channel_id"], 105 | self.web3, 106 | account, 107 | payment_channel_state_service_client, 108 | self.mpe_contract), 109 | channels_opened)) 110 | 111 | def open_channel(self, account, amount, expiration, payment_address, group_id, payment_channel_state_service_client): 112 | receipt = self.mpe_contract.open_channel(account, payment_address, group_id, amount, expiration) 113 | return self._get_newly_opened_channel(account, payment_address, group_id, receipt, payment_channel_state_service_client) 114 | 115 | def deposit_and_open_channel(self, account, amount, expiration, payment_address, group_id, payment_channel_state_service_client): 116 | receipt = self.mpe_contract.deposit_and_open_channel(account, payment_address, group_id, amount, expiration) 117 | return self._get_newly_opened_channel(account, payment_address, group_id, receipt, payment_channel_state_service_client) 118 | 119 | def _get_newly_opened_channel(self, account, payment_address, group_id, receipt, payment_channel_state_service_client): 120 | open_channels = self.get_past_open_channels(account, payment_address, group_id, payment_channel_state_service_client) 121 | if not open_channels: 122 | raise Exception(f"Error while opening channel, please check transaction {receipt.transactionHash.hex()} ") 123 | return open_channels[-1] 124 | 125 | -------------------------------------------------------------------------------- /snet/sdk/payment_strategies/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singnet/snet-sdk-python/00d73f37e33f12e5d85cd3a4162f2a4a658c1337/snet/sdk/payment_strategies/__init__.py -------------------------------------------------------------------------------- /snet/sdk/payment_strategies/default_payment_strategy.py: -------------------------------------------------------------------------------- 1 | from snet.sdk.concurrency_manager import ConcurrencyManager 2 | from snet.sdk.payment_strategies.freecall_payment_strategy import FreeCallPaymentStrategy 3 | from snet.sdk.payment_strategies.paidcall_payment_strategy import PaidCallPaymentStrategy 4 | from snet.sdk.payment_strategies.prepaid_payment_strategy import PrePaidPaymentStrategy 5 | from snet.sdk.payment_strategies.payment_strategy import PaymentStrategy 6 | 7 | 8 | class DefaultPaymentStrategy(PaymentStrategy): 9 | 10 | def __init__(self, concurrent_calls: int = 1): 11 | self.concurrent_calls = concurrent_calls 12 | self.concurrencyManager = ConcurrencyManager(concurrent_calls) 13 | self.channel = None 14 | 15 | def set_concurrency_token(self, token): 16 | self.concurrencyManager.__token = token 17 | 18 | def set_channel(self, channel): 19 | self.channel = channel 20 | 21 | def get_payment_metadata(self, service_client): 22 | free_call_payment_strategy = FreeCallPaymentStrategy() 23 | 24 | if free_call_payment_strategy.is_free_call_available(service_client): 25 | metadata = free_call_payment_strategy.get_payment_metadata(service_client) 26 | else: 27 | if service_client.get_concurrency_flag(): 28 | payment_strategy = PrePaidPaymentStrategy(self.concurrencyManager) 29 | metadata = payment_strategy.get_payment_metadata(service_client, self.channel) 30 | else: 31 | payment_strategy = PaidCallPaymentStrategy() 32 | metadata = payment_strategy.get_payment_metadata(service_client) 33 | 34 | return metadata 35 | 36 | def get_price(self, service_client): 37 | pass 38 | 39 | def get_concurrency_token_and_channel(self, service_client): 40 | payment_strategy = PrePaidPaymentStrategy(self.concurrencyManager) 41 | return payment_strategy.get_concurrency_token_and_channel(service_client) 42 | -------------------------------------------------------------------------------- /snet/sdk/payment_strategies/freecall_payment_strategy.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | from urllib.parse import urlparse 3 | 4 | import grpc 5 | import web3 6 | from snet.sdk.resources.root_certificate import certificate 7 | from snet.sdk.utils.utils import RESOURCES_PATH, add_to_path 8 | 9 | from snet.sdk.payment_strategies.payment_strategy import PaymentStrategy 10 | 11 | 12 | class FreeCallPaymentStrategy(PaymentStrategy): 13 | 14 | def is_free_call_available(self, service_client): 15 | try: 16 | org_id, service_id, group_id, daemon_endpoint = service_client.get_service_details() 17 | email, token_for_free_call, token_expiry_date_block = service_client.get_free_call_config() 18 | 19 | if not token_for_free_call: 20 | return False 21 | 22 | signature, current_block_number = self.generate_signature(service_client) 23 | with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): 24 | state_service_pb2 = importlib.import_module("state_service_pb2") 25 | 26 | with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): 27 | state_service_pb2_grpc = importlib.import_module("state_service_pb2_grpc") 28 | 29 | request = state_service_pb2.FreeCallStateRequest() 30 | request.user_id = email 31 | request.token_for_free_call = token_for_free_call 32 | request.token_expiry_date_block = token_expiry_date_block 33 | request.signature = signature 34 | request.current_block = current_block_number 35 | 36 | endpoint_object = urlparse(daemon_endpoint) 37 | if endpoint_object.port is not None: 38 | channel_endpoint = endpoint_object.hostname + ":" + str(endpoint_object.port) 39 | else: 40 | channel_endpoint = endpoint_object.hostname 41 | 42 | if endpoint_object.scheme == "http": 43 | channel = grpc.insecure_channel(channel_endpoint) 44 | elif endpoint_object.scheme == "https": 45 | channel = grpc.secure_channel(channel_endpoint, grpc.ssl_channel_credentials(root_certificates=certificate)) 46 | else: 47 | raise ValueError('Unsupported scheme in service metadata ("{}")'.format(endpoint_object.scheme)) 48 | 49 | stub = state_service_pb2_grpc.FreeCallStateServiceStub(channel) 50 | response = stub.GetFreeCallsAvailable(request) 51 | if response.free_calls_available > 0: 52 | return True 53 | return False 54 | except Exception as e: 55 | return False 56 | 57 | def get_payment_metadata(self, service_client): 58 | email, token_for_free_call, token_expiry_date_block = service_client.get_free_call_config() 59 | signature, current_block_number = self.generate_signature(service_client) 60 | metadata = [("snet-free-call-auth-token-bin", token_for_free_call), 61 | ("snet-free-call-token-expiry-block", str(token_expiry_date_block)), 62 | ("snet-payment-type", "free-call"), 63 | ("snet-free-call-user-id", email), 64 | ("snet-current-block-number", str(current_block_number)), 65 | ("snet-payment-channel-signature-bin", signature)] 66 | 67 | return metadata 68 | 69 | def select_channel(self, service_client): 70 | pass 71 | 72 | def generate_signature(self, service_client): 73 | org_id, service_id, group_id, daemon_endpoint = service_client.get_service_details() 74 | email, token_for_free_call, token_expiry_date_block = service_client.get_free_call_config() 75 | 76 | if token_expiry_date_block == 0 or len(email) == 0 or len(token_for_free_call) == 0: 77 | raise Exception( 78 | "You are using default 'FreeCallPaymentStrategy' to use this strategy you need to pass " 79 | "'free_call_auth_token-bin','email','free-call-token-expiry-block' in config") 80 | 81 | current_block_number = service_client.get_current_block_number() 82 | 83 | message = web3.Web3.solidity_keccak( 84 | ["string", "string", "string", "string", "string", "uint256", "bytes32"], 85 | ["__prefix_free_trial", email, org_id, service_id, group_id, current_block_number, 86 | token_for_free_call] 87 | ) 88 | return service_client.generate_signature(message), current_block_number 89 | -------------------------------------------------------------------------------- /snet/sdk/payment_strategies/paidcall_payment_strategy.py: -------------------------------------------------------------------------------- 1 | import web3 2 | from snet.sdk.payment_strategies.payment_strategy import PaymentStrategy 3 | 4 | 5 | class PaidCallPaymentStrategy(PaymentStrategy): 6 | def __init__(self, block_offset=240, call_allowance=1): 7 | self.block_offset = block_offset 8 | self.call_allowance = call_allowance 9 | 10 | def get_price(self, service_client): 11 | return service_client.get_price() 12 | 13 | def get_payment_metadata(self, service_client): 14 | channel = self.select_channel(service_client) 15 | amount = channel.state["last_signed_amount"] + int(self.get_price(service_client)) 16 | message = web3.Web3.solidity_keccak( 17 | ["string", "address", "uint256", "uint256", "uint256"], 18 | ["__MPE_claim_message", service_client.mpe_address, channel.channel_id, 19 | channel.state["nonce"], 20 | amount] 21 | ) 22 | signature = service_client.generate_signature(message) 23 | 24 | metadata = [ 25 | ("snet-payment-type", "escrow"), 26 | ("snet-payment-channel-id", str(channel.channel_id)), 27 | ("snet-payment-channel-nonce", str(channel.state["nonce"])), 28 | ("snet-payment-channel-amount", str(amount)), 29 | ("snet-payment-channel-signature-bin", signature) 30 | ] 31 | 32 | return metadata 33 | 34 | def select_channel(self, service_client): 35 | account = service_client.account 36 | service_client.load_open_channels() 37 | service_client.update_channel_states() 38 | payment_channels = service_client.payment_channels 39 | # picking the first pricing strategy as default for now 40 | service_call_price = self.get_price(service_client) 41 | mpe_balance = account.escrow_balance() 42 | default_expiration = service_client.default_channel_expiration() 43 | 44 | if len(payment_channels) < 1: 45 | if service_call_price > mpe_balance: 46 | payment_channel = service_client.deposit_and_open_channel(service_call_price, 47 | default_expiration + self.block_offset) 48 | else: 49 | payment_channel = service_client.open_channel(service_call_price, 50 | default_expiration + self.block_offset) 51 | service_client.payment_channels = service_client.payment_channels + [payment_channel] 52 | service_client.update_channel_states() 53 | else: 54 | payment_channel = payment_channels[0] 55 | 56 | if self._has_sufficient_funds(payment_channel, service_call_price) and not self._is_valid(payment_channel, 57 | default_expiration): 58 | payment_channel.extend_expiration(default_expiration + self.block_offset) 59 | elif not self._has_sufficient_funds(payment_channel, service_call_price) and self._is_valid(payment_channel, 60 | default_expiration): 61 | payment_channel.add_funds(service_call_price * self.call_allowance) 62 | elif not self._has_sufficient_funds(payment_channel, service_call_price) and not self._is_valid(payment_channel, 63 | default_expiration): 64 | payment_channel.extend_and_add_funds(default_expiration + self.block_offset, 65 | service_call_price * self.call_allowance) 66 | 67 | return payment_channel 68 | 69 | @staticmethod 70 | def _has_sufficient_funds(channel, amount): 71 | return channel.state["available_amount"] >= amount 72 | 73 | @staticmethod 74 | def _is_valid(channel, expiry): 75 | return channel.state["expiration"] >= expiry 76 | -------------------------------------------------------------------------------- /snet/sdk/payment_strategies/payment_strategy.py: -------------------------------------------------------------------------------- 1 | class PaymentStrategy(object): 2 | 3 | def get_payment_metadata(self, service_client): 4 | pass 5 | 6 | def get_price(self, service_client): 7 | pass 8 | -------------------------------------------------------------------------------- /snet/sdk/payment_strategies/prepaid_payment_strategy.py: -------------------------------------------------------------------------------- 1 | from snet.sdk.concurrency_manager import ConcurrencyManager 2 | from snet.sdk.payment_strategies.payment_strategy import PaymentStrategy 3 | 4 | 5 | class PrePaidPaymentStrategy(PaymentStrategy): 6 | 7 | def __init__(self, concurrency_manager: ConcurrencyManager, 8 | block_offset: int = 240, call_allowance: int = 1): 9 | self.concurrency_manager = concurrency_manager 10 | self.block_offset = block_offset 11 | self.call_allowance = call_allowance 12 | 13 | def get_price(self, service_client): 14 | return service_client.get_price() * self.concurrency_manager.concurrent_calls 15 | 16 | def get_payment_metadata(self, service_client, channel): 17 | if channel is None: 18 | channel = self.select_channel(service_client) 19 | token = self.concurrency_manager.get_token(service_client, channel, self.get_price(service_client)) 20 | metadata = [ 21 | ("snet-payment-type", "prepaid-call"), 22 | ("snet-payment-channel-id", str(channel.channel_id)), 23 | ("snet-payment-channel-nonce", str(channel.state["nonce"])), 24 | ("snet-prepaid-auth-token-bin", bytes(token, 'UTF-8')) 25 | ] 26 | return metadata 27 | 28 | def get_concurrency_token_and_channel(self, service_client): 29 | channel = self.select_channel(service_client) 30 | token = self.concurrency_manager.get_token(service_client, channel, self.get_price(service_client)) 31 | return token, channel 32 | 33 | def select_channel(self, service_client): 34 | account = service_client.account 35 | service_client.load_open_channels() 36 | service_client.update_channel_states() 37 | payment_channels = service_client.payment_channels 38 | service_call_price = self.get_price(service_client) 39 | extend_channel_fund = service_call_price * self.call_allowance 40 | mpe_balance = account.escrow_balance() 41 | default_expiration = service_client.default_channel_expiration() 42 | 43 | if len(payment_channels) < 1: 44 | if service_call_price > mpe_balance: 45 | payment_channel = service_client.deposit_and_open_channel(service_call_price, 46 | default_expiration + self.block_offset) 47 | else: 48 | payment_channel = service_client.open_channel(service_call_price, 49 | default_expiration + self.block_offset) 50 | else: 51 | payment_channel = payment_channels[0] 52 | 53 | if self.__has_sufficient_funds(payment_channel, service_call_price) \ 54 | and not self.__is_valid(payment_channel, default_expiration): 55 | payment_channel.extend_expiration(default_expiration + self.block_offset) 56 | 57 | elif not self.__has_sufficient_funds(payment_channel, service_call_price) and \ 58 | self.__is_valid(payment_channel, default_expiration): 59 | payment_channel.add_funds(extend_channel_fund) 60 | 61 | elif not self.__has_sufficient_funds(payment_channel, service_call_price) and \ 62 | not self.__is_valid(payment_channel, default_expiration): 63 | payment_channel.extend_and_add_funds(default_expiration + self.block_offset, extend_channel_fund) 64 | 65 | return payment_channel 66 | 67 | @staticmethod 68 | def __has_sufficient_funds(channel, amount): 69 | return channel.state["available_amount"] >= amount 70 | 71 | @staticmethod 72 | def __is_valid(channel, expiry): 73 | return channel.state["expiration"] >= expiry 74 | -------------------------------------------------------------------------------- /snet/sdk/payment_strategies/training_payment_strategy.py: -------------------------------------------------------------------------------- 1 | import web3 2 | 3 | from snet.sdk.payment_strategies.paidcall_payment_strategy import PaidCallPaymentStrategy 4 | 5 | 6 | class TrainingPaymentStrategy(PaidCallPaymentStrategy): 7 | def __init__(self): 8 | super().__init__() 9 | self._call_price = -1 10 | self._train_model_id = "" 11 | 12 | def get_price(self, service_client=None) -> int: 13 | if self._call_price == -1: 14 | raise Exception("Training call price not set") 15 | return self._call_price 16 | 17 | def set_price(self, call_price: int) -> None: 18 | self._call_price = call_price 19 | 20 | def get_model_id(self): 21 | return self._train_model_id 22 | 23 | def set_model_id(self, model_id: str): 24 | self._train_model_id = model_id 25 | 26 | def get_payment_metadata(self, service_client) -> list[tuple[str, str]]: 27 | channel = self.select_channel(service_client) 28 | amount = channel.state["last_signed_amount"] + int(self.get_price(service_client)) 29 | message = web3.Web3.solidity_keccak( 30 | ["string", "address", "uint256", "uint256", "uint256"], 31 | ["__MPE_claim_message", service_client.mpe_address, channel.channel_id, 32 | channel.state["nonce"], 33 | amount] 34 | ) 35 | signature = service_client.generate_signature(message) 36 | 37 | metadata = [ 38 | ("snet-payment-type", "train-call"), 39 | ("snet-payment-channel-id", str(channel.channel_id)), 40 | ("snet-payment-channel-nonce", str(channel.state["nonce"])), 41 | ("snet-payment-channel-amount", str(amount)), 42 | ("snet-train-model-id", self.get_model_id()), 43 | ("snet-payment-channel-signature-bin", signature) 44 | ] 45 | 46 | return metadata 47 | -------------------------------------------------------------------------------- /snet/sdk/resources/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "grpc-tools": "1.9.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /snet/sdk/resources/proto/control_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package escrow; 4 | 5 | service ProviderControlService { 6 | 7 | //get list of all unclaimed "payments". 8 | //in PaymentsSignatureReply signatures MUST be omited 9 | //if signature field is present then response should be considered as invalid 10 | rpc GetListUnclaimed(GetPaymentsListRequest) returns (PaymentsListReply) {} 11 | 12 | //get list of all payments in progress 13 | rpc GetListInProgress(GetPaymentsListRequest) returns (PaymentsListReply) {} 14 | 15 | //initilize claim for specific channel 16 | rpc StartClaim(StartClaimRequest) returns (PaymentReply) {} 17 | } 18 | 19 | 20 | message GetPaymentsListRequest { 21 | //address of MultiPartyEscrow contract 22 | string mpe_address = 1; 23 | //current block number (signature will be valid only for short time around this block number) 24 | uint64 current_block = 2; 25 | //signature of the following message: 26 | //for GetListUnclaimed ("__list_unclaimed", mpe_address, current_block_number) 27 | //for GetListInProgress ("__list_in_progress", mpe_address, current_block_number) 28 | bytes signature = 3; 29 | } 30 | 31 | message StartClaimRequest { 32 | //address of MultiPartyEscrow contract 33 | string mpe_address = 1; 34 | //channel_id contains id of the channel which state is requested. 35 | bytes channel_id = 2; 36 | //signature of the following message ("__start_claim", mpe_address, channel_id, channel_nonce) 37 | bytes signature = 3; 38 | } 39 | 40 | message PaymentReply { 41 | bytes channel_id = 1; 42 | 43 | bytes channel_nonce = 2; 44 | 45 | bytes signed_amount = 3; 46 | 47 | //this filed must be OMITED in GetListUnclaimed request 48 | bytes signature = 4; 49 | } 50 | 51 | message PaymentsListReply { 52 | repeated PaymentReply payments = 1; 53 | } 54 | 55 | 56 | -------------------------------------------------------------------------------- /snet/sdk/resources/proto/control_service_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: control_service.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import descriptor_pool as _descriptor_pool 7 | from google.protobuf import symbol_database as _symbol_database 8 | from google.protobuf.internal import builder as _builder 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | 15 | 16 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x63ontrol_service.proto\x12\x06\x65scrow\"W\n\x16GetPaymentsListRequest\x12\x13\n\x0bmpe_address\x18\x01 \x01(\t\x12\x15\n\rcurrent_block\x18\x02 \x01(\x04\x12\x11\n\tsignature\x18\x03 \x01(\x0c\"O\n\x11StartClaimRequest\x12\x13\n\x0bmpe_address\x18\x01 \x01(\t\x12\x12\n\nchannel_id\x18\x02 \x01(\x0c\x12\x11\n\tsignature\x18\x03 \x01(\x0c\"c\n\x0cPaymentReply\x12\x12\n\nchannel_id\x18\x01 \x01(\x0c\x12\x15\n\rchannel_nonce\x18\x02 \x01(\x0c\x12\x15\n\rsigned_amount\x18\x03 \x01(\x0c\x12\x11\n\tsignature\x18\x04 \x01(\x0c\";\n\x11PaymentsListReply\x12&\n\x08payments\x18\x01 \x03(\x0b\x32\x14.escrow.PaymentReply2\xfc\x01\n\x16ProviderControlService\x12O\n\x10GetListUnclaimed\x12\x1e.escrow.GetPaymentsListRequest\x1a\x19.escrow.PaymentsListReply\"\x00\x12P\n\x11GetListInProgress\x12\x1e.escrow.GetPaymentsListRequest\x1a\x19.escrow.PaymentsListReply\"\x00\x12?\n\nStartClaim\x12\x19.escrow.StartClaimRequest\x1a\x14.escrow.PaymentReply\"\x00\x62\x06proto3') 17 | 18 | _globals = globals() 19 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 20 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'control_service_pb2', _globals) 21 | if _descriptor._USE_C_DESCRIPTORS == False: 22 | DESCRIPTOR._options = None 23 | _globals['_GETPAYMENTSLISTREQUEST']._serialized_start=33 24 | _globals['_GETPAYMENTSLISTREQUEST']._serialized_end=120 25 | _globals['_STARTCLAIMREQUEST']._serialized_start=122 26 | _globals['_STARTCLAIMREQUEST']._serialized_end=201 27 | _globals['_PAYMENTREPLY']._serialized_start=203 28 | _globals['_PAYMENTREPLY']._serialized_end=302 29 | _globals['_PAYMENTSLISTREPLY']._serialized_start=304 30 | _globals['_PAYMENTSLISTREPLY']._serialized_end=363 31 | _globals['_PROVIDERCONTROLSERVICE']._serialized_start=366 32 | _globals['_PROVIDERCONTROLSERVICE']._serialized_end=618 33 | # @@protoc_insertion_point(module_scope) 34 | -------------------------------------------------------------------------------- /snet/sdk/resources/proto/control_service_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | import control_service_pb2 as control__service__pb2 6 | 7 | 8 | class ProviderControlServiceStub(object): 9 | """Missing associated documentation comment in .proto file.""" 10 | 11 | def __init__(self, channel): 12 | """Constructor. 13 | 14 | Args: 15 | channel: A grpc.Channel. 16 | """ 17 | self.GetListUnclaimed = channel.unary_unary( 18 | '/escrow.ProviderControlService/GetListUnclaimed', 19 | request_serializer=control__service__pb2.GetPaymentsListRequest.SerializeToString, 20 | response_deserializer=control__service__pb2.PaymentsListReply.FromString, 21 | ) 22 | self.GetListInProgress = channel.unary_unary( 23 | '/escrow.ProviderControlService/GetListInProgress', 24 | request_serializer=control__service__pb2.GetPaymentsListRequest.SerializeToString, 25 | response_deserializer=control__service__pb2.PaymentsListReply.FromString, 26 | ) 27 | self.StartClaim = channel.unary_unary( 28 | '/escrow.ProviderControlService/StartClaim', 29 | request_serializer=control__service__pb2.StartClaimRequest.SerializeToString, 30 | response_deserializer=control__service__pb2.PaymentReply.FromString, 31 | ) 32 | 33 | 34 | class ProviderControlServiceServicer(object): 35 | """Missing associated documentation comment in .proto file.""" 36 | 37 | def GetListUnclaimed(self, request, context): 38 | """get list of all unclaimed "payments". 39 | in PaymentsSignatureReply signatures MUST be omited 40 | if signature field is present then response should be considered as invalid 41 | """ 42 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 43 | context.set_details('Method not implemented!') 44 | raise NotImplementedError('Method not implemented!') 45 | 46 | def GetListInProgress(self, request, context): 47 | """get list of all payments in progress 48 | """ 49 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 50 | context.set_details('Method not implemented!') 51 | raise NotImplementedError('Method not implemented!') 52 | 53 | def StartClaim(self, request, context): 54 | """initilize claim for specific channel 55 | """ 56 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 57 | context.set_details('Method not implemented!') 58 | raise NotImplementedError('Method not implemented!') 59 | 60 | 61 | def add_ProviderControlServiceServicer_to_server(servicer, server): 62 | rpc_method_handlers = { 63 | 'GetListUnclaimed': grpc.unary_unary_rpc_method_handler( 64 | servicer.GetListUnclaimed, 65 | request_deserializer=control__service__pb2.GetPaymentsListRequest.FromString, 66 | response_serializer=control__service__pb2.PaymentsListReply.SerializeToString, 67 | ), 68 | 'GetListInProgress': grpc.unary_unary_rpc_method_handler( 69 | servicer.GetListInProgress, 70 | request_deserializer=control__service__pb2.GetPaymentsListRequest.FromString, 71 | response_serializer=control__service__pb2.PaymentsListReply.SerializeToString, 72 | ), 73 | 'StartClaim': grpc.unary_unary_rpc_method_handler( 74 | servicer.StartClaim, 75 | request_deserializer=control__service__pb2.StartClaimRequest.FromString, 76 | response_serializer=control__service__pb2.PaymentReply.SerializeToString, 77 | ), 78 | } 79 | generic_handler = grpc.method_handlers_generic_handler( 80 | 'escrow.ProviderControlService', rpc_method_handlers) 81 | server.add_generic_rpc_handlers((generic_handler,)) 82 | 83 | 84 | # This class is part of an EXPERIMENTAL API. 85 | class ProviderControlService(object): 86 | """Missing associated documentation comment in .proto file.""" 87 | 88 | @staticmethod 89 | def GetListUnclaimed(request, 90 | target, 91 | options=(), 92 | channel_credentials=None, 93 | call_credentials=None, 94 | insecure=False, 95 | compression=None, 96 | wait_for_ready=None, 97 | timeout=None, 98 | metadata=None): 99 | return grpc.experimental.unary_unary(request, target, '/escrow.ProviderControlService/GetListUnclaimed', 100 | control__service__pb2.GetPaymentsListRequest.SerializeToString, 101 | control__service__pb2.PaymentsListReply.FromString, 102 | options, channel_credentials, 103 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 104 | 105 | @staticmethod 106 | def GetListInProgress(request, 107 | target, 108 | options=(), 109 | channel_credentials=None, 110 | call_credentials=None, 111 | insecure=False, 112 | compression=None, 113 | wait_for_ready=None, 114 | timeout=None, 115 | metadata=None): 116 | return grpc.experimental.unary_unary(request, target, '/escrow.ProviderControlService/GetListInProgress', 117 | control__service__pb2.GetPaymentsListRequest.SerializeToString, 118 | control__service__pb2.PaymentsListReply.FromString, 119 | options, channel_credentials, 120 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 121 | 122 | @staticmethod 123 | def StartClaim(request, 124 | target, 125 | options=(), 126 | channel_credentials=None, 127 | call_credentials=None, 128 | insecure=False, 129 | compression=None, 130 | wait_for_ready=None, 131 | timeout=None, 132 | metadata=None): 133 | return grpc.experimental.unary_unary(request, target, '/escrow.ProviderControlService/StartClaim', 134 | control__service__pb2.StartClaimRequest.SerializeToString, 135 | control__service__pb2.PaymentReply.FromString, 136 | options, channel_credentials, 137 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 138 | -------------------------------------------------------------------------------- /snet/sdk/resources/proto/state_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package escrow; 4 | 5 | option java_package = "io.singularitynet.daemon.escrow"; 6 | 7 | // PaymentChannelStateService contains methods to get the MultiPartyEscrow 8 | // payment channel state. 9 | // channel_id, channel_nonce, value and amount fields below in fact are 10 | // Solidity uint256 values. Which are big-endian integers, see 11 | // https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding 12 | // These values may be or may be not padded by zeros, service supports both 13 | // options. 14 | service PaymentChannelStateService { 15 | // GetChannelState method returns a channel state by channel id. 16 | rpc GetChannelState(ChannelStateRequest) returns (ChannelStateReply) {} 17 | } 18 | 19 | // ChanelStateRequest is a request for channel state. 20 | message ChannelStateRequest { 21 | // channel_id contains id of the channel which state is requested. 22 | bytes channel_id = 1; 23 | 24 | // signature is a client signature of the message which contains 25 | // channel_id. It is used for client authorization. 26 | bytes signature = 2; 27 | 28 | //current block number (signature will be valid only for short time around this block number) 29 | uint64 current_block = 3; 30 | } 31 | 32 | // ChannelStateReply message contains a latest channel state. current_nonce and 33 | // current_value fields can be different from ones stored in the blockchain if 34 | // server started withdrawing funds froms channel but transaction is still not 35 | // finished. 36 | message ChannelStateReply { 37 | // current_nonce is a latest nonce of the payment channel. 38 | bytes current_nonce = 1; 39 | 40 | // current_signed_amount is a last amount which were signed by client with current_nonce 41 | //it could be absent if none message was signed with current_nonce 42 | bytes current_signed_amount = 2; 43 | 44 | // current_signature is a last signature sent by client with current_nonce 45 | // it could be absent if none message was signed with current nonce 46 | bytes current_signature = 3; 47 | 48 | // last amount which was signed by client with nonce=current_nonce - 1 49 | bytes old_nonce_signed_amount = 4; 50 | 51 | // last signature sent by client with nonce = current_nonce - 1 52 | bytes old_nonce_signature = 5; 53 | } 54 | 55 | //Used to determine free calls available for a given user. 56 | service FreeCallStateService { 57 | rpc GetFreeCallsAvailable(FreeCallStateRequest) returns (FreeCallStateReply) {} 58 | } 59 | 60 | message FreeCallStateRequest { 61 | //Has the user email id 62 | string user_id = 1; 63 | //signer-token = (user@mail, user-public-key, token_issue_date), this is generated my Market place Dapp 64 | //to leverage free calls from SDK/ snet-cli, you will need this signer-token to be downloaded from Dapp 65 | bytes token_for_free_call = 2; 66 | //Token expiration date in Block number 67 | uint64 token_expiry_date_block = 3 ; 68 | //Signature is made up of the below, user signs with the private key corresponding with the public key used to generate the authorized token 69 | //free-call-metadata = ("__prefix_free_trial",user_id,organization_id,service_id,group_id,current_block,authorized_token) 70 | bytes signature = 4; 71 | //current block number (signature will be valid only for short time around this block number) 72 | uint64 current_block = 5; 73 | 74 | } 75 | 76 | message FreeCallStateReply { 77 | //Has the user email id 78 | string user_id = 1; 79 | //Balance number of free calls available 80 | uint64 free_calls_available = 2; 81 | } 82 | -------------------------------------------------------------------------------- /snet/sdk/resources/proto/state_service_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: state_service.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import descriptor_pool as _descriptor_pool 7 | from google.protobuf import symbol_database as _symbol_database 8 | from google.protobuf.internal import builder as _builder 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | 15 | 16 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13state_service.proto\x12\x06\x65scrow\"S\n\x13\x43hannelStateRequest\x12\x12\n\nchannel_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x15\n\rcurrent_block\x18\x03 \x01(\x04\"\xa2\x01\n\x11\x43hannelStateReply\x12\x15\n\rcurrent_nonce\x18\x01 \x01(\x0c\x12\x1d\n\x15\x63urrent_signed_amount\x18\x02 \x01(\x0c\x12\x19\n\x11\x63urrent_signature\x18\x03 \x01(\x0c\x12\x1f\n\x17old_nonce_signed_amount\x18\x04 \x01(\x0c\x12\x1b\n\x13old_nonce_signature\x18\x05 \x01(\x0c\"\x8f\x01\n\x14\x46reeCallStateRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x1b\n\x13token_for_free_call\x18\x02 \x01(\x0c\x12\x1f\n\x17token_expiry_date_block\x18\x03 \x01(\x04\x12\x11\n\tsignature\x18\x04 \x01(\x0c\x12\x15\n\rcurrent_block\x18\x05 \x01(\x04\"C\n\x12\x46reeCallStateReply\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x1c\n\x14\x66ree_calls_available\x18\x02 \x01(\x04\x32i\n\x1aPaymentChannelStateService\x12K\n\x0fGetChannelState\x12\x1b.escrow.ChannelStateRequest\x1a\x19.escrow.ChannelStateReply\"\x00\x32k\n\x14\x46reeCallStateService\x12S\n\x15GetFreeCallsAvailable\x12\x1c.escrow.FreeCallStateRequest\x1a\x1a.escrow.FreeCallStateReply\"\x00\x42!\n\x1fio.singularitynet.daemon.escrowb\x06proto3') 17 | 18 | _globals = globals() 19 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 20 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'state_service_pb2', _globals) 21 | if _descriptor._USE_C_DESCRIPTORS == False: 22 | DESCRIPTOR._options = None 23 | DESCRIPTOR._serialized_options = b'\n\037io.singularitynet.daemon.escrow' 24 | _globals['_CHANNELSTATEREQUEST']._serialized_start=31 25 | _globals['_CHANNELSTATEREQUEST']._serialized_end=114 26 | _globals['_CHANNELSTATEREPLY']._serialized_start=117 27 | _globals['_CHANNELSTATEREPLY']._serialized_end=279 28 | _globals['_FREECALLSTATEREQUEST']._serialized_start=282 29 | _globals['_FREECALLSTATEREQUEST']._serialized_end=425 30 | _globals['_FREECALLSTATEREPLY']._serialized_start=427 31 | _globals['_FREECALLSTATEREPLY']._serialized_end=494 32 | _globals['_PAYMENTCHANNELSTATESERVICE']._serialized_start=496 33 | _globals['_PAYMENTCHANNELSTATESERVICE']._serialized_end=601 34 | _globals['_FREECALLSTATESERVICE']._serialized_start=603 35 | _globals['_FREECALLSTATESERVICE']._serialized_end=710 36 | # @@protoc_insertion_point(module_scope) 37 | -------------------------------------------------------------------------------- /snet/sdk/resources/proto/state_service_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | import state_service_pb2 as state__service__pb2 6 | 7 | 8 | class PaymentChannelStateServiceStub(object): 9 | """PaymentChannelStateService contains methods to get the MultiPartyEscrow 10 | payment channel state. 11 | channel_id, channel_nonce, value and amount fields below in fact are 12 | Solidity uint256 values. Which are big-endian integers, see 13 | https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding 14 | These values may be or may be not padded by zeros, service supports both 15 | options. 16 | """ 17 | 18 | def __init__(self, channel): 19 | """Constructor. 20 | 21 | Args: 22 | channel: A grpc.Channel. 23 | """ 24 | self.GetChannelState = channel.unary_unary( 25 | '/escrow.PaymentChannelStateService/GetChannelState', 26 | request_serializer=state__service__pb2.ChannelStateRequest.SerializeToString, 27 | response_deserializer=state__service__pb2.ChannelStateReply.FromString, 28 | ) 29 | 30 | 31 | class PaymentChannelStateServiceServicer(object): 32 | """PaymentChannelStateService contains methods to get the MultiPartyEscrow 33 | payment channel state. 34 | channel_id, channel_nonce, value and amount fields below in fact are 35 | Solidity uint256 values. Which are big-endian integers, see 36 | https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding 37 | These values may be or may be not padded by zeros, service supports both 38 | options. 39 | """ 40 | 41 | def GetChannelState(self, request, context): 42 | """GetChannelState method returns a channel state by channel id. 43 | """ 44 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 45 | context.set_details('Method not implemented!') 46 | raise NotImplementedError('Method not implemented!') 47 | 48 | 49 | def add_PaymentChannelStateServiceServicer_to_server(servicer, server): 50 | rpc_method_handlers = { 51 | 'GetChannelState': grpc.unary_unary_rpc_method_handler( 52 | servicer.GetChannelState, 53 | request_deserializer=state__service__pb2.ChannelStateRequest.FromString, 54 | response_serializer=state__service__pb2.ChannelStateReply.SerializeToString, 55 | ), 56 | } 57 | generic_handler = grpc.method_handlers_generic_handler( 58 | 'escrow.PaymentChannelStateService', rpc_method_handlers) 59 | server.add_generic_rpc_handlers((generic_handler,)) 60 | 61 | 62 | # This class is part of an EXPERIMENTAL API. 63 | class PaymentChannelStateService(object): 64 | """PaymentChannelStateService contains methods to get the MultiPartyEscrow 65 | payment channel state. 66 | channel_id, channel_nonce, value and amount fields below in fact are 67 | Solidity uint256 values. Which are big-endian integers, see 68 | https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding 69 | These values may be or may be not padded by zeros, service supports both 70 | options. 71 | """ 72 | 73 | @staticmethod 74 | def GetChannelState(request, 75 | target, 76 | options=(), 77 | channel_credentials=None, 78 | call_credentials=None, 79 | insecure=False, 80 | compression=None, 81 | wait_for_ready=None, 82 | timeout=None, 83 | metadata=None): 84 | return grpc.experimental.unary_unary(request, target, '/escrow.PaymentChannelStateService/GetChannelState', 85 | state__service__pb2.ChannelStateRequest.SerializeToString, 86 | state__service__pb2.ChannelStateReply.FromString, 87 | options, channel_credentials, 88 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 89 | 90 | 91 | class FreeCallStateServiceStub(object): 92 | """Used to determine free calls available for a given user. 93 | """ 94 | 95 | def __init__(self, channel): 96 | """Constructor. 97 | 98 | Args: 99 | channel: A grpc.Channel. 100 | """ 101 | self.GetFreeCallsAvailable = channel.unary_unary( 102 | '/escrow.FreeCallStateService/GetFreeCallsAvailable', 103 | request_serializer=state__service__pb2.FreeCallStateRequest.SerializeToString, 104 | response_deserializer=state__service__pb2.FreeCallStateReply.FromString, 105 | ) 106 | 107 | 108 | class FreeCallStateServiceServicer(object): 109 | """Used to determine free calls available for a given user. 110 | """ 111 | 112 | def GetFreeCallsAvailable(self, request, context): 113 | """Missing associated documentation comment in .proto file.""" 114 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 115 | context.set_details('Method not implemented!') 116 | raise NotImplementedError('Method not implemented!') 117 | 118 | 119 | def add_FreeCallStateServiceServicer_to_server(servicer, server): 120 | rpc_method_handlers = { 121 | 'GetFreeCallsAvailable': grpc.unary_unary_rpc_method_handler( 122 | servicer.GetFreeCallsAvailable, 123 | request_deserializer=state__service__pb2.FreeCallStateRequest.FromString, 124 | response_serializer=state__service__pb2.FreeCallStateReply.SerializeToString, 125 | ), 126 | } 127 | generic_handler = grpc.method_handlers_generic_handler( 128 | 'escrow.FreeCallStateService', rpc_method_handlers) 129 | server.add_generic_rpc_handlers((generic_handler,)) 130 | 131 | 132 | # This class is part of an EXPERIMENTAL API. 133 | class FreeCallStateService(object): 134 | """Used to determine free calls available for a given user. 135 | """ 136 | 137 | @staticmethod 138 | def GetFreeCallsAvailable(request, 139 | target, 140 | options=(), 141 | channel_credentials=None, 142 | call_credentials=None, 143 | insecure=False, 144 | compression=None, 145 | wait_for_ready=None, 146 | timeout=None, 147 | metadata=None): 148 | return grpc.experimental.unary_unary(request, target, '/escrow.FreeCallStateService/GetFreeCallsAvailable', 149 | state__service__pb2.FreeCallStateRequest.SerializeToString, 150 | state__service__pb2.FreeCallStateReply.FromString, 151 | options, channel_credentials, 152 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 153 | -------------------------------------------------------------------------------- /snet/sdk/resources/proto/token_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package escrow; 4 | 5 | option java_package = "io.singularitynet.daemon.escrow"; 6 | //It is expected that the user would call the GetChannelState to Determine the Current state of the Channel 7 | //Based on the usage forecast, the user/client will have to sign for an amount L + U , where L is the last amount Signed 8 | //and U is the amount based on expected usage. 9 | //Please be aware that the Signing up an amount upfront ( Pre Paid) does come with a risk and hence the 10 | //user must exercise caution on the amount signed specially with new service providers. 11 | //If there is no need of making concurrent calls then you may consider pay per mode. 12 | //Using a Token, the Client can now make concurrent calls, which was not supported previously with the pay per mode. 13 | //However the pay per mode is a lot secure than the pre-paid mode. 14 | service TokenService { 15 | // GetToken method checks the Signature sent and returns a Token 16 | // 1) The Signature is valid and has to be signed in the below format 17 | //"__MPE_claim_message"+MpeContractAddress+ChannelID+ChannelNonce+SignedAmount 18 | //Signature is to let the Service Provider make a claim 19 | // 2) Signed amount >= Last amount Signed. 20 | // if Signed amount == Last Signed amount , then check if planned_amount < used_amount 21 | // if Signed amount > Last Signed amount , then update the planned amount = Signed Amount 22 | // GetToken method in a way behaves as a renew Token too!. 23 | rpc GetToken(TokenRequest) returns (TokenReply) {} 24 | 25 | 26 | 27 | } 28 | 29 | // TokenRequest is a request for getting a valid token. 30 | message TokenRequest { 31 | // channel_id contains id of the channel which state is requested. 32 | uint64 channel_id = 1; 33 | // current_nonce is a latest nonce of the payment channel. 34 | uint64 current_nonce = 2; 35 | //signed_amount is the amount signed by client with current_nonce 36 | uint64 signed_amount = 3; 37 | // Signature is a client signature of the message which contains 2 parts 38 | //Part 1 : MPE Signature "__MPE_claim_message"+MpeContractAddress+ChannelID+ChannelNonce+SignedAmount 39 | //Part 2 : Current Block Number 40 | bytes signature = 4; 41 | //current block number (signature will be valid only for short time around this block number) 42 | uint64 current_block = 5; 43 | 44 | bytes claim_signature = 6; 45 | 46 | } 47 | 48 | // TokenReply message contains a latest channel state. current_nonce and 49 | message TokenReply { 50 | // current_nonce is a latest nonce of the payment channel. 51 | uint64 channel_id = 1; 52 | 53 | //it could be absent if none message was signed with current_nonce 54 | string token = 2; 55 | 56 | //If the client / user chooses to sign upfront , the planned amount in cogs will be indicative of this. 57 | uint64 planned_amount = 3; 58 | 59 | //If the client / user chooses to sign upfront , the used amount in cogs will be indicative of how much of the 60 | //planned amount has actually been used. 61 | uint64 used_amount = 4; 62 | 63 | } 64 | -------------------------------------------------------------------------------- /snet/sdk/resources/proto/token_service_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: token_service.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import descriptor_pool as _descriptor_pool 7 | from google.protobuf import symbol_database as _symbol_database 8 | from google.protobuf.internal import builder as _builder 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | 15 | 16 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13token_service.proto\x12\x06\x65scrow\"\x93\x01\n\x0cTokenRequest\x12\x12\n\nchannel_id\x18\x01 \x01(\x04\x12\x15\n\rcurrent_nonce\x18\x02 \x01(\x04\x12\x15\n\rsigned_amount\x18\x03 \x01(\x04\x12\x11\n\tsignature\x18\x04 \x01(\x0c\x12\x15\n\rcurrent_block\x18\x05 \x01(\x04\x12\x17\n\x0f\x63laim_signature\x18\x06 \x01(\x0c\"\\\n\nTokenReply\x12\x12\n\nchannel_id\x18\x01 \x01(\x04\x12\r\n\x05token\x18\x02 \x01(\t\x12\x16\n\x0eplanned_amount\x18\x03 \x01(\x04\x12\x13\n\x0bused_amount\x18\x04 \x01(\x04\x32\x46\n\x0cTokenService\x12\x36\n\x08GetToken\x12\x14.escrow.TokenRequest\x1a\x12.escrow.TokenReply\"\x00\x42!\n\x1fio.singularitynet.daemon.escrowb\x06proto3') 17 | 18 | _globals = globals() 19 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 20 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'token_service_pb2', _globals) 21 | if _descriptor._USE_C_DESCRIPTORS == False: 22 | DESCRIPTOR._options = None 23 | DESCRIPTOR._serialized_options = b'\n\037io.singularitynet.daemon.escrow' 24 | _globals['_TOKENREQUEST']._serialized_start=32 25 | _globals['_TOKENREQUEST']._serialized_end=179 26 | _globals['_TOKENREPLY']._serialized_start=181 27 | _globals['_TOKENREPLY']._serialized_end=273 28 | _globals['_TOKENSERVICE']._serialized_start=275 29 | _globals['_TOKENSERVICE']._serialized_end=345 30 | # @@protoc_insertion_point(module_scope) 31 | -------------------------------------------------------------------------------- /snet/sdk/resources/proto/token_service_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | import token_service_pb2 as token__service__pb2 6 | 7 | 8 | class TokenServiceStub(object): 9 | """It is expected that the user would call the GetChannelState to Determine the Current state of the Channel 10 | Based on the usage forecast, the user/client will have to sign for an amount L + U , where L is the last amount Signed 11 | and U is the amount based on expected usage. 12 | Please be aware that the Signing up an amount upfront ( Pre Paid) does come with a risk and hence the 13 | user must exercise caution on the amount signed specially with new service providers. 14 | If there is no need of making concurrent calls then you may consider pay per mode. 15 | Using a Token, the Client can now make concurrent calls, which was not supported previously with the pay per mode. 16 | However the pay per mode is a lot secure than the pre-paid mode. 17 | """ 18 | 19 | def __init__(self, channel): 20 | """Constructor. 21 | 22 | Args: 23 | channel: A grpc.Channel. 24 | """ 25 | self.GetToken = channel.unary_unary( 26 | '/escrow.TokenService/GetToken', 27 | request_serializer=token__service__pb2.TokenRequest.SerializeToString, 28 | response_deserializer=token__service__pb2.TokenReply.FromString, 29 | ) 30 | 31 | 32 | class TokenServiceServicer(object): 33 | """It is expected that the user would call the GetChannelState to Determine the Current state of the Channel 34 | Based on the usage forecast, the user/client will have to sign for an amount L + U , where L is the last amount Signed 35 | and U is the amount based on expected usage. 36 | Please be aware that the Signing up an amount upfront ( Pre Paid) does come with a risk and hence the 37 | user must exercise caution on the amount signed specially with new service providers. 38 | If there is no need of making concurrent calls then you may consider pay per mode. 39 | Using a Token, the Client can now make concurrent calls, which was not supported previously with the pay per mode. 40 | However the pay per mode is a lot secure than the pre-paid mode. 41 | """ 42 | 43 | def GetToken(self, request, context): 44 | """GetToken method checks the Signature sent and returns a Token 45 | 1) The Signature is valid and has to be signed in the below format 46 | "__MPE_claim_message"+MpeContractAddress+ChannelID+ChannelNonce+SignedAmount 47 | Signature is to let the Service Provider make a claim 48 | 2) Signed amount >= Last amount Signed. 49 | if Signed amount == Last Signed amount , then check if planned_amount < used_amount 50 | if Signed amount > Last Signed amount , then update the planned amount = Signed Amount 51 | GetToken method in a way behaves as a renew Token too!. 52 | """ 53 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 54 | context.set_details('Method not implemented!') 55 | raise NotImplementedError('Method not implemented!') 56 | 57 | 58 | def add_TokenServiceServicer_to_server(servicer, server): 59 | rpc_method_handlers = { 60 | 'GetToken': grpc.unary_unary_rpc_method_handler( 61 | servicer.GetToken, 62 | request_deserializer=token__service__pb2.TokenRequest.FromString, 63 | response_serializer=token__service__pb2.TokenReply.SerializeToString, 64 | ), 65 | } 66 | generic_handler = grpc.method_handlers_generic_handler( 67 | 'escrow.TokenService', rpc_method_handlers) 68 | server.add_generic_rpc_handlers((generic_handler,)) 69 | 70 | 71 | # This class is part of an EXPERIMENTAL API. 72 | class TokenService(object): 73 | """It is expected that the user would call the GetChannelState to Determine the Current state of the Channel 74 | Based on the usage forecast, the user/client will have to sign for an amount L + U , where L is the last amount Signed 75 | and U is the amount based on expected usage. 76 | Please be aware that the Signing up an amount upfront ( Pre Paid) does come with a risk and hence the 77 | user must exercise caution on the amount signed specially with new service providers. 78 | If there is no need of making concurrent calls then you may consider pay per mode. 79 | Using a Token, the Client can now make concurrent calls, which was not supported previously with the pay per mode. 80 | However the pay per mode is a lot secure than the pre-paid mode. 81 | """ 82 | 83 | @staticmethod 84 | def GetToken(request, 85 | target, 86 | options=(), 87 | channel_credentials=None, 88 | call_credentials=None, 89 | insecure=False, 90 | compression=None, 91 | wait_for_ready=None, 92 | timeout=None, 93 | metadata=None): 94 | return grpc.experimental.unary_unary(request, target, '/escrow.TokenService/GetToken', 95 | token__service__pb2.TokenRequest.SerializeToString, 96 | token__service__pb2.TokenReply.FromString, 97 | options, channel_credentials, 98 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 99 | -------------------------------------------------------------------------------- /snet/sdk/resources/proto/training.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/descriptor.proto"; // Required for indicators to work 3 | package training; 4 | option go_package = "github.com/singnet/snet-daemon/v5/training"; 5 | 6 | // Methods that the service provider must implement 7 | service Model { 8 | 9 | // Free 10 | // Can pass the address of the model creator 11 | rpc create_model(NewModel) returns (ModelID) {} 12 | 13 | // Free 14 | rpc validate_model_price(ValidateRequest) returns (PriceInBaseUnit) {} 15 | 16 | // Paid 17 | rpc upload_and_validate(stream UploadInput) returns (StatusResponse) {} 18 | 19 | // Paid 20 | rpc validate_model(ValidateRequest) returns (StatusResponse) {} 21 | 22 | // Free, one signature for both train_model_price & train_model methods 23 | rpc train_model_price(ModelID) returns (PriceInBaseUnit) {} 24 | 25 | // Paid 26 | rpc train_model(ModelID) returns (StatusResponse) {} 27 | 28 | // Free 29 | rpc delete_model(ModelID) returns (StatusResponse) { 30 | // After model deletion, the status becomes DELETED in etcd 31 | } 32 | 33 | // Free 34 | rpc get_model_status(ModelID) returns (StatusResponse) {} 35 | } 36 | 37 | message ModelResponse { 38 | string model_id = 1; 39 | Status status = 2; 40 | string created_date = 3; 41 | string updated_date = 4; 42 | string name = 5; 43 | string description = 6; 44 | string grpc_method_name = 7; 45 | string grpc_service_name = 8; 46 | 47 | // List of all addresses that will have access to this model 48 | repeated string address_list = 9; 49 | 50 | // Access to the model is granted only for use and viewing 51 | bool is_public = 10; 52 | 53 | string training_data_link = 11; 54 | 55 | string created_by_address = 12; 56 | string updated_by_address = 13; 57 | } 58 | 59 | // Used as input for new_model requests 60 | // The service provider decides whether to use these fields; returning model_id is mandatory 61 | message NewModel { 62 | string name = 1; 63 | string description = 2; 64 | string grpc_method_name = 3; 65 | string grpc_service_name = 4; 66 | 67 | // List of all addresses that will have access to this model 68 | repeated string address_list = 5; 69 | 70 | // Set this to true if you want your model to be accessible by other AI consumers 71 | bool is_public = 6; 72 | 73 | // These parameters will be passed by the daemon 74 | string organization_id = 7; 75 | string service_id = 8; 76 | string group_id = 9; 77 | } 78 | 79 | // This structure must be used by the service provider 80 | message ModelID { 81 | string model_id = 1; 82 | } 83 | 84 | // This structure must be used by the service provider 85 | // Used in the train_model_price method to get the training/validation price 86 | message PriceInBaseUnit { 87 | uint64 price = 1; // cogs, weis, afet, aasi, etc. 88 | } 89 | 90 | enum Status { 91 | CREATED = 0; 92 | VALIDATING = 1; 93 | VALIDATED = 2; 94 | TRAINING = 3; 95 | READY_TO_USE = 4; // After training is completed 96 | ERRORED = 5; 97 | DELETED = 6; 98 | } 99 | 100 | message StatusResponse { 101 | Status status = 1; 102 | } 103 | 104 | message UploadInput { 105 | string model_id = 1; 106 | bytes data = 2; 107 | string file_name = 3; 108 | uint64 file_size = 4; // in bytes 109 | uint64 batch_size = 5; 110 | uint64 batch_number = 6; 111 | uint64 batch_count = 7; 112 | } 113 | 114 | message ValidateRequest { 115 | string model_id = 2; 116 | string training_data_link = 3; 117 | } 118 | 119 | extend google.protobuf.MethodOptions { 120 | string default_model_id = 50001; 121 | uint64 max_models_per_user = 50002; // max models per method & user 122 | uint64 dataset_max_size_mb = 50003; // max size of dataset 123 | uint64 dataset_max_count_files = 50004; // maximum number of files in the dataset 124 | uint64 dataset_max_size_single_file_mb = 50005; // maximum size of a single file in the dataset 125 | string dataset_files_type = 50006; // allowed files types in dataset. string with array or single value, example: jpg, png, mp3 126 | string dataset_type = 50007; // string with array or single value, example: zip, tar.gz, tar 127 | string dataset_description = 50008; // additional free-form requirements 128 | } 129 | -------------------------------------------------------------------------------- /snet/sdk/resources/proto/training/training.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/descriptor.proto"; // Required for indicators to work 3 | package training; 4 | option go_package = "github.com/singnet/snet-daemon/v5/training"; 5 | 6 | // Methods that the service provider must implement 7 | service Model { 8 | 9 | // Free 10 | // Can pass the address of the model creator 11 | rpc create_model(NewModel) returns (ModelID) {} 12 | 13 | // Free 14 | rpc validate_model_price(ValidateRequest) returns (PriceInBaseUnit) {} 15 | 16 | // Paid 17 | rpc upload_and_validate(stream UploadInput) returns (StatusResponse) {} 18 | 19 | // Paid 20 | rpc validate_model(ValidateRequest) returns (StatusResponse) {} 21 | 22 | // Free, one signature for both train_model_price & train_model methods 23 | rpc train_model_price(ModelID) returns (PriceInBaseUnit) {} 24 | 25 | // Paid 26 | rpc train_model(ModelID) returns (StatusResponse) {} 27 | 28 | // Free 29 | rpc delete_model(ModelID) returns (StatusResponse) { 30 | // After model deletion, the status becomes DELETED in etcd 31 | } 32 | 33 | // Free 34 | rpc get_model_status(ModelID) returns (StatusResponse) {} 35 | } 36 | 37 | message ModelResponse { 38 | string model_id = 1; 39 | Status status = 2; 40 | string created_date = 3; 41 | string updated_date = 4; 42 | string name = 5; 43 | string description = 6; 44 | string grpc_method_name = 7; 45 | string grpc_service_name = 8; 46 | 47 | // List of all addresses that will have access to this model 48 | repeated string address_list = 9; 49 | 50 | // Access to the model is granted only for use and viewing 51 | bool is_public = 10; 52 | 53 | string training_data_link = 11; 54 | 55 | string created_by_address = 12; 56 | string updated_by_address = 13; 57 | } 58 | 59 | // Used as input for new_model requests 60 | // The service provider decides whether to use these fields; returning model_id is mandatory 61 | message NewModel { 62 | string name = 1; 63 | string description = 2; 64 | string grpc_method_name = 3; 65 | string grpc_service_name = 4; 66 | 67 | // List of all addresses that will have access to this model 68 | repeated string address_list = 5; 69 | 70 | // Set this to true if you want your model to be accessible by other AI consumers 71 | bool is_public = 6; 72 | 73 | // These parameters will be passed by the daemon 74 | string organization_id = 7; 75 | string service_id = 8; 76 | string group_id = 9; 77 | } 78 | 79 | // This structure must be used by the service provider 80 | message ModelID { 81 | string model_id = 1; 82 | } 83 | 84 | // This structure must be used by the service provider 85 | // Used in the train_model_price method to get the training/validation price 86 | message PriceInBaseUnit { 87 | uint64 price = 1; // cogs, weis, afet, aasi, etc. 88 | } 89 | 90 | enum Status { 91 | CREATED = 0; 92 | VALIDATING = 1; 93 | VALIDATED = 2; 94 | TRAINING = 3; 95 | READY_TO_USE = 4; // After training is completed 96 | ERRORED = 5; 97 | DELETED = 6; 98 | } 99 | 100 | message StatusResponse { 101 | Status status = 1; 102 | } 103 | 104 | message UploadInput { 105 | string model_id = 1; 106 | bytes data = 2; 107 | string file_name = 3; 108 | uint64 file_size = 4; // in bytes 109 | uint64 batch_size = 5; 110 | uint64 batch_number = 6; 111 | uint64 batch_count = 7; 112 | } 113 | 114 | message ValidateRequest { 115 | string model_id = 2; 116 | string training_data_link = 3; 117 | } 118 | 119 | extend google.protobuf.MethodOptions { 120 | string default_model_id = 50001; 121 | uint64 max_models_per_user = 50002; // max models per method & user 122 | uint64 dataset_max_size_mb = 50003; // max size of dataset 123 | uint64 dataset_max_count_files = 50004; // maximum number of files in the dataset 124 | uint64 dataset_max_size_single_file_mb = 50005; // maximum size of a single file in the dataset 125 | string dataset_files_type = 50006; // allowed files types in dataset. string with array or single value, example: jpg, png, mp3 126 | string dataset_type = 50007; // string with array or single value, example: zip, tar.gz, tar 127 | string dataset_description = 50008; // additional free-form requirements 128 | } 129 | -------------------------------------------------------------------------------- /snet/sdk/resources/proto/training_daemon.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package training; 3 | 4 | import "google/protobuf/descriptor.proto"; // Required for indicators to work 5 | import "google/protobuf/struct.proto"; // Required for google.protobuf.ListValue 6 | import "training.proto"; 7 | import "google/protobuf/empty.proto"; 8 | option go_package = "github.com/singnet/snet-daemon/v5/training"; 9 | 10 | message AuthorizationDetails { 11 | uint64 current_block = 1; // Check for relevance within a range of +/- N blocks 12 | // Signer can specify any message here 13 | string message = 2; 14 | // Signature of the following message: 15 | // ("user specified message", user_address, current_block_number) 16 | bytes signature = 3; 17 | string signer_address = 4; 18 | } 19 | 20 | message NewModelRequest { 21 | AuthorizationDetails authorization = 1; 22 | training.NewModel model = 2; 23 | } 24 | 25 | message AuthValidateRequest { 26 | AuthorizationDetails authorization = 1; 27 | string model_id = 2; 28 | string training_data_link = 3; 29 | } 30 | 31 | message UploadAndValidateRequest { 32 | AuthorizationDetails authorization = 1; 33 | training.UploadInput upload_input = 2; 34 | } 35 | 36 | message CommonRequest { 37 | AuthorizationDetails authorization = 1; 38 | string model_id = 2; 39 | } 40 | 41 | message UpdateModelRequest { 42 | AuthorizationDetails authorization = 1; 43 | string model_id = 2; 44 | optional string model_name = 3; 45 | optional string description = 4; 46 | repeated string address_list = 5; 47 | } 48 | 49 | message ModelsResponse { 50 | repeated training.ModelResponse list_of_models = 1; 51 | } 52 | 53 | // These methods are implemented only by the daemon; the service provider should ignore them 54 | service Daemon { 55 | // Free 56 | rpc create_model(NewModelRequest) returns (training.ModelResponse) {} 57 | 58 | // Free 59 | rpc validate_model_price(AuthValidateRequest) returns (training.PriceInBaseUnit) {} 60 | 61 | // Paid 62 | rpc upload_and_validate(stream UploadAndValidateRequest) returns (training.StatusResponse) {} 63 | 64 | // Paid 65 | rpc validate_model(AuthValidateRequest) returns (training.StatusResponse) {} 66 | 67 | // Free, one signature for both train_model_price & train_model methods 68 | rpc train_model_price(CommonRequest) returns (training.PriceInBaseUnit) {} 69 | 70 | // Paid 71 | rpc train_model(CommonRequest) returns (training.StatusResponse) {} 72 | 73 | // Free 74 | // After deleting the model, the status becomes DELETED in etcd 75 | rpc delete_model(CommonRequest) returns (training.StatusResponse) {} 76 | 77 | rpc get_all_models(AllModelsRequest) returns (ModelsResponse) {} 78 | 79 | rpc get_model(CommonRequest) returns (training.ModelResponse) {} 80 | 81 | rpc update_model(UpdateModelRequest) returns (training.ModelResponse) {} 82 | 83 | // Unique methods by daemon 84 | // One signature for all getters 85 | rpc get_training_metadata(google.protobuf.Empty) returns (TrainingMetadata) {} 86 | 87 | // Free & without authorization 88 | rpc get_method_metadata(MethodMetadataRequest) returns (MethodMetadata) {} 89 | } 90 | 91 | message MethodMetadataRequest { 92 | string model_id = 1; 93 | // Model ID or gRPC method name 94 | string grpc_method_name = 2; 95 | string grpc_service_name = 3; 96 | } 97 | 98 | message AllModelsRequest { 99 | AuthorizationDetails authorization = 1; 100 | // filters: 101 | repeated training.Status statuses = 3; 102 | optional bool is_public = 4; // null - all, false - only private, true - only public models 103 | string grpc_method_name = 5; 104 | string grpc_service_name = 6; 105 | string name = 7; 106 | string created_by_address = 8; 107 | uint64 page_size = 9; 108 | uint64 page = 10; 109 | } 110 | 111 | message TrainingMetadata { 112 | bool trainingEnabled = 1; 113 | bool trainingInProto = 2; 114 | // Key: grpc_service_name, Value: array of grpc_method_name 115 | map trainingMethods = 3; 116 | } 117 | 118 | message MethodMetadata { 119 | string default_model_id = 50001; 120 | uint64 max_models_per_user = 50002; // max models per method & user 121 | uint64 dataset_max_size_mb = 50003; // max size of dataset 122 | uint64 dataset_max_count_files = 50004; // maximum number of files in the dataset 123 | uint64 dataset_max_size_single_file_mb = 50005; // maximum size of a single file in the dataset 124 | string dataset_files_type = 50006; // allowed files types in dataset. string with array or single value, example: jpg, png, mp3 125 | string dataset_type = 50007; // string with array or single value, example: zip, tar.gz, tar 126 | string dataset_description = 50008; // additional free-form requirements 127 | } 128 | -------------------------------------------------------------------------------- /snet/sdk/resources/proto/training_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: training.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import descriptor_pool as _descriptor_pool 7 | from google.protobuf import symbol_database as _symbol_database 8 | from google.protobuf.internal import builder as _builder 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 15 | 16 | 17 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0etraining.proto\x12\x08training\x1a google/protobuf/descriptor.proto\"\xc4\x02\n\rModelResponse\x12\x10\n\x08model_id\x18\x01 \x01(\t\x12 \n\x06status\x18\x02 \x01(\x0e\x32\x10.training.Status\x12\x14\n\x0c\x63reated_date\x18\x03 \x01(\t\x12\x14\n\x0cupdated_date\x18\x04 \x01(\t\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x06 \x01(\t\x12\x18\n\x10grpc_method_name\x18\x07 \x01(\t\x12\x19\n\x11grpc_service_name\x18\x08 \x01(\t\x12\x14\n\x0c\x61\x64\x64ress_list\x18\t \x03(\t\x12\x11\n\tis_public\x18\n \x01(\x08\x12\x1a\n\x12training_data_link\x18\x0b \x01(\t\x12\x1a\n\x12\x63reated_by_address\x18\x0c \x01(\t\x12\x1a\n\x12updated_by_address\x18\r \x01(\t\"\xca\x01\n\x08NewModel\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x18\n\x10grpc_method_name\x18\x03 \x01(\t\x12\x19\n\x11grpc_service_name\x18\x04 \x01(\t\x12\x14\n\x0c\x61\x64\x64ress_list\x18\x05 \x03(\t\x12\x11\n\tis_public\x18\x06 \x01(\x08\x12\x17\n\x0forganization_id\x18\x07 \x01(\t\x12\x12\n\nservice_id\x18\x08 \x01(\t\x12\x10\n\x08group_id\x18\t \x01(\t\"\x1b\n\x07ModelID\x12\x10\n\x08model_id\x18\x01 \x01(\t\" \n\x0fPriceInBaseUnit\x12\r\n\x05price\x18\x01 \x01(\x04\"2\n\x0eStatusResponse\x12 \n\x06status\x18\x01 \x01(\x0e\x32\x10.training.Status\"\x92\x01\n\x0bUploadInput\x12\x10\n\x08model_id\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x11\n\tfile_name\x18\x03 \x01(\t\x12\x11\n\tfile_size\x18\x04 \x01(\x04\x12\x12\n\nbatch_size\x18\x05 \x01(\x04\x12\x14\n\x0c\x62\x61tch_number\x18\x06 \x01(\x04\x12\x13\n\x0b\x62\x61tch_count\x18\x07 \x01(\x04\"?\n\x0fValidateRequest\x12\x10\n\x08model_id\x18\x02 \x01(\t\x12\x1a\n\x12training_data_link\x18\x03 \x01(\t*n\n\x06Status\x12\x0b\n\x07\x43REATED\x10\x00\x12\x0e\n\nVALIDATING\x10\x01\x12\r\n\tVALIDATED\x10\x02\x12\x0c\n\x08TRAINING\x10\x03\x12\x10\n\x0cREADY_TO_USE\x10\x04\x12\x0b\n\x07\x45RRORED\x10\x05\x12\x0b\n\x07\x44\x45LETED\x10\x06\x32\xaa\x04\n\x05Model\x12\x37\n\x0c\x63reate_model\x12\x12.training.NewModel\x1a\x11.training.ModelID\"\x00\x12N\n\x14validate_model_price\x12\x19.training.ValidateRequest\x1a\x19.training.PriceInBaseUnit\"\x00\x12J\n\x13upload_and_validate\x12\x15.training.UploadInput\x1a\x18.training.StatusResponse\"\x00(\x01\x12G\n\x0evalidate_model\x12\x19.training.ValidateRequest\x1a\x18.training.StatusResponse\"\x00\x12\x43\n\x11train_model_price\x12\x11.training.ModelID\x1a\x19.training.PriceInBaseUnit\"\x00\x12<\n\x0btrain_model\x12\x11.training.ModelID\x1a\x18.training.StatusResponse\"\x00\x12=\n\x0c\x64\x65lete_model\x12\x11.training.ModelID\x1a\x18.training.StatusResponse\"\x00\x12\x41\n\x10get_model_status\x12\x11.training.ModelID\x1a\x18.training.StatusResponse\"\x00::\n\x10\x64\x65\x66\x61ult_model_id\x12\x1e.google.protobuf.MethodOptions\x18\xd1\x86\x03 \x01(\t:=\n\x13max_models_per_user\x12\x1e.google.protobuf.MethodOptions\x18\xd2\x86\x03 \x01(\x04:=\n\x13\x64\x61taset_max_size_mb\x12\x1e.google.protobuf.MethodOptions\x18\xd3\x86\x03 \x01(\x04:A\n\x17\x64\x61taset_max_count_files\x12\x1e.google.protobuf.MethodOptions\x18\xd4\x86\x03 \x01(\x04:I\n\x1f\x64\x61taset_max_size_single_file_mb\x12\x1e.google.protobuf.MethodOptions\x18\xd5\x86\x03 \x01(\x04:<\n\x12\x64\x61taset_files_type\x12\x1e.google.protobuf.MethodOptions\x18\xd6\x86\x03 \x01(\t:6\n\x0c\x64\x61taset_type\x12\x1e.google.protobuf.MethodOptions\x18\xd7\x86\x03 \x01(\t:=\n\x13\x64\x61taset_description\x12\x1e.google.protobuf.MethodOptions\x18\xd8\x86\x03 \x01(\tB,Z*github.com/singnet/snet-daemon/v5/trainingb\x06proto3') 18 | 19 | _globals = globals() 20 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 21 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'training_pb2', _globals) 22 | if _descriptor._USE_C_DESCRIPTORS == False: 23 | DESCRIPTOR._options = None 24 | DESCRIPTOR._serialized_options = b'Z*github.com/singnet/snet-daemon/v5/training' 25 | _globals['_STATUS']._serialized_start=923 26 | _globals['_STATUS']._serialized_end=1033 27 | _globals['_MODELRESPONSE']._serialized_start=63 28 | _globals['_MODELRESPONSE']._serialized_end=387 29 | _globals['_NEWMODEL']._serialized_start=390 30 | _globals['_NEWMODEL']._serialized_end=592 31 | _globals['_MODELID']._serialized_start=594 32 | _globals['_MODELID']._serialized_end=621 33 | _globals['_PRICEINBASEUNIT']._serialized_start=623 34 | _globals['_PRICEINBASEUNIT']._serialized_end=655 35 | _globals['_STATUSRESPONSE']._serialized_start=657 36 | _globals['_STATUSRESPONSE']._serialized_end=707 37 | _globals['_UPLOADINPUT']._serialized_start=710 38 | _globals['_UPLOADINPUT']._serialized_end=856 39 | _globals['_VALIDATEREQUEST']._serialized_start=858 40 | _globals['_VALIDATEREQUEST']._serialized_end=921 41 | _globals['_MODEL']._serialized_start=1036 42 | _globals['_MODEL']._serialized_end=1590 43 | # @@protoc_insertion_point(module_scope) 44 | -------------------------------------------------------------------------------- /snet/sdk/storage_provider/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singnet/snet-sdk-python/00d73f37e33f12e5d85cd3a4162f2a4a658c1337/snet/sdk/storage_provider/__init__.py -------------------------------------------------------------------------------- /snet/sdk/storage_provider/storage_provider.py: -------------------------------------------------------------------------------- 1 | import web3 2 | from lighthouseweb3 import Lighthouse 3 | import json 4 | 5 | from snet.sdk.utils.ipfs_utils import get_ipfs_client, get_from_ipfs_and_checkhash 6 | from snet.sdk.utils.utils import bytesuri_to_hash, safe_extract_proto 7 | from snet.sdk.storage_provider.service_metadata import MPEServiceMetadata, mpe_service_metadata_from_json 8 | 9 | class StorageProvider(object): 10 | def __init__(self, config, registry_contract): 11 | self._registry_contract = registry_contract 12 | self._ipfs_client = get_ipfs_client(config) 13 | self.lighthouse_client = Lighthouse(config["lighthouse_token"]) 14 | 15 | def fetch_org_metadata(self,org_id): 16 | org = web3.Web3.to_bytes(text=org_id).ljust(32, b"\0") 17 | 18 | found, _, org_metadata_uri, _, _, _ = self._registry_contract.functions.getOrganizationById(org).call() 19 | if found is not True: 20 | raise Exception('Organization with org ID "{}" not found '.format(org_id)) 21 | 22 | org_provider_type, org_metadata_hash = bytesuri_to_hash(org_metadata_uri) 23 | 24 | if org_provider_type == "ipfs": 25 | org_metadata_json = get_from_ipfs_and_checkhash(self._ipfs_client, org_metadata_hash) 26 | else: 27 | org_metadata_json, _ = self.lighthouse_client.download(org_metadata_hash) 28 | org_metadata = json.loads(org_metadata_json) 29 | 30 | return org_metadata 31 | 32 | def fetch_service_metadata(self, org_id: str, 33 | service_id: str) -> MPEServiceMetadata: 34 | org = web3.Web3.to_bytes(text=org_id).ljust(32, b"\0") 35 | service = web3.Web3.to_bytes(text=service_id).ljust(32, b"\0") 36 | 37 | found, _, service_metadata_uri = ( 38 | self._registry_contract.functions.getServiceRegistrationById( 39 | org, 40 | service 41 | ).call() 42 | ) 43 | if found is not True: 44 | raise Exception(f"No service '{service_id}' " 45 | f"found in organization '{org_id}'") 46 | 47 | service_provider_type, service_metadata_hash = bytesuri_to_hash( 48 | s=service_metadata_uri 49 | ) 50 | 51 | if service_provider_type == "ipfs": 52 | service_metadata_json = get_from_ipfs_and_checkhash( 53 | self._ipfs_client, 54 | service_metadata_hash 55 | ) 56 | else: 57 | service_metadata_json, _ = self.lighthouse_client.download( 58 | cid=service_metadata_hash 59 | ) 60 | service_metadata = mpe_service_metadata_from_json(service_metadata_json) 61 | 62 | return service_metadata 63 | 64 | def enhance_service_metadata(self,org_id,service_id): 65 | service_metadata = self.fetch_service_metadata(org_id, service_id) 66 | org_metadata = self.fetch_org_metadata(org_id) 67 | 68 | org_group_map = {} 69 | for group in org_metadata['groups']: 70 | org_group_map[group['group_name']] = group 71 | 72 | for group in service_metadata.m['groups']: 73 | # merge service group with org_group 74 | group['payment'] = org_group_map[group['group_name']]['payment'] 75 | 76 | return service_metadata 77 | 78 | def fetch_and_extract_proto(self, service_api_source, protodir): 79 | try: 80 | proto_provider_type, service_api_source = bytesuri_to_hash(service_api_source, to_decode=False) 81 | except Exception: 82 | proto_provider_type = "ipfs" 83 | 84 | if proto_provider_type == "ipfs": 85 | spec_tar = get_from_ipfs_and_checkhash(self._ipfs_client, service_api_source) 86 | else: 87 | spec_tar, _ = self.lighthouse_client.download(service_api_source) 88 | 89 | safe_extract_proto(spec_tar, protodir) 90 | 91 | -------------------------------------------------------------------------------- /snet/sdk/training/exceptions.py: -------------------------------------------------------------------------------- 1 | from grpc import RpcError 2 | 3 | 4 | class WrongDatasetException(Exception): 5 | def __init__(self, errors: list[str]): 6 | self.errors = errors 7 | exception_msg = "Dataset check failed:\n" 8 | for check in errors: 9 | exception_msg += f"\t{check}\n" 10 | super().__init__(exception_msg) 11 | 12 | 13 | class WrongMethodException(Exception): 14 | def __init__(self, method_name: str): 15 | super().__init__(f"Method with name {method_name} not found!") 16 | 17 | 18 | class NoTrainingException(Exception): 19 | def __init__(self, org_id: str, service_id: str): 20 | super().__init__(f"Training is not implemented for the service with org_id={org_id} and service_id={service_id}!") 21 | 22 | 23 | class GRPCException(RpcError): 24 | def __init__(self, error: RpcError): 25 | super().__init__(f"An error occurred during the grpc call: {error}.") 26 | 27 | 28 | class NoSuchModelException(Exception): 29 | def __init__(self, model_id: str): 30 | super().__init__(f"Model with id {model_id} not found!") 31 | 32 | -------------------------------------------------------------------------------- /snet/sdk/training/responses.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Any 3 | 4 | 5 | class ModelMethodMessage(Enum): 6 | CreateModel = "create_model" 7 | ValidateModelPrice = "validate_model_price" 8 | TrainModelPrice = "train_model_price" 9 | DeleteModel = "delete_model" 10 | GetTrainingMetadata = "get_training_metadata" 11 | GetAllModels = "get_all_models" 12 | GetModel = "get_model" 13 | UpdateModel = "update_model" 14 | GetMethodMetadata = "get_method_metadata" 15 | UploadAndValidate = "upload_and_validate" 16 | ValidateModel = "validate_model" 17 | TrainModel = "train_model" 18 | 19 | 20 | class ModelStatus(Enum): 21 | CREATED = 0 22 | VALIDATING = 1 23 | VALIDATED = 2 24 | TRAINING = 3 25 | READY_TO_USE = 4 26 | ERRORED = 5 27 | DELETED = 6 28 | 29 | 30 | def to_string(obj: Any): 31 | res_str = "" 32 | for key, value in obj.__dict__.items(): 33 | key = key.split("__")[1].replace("_", " ") 34 | res_str += f"{key}: {value}\n" 35 | return res_str 36 | 37 | 38 | class Model: 39 | def __init__(self, model_response): 40 | self.__model_id = model_response.model_id 41 | self.__status = ModelStatus(model_response.status) 42 | self.__created_date = model_response.created_date 43 | self.__updated_date = model_response.updated_date 44 | self.__name = model_response.name 45 | self.__description = model_response.description 46 | self.__grpc_method_name = model_response.grpc_method_name 47 | self.__grpc_service_name = model_response.grpc_service_name 48 | self.__address_list = model_response.address_list 49 | self.__is_public = model_response.is_public 50 | self.__training_data_link = model_response.training_data_link 51 | self.__created_by_address = model_response.created_by_address 52 | self.__updated_by_address = model_response.updated_by_address 53 | 54 | def __str__(self): 55 | return to_string(self) 56 | 57 | @property 58 | def model_id(self): 59 | return self.__model_id 60 | 61 | @property 62 | def status(self): 63 | return self.__status 64 | 65 | @property 66 | def created_date(self): 67 | return self.__created_date 68 | 69 | @property 70 | def updated_date(self): 71 | return self.__updated_date 72 | 73 | @property 74 | def name(self): 75 | return self.__name 76 | 77 | @property 78 | def description(self): 79 | return self.__description 80 | 81 | @property 82 | def grpc_method_name(self): 83 | return self.__grpc_method_name 84 | 85 | @property 86 | def grpc_service_name(self): 87 | return self.__grpc_service_name 88 | 89 | @property 90 | def address_list(self): 91 | return self.__address_list 92 | 93 | @property 94 | def is_public(self): 95 | return self.__is_public 96 | 97 | @property 98 | def training_data_link(self): 99 | return self.__training_data_link 100 | 101 | @property 102 | def created_by_address(self): 103 | return self.__created_by_address 104 | 105 | @property 106 | def updated_by_address(self): 107 | return self.__updated_by_address 108 | 109 | 110 | class TrainingMetadata: 111 | def __init__(self, 112 | training_enabled: bool, 113 | training_in_proto: bool, 114 | training_methods: Any): 115 | 116 | self.__training_enabled = training_enabled 117 | self.__training_in_proto = training_in_proto 118 | self.__training_methods = {} 119 | 120 | services_methods = dict(training_methods) 121 | for k, v in services_methods.items(): 122 | self.__training_methods[k] = [value.string_value for value in v.values] 123 | 124 | def __str__(self): 125 | return to_string(self) 126 | 127 | @property 128 | def training_enabled(self): 129 | return self.__training_enabled 130 | 131 | @property 132 | def training_in_proto(self): 133 | return self.__training_in_proto 134 | 135 | @property 136 | def training_methods(self): 137 | return self.__training_methods 138 | 139 | 140 | class MethodMetadata: 141 | def __init__(self, 142 | default_model_id: str, 143 | max_models_per_user: int, 144 | dataset_max_size_mb: int, 145 | dataset_max_count_files: int, 146 | dataset_max_size_single_file_mb: int, 147 | dataset_files_type: str, 148 | dataset_type: str, 149 | dataset_description: str): 150 | 151 | self.__default_model_id = default_model_id 152 | self.__max_models_per_user = max_models_per_user 153 | self.__dataset_max_size_mb = dataset_max_size_mb 154 | self.__dataset_max_count_files = dataset_max_count_files 155 | self.__dataset_max_size_single_file_mb = dataset_max_size_single_file_mb 156 | self.__dataset_files_type = dataset_files_type 157 | self.__dataset_type = dataset_type 158 | self.__dataset_description = dataset_description 159 | 160 | def __str__(self): 161 | return to_string(self) 162 | 163 | @property 164 | def default_model_id(self): 165 | return self.__default_model_id 166 | 167 | @property 168 | def max_models_per_user(self): 169 | return self.__max_models_per_user 170 | 171 | @property 172 | def dataset_max_size_mb(self): 173 | return self.__dataset_max_size_mb 174 | 175 | @property 176 | def dataset_max_count_files(self): 177 | return self.__dataset_max_count_files 178 | 179 | @property 180 | def dataset_max_size_single_file_mb(self): 181 | return self.__dataset_max_size_single_file_mb 182 | 183 | @property 184 | def dataset_files_type(self): 185 | return self.__dataset_files_type 186 | 187 | @property 188 | def dataset_type(self): 189 | return self.__dataset_type 190 | 191 | @property 192 | def dataset_description(self): 193 | return self.__dataset_description 194 | 195 | 196 | -------------------------------------------------------------------------------- /snet/sdk/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singnet/snet-sdk-python/00d73f37e33f12e5d85cd3a4162f2a4a658c1337/snet/sdk/utils/__init__.py -------------------------------------------------------------------------------- /snet/sdk/utils/call_utils.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import grpc 3 | 4 | 5 | class _ClientCallDetails( 6 | collections.namedtuple( 7 | '_ClientCallDetails', 8 | ('method', 'timeout', 'metadata', 'credentials')), 9 | grpc.ClientCallDetails): 10 | pass 11 | 12 | 13 | def create_intercept_call_func(get_metadata_func: callable, service_client) -> callable: 14 | def intercept_call(client_call_details, request_iterator, request_streaming, response_streaming): 15 | metadata = [] 16 | if client_call_details.metadata is not None: 17 | metadata = list(client_call_details.metadata) 18 | metadata.extend(get_metadata_func(service_client)) 19 | client_call_details = _ClientCallDetails( 20 | client_call_details.method, client_call_details.timeout, metadata, 21 | client_call_details.credentials) 22 | return client_call_details, request_iterator, None 23 | 24 | return intercept_call 25 | -------------------------------------------------------------------------------- /snet/sdk/utils/ipfs_utils.py: -------------------------------------------------------------------------------- 1 | """ Utilities related to ipfs """ 2 | import base58 3 | import ipfshttpclient 4 | import multihash 5 | 6 | 7 | def get_from_ipfs_and_checkhash(ipfs_client, ipfs_hash_base58, validate=True): 8 | """ 9 | Get file from IPFS. If validate is True, verify the integrity of the file using its hash. 10 | """ 11 | 12 | data = ipfs_client.cat(ipfs_hash_base58) 13 | 14 | if validate: 15 | block_data = ipfs_client.block.get(ipfs_hash_base58) 16 | 17 | # print(f"IPFS hash (Base58): {ipfs_hash_base58}") 18 | # print(f"Block data length: {len(block_data)}") 19 | 20 | # Decode Base58 bash to multihash 21 | try: 22 | mh = multihash.decode(ipfs_hash_base58.encode('ascii'), "base58") 23 | except Exception as e: 24 | raise ValueError(f"Invalid multihash for IPFS hash: {ipfs_hash_base58}. Error: {str(e)}") from e 25 | 26 | if not mh.verify(block_data): # Correctly using mh instance for verification 27 | raise Exception("IPFS hash mismatch with data") 28 | 29 | return data 30 | 31 | def get_ipfs_client(config): 32 | ipfs_endpoint = config.get_ipfs_endpoint() 33 | return ipfshttpclient.connect(ipfs_endpoint) 34 | 35 | -------------------------------------------------------------------------------- /snet/sdk/utils/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import subprocess 3 | import sys 4 | import importlib.resources 5 | from urllib.parse import urlparse 6 | from pathlib import Path, PurePath 7 | import os 8 | import tarfile 9 | import io 10 | 11 | import web3 12 | from eth_typing import BlockNumber 13 | from grpc_tools.protoc import main as protoc 14 | 15 | from snet import sdk 16 | 17 | RESOURCES_PATH = PurePath(os.path.dirname(sdk.__file__)).joinpath("resources") 18 | 19 | 20 | def safe_address_converter(a): 21 | if not web3.Web3.is_checksum_address(a): 22 | raise Exception("%s is not is not a valid Ethereum checksum address" % a) 23 | return a 24 | 25 | 26 | def type_converter(t): 27 | if t.endswith("[]"): 28 | return lambda x: list(map(type_converter(t.replace("[]", "")), json.loads(x))) 29 | else: 30 | if "int" in t: 31 | return lambda x: web3.Web3.to_int(text=x) 32 | elif "bytes32" in t: 33 | return lambda x: web3.Web3.to_bytes(text=x).ljust(32, b"\0") if not x.startswith( 34 | "0x") else web3.Web3.to_bytes(hexstr=x).ljust(32, b"\0") 35 | elif "byte" in t: 36 | return lambda x: web3.Web3.to_bytes(text=x) if not x.startswith("0x") else web3.Web3.to_bytes(hexstr=x) 37 | elif "address" in t: 38 | return safe_address_converter 39 | else: 40 | return str 41 | 42 | 43 | def bytes32_to_str(b): 44 | return b.rstrip(b"\0").decode("utf-8") 45 | 46 | 47 | def compile_proto( 48 | entry_path: Path, 49 | codegen_dir: Path, 50 | proto_file: str | None = None, 51 | target_language: str = "python", 52 | add_training: bool = False 53 | ) -> bool: 54 | try: 55 | if not os.path.exists(codegen_dir): 56 | os.makedirs(codegen_dir) 57 | proto_include = importlib.resources.files('grpc_tools') / '_proto' 58 | 59 | compiler_args = [ 60 | "-I{}".format(entry_path), 61 | "-I{}".format(proto_include) 62 | ] 63 | 64 | if add_training: 65 | training_include = RESOURCES_PATH.joinpath("proto", "training") 66 | compiler_args.append("-I{}".format(training_include)) 67 | 68 | if target_language == "python": 69 | compiler_args.insert(0, "protoc") 70 | compiler_args.append("--python_out={}".format(codegen_dir)) 71 | compiler_args.append("--grpc_python_out={}".format(codegen_dir)) 72 | compiler = protoc 73 | else: 74 | raise Exception("We only support python target language for proto compiling") 75 | 76 | if proto_file: 77 | compiler_args.append(str(proto_file)) 78 | else: 79 | compiler_args.extend([str(p) for p in entry_path.glob("**/*.proto")]) 80 | 81 | if add_training: 82 | compiler_args.append(str(training_include.joinpath("training.proto"))) 83 | 84 | if not compiler(compiler_args): 85 | return True 86 | else: 87 | return False 88 | 89 | except Exception as e: 90 | print(e) 91 | return False 92 | 93 | 94 | def is_valid_endpoint(url): 95 | """ 96 | Just ensures the url has a scheme (http/https), and a net location (IP or domain name). 97 | Can make more advanced or do on-network tests if needed, but this is really just to catch obvious errors. 98 | >>> is_valid_endpoint("https://34.216.72.29:6206") 99 | True 100 | >>> is_valid_endpoint("blahblah") 101 | False 102 | >>> is_valid_endpoint("blah://34.216.72.29") 103 | False 104 | >>> is_valid_endpoint("http://34.216.72.29:%%%") 105 | False 106 | >>> is_valid_endpoint("http://192.168.0.2:9999") 107 | True 108 | """ 109 | try: 110 | result = urlparse(url) 111 | if result.port: 112 | _port = int(result.port) 113 | return ( 114 | all([result.scheme, result.netloc]) and 115 | result.scheme in ['http', 'https'] 116 | ) 117 | except ValueError: 118 | return False 119 | 120 | 121 | def normalize_private_key(private_key): 122 | if private_key.startswith("0x"): 123 | private_key = bytes(bytearray.fromhex(private_key[2:])) 124 | else: 125 | private_key = bytes(bytearray.fromhex(private_key)) 126 | return private_key 127 | 128 | 129 | def get_address_from_private(private_key): 130 | return web3.Account.from_key(private_key).address 131 | 132 | 133 | def get_current_block_number() -> BlockNumber: 134 | return web3.Web3().eth.block_number 135 | 136 | 137 | class add_to_path: 138 | def __init__(self, path): 139 | self.path = path 140 | 141 | def __enter__(self): 142 | sys.path.insert(0, self.path) 143 | 144 | def __exit__(self, exc_type, exc_value, traceback): 145 | try: 146 | sys.path.remove(self.path) 147 | except ValueError: 148 | pass 149 | 150 | 151 | def find_file_by_keyword(directory, keyword, exclude=None): 152 | if exclude is None: 153 | exclude = [] 154 | for root, dirs, files in os.walk(directory): 155 | for file in files: 156 | if keyword in file and all(e not in file for e in exclude): 157 | return file 158 | 159 | 160 | def bytesuri_to_hash(s, to_decode=True): 161 | if to_decode: 162 | s = s.rstrip(b"\0").decode('ascii') 163 | if s.startswith("ipfs://"): 164 | return "ipfs", s[7:] 165 | elif s.startswith("filecoin://"): 166 | return "filecoin", s[11:] 167 | else: 168 | raise Exception("We support only ipfs and filecoin uri in Registry") 169 | 170 | 171 | def safe_extract_proto(spec_tar, protodir): 172 | """ 173 | Tar files might be dangerous (see https://bugs.python.org/issue21109, 174 | and https://docs.python.org/3/library/tarfile.html, TarFile.extractall warning) 175 | we extract only simple files 176 | """ 177 | with tarfile.open(fileobj=io.BytesIO(spec_tar)) as f: 178 | for m in f.getmembers(): 179 | if os.path.dirname(m.name) != "": 180 | raise Exception( 181 | "tarball has directories. We do not support it.") 182 | if not m.isfile(): 183 | raise Exception( 184 | "tarball contains %s which is not a file" % m.name) 185 | fullname = os.path.join(protodir, m.name) 186 | if os.path.exists(fullname): 187 | os.remove(fullname) 188 | print(f"{fullname} removed.") 189 | # now it is safe to call extractall 190 | f.extractall(path=protodir) 191 | -------------------------------------------------------------------------------- /testcases/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singnet/snet-sdk-python/00d73f37e33f12e5d85cd3a4162f2a4a658c1337/testcases/__init__.py -------------------------------------------------------------------------------- /testcases/functional_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singnet/snet-sdk-python/00d73f37e33f12e5d85cd3a4162f2a4a658c1337/testcases/functional_tests/__init__.py -------------------------------------------------------------------------------- /testcases/functional_tests/snetd.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "payment_channel_storage_server": { 3 | "enabled": true 4 | }, 5 | "SERVICE_ID": "test_service", 6 | "ORGANIZATION_ID": "test_org", 7 | "DAEMON_END_POINT": "localhost:5051", 8 | "ETHEREUM_JSON_RPC_ENDPOINT": "http://localhost:8545", 9 | "PASSTHROUGH_ENABLED": true, 10 | "PASSTHROUGH_ENDPOINT": "http://localhost:7003", 11 | "IPFS_END_POINT": "http://localhost:5002", 12 | "REGISTRY_ADDRESS_KEY": "0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2", 13 | "pvt_key_for_metering": "efed2ea91d5ace7f9f7bd91e21223cfded31a6e3f1a746bc52821659e0c94e17", 14 | "metering_end_point": "http://demo8325345.mockable.io", 15 | "free_call_signer_address": "0x7DF35C98f41F3Af0df1dc4c7F7D4C19a71Dd059F", 16 | "log": { 17 | "level": "debug", 18 | "output": { 19 | "type": "stdout" 20 | } 21 | }, 22 | "metering_enabled": false, 23 | "token_secret_key": "ABCDE", 24 | "token_expiry_in_seconds": 3600 25 | } -------------------------------------------------------------------------------- /testcases/functional_tests/test_prepaid_payment.py: -------------------------------------------------------------------------------- 1 | from snet import sdk 2 | import examples_service_pb2_grpc 3 | import examples_service_pb2 4 | 5 | 6 | def use_freecalls(service_client): 7 | service_call(service_client, a=1, b=2) 8 | service_call(service_client, a=1, b=2) 9 | 10 | 11 | def service_call(service_client, a, b): 12 | request = examples_service_pb2.Numbers(a=a, b=b) 13 | result = service_client.service.mul(request) 14 | assert result.value == a * b 15 | 16 | 17 | def make_cuncurrent_calls(service_client): 18 | service_call(service_client, a=1, b=2) 19 | service_call(service_client, a=1, b=2) 20 | 21 | 22 | def check_channel_status(service_client, last_signed_amount): 23 | service_client.load_open_channels() 24 | channels = service_client.update_channel_states() 25 | assert channels[0].channel_id == 0 26 | assert channels[0].state['last_signed_amount'] == last_signed_amount 27 | 28 | 29 | def test_sdk(): 30 | org_id = "test_org" 31 | service_id = "test_service" 32 | group_name = "default_group" 33 | 34 | config = { 35 | "private_key": "0xc71478a6d0fe44e763649de0a0deb5a080b788eefbbcf9c6f7aef0dd5dbd67e0", 36 | "eth_rpc_endpoint": "http://localhost:8545", 37 | "mpe_contract_address": "0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e", 38 | "registry_contract_address": "0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2", 39 | "token_contract_address": "0x6e5f20669177f5bdf3703ec5ea9c4d4fe3aabd14", 40 | "ipfs_rpc_endpoint": "http://localhost:5002", 41 | "free_call_auth_token-bin": "f2548d27ffd319b9c05918eeac15ebab934e5cfcd68e1ec3db2b927653892959012b48da17a7973d57f72fac3c1eccd97862a4fa953c3726da65dec42f5989ee1b", 42 | "free-call-token-expiry-block": 172800, 43 | "email": "test@test.com" 44 | } 45 | 46 | snet_sdk = sdk.SnetSDK(config) 47 | service_client = snet_sdk.create_service_client(org_id, service_id, examples_service_pb2_grpc.CalculatorStub, 48 | group_name, concurrent_calls=3) 49 | check_channel_status(service_client, 3000) 50 | make_cuncurrent_calls(service_client) 51 | check_channel_status(service_client, 6000) 52 | 53 | 54 | if __name__ == '__main__': 55 | test_sdk() 56 | -------------------------------------------------------------------------------- /testcases/functional_tests/test_sdk_client.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | 4 | from snet import sdk 5 | 6 | 7 | class TestSDKClient(unittest.TestCase): 8 | def setUp(self): 9 | self.service_client = get_test_service_data() 10 | channel = self.service_client.deposit_and_open_channel(123456, 33333) 11 | 12 | def test_call_to_service(self): 13 | result = self.service_client.call_rpc("mul", "Numbers", a=20, b=3) 14 | self.assertEqual(60.0, result.value) 15 | 16 | 17 | def get_test_service_data(): 18 | config = sdk.config.Config(private_key=os.environ['SNET_TEST_WALLET_PRIVATE_KEY'], 19 | eth_rpc_endpoint=f"https://sepolia.infura.io/v3/{os.environ['SNET_TEST_INFURA_KEY']}", 20 | concurrency=False, 21 | force_update=False) 22 | 23 | snet_sdk = sdk.SnetSDK(config) 24 | 25 | service_client = snet_sdk.create_service_client(org_id="26072b8b6a0e448180f8c0e702ab6d2f", 26 | service_id="Exampleservice", group_name="default_group") 27 | return service_client 28 | 29 | 30 | if __name__ == '__main__': 31 | unittest.main() 32 | -------------------------------------------------------------------------------- /testcases/utils/reset_environment.sh: -------------------------------------------------------------------------------- 1 | # This is a part of circleci functional tests 2 | # This script does following: 3 | # - restart ipfs 4 | # - restart ganache and remigrate platform-contracts 5 | # - set correct networks/*json for Registry and MultiPartyEscrow (but not for SingularityNetToken !) 6 | # - reset .snet configuration 7 | # - add snet-user to snet-cli with first ganache idenity 8 | 9 | if [ ! $1 = "--i-no-what-i-am-doing" ]; then 10 | echo "This script is intended to be run from circleci" 11 | exit 1 12 | fi 13 | 14 | cwd=$(pwd) 15 | 16 | # I. restart ipfs 17 | ipfs shutdown || echo "supress an error" 18 | 19 | rm -rf ~/.ipfs 20 | ipfs init 21 | ipfs bootstrap rm --all 22 | ipfs config Addresses.API /ip4/127.0.0.1/tcp/5002 23 | ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8081 24 | nohup ipfs daemon >ipfs.log 2>&1 & 25 | 26 | # II. restart ganache and remigrate platform-contracts 27 | killall node || echo "supress an error" 28 | cd ../platform-contracts 29 | nohup ./node_modules/.bin/ganache-cli --mnemonic 'gauge enact biology destroy normal tunnel slight slide wide sauce ladder produce' --networkId 829257324 >/dev/null & 30 | ./node_modules/.bin/truffle migrate --network local 31 | ###################### 32 | # III. remove old snet-cli configuration 33 | rm -rf ~/.snet 34 | 35 | # # IV. Configure SNET-CLI. 36 | 37 | # # set correct ipfs endpoint 38 | # # (the new new configuration file with default values will be created automatically) 39 | # snet set default_ipfs_endpoint http://localhost:5002 40 | 41 | # # Add local network and switch to it 42 | # snet network create local http://localhost:8545 43 | 44 | # # swith to local network 45 | # snet network local 46 | 47 | # # Configure contract addresses for local network (it will not be necessary for goerli or mainnet! ) 48 | # snet set current_singularitynettoken_at 0x6e5f20669177f5bdf3703ec5ea9c4d4fe3aabd14 49 | # snet set current_registry_at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 50 | # snet set current_multipartyescrow_at 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 51 | 52 | # # Create First identity (snet-user = first ganache). 53 | # # (snet will automatically swith to this new identity) 54 | # snet identity create snet-user rpc --network local 55 | snet identity create --private-key "$SNET_TEST_WALLET_PRIVATE_KEY" test key --network sepolia 56 | sed -i "s/$FORMER_SNET_TEST_INFURA_KEY/$SNET_TEST_INFURA_KEY/g" ~/.snet/config 57 | export PYTHONPATH=$cwd 58 | python3 $cwd"/packages/snet_cli/test/functional_tests/mint/mint.py" 59 | snet account deposit 10000000 -y -q 60 | # snet account balance 61 | ############ 62 | # service provider has --wallet-index==9 (0x52653A9091b5d5021bed06c5118D24b23620c529) 63 | # make two endpoints (both are actually valid) 64 | cd ../snet-sdk-python/testcases 65 | 66 | 67 | snet organization metadata-init test_org test_org organization 68 | snet organization add-group default_group 0x52653A9091b5d5021bed06c5118D24b23620c529 http://localhost:2379 69 | snet organization create test_org -y -q 70 | 71 | 72 | 73 | 74 | snet service metadata-init ./functional_tests/service_spec1/ ExampleService --group-name default_group --fixed-price 0.00001 --endpoints http://localhost:5051 75 | snet service metadata-set-free-calls default_group 2 76 | snet service metadata-set-freecall-signer-address default_group 0x7DF35C98f41F3Af0df1dc4c7F7D4C19a71Dd059F 77 | snet service publish test_org test_service -y -q 78 | 79 | snet organization print-metadata test_org test_org 80 | snet service print-metadata test_org test_service 81 | 82 | pwd 83 | cd ../example-service 84 | pip3 install -r requirements.txt 85 | sh buildproto.sh 86 | nohup python3 run_example_service.py --no-daemon & 87 | 88 | 89 | cd ../../snet-daemon/snet-daemon-v5.0.1-linux-amd64 90 | nohup ./snetd & 91 | 92 | #wait for daemon to come up 93 | sleep 20 94 | 95 | #cd ~/singnet/snet-cli 96 | 97 | -------------------------------------------------------------------------------- /testcases/utils/run_all_functional.sh: -------------------------------------------------------------------------------- 1 | #./testcases/utils/reset_environment.sh 2 | 3 | cd testcases/functional_tests 4 | python3 test_sdk_client.py 5 | 6 | #python3 test_prepaid_payment.py 7 | -------------------------------------------------------------------------------- /tests/unit_tests/test_account.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from unittest.mock import MagicMock, patch 4 | 5 | from dotenv import load_dotenv 6 | from web3 import Web3 7 | 8 | from snet.sdk.account import Account, TransactionError 9 | from snet.sdk.config import Config 10 | from snet.sdk.mpe.mpe_contract import MPEContract 11 | 12 | load_dotenv() 13 | 14 | 15 | class TestAccount(unittest.TestCase): 16 | @patch("snet.sdk.account.get_contract_object") 17 | def setUp(self, mock_get_contract_object): 18 | # Mock main fields 19 | self.mock_web3 = MagicMock(spec=Web3) 20 | self.mock_config = MagicMock(spec=Config) 21 | self.mock_mpe_contract = MagicMock(spec=MPEContract) 22 | 23 | # Mock additional fields 24 | self.mock_web3.eth = MagicMock() 25 | self.mock_web3.net = MagicMock() 26 | self.mock_mpe_contract.contract = MagicMock() 27 | 28 | # Config mock return values 29 | self.mock_config.get.side_effect = lambda key, default=None: { 30 | "private_key": os.getenv("PRIVATE_KEY"), 31 | "signer_private_key": None, 32 | "token_contract_address": None, 33 | }.get(key, default) 34 | 35 | # Mock token contract 36 | self.mock_token_contract = MagicMock() 37 | self.mock_get_contract_object = mock_get_contract_object 38 | self.mock_get_contract_object.return_value = self.mock_token_contract 39 | 40 | self.account = Account(self.mock_web3, self.mock_config, 41 | self.mock_mpe_contract) 42 | 43 | def test_get_nonce(self): 44 | for i in [4, 5]: 45 | self.mock_web3.eth.get_transaction_count.return_value = i 46 | self.account.nonce = 4 47 | nonce = self.account._get_nonce() 48 | self.assertEqual(nonce, 5) 49 | self.assertEqual(self.account.nonce, 5) 50 | 51 | def test_get_gas_price(self): 52 | # Test different gas price levels 53 | gas_price = 10000000000 54 | self.mock_web3.eth.gas_price = gas_price 55 | gas_price = self.account._get_gas_price() 56 | self.assertEqual(gas_price, int(gas_price + (gas_price * 1 / 3))) 57 | 58 | gas_price = 16000000000 59 | self.mock_web3.eth.gas_price = gas_price 60 | gas_price = self.account._get_gas_price() 61 | self.assertEqual(gas_price, int(gas_price + (gas_price * 1 / 5))) 62 | 63 | gas_price = 51200000000 64 | self.mock_web3.eth.gas_price = 51200000000 65 | gas_price = self.account._get_gas_price() 66 | self.assertEqual(gas_price, int(gas_price + 7000000000)) 67 | 68 | gas_price = 150000000001 69 | self.mock_web3.eth.gas_price = 150000000001 70 | gas_price = self.account._get_gas_price() 71 | self.assertEqual(gas_price, int(gas_price + (gas_price * 1 / 10))) 72 | 73 | # @patch("snet.sdk.web3.Web3.to_hex", side_effect=lambda x: "mock_txn_hash") 74 | # def test_send_signed_transaction(self, mock_to_hex): 75 | # # Mock contract function 76 | # mock_contract_fn = MagicMock() 77 | # mock_contract_fn.return_value.build_transaction.return_value = {"mock": "txn"} 78 | 79 | # # Test transaction sending 80 | # txn_hash = self.account._send_signed_transaction(mock_contract_fn) 81 | # self.assertEqual(txn_hash, "mock_txn_hash") 82 | # self.mock_web3.eth.account.sign_transaction.assert_called_once() 83 | # self.mock_web3.eth.send_raw_transaction.assert_called_once() 84 | 85 | def test_parse_receipt_success(self): 86 | # Mock receipt and event 87 | mock_receipt = MagicMock() 88 | mock_receipt.status = 1 89 | mock_event = MagicMock() 90 | mock_event.return_value.processReceipt.return_value = [ 91 | {"args": {"key": "value"}} 92 | ] 93 | 94 | result = self.account._parse_receipt(mock_receipt, mock_event) 95 | self.assertEqual(result, '{"key": "value"}') 96 | 97 | def test_parse_receipt_failure(self): 98 | # Mock a failing receipt 99 | mock_receipt = MagicMock() 100 | mock_receipt.status = 0 101 | 102 | with self.assertRaises(TransactionError) as context: 103 | self.account._parse_receipt(mock_receipt, None) 104 | 105 | self.assertEqual(str(context.exception), "Transaction failed") 106 | self.assertEqual(context.exception.receipt, mock_receipt) 107 | 108 | def test_escrow_balance(self): 109 | self.mock_mpe_contract.balance.return_value = 120000000 110 | balance = self.account.escrow_balance() 111 | self.assertIsInstance(balance, int) 112 | self.assertEqual(balance, 120000000) 113 | self.mock_mpe_contract.balance.assert_called_once_with( 114 | self.account.address 115 | ) 116 | 117 | def test_deposit_to_escrow_account(self): 118 | self.account.allowance = MagicMock(return_value=0) 119 | self.account.approve_transfer = MagicMock() 120 | self.mock_mpe_contract.deposit.return_value = "0x51ec7c89064d95416be4" 121 | 122 | result = self.account.deposit_to_escrow_account(100) 123 | self.account.approve_transfer.assert_called_once_with(100) 124 | self.mock_mpe_contract.deposit.assert_called_once_with(self.account, 125 | 100) 126 | self.assertEqual(result, "0x51ec7c89064d95416be4") 127 | 128 | def test_approve_transfer(self): 129 | self.mock_web3.eth.gas_price = 10000000000 130 | self.mock_web3.eth.get_transaction_count.return_value = 1 131 | result = self.account.approve_transfer(500) 132 | self.assertIsNotNone(result) 133 | self.mock_token_contract.functions.approve.assert_called_once_with( 134 | self.mock_mpe_contract.contract.address, 500 135 | ) 136 | # def test_approve_transfer(self): 137 | # self.account.send_transaction = MagicMock() 138 | # self.account.send_transaction.return_value = "TxReceipt" 139 | # result = self.account.approve_transfer(500) 140 | # self.assertEqual(result, "TxReceipt") 141 | 142 | def test_allowance(self): 143 | self.mock_token_contract.functions.allowance.return_value.call \ 144 | .return_value = 100 145 | allowance = self.account.allowance() 146 | self.assertEqual(allowance, 100) 147 | self.mock_token_contract.functions.allowance.assert_called_once_with( 148 | self.account.address, self.mock_mpe_contract.contract.address 149 | ) 150 | 151 | 152 | if __name__ == "__main__": 153 | unittest.main() 154 | -------------------------------------------------------------------------------- /tests/unit_tests/test_config.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from snet.sdk.config import Config 4 | 5 | 6 | class TestConfig(unittest.TestCase): 7 | def setUp(self): 8 | self.private_key = "test_private_key" 9 | self.eth_rpc_endpoint = "http://localhost:8545" 10 | self.wallet_index = 1 11 | self.ipfs_endpoint = "http://custom-ipfs-endpoint.io" 12 | self.mpe_contract_address = "0xMPEAddress" 13 | self.token_contract_address = "0xTokenAddress" 14 | self.registry_contract_address = "0xRegistryAddress" 15 | self.signer_private_key = "signer_key" 16 | 17 | def test_initialization(self): 18 | config = Config( 19 | private_key=self.private_key, 20 | eth_rpc_endpoint=self.eth_rpc_endpoint, 21 | wallet_index=self.wallet_index, 22 | ipfs_endpoint=self.ipfs_endpoint, 23 | concurrency=False, 24 | force_update=True, 25 | mpe_contract_address=self.mpe_contract_address, 26 | token_contract_address=self.token_contract_address, 27 | registry_contract_address=self.registry_contract_address, 28 | signer_private_key=self.signer_private_key 29 | ) 30 | 31 | self.assertEqual(config["private_key"], 32 | self.private_key) 33 | self.assertEqual(config["eth_rpc_endpoint"], 34 | self.eth_rpc_endpoint) 35 | self.assertEqual(config["wallet_index"], 36 | self.wallet_index) 37 | self.assertEqual(config["ipfs_endpoint"], 38 | self.ipfs_endpoint) 39 | self.assertEqual(config["mpe_contract_address"], 40 | self.mpe_contract_address) 41 | self.assertEqual(config["token_contract_address"], 42 | self.token_contract_address) 43 | self.assertEqual(config["registry_contract_address"], 44 | self.registry_contract_address) 45 | self.assertEqual(config["signer_private_key"], 46 | self.signer_private_key) 47 | self.assertEqual(config["lighthouse_token"], " ") 48 | self.assertFalse(config["concurrency"]) 49 | self.assertTrue(config["force_update"]) 50 | 51 | def test_default_values(self): 52 | config = Config( 53 | private_key=self.private_key, 54 | eth_rpc_endpoint=self.eth_rpc_endpoint 55 | ) 56 | 57 | self.assertEqual(config["wallet_index"], 0) 58 | self.assertEqual(config["ipfs_endpoint"], 59 | "/dns/ipfs.singularitynet.io/tcp/80/") 60 | self.assertTrue(config["concurrency"]) 61 | self.assertFalse(config["force_update"]) 62 | self.assertIsNone(config["mpe_contract_address"]) 63 | self.assertIsNone(config["token_contract_address"]) 64 | self.assertIsNone(config["registry_contract_address"]) 65 | self.assertIsNone(config["signer_private_key"]) 66 | 67 | def test_get_method(self): 68 | config = Config(private_key=self.private_key, 69 | eth_rpc_endpoint=self.eth_rpc_endpoint) 70 | 71 | self.assertEqual(config.get("private_key"), self.private_key) 72 | self.assertEqual(config.get("non_existent_key", 73 | "default_value"), "default_value") 74 | self.assertIsNone(config.get("non_existent_key")) 75 | 76 | def test_get_ipfs_endpoint(self): 77 | config = Config(private_key=self.private_key, 78 | eth_rpc_endpoint=self.eth_rpc_endpoint) 79 | self.assertEqual(config.get_ipfs_endpoint(), 80 | "/dns/ipfs.singularitynet.io/tcp/80/") 81 | 82 | config_with_custom_ipfs = Config( 83 | private_key=self.private_key, 84 | eth_rpc_endpoint=self.eth_rpc_endpoint, 85 | ipfs_endpoint=self.ipfs_endpoint 86 | ) 87 | self.assertEqual(config_with_custom_ipfs.get_ipfs_endpoint(), 88 | self.ipfs_endpoint) 89 | 90 | 91 | if __name__ == "__main__": 92 | unittest.main() 93 | -------------------------------------------------------------------------------- /tests/unit_tests/test_files.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singnet/snet-sdk-python/00d73f37e33f12e5d85cd3a4162f2a4a658c1337/tests/unit_tests/test_files.zip -------------------------------------------------------------------------------- /tests/unit_tests/test_training_v2.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import unittest 3 | from unittest.mock import patch, MagicMock 4 | 5 | from snet.sdk.training.responses import MethodMetadata 6 | from snet.sdk.training.training import Training 7 | from snet.sdk.service_client import ServiceClient 8 | from snet.sdk.training.exceptions import WrongDatasetException 9 | 10 | 11 | class TestTrainingV2(unittest.TestCase): 12 | def setUp(self): 13 | self.mock_service_client = MagicMock(spec=ServiceClient) 14 | self.training = Training(self.mock_service_client, training_added=True) 15 | self.file_path = os.path.join(os.path.dirname(__file__), "test_files.zip") 16 | self.get_metadata_path = "snet.sdk.training.training.Training.get_method_metadata" 17 | 18 | def test_check_dataset_positive(self): 19 | method_metadata = MethodMetadata("test", 20 | 5, 21 | 50, 22 | 10, 23 | 25, 24 | "jpg, png, wav", 25 | "zip", 26 | "test") 27 | 28 | with patch(self.get_metadata_path, return_value=method_metadata): 29 | try: 30 | self.training._check_dataset("test", self.file_path) 31 | except WrongDatasetException as e: 32 | print(e) 33 | assert False 34 | assert True 35 | 36 | def test_check_dataset_negative(self): 37 | method_metadata = MethodMetadata("test", 38 | 5, 39 | 10, 40 | 10, 41 | 5, 42 | "png, mp3, txt", 43 | "zip", 44 | "test") 45 | 46 | with patch(self.get_metadata_path, return_value=method_metadata): 47 | with self.assertRaises(WrongDatasetException): 48 | self.training._check_dataset("test", self.file_path) 49 | -------------------------------------------------------------------------------- /version.py: -------------------------------------------------------------------------------- 1 | __version__ = "4.0.0" 2 | --------------------------------------------------------------------------------