├── README.md ├── __init__.py ├── client.py ├── errors.py ├── models.py ├── rpc_details.py └── wallet.py /README.md: -------------------------------------------------------------------------------- 1 | # Python SDK for SUI 2 | 3 | This SDK is in alpha version, so there are possible errors. 4 | All functions can be viewed here: https://docs.sui.io/sui-jsonrpc 5 | 6 | ## Author's additions: 7 | 8 | - Function to get the balance 9 | - Function for mint a NFT (example/wizard) 10 | - Function to generate a random wallet 11 | - Function to request test tokens from a faucet 12 | - Function to get public key as b64 string 13 | - Function to get transaction ID 14 | - Function to auto sign and execute client transactions 15 | 16 | ## Installation 17 | 18 | Requires `Python 3.10-3.11` 19 | 20 | ``` 21 | pip install pysdk-sui 22 | ``` 23 | 24 | 25 | 26 | ## Example usage 27 | ``` 28 | from pysdk_sui import Client, Wallet, RpcDetails, WalletInfoParams, MoveCallParams, TransferObjectParams, GetTransactionsIDParams, MoveFunctionArgTypesParams 29 | 30 | faucet_url_ = 'https://faucet.testnet.sui.io/gas' 31 | rpc_url_ = 'https://fullnode.testnet.sui.io/' 32 | test_mnemonic = 'trip offer end cloth patrol core pioneer debate cigar swarm patch tattoo' 33 | 34 | 35 | client = Client(rpc_url=rpc_url_, faucet_url=faucet_url_, mnemonic=test_mnemonic) 36 | wallet = Wallet(rpc_url=rpc_url_, faucet_url=faucet_url_, mnemonic=test_mnemonic) 37 | rpc = RpcDetails(rpc_url=rpc_url_, faucet_url=faucet_url_, mnemonic=test_mnemonic) 38 | 39 | 40 | def example_wallet(): 41 | # get balance 42 | balance = wallet.get_balance() 43 | 44 | # generate wallet (return NamedTuple with mnemonic, private key, public key and address) 45 | wallet_data = wallet.generate_wallet() 46 | 47 | # get my wallet info (return NamedTuple with mnemonic, private key, public key and address) 48 | my_wallet_info = wallet.get_wallet_info() 49 | 50 | # get another wallet info (return NamedTuple with mnemonic, private key, public key and address) 51 | another_wallet_info = wallet.get_another_wallet_info( 52 | WalletInfoParams( 53 | 'params' 54 | )) 55 | 56 | # request test tokens from faucet 57 | response_data = wallet.request_tokens_from_faucet() 58 | 59 | # get public key as b64 string 60 | public_b64_key = wallet.get_public_key_as_b64_string() 61 | 62 | '''ANOTHER FUNCTION USING SAME''' 63 | 64 | 65 | def example_client(): 66 | # get example NFT 67 | response_data = client.mint_example_nft() 68 | 69 | # move call 70 | response_data = client.move_call( 71 | MoveCallParams( 72 | 'params' 73 | )) 74 | 75 | # transfer object 76 | response_data = client.transfer_object( 77 | TransferObjectParams( 78 | 'params' 79 | )) 80 | 81 | '''ANOTHER FUNCTION USING SAME''' 82 | 83 | 84 | def example_rpc(): 85 | # get transaction ID (digest) 86 | transaction_id = rpc.get_transaction_id( 87 | GetTransactionsIDParams( 88 | params 89 | )) 90 | 91 | # get move function arg types 92 | data = rpc.get_move_function_arg_types( 93 | MoveFunctionArgTypesParams( 94 | params 95 | )) 96 | 97 | '''ANOTHER FUNCTION USING SAME''' 98 | 99 | 100 | ``` 101 | 102 | >GOOD LUCK :D 103 | 104 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .client import Client 2 | from .wallet import Wallet 3 | from .rpc_details import RpcDetails 4 | from .models import SignatureScheme, ExecuteType, WalletInfo, WalletInfoParams, GetPublicKeyAsb64StringParams, \ 5 | DryRunTransactionParams, GetTransactionsIDParams, GetObjectsOwnedByAddressParams, GetObjectsOwnedByObjectParams, \ 6 | GetNormalizedMoveFunctionParams, GetNormalizedMoveModuleParams, GetNormalizedMoveModulesByPackageParams, \ 7 | GetNormalizedMoveStructParams, GetObjectParams, MergeCoinsParams, PayParams, PayAllParams, PublishParams, \ 8 | SplitCoinParams, SplitCoinEqualParams, MoveCallParams, TransferObjectParams, TransferSuiParams, \ 9 | BatchTransactionParams, MoveFunctionArgTypesParams, GetRawObjectParams, GetTransactionsParams, \ 10 | GetTransactionParams, GetEventsByTransactionParams, GetTotalTransactionsNumberInRangeParams 11 | -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import uuid 3 | import bip_utils 4 | import hashlib 5 | import nacl 6 | import requests 7 | import pyuseragents 8 | 9 | from typing import Optional 10 | from mnemonic import Mnemonic 11 | from .models import SignatureScheme, TransferSuiParams, TransferObjectParams, MoveCallParams, ExecuteType, \ 12 | BatchTransactionParams, DryRunTransactionParams, MergeCoinsParams, PayParams, PayAllParams, PublishParams, \ 13 | SplitCoinParams, SplitCoinEqualParams 14 | 15 | 16 | mnemo = Mnemonic("english") 17 | 18 | 19 | class Client: 20 | def __init__(self, faucet_url: str, rpc_url: str, mnemonic: str = True, 21 | derivation_path="m/44'/784'/0'/0'/0'"): 22 | 23 | self.faucet_url = faucet_url 24 | self.rpc_url = rpc_url 25 | self.derivation_path = derivation_path 26 | 27 | self.session = requests.Session() 28 | self.headers = { 29 | 'authority': 'fullnode.testnet.sui.io', 30 | 'accept': '*/*', 31 | 'accept-language': 'uk,en;q=0.9,en-GB;q=0.8,en-US;q=0.7', 32 | 'origin': 'chrome-extension://opcgpfmipidbgpenhmajoajpbobppdil', 33 | 'user-agent': pyuseragents.random(), 34 | } 35 | 36 | if mnemonic: 37 | 38 | self.mnemonic = mnemonic 39 | 40 | self.bip39_seed = bip_utils.Bip39SeedGenerator(self.mnemonic).Generate() 41 | self.bip32_ctx = bip_utils.Bip32Slip10Ed25519.FromSeed(self.bip39_seed) 42 | self.bip32_der_ctx = self.bip32_ctx.DerivePath(derivation_path) 43 | 44 | self.private_key: bytes = self.bip32_der_ctx.PrivateKey().Raw().ToBytes() 45 | self.public_key: bytes = self.bip32_der_ctx.PublicKey().RawCompressed().ToBytes() 46 | 47 | 48 | def get_address(self) -> Optional[str]: 49 | return "0x" + hashlib.sha3_256(self.bip32_der_ctx.PublicKey().RawCompressed().ToBytes()).digest().hex()[:40] 50 | 51 | def get_public_key_as_b64_string(self) -> Optional[str]: 52 | return base64.b64encode(self.public_key[1:]).decode() 53 | 54 | 55 | def batch_transaction(self, tx: BatchTransactionParams): 56 | response = self.send_request_to_rpc( 57 | method="sui_batchTransaction", 58 | params=[ 59 | self.get_address(), 60 | tx.single_transaction_params, 61 | tx.gas_budget, 62 | tx.gas, 63 | tx.gas_budget 64 | ]) 65 | 66 | tx_bytes = base64.b64decode(str(response['result']['txBytes'])) 67 | return self.sign_and_execute_transaction(tx_bytes) 68 | 69 | 70 | def dryrun_transaction(self, tx: DryRunTransactionParams): 71 | return self.send_request_to_rpc( 72 | method='sui_dryRunTransaction', 73 | params=[ 74 | tx.tx_bytes 75 | ] 76 | ) 77 | 78 | 79 | def mint_example_nft(self) -> Optional[dict]: 80 | response = self.send_request_to_rpc( 81 | method='sui_moveCall', 82 | params=[ 83 | self.get_address()[2:], 84 | '0x2', 85 | 'devnet_nft', 86 | 'mint', 87 | [], 88 | ["Example NFT", "An NFT created by Sui Wallet", 89 | "ipfs://QmZPWWy5Si54R3d26toaqRiqvCH7HkGdXkxwUgCm2oKKM2?filename=img-sq-01.png"], 90 | None, 91 | 10000, 92 | ]) 93 | 94 | tx_bytes = base64.b64decode(str(response['result']['txBytes'])) 95 | return self.sign_and_execute_transaction(tx_bytes) 96 | 97 | def mint_wizard_nft(self) -> Optional[dict]: 98 | response = self.send_request_to_rpc( 99 | method='sui_moveCall', 100 | params=[ 101 | self.get_address()[2:], 102 | '0x2', 103 | 'devnet_nft', 104 | 'mint', 105 | [], 106 | ["Wizard Land", "Expanding The Magic Land", 107 | "https://gateway.pinata.cloud/ipfs/QmYfw8RbtdjPAF3LrC6S3wGVwWgn6QKq4LGS4HFS55adU2?w=800&h=450&c=crop"], 108 | None, 109 | 10000, 110 | ]) 111 | 112 | tx_bytes = base64.b64decode(str(response['result']['txBytes'])) 113 | return self.sign_and_execute_transaction(tx_bytes) 114 | 115 | 116 | def move_call(self, tx: MoveCallParams): 117 | response = self.send_request_to_rpc( 118 | method="sui_moveCall", 119 | params=[ 120 | self.get_address(), 121 | tx.package_object_id, 122 | tx.module, 123 | tx.function, 124 | tx.type_arguments, 125 | tx.arguments, 126 | tx.gas_payment, 127 | tx.gas_budget, 128 | ]) 129 | 130 | tx_bytes = base64.b64decode(str(response['result']['txBytes'])) 131 | return self.sign_and_execute_transaction(tx_bytes) 132 | 133 | 134 | 135 | 136 | def transfer_object(self, tx: TransferObjectParams): 137 | response = self.send_request_to_rpc( 138 | method="sui_transferObject", 139 | params=[ 140 | self.get_address(), 141 | tx.object_id, 142 | tx.gas_payment, 143 | tx.gas_budget, 144 | tx.recipient 145 | ]) 146 | 147 | tx_bytes = base64.b64decode(str(response['result']['txBytes'])) 148 | return self.sign_and_execute_transaction(tx_bytes) 149 | 150 | 151 | def transfer_sui(self, tx: TransferSuiParams): 152 | response = self.send_request_to_rpc( 153 | method="sui_transferObject", 154 | params=[ 155 | self.get_address(), 156 | tx.sui_object_id, 157 | tx.gas_budget, 158 | tx.recipient, 159 | tx.amount 160 | ]) 161 | 162 | tx_bytes = base64.b64decode(str(response['result']['txBytes'])) 163 | return self.sign_and_execute_transaction(tx_bytes) 164 | 165 | 166 | def merge_coins(self, tx: MergeCoinsParams): 167 | response = self.send_request_to_rpc( 168 | method='sui_mergeCoins', 169 | params=[ 170 | self.get_address(), 171 | tx.primary_coin, 172 | tx.coin_to_merge, 173 | tx.gas, 174 | tx.gas_budget 175 | ]) 176 | 177 | tx_bytes = base64.b64decode(str(response['result']['txBytes'])) 178 | return self.sign_and_execute_transaction(tx_bytes) 179 | 180 | def pay_sui(self, tx: PayParams): 181 | response = self.send_request_to_rpc( 182 | method='sui_pay', 183 | params=[ 184 | self.get_address(), 185 | tx.input_coins, 186 | tx.recipients, 187 | tx.amounts, 188 | tx.gas, 189 | tx.gas_budget 190 | ] 191 | ) 192 | 193 | tx_bytes = base64.b64decode(str(response['result']['txBytes'])) 194 | return self.sign_and_execute_transaction(tx_bytes) 195 | 196 | 197 | def pay_all_sui(self, tx: PayAllParams): 198 | response = self.send_request_to_rpc( 199 | method='sui_payAllSui', 200 | params=[ 201 | self.get_address(), 202 | tx.input_coins, 203 | tx.recipient, 204 | tx.gas_budget 205 | ] 206 | ) 207 | 208 | tx_bytes = base64.b64decode(str(response['result']['txBytes'])) 209 | return self.sign_and_execute_transaction(tx_bytes) 210 | 211 | def publish(self, tx: PublishParams): 212 | response = self.send_request_to_rpc( 213 | method='sui_publish', 214 | params=[ 215 | self.get_address(), 216 | tx.compiled_modules, 217 | tx.gas, 218 | tx.gas_budget 219 | ] 220 | ) 221 | 222 | tx_bytes = base64.b64decode(str(response['result']['txBytes'])) 223 | return self.sign_and_execute_transaction(tx_bytes) 224 | 225 | def split_coin(self, tx: SplitCoinParams): 226 | response = self.send_request_to_rpc( 227 | method='sui_splitCoin', 228 | params=[ 229 | self.get_address(), 230 | tx.coin_object_id, 231 | tx.split_amounts, 232 | tx.gas, 233 | tx.gas_budget 234 | ] 235 | ) 236 | 237 | tx_bytes = base64.b64decode(str(response['result']['txBytes'])) 238 | return self.sign_and_execute_transaction(tx_bytes) 239 | 240 | def split_coin_equal(self, tx: SplitCoinEqualParams): 241 | response = self.send_request_to_rpc( 242 | method='sui_splitCoinEqual', 243 | params=[ 244 | self.get_address(), 245 | tx.coin_object_id, 246 | tx.split_count, 247 | tx.gas, 248 | tx.gas_budget 249 | ] 250 | ) 251 | 252 | tx_bytes = base64.b64decode(str(response['result']['txBytes'])) 253 | return self.sign_and_execute_transaction(tx_bytes) 254 | 255 | 256 | def sign_data(self, data: bytes) -> Optional[bytes]: 257 | return nacl.signing.SigningKey(self.private_key).sign(data)[:64] 258 | 259 | 260 | def sign_and_execute_transaction(self, tx_bytes: bytes) -> Optional[dict]: 261 | signature_bytes = self.sign_data(tx_bytes) 262 | 263 | x_bytes_b64_encoded = base64.b64encode(tx_bytes).decode() 264 | signature_b64_encoded = base64.b64encode(signature_bytes).decode() 265 | pubkey_b64_encoded = self.get_public_key_as_b64_string() 266 | signature_scheme = SignatureScheme.ED25519 267 | execute_type = ExecuteType.WaitForLocalExecution 268 | 269 | return self.send_request_to_rpc(method='sui_executeTransaction', params=[ 270 | x_bytes_b64_encoded, 271 | signature_scheme, 272 | signature_b64_encoded, 273 | pubkey_b64_encoded, 274 | execute_type 275 | ]) 276 | 277 | 278 | def send_request_to_rpc( 279 | self, 280 | method: str, 281 | params: list = None, 282 | request_id: str = None 283 | ) -> Optional[dict]: 284 | 285 | response = requests.post( 286 | self.rpc_url, json={ 287 | "jsonrpc": "2.0", 288 | "method": method, 289 | "params": params or [], 290 | "id": request_id or str(uuid.uuid4()), 291 | }) 292 | 293 | if response.status_code <= 201: 294 | return response.json() 295 | else: 296 | raise Exception( 297 | f'Failed to send transaction | Response status: {response.status_code} | Details: {response.text}') 298 | -------------------------------------------------------------------------------- /errors.py: -------------------------------------------------------------------------------- 1 | class SuiError: 2 | def __init__(self, error_number: int): 3 | self.error_number = error_number 4 | 5 | if self.error_number == -32000: 6 | raise Exception('Not enough money to pay for gas') 7 | -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union, Any, List, NamedTuple 2 | 3 | 4 | class SignatureScheme: 5 | ED25519 = 'ED25519' 6 | Secp256k1 = 'Secp256k1' 7 | 8 | 9 | class ExecuteType: 10 | ImmediateReturn = "ImmediateReturn" 11 | WaitForTxCert = "WaitForTxCert" 12 | WaitForEffectsCert = "WaitForEffectsCert" 13 | WaitForLocalExecution = "WaitForLocalExecution" 14 | 15 | 16 | class WalletInfo(NamedTuple): 17 | mnemonic: str 18 | private_key: bytes 19 | public_key: bytes 20 | address: str 21 | 22 | 23 | class WalletInfoParams: 24 | def __init__( 25 | self, 26 | mnemonic: str 27 | ): 28 | self.mnemonic = mnemonic 29 | 30 | 31 | class GetPublicKeyAsb64StringParams: 32 | def __init__( 33 | self, 34 | public_key: bytes 35 | ): 36 | self.public_key = public_key 37 | 38 | 39 | class DryRunTransactionParams: 40 | def __init__( 41 | self, 42 | tx_bytes: str 43 | ): 44 | self.tx_bytes = tx_bytes 45 | 46 | 47 | 48 | class GetTransactionsIDParams: 49 | def __init__( 50 | self, 51 | response: dict 52 | ): 53 | self.response = response 54 | 55 | 56 | class GetObjectsOwnedByAddressParams: 57 | def __init__( 58 | self, 59 | address: str 60 | ): 61 | self.address = address 62 | 63 | 64 | class GetObjectsOwnedByObjectParams: 65 | def __init__( 66 | self, 67 | object_id: str 68 | ): 69 | self.object_id = object_id 70 | 71 | 72 | class GetNormalizedMoveFunctionParams: 73 | def __init__( 74 | self, 75 | package: str, 76 | module_name: str, 77 | function_name: str 78 | ): 79 | self.package = package 80 | self.module_name = module_name 81 | self.function_name = function_name 82 | 83 | 84 | class GetNormalizedMoveModuleParams: 85 | def __init__( 86 | self, 87 | package: str, 88 | module_name: str 89 | ): 90 | self.package = package 91 | self.module_name = module_name 92 | 93 | 94 | class GetNormalizedMoveModulesByPackageParams: 95 | def __init__( 96 | self, 97 | package: str 98 | ): 99 | self.package = package 100 | 101 | 102 | class GetNormalizedMoveStructParams: 103 | def __init__( 104 | self, 105 | package: str, 106 | module_name: str, 107 | struct_name: str 108 | ): 109 | self.package = package 110 | self.module_name = module_name 111 | self.struct_name = struct_name 112 | 113 | 114 | class GetObjectParams: 115 | def __init__( 116 | self, 117 | object_id: str 118 | ): 119 | self.object_id = object_id 120 | 121 | 122 | class MergeCoinsParams: 123 | def __init__( 124 | self, 125 | primary_coin: str, 126 | coin_to_merge: str, 127 | gas: str, 128 | gas_budget: int 129 | ): 130 | self.primary_coin = primary_coin 131 | self.coin_to_merge = coin_to_merge 132 | self.gas = gas 133 | self.gas_budget = gas_budget 134 | 135 | 136 | class PayParams: 137 | def __init__( 138 | self, 139 | input_coins: List[str] or str, 140 | recipients: List[str] or str, 141 | amounts: List[int] or int, 142 | gas: str, 143 | gas_budget: int 144 | ): 145 | self.input_coins = input_coins 146 | self.recipients = recipients 147 | self.amounts = amounts 148 | self.gas = gas 149 | self.gas_budget = gas_budget 150 | 151 | 152 | class PayAllParams: 153 | def __init__( 154 | self, 155 | input_coins: List[str] or str, 156 | recipient: str, 157 | gas_budget: int 158 | ): 159 | self.input_coins = input_coins 160 | self.recipient = recipient 161 | self.gas_budget = gas_budget 162 | 163 | 164 | class PublishParams: 165 | def __init__( 166 | self, 167 | compiled_modules: str, 168 | gas: str, 169 | gas_budget: int 170 | ): 171 | self.compiled_modules = compiled_modules 172 | self.gas = gas, 173 | self.gas_budget = gas_budget 174 | 175 | 176 | class SplitCoinParams: 177 | def __init__( 178 | self, 179 | coin_object_id: str, 180 | split_amounts: int, 181 | gas: str, 182 | gas_budget: int 183 | ): 184 | self.coin_object_id = coin_object_id 185 | self.split_amounts = split_amounts 186 | self.gas = gas, 187 | self.gas_budget = gas_budget 188 | 189 | 190 | class SplitCoinEqualParams: 191 | def __init__( 192 | self, 193 | coin_object_id: str, 194 | split_count: int, 195 | gas: str, 196 | gas_budget: int 197 | ): 198 | self.coin_object_id = coin_object_id 199 | self.split_count = split_count 200 | self.gas = gas, 201 | self.gas_budget = gas_budget 202 | 203 | 204 | class MoveCallParams: 205 | def __init__( 206 | self, 207 | package_object_id: str, 208 | module: str, 209 | function: str, 210 | type_arguments: Union[List[str], List[Any]], 211 | arguments: List[Union[bool, int, str, List[Any]]], 212 | gas_budget: int, 213 | gas_payment: Optional[str] = None 214 | ): 215 | self.package_object_id = package_object_id 216 | self.module = module 217 | self.function = function 218 | self.type_arguments = type_arguments 219 | self.arguments = arguments 220 | self.gas_budget = gas_budget 221 | self.gas_payment = gas_payment 222 | 223 | 224 | class TransferObjectParams: 225 | def __init__( 226 | self, 227 | object_id: str, 228 | recipient: str, 229 | gas_budget: int, 230 | gas_payment: Optional[str] = None 231 | ): 232 | self.object_id = object_id 233 | self.gas_payment = gas_payment 234 | self.gas_budget = gas_budget 235 | self.recipient = recipient 236 | 237 | 238 | class TransferSuiParams: 239 | def __init__( 240 | self, 241 | sui_object_id: str, 242 | recipient: str, 243 | gas_budget: int, 244 | amount: Optional[int] = None 245 | ): 246 | self.sui_object_id = sui_object_id 247 | self.recipient = recipient 248 | self.gas_budget = gas_budget 249 | self.amount = amount 250 | 251 | 252 | class BatchTransactionParams: 253 | def __init__( 254 | self, 255 | single_transaction_params: list, 256 | gas: str, 257 | gas_budget: int 258 | ): 259 | self.single_transaction_params = single_transaction_params 260 | self.gas = gas 261 | self.gas_budget = gas_budget 262 | 263 | 264 | class MoveFunctionArgTypesParams: 265 | def __init__( 266 | self, 267 | package: str, 268 | module: str, 269 | function: str 270 | ): 271 | self.package = package 272 | self.module = module 273 | self.function = function 274 | 275 | 276 | class GetRawObjectParams: 277 | def __init__( 278 | self, 279 | object_id: str 280 | ): 281 | self.object_id = object_id 282 | 283 | 284 | class GetTransactionsParams: 285 | def __init__( 286 | self, 287 | query: str, 288 | cursor: str, 289 | limit: int, 290 | descending_order: bool = False 291 | ): 292 | self.query = query 293 | self.cursor = cursor 294 | self.limit = limit 295 | self.descending_order = descending_order 296 | 297 | 298 | 299 | class GetTransactionParams: 300 | def __init__( 301 | self, 302 | digest: str 303 | ): 304 | self.digest = digest 305 | 306 | 307 | 308 | class GetEventsByTransactionParams: 309 | def __init__( 310 | self, 311 | query: str, 312 | cursor: str, 313 | limit: int 314 | ): 315 | self.query = query 316 | self.cursor = cursor 317 | self.limit = limit 318 | 319 | 320 | class GetTotalTransactionsNumberInRangeParams: 321 | def __init__( 322 | self, 323 | start_of_range: int, 324 | end_of_range: int 325 | ): 326 | self.start_of_range = start_of_range 327 | self.end_of_range = end_of_range 328 | -------------------------------------------------------------------------------- /rpc_details.py: -------------------------------------------------------------------------------- 1 | from .client import Client 2 | from .models import MoveFunctionArgTypesParams, GetTransactionsParams, GetTransactionsIDParams, \ 3 | GetObjectsOwnedByAddressParams, GetObjectsOwnedByObjectParams, GetObjectParams, GetTransactionParams, \ 4 | GetEventsByTransactionParams, GetTotalTransactionsNumberInRangeParams, GetNormalizedMoveFunctionParams, \ 5 | GetNormalizedMoveModuleParams, GetNormalizedMoveModulesByPackageParams, GetNormalizedMoveStructParams, \ 6 | GetRawObjectParams 7 | 8 | 9 | 10 | 11 | 12 | class RpcDetails(Client): 13 | 14 | @staticmethod 15 | def get_transaction_id(tx: GetTransactionsIDParams) -> str: 16 | return tx.response['result']['EffectsCert']['certificate']['transactionDigest'] 17 | 18 | def get_rpc_version(self): 19 | return self.send_request_to_rpc( 20 | method="rpc.discover" 21 | ) 22 | 23 | 24 | def get_move_function_arg_types(self, tx: MoveFunctionArgTypesParams): 25 | return self.send_request_to_rpc( 26 | method="sui_getMoveFunctionArgTypes", 27 | params=[ 28 | tx.package, 29 | tx.module, 30 | tx.function 31 | ]) 32 | 33 | def get_normalized_move_function(self, tx: GetNormalizedMoveFunctionParams): 34 | return self.send_request_to_rpc( 35 | method='sui_getNormalizedMoveFunction', 36 | params=[ 37 | tx.package, 38 | tx.module_name, 39 | tx.function_name 40 | ] 41 | ) 42 | 43 | def get_normalized_move_module(self, tx: GetNormalizedMoveModuleParams): 44 | return self.send_request_to_rpc( 45 | method='sui_getNormalizedMoveModule', 46 | params=[ 47 | tx.package, 48 | tx.module_name 49 | ] 50 | ) 51 | 52 | 53 | def get_normalized_move_modules_by_package(self, tx: GetNormalizedMoveModulesByPackageParams): 54 | return self.send_request_to_rpc( 55 | method='sui_getNormalizedMoveModulesByPackage', 56 | params=[ 57 | tx.package 58 | ] 59 | ) 60 | 61 | def get_normalized_move_struct(self, tx: GetNormalizedMoveStructParams): 62 | return self.send_request_to_rpc( 63 | method='sui_getNormalizedMoveStruct', 64 | params=[ 65 | tx.package, 66 | tx.module_name, 67 | tx.struct_name 68 | ] 69 | ) 70 | 71 | def get_object(self, tx: GetObjectParams) -> dict: 72 | return self.send_request_to_rpc( 73 | method="sui_getObject", 74 | params=[ 75 | tx.object_id 76 | ]) 77 | 78 | def get_objects_owned_by_address(self, tx: GetObjectsOwnedByAddressParams) -> dict: 79 | return self.send_request_to_rpc( 80 | method="sui_getObjectsOwnedByAddress", 81 | params=[ 82 | tx.address 83 | ]) 84 | 85 | def get_objects_owned_by_object(self, tx: GetObjectsOwnedByObjectParams) -> dict: 86 | return self.send_request_to_rpc( 87 | method="sui_getObjectsOwnedByObject", 88 | params=[ 89 | tx.object_id 90 | ]) 91 | 92 | def get_raw_object(self, tx: GetRawObjectParams): 93 | return self.send_request_to_rpc( 94 | method='sui_getRawObject', 95 | params=[ 96 | tx.object_id 97 | ] 98 | ) 99 | 100 | 101 | def get_transactions(self, tx: GetTransactionsParams) -> dict: 102 | return self.send_request_to_rpc( 103 | method="sui_getTransactions", 104 | params=[ 105 | tx.query, 106 | tx.cursor, 107 | tx.limit, 108 | tx.descending_order 109 | ]) 110 | 111 | 112 | def get_transaction(self, tx: GetTransactionParams) -> dict: 113 | return self.send_request_to_rpc( 114 | method="sui_getTransaction", 115 | params=[ 116 | tx.digest 117 | ]) 118 | 119 | def get_events_by_transaction(self, tx: GetEventsByTransactionParams): 120 | return self.send_request_to_rpc( 121 | method="sui_getEventsByTransaction", 122 | params=[ 123 | tx.query, 124 | tx.cursor, 125 | tx.limit 126 | ]) 127 | 128 | def get_total_transaction_number(self) -> dict: 129 | return self.send_request_to_rpc( 130 | method="sui_getTotalTransactionNumber" 131 | ) 132 | 133 | def get_total_transaction_in_range(self, tx: GetTotalTransactionsNumberInRangeParams) -> dict: 134 | return self.send_request_to_rpc( 135 | method="sui_getTransactionsInRange", 136 | params=[ 137 | tx.start_of_range, 138 | tx.end_of_range 139 | ]) 140 | -------------------------------------------------------------------------------- /wallet.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import hashlib 3 | import bip_utils 4 | 5 | from typing import Optional 6 | from .client import Client 7 | from .errors import SuiError 8 | from .models import WalletInfo, WalletInfoParams 9 | 10 | 11 | class Wallet(Client): 12 | 13 | def generate_wallet(self) -> WalletInfo: 14 | mnemonic = bip_utils.Bip39MnemonicGenerator().FromWordsNumber(bip_utils.Bip39WordsNum.WORDS_NUM_12).ToStr() 15 | 16 | bip39_seed = bip_utils.Bip39SeedGenerator(mnemonic).Generate() 17 | bip32_ctx = bip_utils.Bip32Slip10Ed25519.FromSeed(bip39_seed) 18 | bip32_der_ctx = bip32_ctx.DerivePath(self.derivation_path) 19 | 20 | private_key: bytes = bip32_der_ctx.PrivateKey().Raw().ToBytes() 21 | public_key: bytes = bip32_der_ctx.PublicKey().RawCompressed().ToBytes() 22 | address: str = "0x" + hashlib.sha3_256(bip32_der_ctx.PublicKey().RawCompressed().ToBytes()).digest().hex()[:40] 23 | 24 | return WalletInfo(mnemonic, private_key, public_key, address) 25 | 26 | 27 | def get_wallet_info(self) -> WalletInfo: 28 | return WalletInfo(self.mnemonic, self.private_key, self.public_key, self.get_address()) 29 | 30 | 31 | def get_another_wallet_info(self, tx: WalletInfoParams) -> WalletInfo: 32 | bip39_seed = bip_utils.Bip39SeedGenerator(tx.mnemonic).Generate() 33 | bip32_ctx = bip_utils.Bip32Slip10Ed25519.FromSeed(bip39_seed) 34 | bip32_der_ctx = bip32_ctx.DerivePath(self.derivation_path) 35 | 36 | private_key: bytes = bip32_der_ctx.PrivateKey().Raw().ToBytes() 37 | public_key: bytes = bip32_der_ctx.PublicKey().RawCompressed().ToBytes() 38 | address: str = "0x" + hashlib.sha3_256(bip32_der_ctx.PublicKey().RawCompressed().ToBytes()).digest().hex()[:40] 39 | 40 | return WalletInfo(tx.mnemonic, private_key, public_key, address) 41 | 42 | 43 | 44 | def get_address(self) -> Optional[str]: 45 | return "0x" + hashlib.sha3_256(self.bip32_der_ctx.PublicKey().RawCompressed().ToBytes()).digest().hex()[:40] 46 | 47 | 48 | def get_balance(self, balance=0) -> Optional[float]: 49 | 50 | response = self.send_request_to_rpc( 51 | method='sui_getObjectsOwnedByAddress', 52 | params=[ 53 | self.get_address() 54 | ]) 55 | 56 | for el in response['result']: 57 | object_id = el['objectId'] 58 | 59 | object_data = self.send_request_to_rpc( 60 | method='sui_getObject', 61 | params=[ 62 | str(object_id) 63 | ]) 64 | 65 | try: 66 | balance += int(object_data['result']['details']['data']['fields']['balance']) 67 | except KeyError: 68 | continue 69 | 70 | else: 71 | return balance / 1000000000 72 | 73 | 74 | def request_tokens_from_faucet(self) -> Optional[dict]: 75 | response = self.session.post( 76 | self.faucet_url, 77 | json={ 78 | "FixedAmountRequest": {"recipient": self.get_address()} 79 | }) 80 | 81 | if response.status_code <= 201: 82 | return response.json() 83 | else: 84 | raise Exception(f'Failed to get tokens | Response status: {response.status_code} | Details: {response.text}') 85 | 86 | 87 | def get_public_key_as_b64_string(self) -> Optional[str]: 88 | return base64.b64encode(self.public_key[1:]).decode() 89 | 90 | --------------------------------------------------------------------------------