def sign_transaction_from_xdr(wallet_file, transaction_xdr, test_mode=True, trezor_mode=False, just_sign=False, vzero=False): network_settings = get_network_settings(test_mode=test_mode) if not trezor_mode: transaction = TransactionEnvelope.from_xdr( transaction_xdr, network_passphrase=network_settings.get("network_passphrase")) confirmation = show_transaction_data(transaction) if not confirmation: return (private_key, public_key) = load_wallet(wallet_file=wallet_file) k = Keypair.from_secret(secret=private_key) transaction.sign(k) else: transaction = TransactionEnvelope.from_xdr( transaction_xdr, network_passphrase=network_settings.get("network_passphrase")) confirmation = show_transaction_data(transaction) if not confirmation: return public_key = get_trezor_public_key() k = Keypair.from_public_key(public_key=public_key) transaction = sign_trezor_transaction( transaction=transaction, public_key=k, network_passphrase=network_settings.get("network_passphrase")) if just_sign: print("TX SIGNED DATA:\n{}".format(transaction.to_xdr())) else: broadcast_tx(transaction=transaction, test_mode=test_mode)
def test_post_success_account_doesnt_exist(mock_load_account, client): kp = Keypair.random() challenge_xdr = build_challenge_transaction( server_secret=settings.SIGNING_SEED, client_account_id=kp.public_key, home_domain=settings.SEP10_HOME_DOMAINS[0], web_auth_domain=urlparse(settings.HOST_URL).netloc, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, ) envelope = TransactionEnvelope.from_xdr( challenge_xdr, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE) envelope.sign(kp) signed_challenge_xdr = envelope.to_xdr() mock_load_account.side_effect = NotFoundError(MagicMock()) response = client.post(AUTH_PATH, {"transaction": signed_challenge_xdr}) content = response.json() assert response.status_code == 200, json.dumps(content, indent=2) jwt_contents = jwt.decode(content["token"], settings.SERVER_JWT_KEY, algorithms=["HS256"]) iat = jwt_contents.pop("iat") exp = jwt_contents.pop("exp") assert exp - iat == 24 * 60 * 60 assert jwt_contents == { "iss": os.path.join(settings.HOST_URL, "auth"), "sub": kp.public_key, "jti": envelope.hash().hex(), "client_domain": None, }
async def send_operation_details(destination, envelope: str, network_type): """ Send information to sender on operations inside transaction """ data = TransactionEnvelope.from_xdr(envelope, network_type) operations = data.transaction.operations count = 1 for op in operations: op_info = Embed(title=f'Operation No.{count}', colour=Colour.green()) if isinstance(op, Payment): print("++++++++++++++++++++++") op_info.add_field(name=f'Payment To:', value=f'```{op.destination}```', inline=False) if isinstance(op.asset, Asset): op_info.add_field(name=f'Payment Value', value=f'`{op.amount} {op.asset.code}`') elif isinstance(op, CreateAccount): op_info.add_field(name=f'Create Account for', value=f'{op.destination}') op_info.add_field(name=f'Starting Balance', value=f'{op.starting_balance}') await destination.send(embed=op_info) count += 1
def test_post_fails_account_doesnt_exist_no_client_attribution_signature( mock_load_account, client): kp = Keypair.random() client_domain_kp = Keypair.random() challenge_xdr = build_challenge_transaction( server_secret=settings.SIGNING_SEED, client_account_id=kp.public_key, home_domain=settings.SEP10_HOME_DOMAINS[0], web_auth_domain=urlparse(settings.HOST_URL).netloc, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, client_domain="test.com", client_signing_key=client_domain_kp.public_key, ) envelope = TransactionEnvelope.from_xdr( challenge_xdr, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE) envelope.sign(kp) signed_challenge_xdr = envelope.to_xdr() mock_load_account.side_effect = NotFoundError(MagicMock()) response = client.post(AUTH_PATH, {"transaction": signed_challenge_xdr}) content = response.json() assert response.status_code == 400, json.dumps(content, indent=2) assert ( content["error"] == "error while validating challenge: " "Transaction not signed by the source account of the 'client_domain' ManageData operation" )
def test_deposit_authenticated_success(client, acc1_usd_deposit_transaction_factory): """`GET /deposit` succeeds with the SEP 10 authentication flow.""" deposit = acc1_usd_deposit_transaction_factory() # SEP 10. response = client.get(f"/auth?account={client_address}", follow=True) content = json.loads(response.content) envelope_xdr = content["transaction"] envelope_object = TransactionEnvelope.from_xdr( envelope_xdr, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE ) client_signing_key = Keypair.from_secret(client_seed) envelope_object.sign(client_signing_key) client_signed_envelope_xdr = envelope_object.to_xdr() response = client.post( "/auth", data={"transaction": client_signed_envelope_xdr}, content_type="application/json", ) content = json.loads(response.content) encoded_jwt = content["token"] assert encoded_jwt header = {"HTTP_AUTHORIZATION": f"Bearer {encoded_jwt}"} response = client.post( DEPOSIT_PATH, {"asset_code": "USD", "account": deposit.stellar_account}, follow=True, **header, ) content = json.loads(response.content) assert response.status_code == 200 assert content["type"] == "interactive_customer_info_needed"
def create_stellar_deposit(transaction: Transaction) -> bool: """ Performs final status and signature checks before calling submit_stellar_deposit(). Returns true on successful submission, false otherwise. `transaction` will be placed in the error status if submission fails or if it is a multisig transaction and is not signed by the channel account. """ if transaction.status not in [ Transaction.STATUS.pending_anchor, Transaction.STATUS.pending_trust, ]: raise ValueError( f"unexpected transaction status {transaction.status} for " "create_stellar_deposit", ) elif transaction.amount_in is None or transaction.amount_fee is None: transaction.status = Transaction.STATUS.error transaction.status_message = ( "`amount_in` and `amount_fee` must be populated, skipping transaction" ) transaction.save() raise ValueError(transaction.status_message) # if the distribution account's master signer's weight is great or equal to the its # medium threshold, verify the transaction is signed by it's channel account master_signer = None if transaction.asset.distribution_account_master_signer: master_signer = transaction.asset.distribution_account_master_signer thresholds = transaction.asset.distribution_account_thresholds if not master_signer or master_signer["weight"] < thresholds[ "med_threshold"]: envelope = TransactionEnvelope.from_xdr( transaction.envelope_xdr, settings.STELLAR_NETWORK_PASSPHRASE) try: _verify_te_signed_by_account_id(envelope, transaction.channel_account) except InvalidSep10ChallengeError: transaction.status = Transaction.STATUS.error transaction.status_message = gettext( "Multisig transaction's envelope was not signed by channel account" ) transaction.save() return False # otherwise, create the envelope and sign it with the distribution account's secret else: distribution_acc, _ = get_account_obj( Keypair.from_public_key(transaction.asset.distribution_account)) envelope = create_transaction_envelope(transaction, distribution_acc) envelope.sign(transaction.asset.distribution_seed) try: submit_stellar_deposit(transaction, envelope) except (RuntimeError, BaseHorizonError) as e: transaction.status_message = f"{e.__class__.__name__}: {e.message}" transaction.status = Transaction.STATUS.error transaction.save() logger.error(transaction.status_message) return False else: return True
def submit_transaction(transaction_xdr, test_mode=True, vzero=False): network_settings = get_network_settings(test_mode=test_mode) transaction = TransactionEnvelope.from_xdr( transaction_xdr, network_passphrase=network_settings.get("network_passphrase")) confirmation = show_transaction_data_before_submit(transaction) if not confirmation: return else: broadcast_tx(transaction=transaction, test_mode=test_mode)
def test_xdr(parameters, result): from stellar_sdk import TransactionEnvelope envelope = TransactionEnvelope.from_xdr(parameters["xdr"], parameters["network_passphrase"]) tx, operations = stellar.from_envelope(envelope) tx.address_n = parse_path(parameters["address_n"]) tx_expected, operations_expected = parameters_to_proto(parameters) assert tx == tx_expected for expected, actual in zip(operations_expected, operations): assert expected == actual
def set_unlock_transaction(self, unlock_transaction): """ Adds a xdr encoded unlocktransaction :param unlock_transaction: xdr encoded unlocktransactionaddress of the destination. :type destination_address: str """ txe = TransactionEnvelope.from_xdr(unlock_transaction, _NETWORK_PASSPHRASES[str(self.network)]) tx_hash = txe.hash() unlock_hash = strkey.StrKey.encode_pre_auth_tx(tx_hash) self._create_unlockhash_transaction(unlock_hash=unlock_hash, transaction_xdr=txe.to_xdr())
def _set_unlock_conditions(self): for unlockhash in self.unlockhashes: unlockhash_tx = self._get_unlockhash_transaction( unlockhash=unlockhash) if unlockhash_tx is None: return txe = TransactionEnvelope.from_xdr( unlockhash_tx["transaction_xdr"], self.network_passphrase) tx = txe.transaction if tx.time_bounds is not None: self.unlock_time = tx.time_bounds.min_time
def submit_stellar_deposit(transaction, multisig=False) -> bool: transaction.status = Transaction.STATUS.pending_stellar transaction.save() logger.info(f"Transaction {transaction.id} now pending_stellar") envelope = TransactionEnvelope.from_xdr( transaction.envelope_xdr, settings.STELLAR_NETWORK_PASSPHRASE) try: response = settings.HORIZON_SERVER.submit_transaction(envelope) except BaseHorizonError as e: logger.info(e.__class__.__name__) tx_result = TransactionResult.from_xdr(e.result_xdr) op_results = tx_result.result.results if isinstance(e, BadRequestError): if op_results[0].code == -1: # Bad Auth handle_bad_signatures_error(e, transaction, multisig=multisig) return False # handle_bad_signatures_error() saves transaction else: transaction.status = Transaction.STATUS.error transaction.status_message = ( f"tx failed with codes: {op_results}. Result XDR: {e.result_xdr}" ) elif op_results[0].tr.paymentResult.code == const.PAYMENT_NO_TRUST: transaction.status = Transaction.STATUS.pending_trust transaction.status_message = ( "trustline error when submitting transaction to horizon") else: raise RuntimeError("Unable to submit payment to horizon, " f"non-trustline failure: {e.message}") transaction.save() logger.error(transaction.status_message) return False if not response.get("successful"): transaction_result = TransactionResult.from_xdr(response["result_xdr"]) raise RuntimeError( "Stellar transaction failed when submitted to horizon: " f"{transaction_result.result.results}") transaction.paging_token = response["paging_token"] transaction.stellar_transaction_id = response["id"] transaction.status = Transaction.STATUS.completed transaction.completed_at = datetime.datetime.now(datetime.timezone.utc) transaction.status_eta = 0 transaction.amount_out = round( Decimal(transaction.amount_in) - Decimal(transaction.amount_fee), transaction.asset.significant_decimals, ) transaction.save() logger.info(f"Transaction {transaction.id} completed.") return True
def _set_unlock_conditions(self): unlockhash_tx_model = j.threebot.packages.threefoldfoundation.unlock_service.bcdb.model_get( url="threefoldfoundation.unlock_service.unlockhash_transaction") for unlockhash in self.unlockhashes: unlockhash_tx = unlockhash_tx_model.find(unlockhash=unlockhash) if len(unlockhash_tx) is 0: return unlockhash_tx = unlockhash_tx[0] txe = TransactionEnvelope.from_xdr(unlockhash_tx.transaction_xdr, self.network_passphrase) tx = txe.transaction if tx.time_bounds is not None: self.unlock_time = tx.time_bounds.min_time
def sign_multisig_transactions(self): transactions = Transaction.objects.filter( pending_signatures=True, envelope_xdr__isnull=False, status=Transaction.STATUS.pending_anchor, ).all() for t in transactions: envelope = TransactionEnvelope.from_xdr( t.envelope_xdr, settings.STELLAR_NETWORK_PASSPHRASE) envelope.sign(t.asset.distribution_seed) if not django_settings.MULT_ASSET_ADDITIONAL_SIGNING_SEED: raise CommandError( "MULT's 2nd signer is not specified in the environment") envelope.sign(django_settings.MULT_ASSET_ADDITIONAL_SIGNING_SEED) t.envelope_xdr = envelope.to_xdr() t.pending_signatures = False t.save() logger.info(f"Transaction {t.id} signatures have been collected")
def sign_multisig_transaction(self, tx_xdr): """sign_multisig_transaction signs a transaction xdr and tries to submit it to the network if it fails it returns the signed transaction xdr :param tx_xdr: transaction to sign in xdr format. :type tx_xdr: str """ server = self._get_horizon_server() source_keypair = Keypair.from_secret(self.secret) tx = TransactionEnvelope.from_xdr(tx_xdr, _NETWORK_PASSPHRASES[str(self.network)]) tx.sign(source_keypair) try: response = server.submit_transaction(tx) self._log_info(response) self._log_info("Multisig tx signed and sent") except BadRequestError: self._log_info("Transaction need additional signatures in order to send") return tx.to_xdr()
def decode_transaction_envelope(self, envelope_xdr): """ Decode envelope and get details Credits to overcat : https://stellar.stackexchange.com/questions/3022/how-can-i-get-the-value-of-the-stellar-transaction/3025#3025 :param envelope_xdr: Xdr envelope from stellar network :return: Decoded transaction details """ te = TransactionEnvelope.from_xdr(envelope_xdr, self.network_phrase) operations = te.transaction.operations # TODO make multiple payments inside one transaction for op in operations: if isinstance(op, Payment): asset = op.asset.to_dict() if asset.get('type') == 'native': asset['code'] = 'XLM' # Appending XLM code to asset incase if native asset["amount"] = op.to_xdr_amount(op.amount) # TODO count all deposits return asset
def test_post_success_account_exists_client_attribution( mock_load_account, client): kp = Keypair.random() client_domain_kp = Keypair.random() challenge_xdr = build_challenge_transaction( server_secret=settings.SIGNING_SEED, client_account_id=kp.public_key, home_domain=settings.SEP10_HOME_DOMAINS[0], web_auth_domain=urlparse(settings.HOST_URL).netloc, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, client_domain="test.com", client_signing_key=client_domain_kp.public_key, ) envelope = TransactionEnvelope.from_xdr( challenge_xdr, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE) envelope.sign(kp) envelope.sign(client_domain_kp) signed_challenge_xdr = envelope.to_xdr() mock_load_account.return_value = Mock( load_ed25519_public_key_signers=Mock( return_value=[Ed25519PublicKeySigner(kp.public_key, 0)]), thresholds=Mock(med_threshold=0), ) response = client.post(AUTH_PATH, {"transaction": signed_challenge_xdr}) content = response.json() assert response.status_code == 200, json.dumps(content, indent=2) jwt_contents = jwt.decode(content["token"], settings.SERVER_JWT_KEY, algorithms=["HS256"]) iat = jwt_contents.pop("iat") exp = jwt_contents.pop("exp") assert exp - iat == 24 * 60 * 60 assert jwt_contents == { "iss": os.path.join(settings.HOST_URL, "auth"), "sub": kp.public_key, "jti": envelope.hash().hex(), "client_domain": "test.com", }
def test_deposit_authenticated_success(client, acc1_usd_deposit_transaction_factory): """`GET /deposit` succeeds with the SEP 10 authentication flow.""" client_address = "GDKFNRUATPH4BSZGVFDRBIGZ5QAFILVFRIRYNSQ4UO7V2ZQAPRNL73RI" client_seed = "SDKWSBERDHP3SXW5A3LXSI7FWMMO5H7HG33KNYBKWH2HYOXJG2DXQHQY" deposit = acc1_usd_deposit_transaction_factory() # SEP 10. response = client.get(f"/auth?account={client_address}", follow=True) content = json.loads(response.content) envelope_xdr = content["transaction"] envelope_object = TransactionEnvelope.from_xdr( envelope_xdr, network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE) client_signing_key = Keypair.from_secret(client_seed) envelope_object.sign(client_signing_key) client_signed_envelope_xdr = envelope_object.to_xdr() response = client.post( "/auth", data={"transaction": client_signed_envelope_xdr}, content_type="application/json", ) content = json.loads(response.content) encoded_jwt = content["token"] assert encoded_jwt header = {"HTTP_AUTHORIZATION": f"Bearer {encoded_jwt}"} response = client.get( f"/deposit?asset_code=USD&account={deposit.stellar_account}", follow=True, **header, ) content = json.loads(response.content) assert response.status_code == 403 assert content["type"] == "interactive_customer_info_needed"
def create_stellar_deposit(transaction: Transaction, destination_exists: bool = False) -> bool: """ Create and submit the Stellar transaction for the deposit. The Transaction can be either `pending_anchor` if the task is called from `poll_pending_deposits()` or `pending_trust` if called from the `check_trustlines()`. """ if transaction.status not in [ Transaction.STATUS.pending_anchor, Transaction.STATUS.pending_trust, ]: raise ValueError( f"unexpected transaction status {transaction.status} for " "create_stellar_deposit", ) elif transaction.amount_in is None or transaction.amount_fee is None: transaction.status = Transaction.STATUS.error transaction.status_message = ( "`amount_in` and `amount_fee` must be populated, skipping transaction" ) transaction.save() raise ValueError(transaction.status_message) # if we don't know if the destination account exists if not destination_exists: try: _, created, pending_trust = get_or_create_transaction_destination_account( transaction) except RuntimeError as e: transaction.status = Transaction.STATUS.error transaction.status_message = str(e) transaction.save() logger.error(transaction.status_message) return False if created or pending_trust: # the account is pending_trust for the asset to be received if pending_trust and transaction.status != Transaction.STATUS.pending_trust: transaction.status = Transaction.STATUS.pending_trust transaction.save() return False # if the distribution account's master signer's weight is great or equal to the its # medium threshold, verify the transaction is signed by it's channel account master_signer = None if transaction.asset.distribution_account_master_signer: master_signer = transaction.asset.distribution_account_master_signer thresholds = transaction.asset.distribution_account_thresholds if not (master_signer and master_signer["weight"] >= thresholds["med_threshold"]): multisig = True envelope = TransactionEnvelope.from_xdr( transaction.envelope_xdr, settings.STELLAR_NETWORK_PASSPHRASE) try: _verify_te_signed_by_account_id(envelope, transaction.channel_account) except InvalidSep10ChallengeError: transaction.status = Transaction.STATUS.error transaction.status_message = gettext( "Multisig transaction's envelope was not signed by channel account" ) transaction.save() return False # otherwise, create the envelope and sign it with the distribution account's secret else: multisig = False distribution_acc, _ = get_account_obj( Keypair.from_public_key(transaction.asset.distribution_account)) envelope = create_transaction_envelope(transaction, distribution_acc) envelope.sign(transaction.asset.distribution_seed) transaction.envelope_xdr = envelope.to_xdr() try: return submit_stellar_deposit(transaction, multisig=multisig) except RuntimeError as e: transaction.status_message = str(e) transaction.status = Transaction.STATUS.error transaction.save() logger.error(transaction.status_message) return False
def transfer( self, destination_address, amount, asset="XLM", locked_until=None, memo_text=None, memo_hash=None, fund_transaction=True, from_address=None, ): """Transfer assets to another address :param destination_address: address of the destination. :type destination_address: str :param amount: amount, can be a floating point number with 7 numbers after the decimal point expressed as a Decimal or a string. :type amount: Union[str, Decimal] :param asset: asset to transfer (if none is specified the default 'XLM' is used), if you wish to specify an asset it should be in format 'assetcode:issuer'. Where issuer is the address of the issuer of the asset. :type asset: str :param locked_until: optional epoch timestamp indicating until when the tokens should be locked. :type locked_until: float :param text_memo: optional memo text to add to the transaction, a string encoded using either ASCII or UTF-8, up to 28-bytes long :type: Union[str, bytes] :param memo_hash: optional memo hash to add to the transaction, A 32 byte hash :type: Union[str, bytes] :param fund_transaction: use the threefoldfoundation transaction funding service :type: fund_transaction: bool :param from_address: Use a different address to send the tokens from, useful in multisig use cases. :type from_address: str """ issuer = None self._log_info(f"Sending {amount} {asset} to {destination_address}") if asset != "XLM": assetStr = asset.split(":") if len(assetStr) != 2: raise Exception("Wrong asset format") asset = assetStr[0] issuer = assetStr[1] if locked_until is not None: return self._transfer_locked_tokens( destination_address, amount, asset, issuer, locked_until, memo_text=memo_text, memo_hash=memo_hash, fund_transaction=fund_transaction, from_address=from_address) horizon_server = self._get_horizon_server() base_fee = horizon_server.fetch_base_fee() if from_address: source_account = horizon_server.load_account(from_address) else: source_account = self.load_account() transaction_builder = TransactionBuilder( source_account=source_account, network_passphrase=_NETWORK_PASSPHRASES[str(self.network)], base_fee=base_fee) transaction_builder.append_payment_op( destination=destination_address, amount=amount, asset_code=asset, asset_issuer=issuer, source=source_account.account_id, ) transaction_builder.set_timeout(30) if memo_text is not None: transaction_builder.add_text_memo(memo_text) if memo_hash is not None: transaction_builder.add_hash_memo(memo_hash) transaction = transaction_builder.build() transaction = transaction.to_xdr() if asset in _NETWORK_KNOWN_TRUSTS[str(self.network)]: if fund_transaction: transaction = self._fund_transaction(transaction=transaction) transaction = transaction["transaction_xdr"] transaction = TransactionEnvelope.from_xdr( transaction, _NETWORK_PASSPHRASES[str(self.network)]) my_keypair = Keypair.from_secret(self.secret) transaction.sign(my_keypair) try: response = horizon_server.submit_transaction(transaction) tx_hash = response["hash"] self._log_info("Transaction hash: {}".format(tx_hash)) return tx_hash except BadRequestError as e: result_codes = e.extras.get("result_codes") operations = result_codes.get("operations") if operations is not None: for op in operations: if op == "op_underfunded": raise e # if op_bad_auth is returned then we assume the transaction needs more signatures # so we return the transaction as xdr elif op == "op_bad_auth": self._log_info( "Transaction might need additional signatures in order to send" ) return transaction.to_xdr() raise e
def submit(cls, transaction: Transaction) -> bool: valid_statuses = [ Transaction.STATUS.pending_user_transfer_start, Transaction.STATUS.pending_external, Transaction.STATUS.pending_anchor, Transaction.STATUS.pending_trust, ] if transaction.status not in valid_statuses: raise ValueError( f"Unexpected transaction status: {transaction.status}, expecting " f"{' or '.join(valid_statuses)}.") transaction.status = Transaction.STATUS.pending_anchor transaction.save() logger.info(f"Initiating Stellar deposit for {transaction.id}") maybe_make_callback(transaction) if transaction.envelope_xdr: try: envelope = TransactionEnvelope.from_xdr( transaction.envelope_xdr, settings.STELLAR_NETWORK_PASSPHRASE) except Exception: cls._handle_error(transaction, "Failed to decode transaction envelope") return False else: distribution_acc, _ = get_account_obj( Keypair.from_public_key( transaction.asset.distribution_account)) envelope = cls.create_deposit_envelope(transaction, distribution_acc) envelope.sign(transaction.asset.distribution_seed) transaction.status = Transaction.STATUS.pending_stellar transaction.save() logger.info(f"Transaction {transaction.id} now pending_stellar") maybe_make_callback(transaction) try: response = settings.HORIZON_SERVER.submit_transaction(envelope) except BaseHorizonError as e: cls._handle_error(transaction, f"{e.__class__.__name__}: {e.message}") return False if not response.get("successful"): cls._handle_error( transaction, f"Stellar transaction failed when submitted to horizon: {response['result_xdr']}", ) return False elif transaction.claimable_balance_supported: transaction.claimable_balance_id = cls.get_balance_id(response) transaction.envelope_xdr = response["envelope_xdr"] transaction.paging_token = response["paging_token"] transaction.stellar_transaction_id = response["id"] transaction.status = Transaction.STATUS.completed transaction.completed_at = datetime.datetime.now(datetime.timezone.utc) transaction.amount_out = round( Decimal(transaction.amount_in) - Decimal(transaction.amount_fee), transaction.asset.significant_decimals, ) transaction.save() logger.info(f"Transaction {transaction.id} completed.") maybe_make_callback(transaction) return True
def signTransaction(txnXdr, signature): txnEnv = TransactionEnvelope.from_xdr(txnXdr) txnEnv.sign(signature) txnEnv.to_xdr()