def make_envelope(self, *args, **kwargs): from stellar_base.transaction import Transaction from stellar_base.keypair import Keypair from stellar_base.transaction_envelope import TransactionEnvelope as Te opts = { 'sequence': horizon.account(self.address)['sequence'], 'fee': self.fee * len(args) } for opt, value in kwargs.items(): opts[opt] = value tx = Transaction(source=self.address, opts=opts) for count, op in enumerate(args): tx.add_operation(operation=op) envelope = Te(tx=tx, opts={"network_id": "TESTNET"}) signer = Keypair.from_seed(seed=self.seed) envelope.sign(keypair=signer) envelope_xdr = envelope.xdr() print(envelope_xdr) return envelope_xdr
def create_envelope_submits(user_kaykair, sequence, memo, opers, FEE=FEE): """事务封包,并提交""" tx = Transaction( source=user_kaykair[0].address().decode(), # 事务发起着的公钥 opts={ 'sequence': sequence, # 事务发起着的序列号 'memo': TextMemo(memo), # 备注 'fee': len(opers) * FEE, # 手续费 'operations': opers, }, ) # 操作 envelope = Te(tx=tx, opts=dict(network_id='XIANDA_DEV_NET')) # 操作封包的类Te for i in user_kaykair: print 2222, user_kaykair envelope.sign(i) # envelope.sign(user_kaykair) # 事务发起着签名 te = envelope.xdr() # 转换xdr格式数据 return submit(te, envelope)
def main(inflation): inflation = XLM_Decimal(inflation) conn = sqlite3.connect(db_address) transactions = [] sequence = horizon.account(pool_address).get('sequence') total_payments_cost = 0 num_payments = 0 total_fee_cost = 0 for batch in accounts_payout(conn, pool_address, inflation): tx = Transaction( source = pool_address, opts = { 'sequence': sequence, 'operations': [make_payment_op(aid, amount) for aid, amount in batch] } ) tx.fee = len(tx.operations) * 100 envelope = Te(tx = tx, opts = {"network_id": network}) transactions.append(envelope.xdr().decode("utf-8")) total_fee_cost += XLM_Decimal(tx.fee) / XLM_STROOP total_payments_cost += sum([XLM_Decimal(payment.amount) for payment in tx.operations]) num_payments += len(tx.operations) sequence = int(sequence) + 1 print( "Stats: \n\ Inflation received: " + str(inflation) + "\n\ A total of " + str(total_payments_cost) +\ " XLM paid over " + str(num_payments) +\ " inflation payments using " + str(total_fee_cost) + " XLM in fees. \n\ People donated " + str(sum([n for n in donations.values()])) +\ " XLM to " + str(len(donations.keys())) +\ " different addresses.\n" ) with open("transactions.json", 'w') as outf: json.dump(transactions, outf) print("Done. Output to transactions.json")
def Sending_lumen(sourceAdd, sourceSeed, desAdd, amount): asset = Asset('XLM') sequence = horizon.account(sourceAdd).get('sequence') op = Payment({ 'asset': asset, 'amount': str(amount), 'destination': desAdd }) tx = Transaction(source=sourceAdd, opts={ 'sequence': sequence, 'operations': [op] }) envelope_send = Te(tx=tx, opts={"network_id": "PUBLIC"}) kp = Keypair.from_seed(sourceSeed) envelope_send.sign(kp) xdr = envelope_send.xdr() response = horizon.submit(xdr) result = passed_or_not(response) return response
def create_account(self, old_account_seed, new_account_address, amount, memo): """ 用已有账户创建新账户 """ horizon = self.client account_seed = old_account_seed old_account_keypair = Keypair.from_seed(account_seed) account_addr = new_account_address start_amount = amount # Your new account minimum balance (in XLM) to transfer over # create the CreateAccount operation opts = {'destination': account_addr, 'starting_balance': start_amount} op = CreateAccount(opts) # create a memo txt_memo = TextMemo(memo) # Get the current sequence of the source account by contacting Horizon. You # should also check the response for errors! try: sequence = horizon.account( old_account_keypair.address().decode()).get('sequence') except Exception as e: logger.exception(e) # Create a transaction with our single create account operation, with the # default fee of 100 stroops as of this writing (0.00001 XLM) trans_ops = { 'sequence': sequence, 'memo': txt_memo, 'operations': [op] } tx = Transaction(source=old_account_keypair.address().decode(), opts=trans_ops) env_opts = {'network_id': 'TESTNET'} envelope = TransactionEnvelope(tx=tx, opts=env_opts) # Sign the transaction envelope with the source keypair envelope.sign(old_account_keypair) # Submit the transaction to Horizon te_xdr = envelope.xdr() response = horizon.submit(te_xdr) return response
def Account_creation(IssuerAdd, IssuerSeed, XLMamount): from stellar_base.operation import CreateAccount NewKP = Keypair.random() NewAdd = NewKP.address().decode() sequence = horizon.account(IssuerAdd).get('sequence') op = CreateAccount({ 'destination': NewAdd, 'starting_balance': str(XLMamount) }) tx = Transaction(source=IssuerAdd, opts={ 'sequence': sequence, 'operations': [op] }) envelope = Te(tx=tx, opts={"network_id": "PUBLIC"}) kp = Keypair.from_seed(IssuerSeed) envelope.sign(kp) xdr = envelope.xdr() response = horizon.submit(xdr) result = passed_or_not(response) return NewKP
def Sending_asset(assetName, issuerAdd, sourceAdd, sourceSeed, desAdd, amount): asset = Asset(assetName, issuerAdd) sequence1 = horizon.account(sourceAdd).get('sequence') op_pay = Payment({ 'asset': asset, 'amount': str(amount), 'destination': desAdd }) tx_pay = Transaction(source=sourceAdd, opts={ 'sequence': sequence1, 'operations': [ op_pay, ] }) envelope_pay = Te(tx=tx_pay, opts={"network_id": "PUBLIC"}) kp = Keypair.from_seed(sourceSeed) envelope_pay.sign(kp) xdr1 = envelope_pay.xdr() response = horizon.submit(xdr1) result = passed_or_not(response) return response
def Trusting_asset(assetName,issuerAdd,limit): data = Get_data() sourceAdd = data['Address'] asset = asset_Identifier(assetName,issuerAdd) sequence2 = horizon.account(sourceAdd).get('sequence') op_ct = ChangeTrust({'asset':asset, 'limit':str(limit)}) tx_ct = Transaction(source=sourceAdd, opts = {'sequence':sequence2, 'operations':[op_ct,]}) envelope_ct = Te(tx=tx_ct, opts={"network_id": "PUBLIC"}) kp = Keypair.from_seed(Trezor_Access()) envelope_ct.sign(kp) #sign xdr2 = envelope_ct.xdr() response=horizon.submit(xdr2) #submit passed_or_not(response) return response
def sending_lumen(desAdd,amount): '''Using trezor to send lumen. The required parameters are desAdd:string, the address of the lumen receiver. amount:float, the amount of lumens you want to send. msg:string, optional, the message you want to attach to the transaction.''' data = Get_data() sourceAdd = data['Address'] asset = Asset('XLM') sequence = horizon.account(sourceAdd).get('sequence') op = Payment({'asset':asset,'amount':str(amount),'destination':desAdd}) tx = Transaction(source=sourceAdd,opts={'sequence':sequence,'operations':[op]}) envelope_send = Te(tx = tx,opts = {"network_id":"PUBLIC"}) try: envelope_send.sign(Trezor_Access()) except: raise Exception("Device is currently in use, try reconnect the device") xdr = envelope_send.xdr() xdr = xdr.decode() response = horizon.submit(xdr) passed_or_not(response) return response
def create_pool_account(horizon, network_id, account_secret_key, pool_keypair): funding_account_kp = None try: funding_account_kp = Keypair.from_seed(account_secret_key) except DecodeError: logger.error("Invalid secret key, aborting") return False creation_operation = CreateAccount({ "destination": pool_keypair.address().decode(), "starting_balance": STARTING_BALANCE }) sequence = horizon.account( funding_account_kp.address().decode()).get('sequence') tx = Transaction( source=funding_account_kp.address().decode(), opts={ 'sequence': sequence, 'operations': [creation_operation], }, ) envelope = Te(tx=tx, opts={"network_id": network_id}) envelope.sign(funding_account_kp) xdr = envelope.xdr() response = horizon.submit(xdr) if "_links" in response: logger.debug("Creation of account transaction link: %s" % ( response["_links"]["transaction"]["href"],)) return True else: logger.error("Failed to create account. Dumping response:") logger.error(response) return False
def send(self): """ The method to make a payment """ address = self.receiverInput.get() asset = self.assetChosen.get() quantity = self.quantitySpin.get() valid = True #Verify address if self.address == address: messagebox.showerror("Bad address", "The destination address is your address") valid = False else: addressInfos = self.horizon.account(address) if "status" in addressInfos: messagebox.showerror( "Invalid address", "The address you provided doesn't refer to any account.") valid = False #Verifying that the destination accepts this asset if valid: valid = False if asset == "XLM": valid = True else: for i in addressInfos["balances"]: if i["asset_type"] != "native": #Else getting a Keyerror because the responded dict is different is asset is XLM if i["asset_code"] == asset: valid = True break if not valid: messagebox.showwarning( "Missing trustline", "The destination account hasn't enabled the trustline for this currency" ) #Verifying the quantity if valid: #Getting fresh infos about the user account freshInfos = self.horizon.account(self.address) #Checking the balances, we need greater or equal than the quantity if not XLM (and the XLM as minimum balance), if XLM we need more or greater than #the minimum balance (=1XLM + 0.5 XLM per subentry cf https://galactictalk.org/d/1371-cost-of-a-trustline, and 1 for the minimum balance cf https://www.stellar.org/faq/#_Why_is_there_a_minimum_balance) for i in freshInfos["balances"]: if i["asset_type"] != "native": if i["asset_code"] == asset: if float(i["balance"]) >= 0: valid = True else: messagebox.showerror( "Invalid Quantity", "The quantity you entered doesn't match your balances" ) valid = False else: if asset == "XLM": if float(i["balance"]) - float( quantity) > self.nbOfSubentrys / 2 + 1: valid = True else: messagebox.showerror( "Invalid Quantity", "The quantity you entered doesn't match your balances" ) valid = False else: if float(i["balance"]) > self.nbOfSubentrys / 2 + 1: valid = True else: messagebox.showerror( "Invalid Quantity", "The quantity you entered doesn't match your balances" ) valid = False #If all the verification are passed, we build the tx if valid: #We create the operation if asset != "XLM": #We fetch the issuer of the token issuer = self.horizon.assets({ "asset_code": asset }).get("_embedded").get("records")[0].get( "asset_issuer") #ouch, cf Stellar.org and python SDK asset = Asset(asset, issuer) else: asset = Asset("XLM") operation = Payment({ 'source': self.address, 'destination': address, 'asset': asset, 'amount': quantity }) #MEMO ?? #The sequence of the sender sequence = self.horizon.account(self.address).get("sequence") #Finally the tx tx = Transaction(source=self.address, opts={ 'sequence': sequence, 'operations': [operation] }) #We enveloppe, sign and submit it env = Te(tx=tx, opts={'network_id': 'PUBLIC'}) env.sign(self.kp) response = self.horizon.submit(env.xdr()) if response["status"] == 400: reason = response.get("extras").get("result_codes") messagebox.showerror("Transaction failed", reason.get("operations")) #To update self.run()
# create a memo msg = TextMemo('Buy yourself a beer !') # get sequence of Alice # Python 3 sequence = horizon.account(Alice.address().decode('utf-8')).get('sequence') # Python 2 # sequence = horizon.account(Alice.address()).get('sequence') # construct Tx tx = Transaction( source=Alice.address().decode(), opts={ 'sequence': sequence, # 'timeBounds': [], 'memo': msg, # 'fee': 100, 'operations': [ op, ], }, ) # build envelope #envelope = Te(tx=tx, opts={"network_id": "TESTNET"}) # envelope = Te(tx=tx, opts={"network_id": "PUBLIC"}) for LIVENET # Build a transaction envelope, ready to be signed. envelope = Te(tx=tx, opts={"network_id": "PUBLIC"}) # sign envelope.sign(Alice) # submit
def send_payment(sender_seed, tx): # Generate the sender's Keypair for signing and setting as the source sender_kp = Keypair.from_seed(sender_seed) tx = {key.decode("utf-8"): val.decode("utf-8") for key, val in tx.items()} # Address for the destination destination = tx.get("to") # create op amount = tx.get("amount") if tx.get("currency").upper() == "XLM": asset = Asset("XLM") else: raise UnknownIssuerError("Unknown currency and/or issuer.") # TODO: # Issuer's address # ISSUER = tx.get('issuer') # asset = Asset(tx.get('currency').upper(), ISSUER) op = Payment( # Source is also inferred from the transaction source, so it's optional. source=sender_kp.address().decode(), destination=destination, asset=asset, amount=amount, ) # create a memo msg = TextMemo("Stellar-SMS is dope!!!") horizon = horizon_testnet() # horizon = horizon_livenet() for LIVENET # Get the current sequence of sender sequence = horizon.account( sender_kp.address().decode("utf-8")).get("sequence") # TODO: track sequence locally for better accuracy, speed, and robustness # Construct a transaction tx = Transaction( source=sender_kp.address().decode(), sequence=sequence, # time_bounds = {'minTime': 1531000000, 'maxTime': 1531234600}, memo=msg, fee=100, # Can specify a fee or use the default by not specifying it operations=[op], ) # Build transaction envelope envelope = Te(tx=tx, network_id="TESTNET") # or 'PUBLIC' # Sign the envelope envelope.sign(sender_kp) # Submit the transaction to Horizon! xdr = envelope.xdr() response = horizon.submit(xdr) log.debug(str(response)) if response.get("status") not in [None, 200]: log.error( f"Submission unsuccessful. Horizon retured with error: {response.detail}" ) return log.debug("Transaction was successfully submitted to the network.") return True
def clean(self): """ Override to verify Stellar account using Data Entry added client-side. Decoded data entry must match request.user.id """ # TODO: HANDLE THE RAISED ERRORS TO OUTPUT A VALIDATION ERROR (WORRY ABOUT TAMPERED WITH SIGNATURE ERROR) # Call the super super(AccountCreateForm, self).clean() # Obtain form input parameters creating_stellar = self.cleaned_data.get("creating_stellar") public_key = self.cleaned_data.get("public_key") if creating_stellar: # Creating a new Stellar account # NOTE: https://stellar-base.readthedocs.io/en/latest/quickstart.html#create-an-account request_user = self.request_user # Check current request user is an admin allowed to approve funding if not request_user.is_superuser: raise ValidationError( _('Invalid request. You must be an admin to approve funding of new accounts.' )) # Check that account for this public key does not already exist elif Account.objects.filter(public_key=public_key).exists(): raise ValidationError( _('Invalid public key. This account has already been funded.' )) # Check that a funding request exists for this public key and fetch it try: funding_request = AccountFundRequest.objects.get( public_key=public_key) except ObjectDoesNotExist: funding_request = None raise ValidationError( _('Funding request for this public key does not exist.')) # Make a call to Horizon to fund new account with Nucleo base account horizon = settings.STELLAR_HORIZON_INITIALIZATION_METHOD() # Assemble the CreateAccount operation amount = settings.STELLAR_CREATE_ACCOUNT_MINIMUM_BALANCE memo = TextMemo('Nucleo Created Account') op_create = CreateAccount({ 'destination': public_key, 'starting_balance': amount, }) # Get the current sequence of the source account by contacting Horizon. # TODO: You should also check the response for errors! sequence = horizon.account( settings.STELLAR_BASE_KEY_PAIR.address()).get('sequence') # Create a transaction with our single create account operation, with the # default fee of 100 stroops as of this writing (0.00001 XLM) tx = Transaction( source=settings.STELLAR_BASE_KEY_PAIR.address().decode(), opts={ 'sequence': sequence, 'memo': memo, 'operations': [op_create], }, ) # Build a transaction envelope, ready to be signed. envelope = Te(tx=tx, opts={"network_id": settings.STELLAR_NETWORK}) # Sign the transaction envelope with the source keypair envelope.sign(settings.STELLAR_BASE_KEY_PAIR) # Submit the transaction to Horizon # TODO: Make sure to look at the response body carefully, as it can be an error or a successful response. te_xdr = envelope.xdr() response = horizon.submit(te_xdr) # Check whether account actually created on ledger if 'hash' not in response: raise ValidationError( _('Nucleo was not able to create a Stellar account at this time' )) # If successful, increment the user's account quota val by one user_funding = funding_request.requester self.account_user = user_funding profile_funding = user_funding.profile profile_funding.accounts_created += 1 profile_funding.save() # Delete the funding request funding_request.delete() # Email the requester to notify them of approved funding for new account profile_path = reverse('nc:user-detail', kwargs={'slug': user_funding.username}) profile_url = build_absolute_uri(self.request, profile_path) current_site = get_current_site(self.request) email_settings_path = reverse('nc:user-settings-redirect') email_settings_url = build_absolute_uri(self.request, email_settings_path) ctx_email = { 'current_site': current_site, 'username': user_funding.username, 'account_public_key': public_key, 'profile_url': profile_url, 'email_settings_url': email_settings_url, } get_adapter(self.request).send_mail('nc/email/account_create', user_funding.email, ctx_email) else: # Verify Stellar public key with the added Data Entry # Get the Stellar account for given public key # NOTE: Need to decouple Address initialization from get() method to work! address = Address(address=public_key, network=settings.STELLAR_NETWORK) try: address.get() except AccountNotExistError: raise ValidationError(_( 'Invalid account. Stellar Account associated with given public key does not exist.' ), code='invalid_account') # Obtain the signed_user data entry signed_user = address.data.get( settings.STELLAR_DATA_VERIFICATION_KEY, None) if not signed_user: raise ValidationError(_( 'Invalid data entry. Decoded Stellar Data Entry does not exist.' ), code='invalid_data_entry') # Now decode and verify data self.loaded_user_id = signing.loads(base64.b64decode(signed_user)) if self.request_user.id != self.loaded_user_id: raise ValidationError(_( 'Invalid user id. Decoded Stellar Data Entry does not match your user id.' ), code='invalid_user') # Associate request user with this account self.account_user = self.request_user # Delete any existing funding request associated with that key AccountFundRequest.objects.filter(public_key=public_key).delete() # TODO: SEND EMAIL IF KEY HAS BEEN COMPROMISED, SOMEHOW ALLOW UNFUNDED ACCOUNTS TO BE SEEN? return self.cleaned_data
def transfer_receive(tmp_address, receiver_kp, asset): """ Receive a transfer. This is used by a wallet on behalf of the receiving user to pull the new asset in. When it's done the receiving account has all of the asset from tmp_address, and all of the XLM reserve required to perform the transfer. Args: tmp_address (string): address of temporary account containing the transfer asset receiver_kp (Keypair): Keypair for the (optionally created) receiving account asset (Asset): asset to receive Returns: response: the Horizon response """ account_exists = False receiver_address = receiver_kp.address() receiver_acct = Address(receiver_address) try: receiver_acct.get() account_exists = True except stellar_base.exceptions.HorizonError: pass needs_trustline = True if account_exists: for b in receiver_acct.balances: if balance_to_asset(b) == asset: needs_trustline = False break tmp_acct = Address(tmp_address) tmp_acct.get() # assumes that the temp account cointains the specified asset amount = [ b['balance'] for b in tmp_acct.balances if balance_to_asset(b) == asset ][0] operations = [] if not account_exists: operations.append(CreateAccount(receiver_address, "1")) if needs_trustline: operations.extend([ # enough for trustline and one offer Payment(receiver_address, Asset.native(), "1"), ChangeTrust(asset, source=receiver_kp.address()) ]) else: operations.append( # enough for one offer Payment(receiver_address, Asset.native(), "0.5"), ) operations.extend([ # Send Asset Payment(receiver_address, asset, amount), # Clear signers SetOptions(signer_weight=0, signer_address=receiver_address), # Clear trustlines ChangeTrust(asset, "0"), # Merge Account AccountMerge(receiver_address) ]) txn = Transaction(source=tmp_acct.address, sequence=tmp_acct.sequence, fee=100 * len(operations), operations=operations) txe = Te(tx=txn, network_id="TESTNET") txe.sign(receiver_kp) # Potentially the issuer needs to sign this too with an allow trust -- # depends on the asset in question! response = horizon.submit(txe.xdr()) return response
def main(inflation): # TODO: Let user select the connection type # The stellar/quickstart Docker image uses PostgreSQL logger.debug("Connecting to database...") conn = sqlite3.connect(db_address) logger.debug("Connected!") logger.debug("Getting the next transaction sequence number...") sequence = horizon.account(pool_address).get('sequence') logger.debug("Done! - Transaction Sequence: %(sequence)s") inflation = XLM_Decimal(inflation) transactions = [] total_payments_cost = 0 num_payments = 0 total_fee_cost = 0 logger.debug("Processing account batches...") # Create one transaction for each batch for batch in accounts_payouts(conn, pool_address, inflation): logger.debug("\tProcessing %(batch.aid)s with %(batch.amount)s") op_count = 0 ops = {'sequence': sequence, 'operations': []} for aid, amount in batch: # Include payment operation on ops{} paymentOp = make_payment_op(aid, amount) logger.debug("\t\t\Created Payment %(paymentOp)s") ops['operations'].append(paymentOp) op_count += 1 logger.debug("\t\tBuilding Transaction...") tx = Transaction(source=pool_address, opts=ops) tx.fee = op_count * BASE_FEE envelope = Te(tx=tx, opts={"network_id": network}) # Append the transaction plain-text (JSON) on the list transaction = envelope.xdr().decode("utf-8") logger.debug("\t\tTransaction Created") logger.debug("\t\t%(transaction)s") transactions.append(transaction) # Calculate stats total_fee_cost += XLM_Decimal(tx.fee) / XLM_STROOP total_payments_cost += sum( [XLM_Decimal(payment.amount) for payment in tx.operations]) num_payments += len(tx.operations) # Prepare the next sequence number for the transactions sequence = int(sequence) + 1 logger.info(("Stats: \n" "\tInflation received: %s\n" "\tA total of %s XLM paid over %s inflation payments " "using %s XLM in fees. \n" "\tNumber of unique donation addresses: %s\n") % ( inflation, total_payments_cost, num_payments, total_fee_cost, len(donation_payouts), )) with open("transactions.json", 'w') as outf: json.dump(transactions, outf) logger.info("Output to transactions.json")
set_home_domain_op = SetOptions(home_domain='fed.network') # create a memo msg = TextMemo('Buy yourself a beer!') # get sequence of Alice # Python 3 sequence = horizon.account(Alice.address().decode('utf-8')).get('sequence') # Python 2 # sequence = horizon.account(Alice.address()).get('sequence') operations = [payment_op, set_home_domain_op] # construct Tx tx = Transaction( source=Alice.address().decode(), sequence=int(sequence), # time_bounds = {'minTime': 1531000000, 'maxTime': 1531234600}, memo=msg, fee=100 * len(operations), operations=operations, ) # build envelope envelope = Te(tx=tx, network_id="TESTNET") # envelope = Te(tx=tx, network_id="PUBLIC") for LIVENET # sign envelope.sign(Alice) # submit xdr = envelope.xdr() response = horizon.submit(xdr) print(response)
def main(inflation): # TODO: Let user select the connection type # The stellar/quickstart Docker image uses PostgreSQL conn = sqlite3.connect(db_address) cur = conn.cursor() # Get the next sequence number for the transactions sequence = query_one(cur, select_sequence_num, (pool_address, )) inflation = XLM_Decimal(inflation) transactions = [] num_payments = 0 total_payments_cost = XLM_Decimal(0) total_fee_cost = XLM_Decimal(0) batches, total_balance, num_accounts = accounts_payouts(cur, pool_address, inflation) num_batches = len(batches) dest_sequence = sequence + num_batches i = 1.0 # Create one transaction for each batch for batch in batches: writeflushed("\rCreating and encoding transactions: %d%%" % (i / num_batches * 100)) i += 1 operations = [] for aid, amount in batch: # Include payment operation on ops{} payment_op = make_payment_op(aid, amount) operations.append(payment_op) total_payments_cost += amount # Build transaction tx = Transaction( source=pool_address, opts={"sequence": sequence, "operations": operations, "fee": len(batch) * BASE_FEE} ) # Bundle transaction into an envelope to be encoded to xdr envelope = Te(tx=tx, opts={"network_id": network}) # Append the transaction plain-text (JSON) on the list transactions.append(envelope.xdr().decode("utf-8")) # Calculate stats total_fee_cost += tx.fee num_payments += len(operations) # Prepare the next sequence number for the transactions sequence += 1 with open("transactions.json", 'w') as outf: json.dump(transactions, outf) writeflushed("\rSuccessfully built transaction file: written to transactions.json.\n\n") print(( "Stats: \n" "\tInflation received: %s\n" "\tNumber of accounts voting for Lumenaut: %d (%s XLM)\n" "\tA total of %s XLM paid over %s inflation payments using %s XLM in fees.\n" "\tNumber of transactions needed: %s\n" "\tNumber of unique donation addresses: %s\n") % ( inflation, num_accounts, (total_balance / XLM_STROOP), total_payments_cost, num_payments, (total_fee_cost / XLM_STROOP), len(transactions), len(donation_payouts) ) )