def create_tx(from_, to, amount, currency, action="send"): log.debug("Creating transaction.. ") user = User.query.filter_by(phone_number=from_).first() if user is None: log.error("User does not exist.") return if currency is None or currency == "": currency = "XLM" conn = redis.Redis.from_url(REDIS_URL) tx_key = "tx:" + from_ tx = { "from": from_.strip(), "to": to.strip(), "amount": amount.strip(), "currency": currency.strip(), } conn.hmset(tx_key, tx) payment = Payment( destination=tx["to"], amount=tx["amount"], asset=tx["currency"], fee="100", sender=user, ) db.session.add(payment) db.session.commit() log.debug(f"Created transaction with key {tx_key} and tx {tx}.") return tx_summary(from_, to, amount, currency)
def process_tx(from_): log.debug("Processing transaction.. ") conn = redis.Redis.from_url(REDIS_URL) tx_key = "tx:" + from_ tx = conn.hgetall(tx_key) user = User.query.filter_by( phone_number=tx.get(b"from").decode("utf-8")).first() sender_seed = user.keypair_seed log.debug("Transaction is ready for submission.") # lock tx to ensure only 1 worker is working on the tx at a given time # and watch for any changes made to the tx by another process during submission lockname = "lock:" + tx_key lock = acquire_lock(conn, lockname) if not lock: # return if unable to get lock, i.e. if another worker process is working on the tx return pipe = conn.pipeline(True) pipe.watch(tx_key) if send_payment(sender_seed, tx): pipe.multi() pipe.delete(tx_key) pipe.execute() else: pipe.unwatch() release_lock(conn, lockname, lock)
def address_lookup(from_, username): try: user, address = (db.session.query( User, Address).filter(User.id == Address.user_id).filter( Address.username == username).first()) return address.address except Exception as e: log.debug(f"error during address lookup - {e}") return None
def incoming_sms(): try: message = request.values.to_dict() log.debug(message) resp_body = sms_handler(request.values) except Exception as e: log.error(e) return str(MessagingResponse()) resp = MessagingResponse() resp.message(resp_body) return str(resp)
def sms_handler(message): log.debug(f'Received message - {message.get("Body")}') body = message.get("Body").strip().lower() from_ = message.get("From") if tx_pending(from_): if body in ["y", "yes"]: otp = otp_required(from_) if otp: return "Incorrect password. Your transaction has been canceled." process_tx.delay(from_) return "Your transaction has been submitted." if check_otp(from_, body): process_tx.delay(from_) return "Your transaction has been submitted." return "Your transaction has been canceled." return sms_parser(from_, body)
def sms_parser(from_, sms): log.debug(f"Parsing sms - {sms}") match = SEND_PATTERN.match(sms) if not match: log.debug("sms does not match send pattern regex.") return "Invalid transaction. Please try again." if len(match.group(2)) == 56: # stellar address (public key) log.debug("sms contains a Stellar address.") create_tx.delay( from_=from_, to=match.group(2), amount=match.group(3), currency=match.group(4), ) return "" address = address_lookup(from_, match.group(2)) if address: log.debug("Address lookup successful.") create_tx.delay(from_=from_, to=address, amount=match.group(3), currency=match.group(4)) return "" return "Invalid address. Please try again."
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