def withdraw(priv, pub, to_address, amount): substrate = SubstrateInterface(url='wss://rpc.polkadot.io', ss58_format=0, type_registry_preset='polkadot', use_remote_preset=True) call = substrate.compose_call( call_module='Balances', call_function='transfer', call_params={'dest': to_address, 'value': int(amount * 1e8)} ) from_address = public_key_to_address(pub) nonce = substrate.get_account_nonce(from_address) payload = substrate.generate_signature_payload(call, nonce=nonce).data signature = '0x' + eddsa_sign.eddsa_sign(priv, payload).hex() keypair = Keypair(ss58_address=from_address, ss58_format=0, crypto_type=KeypairType.ED25519) extrinsic = substrate.create_signed_extrinsic(call=call, keypair=keypair, nonce=nonce, signature=signature) result=substrate.submit_extrinsic(extrinsic=extrinsic) print(result)
class JSONRPCResource(BaseResource): def __init__(self, cache_region): self.cache_region = cache_region # Check for custom types in Redis self.substrate = None custom_type_registry = self.cache_region.get('CUSTOM_TYPE_REGISTRY') self.init_type_registry(custom_type_registry) self.block_hash = None self.metadata_decoder = None self.runtime_version = None self.metadata_cache = {} self.methods = [ 'rpc_methods', 'runtime_decodeScale', 'runtime_encodeScale', 'runtime_getMetadata', 'runtime_getMetadataModules', 'runtime_getMetadataCallFunctions', 'runtime_getMetadataCallFunction', 'runtime_getMetadataEvents', 'runtime_getMetadataEvent', 'runtime_getMetadataConstants', 'runtime_getMetadataConstant', 'runtime_getMetadataStorageFunctions', 'runtime_getMetadataStorageFunction', 'runtime_getMetadataErrors', 'runtime_getMetadataError', 'runtime_getState', 'runtime_getTypeRegistry', 'runtime_getType', 'runtime_getCustomTypes', 'runtime_addCustomType', 'runtime_setCustomTypes', 'runtime_removeCustomType', 'runtime_resetCustomTypes', 'runtime_getBlock', 'runtime_createSignaturePayload', 'runtime_createExternalSignerPayload', 'runtime_createExtrinsic', 'runtime_submitExtrinsic', 'runtime_getPaymentInfo', 'keypair_create', 'keypair_inspect', 'keypair_sign', 'keypair_verify' ] def get_request_param(self, params): try: return params.pop(0) except IndexError: raise ValueError("Not enough parameters provided") def init_type_registry(self, custom_type_registry=None): if settings.TYPE_REGISTRY_FILE: type_registry = load_type_registry_file(settings.TYPE_REGISTRY_FILE) else: type_registry = {} if custom_type_registry: type_registry.update(custom_type_registry) self.substrate = SubstrateInterface( url=settings.SUBSTRATE_RPC_URL, ss58_format=settings.SUBSTRATE_ADDRESS_TYPE, type_registry_preset=settings.TYPE_REGISTRY, type_registry=custom_type_registry ) if settings.DEBUG: print('Custom types at init: ', custom_type_registry) self.substrate.debug = True def init_request(self, params=None): if params: self.block_hash = self.get_request_param(params) if type(self.block_hash) is int: self.block_hash = self.substrate.get_block_hash(self.block_hash) def on_post(self, req, resp): self.block_hash = None self.metadata_decoder = None self.runtime_version = None self.substrate.request_id = req.media.get('id') method = req.media.get('method') params = req.media.get('params', []) # Check request requirements if not req.media.get('jsonrpc'): resp.media = { "error": { "code": -32600, "message": "Unsupported JSON-RPC protocol version" }, "id": req.media.get('id') } elif not method: resp.media = { "error": { "code": -32601, "message": "Method not found" }, "id": req.media.get('id') } elif method not in self.methods: # Default pass through request to Substrate RPC resp.media = self.substrate.rpc_request(method, params) else: resp.status = falcon.HTTP_200 try: # Process methods if method == 'runtime_getBlock': self.init_request(params) block = self.substrate.get_block(block_hash=self.block_hash) if block: block['extrinsics'] = [extrinsic.value for extrinsic in block['extrinsics']] block['header']["digest"]["logs"] = [log.value for log in block['header']["digest"]["logs"]] response = { "jsonrpc": "2.0", "result": block, "id": req.media.get('id') } elif method == 'runtime_getState': # Init params storage_params = None # Process params module = self.get_request_param(params) storage_function = self.get_request_param(params) if params: storage_params = self.get_request_param(params) self.init_request(params) # Get response obj = self.substrate.query( module=module, storage_function=storage_function, params=storage_params, block_hash=self.block_hash ) response = {'result': obj.value if obj else None} elif method == 'runtime_getMetadata': # Process params self.init_request(params) # Get response response = self.substrate.get_runtime_metadata(block_hash=self.block_hash) elif method in ['runtime_createSignaturePayload', 'runtime_createExternalSignerPayload']: account = self.get_request_param(params) call_module = self.get_request_param(params) call_function = self.get_request_param(params) call_params = self.get_request_param(params) tip = self.get_request_param(params) or 0 era = self.get_request_param(params) self.init_request(params) try: # Create call call = self.substrate.compose_call( call_module=call_module, call_function=call_function, call_params=call_params, block_hash=self.block_hash ) nonce = self.substrate.get_account_nonce(account) or 0 if isinstance(era, dict) and 'current' not in era and 'phase' not in era: # Retrieve current block id era['current'] = self.substrate.get_block_number(self.substrate.get_chain_head()) if method == 'runtime_createExternalSignerPayload': include_call_length = True else: include_call_length = False # Generate signature payload signature_payload = self.substrate.generate_signature_payload( call=call, nonce=nonce, tip=tip, era=era, include_call_length=include_call_length ) response = { "jsonrpc": "2.0", "result": { 'signature_payload': str(signature_payload), 'nonce': nonce, 'era': era }, "id": req.media.get('id') } except ValueError as e: response = { "jsonrpc": "2.0", "error": { "code": -999, "message": str(e) }, "id": req.media.get('id') } elif method in ['runtime_submitExtrinsic', 'runtime_createExtrinsic']: account = self.get_request_param(params) call_module = self.get_request_param(params) call_function = self.get_request_param(params) call_params = self.get_request_param(params) tip = self.get_request_param(params) or 0 era = self.get_request_param(params) crypto_type = int(self.get_request_param(params) or 1) signature = self.get_request_param(params) self.init_request(params) try: # Create call call = self.substrate.compose_call( call_module=call_module, call_function=call_function, call_params=call_params, block_hash=self.block_hash ) nonce = self.substrate.get_account_nonce(account) or 0 # Create keypair with only public given given in request keypair = Keypair(ss58_address=account, crypto_type=crypto_type) if isinstance(era, dict) and 'current' in era: era['current'] = int(era['current']) # Create extrinsic extrinsic = self.substrate.create_signed_extrinsic( call=call, keypair=keypair, nonce=nonce, signature=signature, tip=tip, era=era ) if method == 'runtime_createExtrinsic': result = str(extrinsic.data) else: # Submit extrinsic to the node extrinsic_result = self.substrate.submit_extrinsic( extrinsic=extrinsic ) result = { "extrinsic_hash": extrinsic_result.extrinsic_hash, "block_hash": extrinsic_result.block_hash, "finalized": extrinsic_result.finalized, } response = { "jsonrpc": "2.0", "result": result, "id": req.media.get('id') } except ValueError as e: response = { "jsonrpc": "2.0", "error": { "code": -999, "message": str(e) }, "id": req.media.get('id') } except SubstrateRequestException as e: response = { "jsonrpc": "2.0", "error": e.args[0], "id": req.media.get('id') } elif method == 'runtime_getPaymentInfo': account = self.get_request_param(params) call_module = self.get_request_param(params) call_function = self.get_request_param(params) call_params = self.get_request_param(params) # Create call call = self.substrate.compose_call( call_module=call_module, call_function=call_function, call_params=call_params ) # Create keypair with only public given given in request keypair = Keypair(ss58_address=account) response = { "jsonrpc": "2.0", "result": self.substrate.get_payment_info(call=call, keypair=keypair), "id": req.media.get('id') } elif method == 'runtime_getMetadataModules': self.init_request(params) response = { "jsonrpc": "2.0", "result": self.substrate.get_metadata_modules(block_hash=self.block_hash), "id": req.media.get('id') } elif method == 'runtime_getMetadataCallFunctions': self.init_request(params) call_list = self.substrate.get_metadata_call_functions(block_hash=self.block_hash) response = { "jsonrpc": "2.0", "result": call_list, "id": req.media.get('id') } elif method == 'runtime_getMetadataCallFunction': param_call_module = self.get_request_param(params) param_call_module_function = self.get_request_param(params) self.init_request(params) result = self.substrate.get_metadata_call_function( module_name=param_call_module, call_function_name=param_call_module_function, block_hash=self.block_hash ) response = { "jsonrpc": "2.0", "result": result.value, "id": req.media.get('id') } elif method == 'runtime_getMetadataEvents': self.init_request(params) event_list = self.substrate.get_metadata_events(block_hash=self.block_hash) response = { "jsonrpc": "2.0", "result": event_list, "id": req.media.get('id') } elif method == 'runtime_getMetadataEvent': param_call_module = self.get_request_param(params) param_call_module_event = self.get_request_param(params) self.init_request(params) result = self.substrate.get_metadata_event( module_name=param_call_module, event_name=param_call_module_event, block_hash=self.block_hash ) response = { "jsonrpc": "2.0", "result": result, "id": req.media.get('id') } elif method == 'runtime_getMetadataConstants': self.init_request(params) constant_list = self.substrate.get_metadata_constants(block_hash=self.block_hash) response = { "jsonrpc": "2.0", "result": constant_list, "id": req.media.get('id') } elif method == 'runtime_getMetadataConstant': module_name = self.get_request_param(params) constant_name = self.get_request_param(params) self.init_request(params) result = self.substrate.get_metadata_constant( module_name=module_name, constant_name=constant_name, block_hash=self.block_hash ) response = { "jsonrpc": "2.0", "result": result, "id": req.media.get('id') } elif method == 'runtime_getMetadataStorageFunctions': self.init_request(params) storage_list = self.substrate.get_metadata_storage_functions(block_hash=self.block_hash) response = { "jsonrpc": "2.0", "result": storage_list, "id": req.media.get('id') } elif method == 'runtime_getMetadataStorageFunction': module_name = self.get_request_param(params) storage_name = self.get_request_param(params) self.init_request(params) result = self.substrate.get_metadata_storage_function( module_name=module_name, storage_name=storage_name, block_hash=self.block_hash ) response = { "jsonrpc": "2.0", "result": result.value, "id": req.media.get('id') } elif method == 'runtime_getMetadataErrors': self.init_request(params) error_list = self.substrate.get_metadata_errors(block_hash=self.block_hash) response = { "jsonrpc": "2.0", "result": error_list, "id": req.media.get('id') } elif method == 'runtime_getMetadataError': module_name = self.get_request_param(params) error_name = self.get_request_param(params) self.init_request(params) result = self.substrate.get_metadata_error( module_name=module_name, error_name=error_name, block_hash=self.block_hash ) response = { "jsonrpc": "2.0", "result": result, "id": req.media.get('id') } elif method == 'runtime_getTypeRegistry': self.init_request(params) result = self.substrate.get_type_registry(block_hash=self.block_hash) if result: result = list(result.values()) response = { "jsonrpc": "2.0", "result": result, "id": req.media.get('id') } elif method == 'runtime_getType': type_string = self.get_request_param(params) self.init_request(params) response = { "jsonrpc": "2.0", "result": self.substrate.get_type_definition(type_string, block_hash=self.block_hash), "id": req.media.get('id') } elif method == 'runtime_addCustomType': type_string = self.get_request_param(params) type_definition = self.get_request_param(params) # Retrieve current custom type registry custom_type_registry = self.cache_region.get('CUSTOM_TYPE_REGISTRY') if not custom_type_registry: custom_type_registry = { 'types': { } } custom_type_registry['types'][type_string] = type_definition # TODO Try to decode given type definition # Store updated custom type registry self.cache_region.set('CUSTOM_TYPE_REGISTRY', custom_type_registry) if settings.DEBUG: print('Custom types updated to: ', custom_type_registry) # Update runtime configuration RuntimeConfiguration().update_type_registry(custom_type_registry) response = { "jsonrpc": "2.0", "result": "Type registry updated", "id": req.media.get('id') } elif method == 'runtime_setCustomTypes': custom_types = self.get_request_param(params) if type(custom_types) is not dict: raise ValueError('custom types must be in format: {"type_string": "type_definition"}') custom_type_registry = { 'types': custom_types } # Store updated custom type registry self.cache_region.set('CUSTOM_TYPE_REGISTRY', custom_type_registry) # Reset runtime configuration RuntimeConfiguration().clear_type_registry() self.init_type_registry(custom_type_registry) if settings.DEBUG: print('Custom types updated to: ', custom_type_registry) response = { "jsonrpc": "2.0", "result": "Type registry updated", "id": req.media.get('id') } elif method == 'runtime_resetCustomTypes': custom_type_registry = None # Store updated custom type registry self.cache_region.set('CUSTOM_TYPE_REGISTRY', custom_type_registry) # Reset runtime configuration RuntimeConfiguration().clear_type_registry() self.init_type_registry() if settings.DEBUG: print('Custom types cleared') response = { "jsonrpc": "2.0", "result": "Custom types cleared", "id": req.media.get('id') } elif method == 'runtime_removeCustomType': type_string = self.get_request_param(params) # Retrieve current custom type registry custom_type_registry = self.cache_region.get('CUSTOM_TYPE_REGISTRY') if custom_type_registry and type_string in custom_type_registry.get('types', {}): del custom_type_registry['types'][type_string] # Store updated custom type registry self.cache_region.set('CUSTOM_TYPE_REGISTRY', custom_type_registry) # Reset runtime configuration RuntimeConfiguration().clear_type_registry() self.init_type_registry(custom_type_registry) result = '"{}" removed from custom type registry'.format(type_string) else: result = '"{}" not found in custom type registry'.format(type_string) response = { "jsonrpc": "2.0", "result": result, "id": req.media.get('id') } elif method == 'runtime_getCustomTypes': custom_type_registry = self.cache_region.get('CUSTOM_TYPE_REGISTRY') if custom_type_registry: result = custom_type_registry.get('types') else: result = {} response = { "jsonrpc": "2.0", "result": result, "id": req.media.get('id') } elif method == 'runtime_decodeScale': type_string = self.get_request_param(params) scale_hex_bytes = self.get_request_param(params) self.init_request(params) result = self.substrate.decode_scale( type_string=type_string, scale_bytes=scale_hex_bytes, block_hash=self.block_hash ) response = { "jsonrpc": "2.0", "result": result, "id": req.media.get('id') } elif method == 'runtime_encodeScale': type_string = self.get_request_param(params) value = self.get_request_param(params) self.init_request(params) result = self.substrate.encode_scale( type_string=type_string, value=value, block_hash=self.block_hash ) response = { "jsonrpc": "2.0", "result": result, "id": req.media.get('id') } elif method == 'keypair_create': word_count = self.get_request_param(params) or 0 crypto_type = int(self.get_request_param(params) or 1) mnemonic = Keypair.generate_mnemonic(word_count) keypair = Keypair.create_from_mnemonic( mnemonic=mnemonic, ss58_format=settings.SUBSTRATE_ADDRESS_TYPE, crypto_type=crypto_type ) response = { "jsonrpc": "2.0", "result": { 'ss58_address': keypair.ss58_address, 'public_key': keypair.public_key, 'private_key': keypair.private_key, 'mnemonic': keypair.mnemonic, }, "id": req.media.get('id') } elif method == 'keypair_inspect': mnemonic = self.get_request_param(params) crypto_type = int(self.get_request_param(params) or 1) keypair = Keypair.create_from_mnemonic( mnemonic=mnemonic, ss58_format=settings.SUBSTRATE_ADDRESS_TYPE, crypto_type=crypto_type ) response = { "jsonrpc": "2.0", "result": { 'ss58_address': keypair.ss58_address, 'public_key': keypair.public_key, 'private_key': keypair.private_key, 'mnemonic': keypair.mnemonic, }, "id": req.media.get('id') } elif method == 'keypair_sign': mnemonic = self.get_request_param(params) data = self.get_request_param(params) crypto_type = int(self.get_request_param(params) or 1) keypair = Keypair.create_from_mnemonic( mnemonic=mnemonic, ss58_format=settings.SUBSTRATE_ADDRESS_TYPE, crypto_type=crypto_type ) signature = keypair.sign(data) response = { "jsonrpc": "2.0", "result": {'signature': signature}, "id": req.media.get('id') } elif method == 'keypair_verify': account_address = self.get_request_param(params) data = self.get_request_param(params) signature = self.get_request_param(params) crypto_type = int(self.get_request_param(params) or 1) keypair = Keypair( ss58_address=account_address, ss58_format=settings.SUBSTRATE_ADDRESS_TYPE, crypto_type=crypto_type ) result = keypair.verify(data, signature) response = { "jsonrpc": "2.0", "result": {'verified': result}, "id": req.media.get('id') } elif method == 'rpc_methods': response = self.substrate.rpc_request(method, params) # Add additional implemented method response['result']['methods'] = sorted(response['result']['methods'] + self.methods) else: raise NotImplementedError('Method \'{}\' not implemented yet'.format(method)) except (ValueError, NotImplementedError) as e: response = { "error": { "code": -999, "message": str(e) }, "id": req.media.get('id') } except (InvalidScaleTypeValueException, RemainingScaleBytesNotEmptyException) as e: response = { "error": { "code": -998, "message": "Decoding error, given SCALE-value or type registry might be invalid " }, "id": req.media.get('id') } resp.media = response
def cmd_pay(args, config): substrate = SubstrateInterface(url=get_config(args, config, 'rpcurl'), type_registry_preset=get_config( args, config, 'network')) active_era = substrate.query(module='Staking', storage_function='ActiveEra') active_era = active_era.value['index'] depth = get_config(args, config, 'deptheras') depth = int(depth) if depth is not None else 84 minEras = get_config(args, config, 'mineras') minEras = int(minEras) if minEras is not None else 5 start = active_era - depth end = active_era eras_payment_info = get_eras_payment_info_filtered( substrate, start, end, accounts=get_included_accounts(args, config), only_unclaimed=True) eras_payment_info = OrderedDict( sorted(eras_payment_info.items(), reverse=True)) if len(eras_payment_info.keys()) == 0: print(f"There are no rewards to claim in the last {depth} era(s)") return if len(eras_payment_info) < int(get_config(args, config, 'mineras')): max_age_unclaimed = int(get_config(args, config, 'maxageunclaimed')) if max_age_unclaimed is None: max_age_unclaimed = 84 if not old_unclaimed_exist(eras_payment_info, max_age_unclaimed, active_era): print( f"There are rewards to claim on {len(eras_payment_info.keys())} era(s), " + f"but those are not enough to reach the minimum threshold ({minEras})" ) return keypair = get_keypair(args, config) payout_calls = [] for era in eras_payment_info: for accountId in eras_payment_info[era]: payout_calls.append({ 'call_module': 'Staking', 'call_function': 'payout_stakers', 'call_args': { 'validator_stash': accountId, 'era': era, } }) call = substrate.compose_call(call_module='Utility', call_function='batch', call_params={'calls': payout_calls}) payment_info = substrate.get_payment_info(call=call, keypair=keypair) account_info = get_account_info(substrate, get_config(args, config, 'signingaccount')) expected_fees = payment_info['partialFee'] free_balance = account_info['data']['free'] existential_deposit = get_existential_deposit(substrate) if (free_balance - expected_fees) < existential_deposit: print( f"Account with not enough funds. Needed {existential_deposit + expected_fees}, but got {free_balance}" ) return signature_payload = substrate.generate_signature_payload( call=call, nonce=account_info['nonce']) signature = keypair.sign(signature_payload) extrinsic = substrate.create_signed_extrinsic(call=call, keypair=keypair, nonce=account_info['nonce'], signature=signature) print("Submitting batch extrinsic to claim " + f"{len(payout_calls)} rewards (in {len(eras_payment_info)} eras)") extrinsic_receipt = substrate.submit_extrinsic(extrinsic=extrinsic, wait_for_inclusion=True) tweet_text = Twitter.generate_tweet_text(eras_payment_info) Twitter(config).update_status(tweet_text) fees = extrinsic_receipt.total_fee_amount print(f"\t Extrinsic hash: {extrinsic_receipt.extrinsic_hash}") print(f"\t Block hash: {extrinsic_receipt.block_hash}") print(f"\t Fee: {format_balance_to_symbol(substrate, fees)} ({fees})") print(f"\t Status: {'ok' if extrinsic_receipt.is_success else 'error'}") if not extrinsic_receipt.is_success: print( f"\t Error message: {extrinsic_receipt.error_message.get('docs')}")