Skip to content

Commit

Permalink
Python mobius SDK
Browse files Browse the repository at this point in the history
  • Loading branch information
Your Name committed Jul 15, 2018
0 parents commit 237f4cd
Show file tree
Hide file tree
Showing 23 changed files with 680 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# mobius-client-python
Empty file.
Empty file.
106 changes: 106 additions & 0 deletions mobius_client_python/app/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from stellar_base.builder import Builder
from stellar_base.operation import Payment

from ..client import Client

class App(object):
def __init__(self, app_account, user_account):
self.app_account = app_account
self.user_account = user_account
self.client_instance = Client().get_horizon_client()

def authorized(self):
return self.user_account.authorized(self.app_keypair())

def app_account(self):
return self.app_account

def app_balance(self):
return self.app_account.balance()

def app_keypair(self):
keypair = self.app_account.get_keypair()
if not keypair:
keypair = self.user_account.keypair
return keypair

def user_account(self):
return self.user_account.get_keypair()

def user_balance(self):
self.validate_user_balance()

return self.user_account.balance()

def user_keypair(self):
keypair = self.user_account.get_keypair()
if not keypair:
keypair = self.user_account.keypair
return keypair
def charge(self, amount, destination=None):
if self.user_balance() < float(amount):
raise Exception('Insufficient Funds')

tx = self.submit_tx()

if destination:
return tx.add_operation(self.app_payment_op(amount,destination))
else:
return tx.add_operation(self.user_payment_op(amount,self.app_keypair().address().decode()))

def payout(self, amount, asset, destination=None):

if not destination:
destination = self.user_keypair().address().decode()

if self.app_balance(asset) < float(amount):
raise Exception('Insufficient Funds')

tx = self.submit_tx()

return tx.add_operation(self.app_payment_op(amount,destination))

def transfer(self, amount, destination):
if self.user_balance() < float(amount):
raise Exception('Insufficient Funds')

tx = self.submit_tx()
return tx.add_operation(self.user_payment_op(amount,destination))

def submit_tx(self):
builder = Builder(address=self.user_account.get_info()['account_id'])

te = builder.gen_te()

te.sign(self.app_keypair())

response = self.client_instance.submit(te)

self.reload()

return te.tx

def reload(self):
return self.app_account.reload() and self.user_account.reload()


def user_payment_op(self, amount, destination):
return Payment(opts={'destination': destination,
'amount': amount,
'asset': Client().get_stellar_asset(),
'source': self.user_keypair().address().decode()
})

def app_payment_op(self, amount, destination):
return Payment(opts={'destination': destination,
'amount': amount,
'asset': Client().get_stellar_asset(),
'source': self.app_keypair().address().decode()
})

def validate_user_balance(self):
if not self.authorized():
raise Exception('Authorisation missing')

if not self.user_account.trust_line_exsists():
raise Exception('Trustline not found')
14 changes: 14 additions & 0 deletions mobius_client_python/app/app_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from stellar_base.keypair import Keypair

from ..blockchain.account_builder import AccountBuilder
from .app import App

class AppBuilder(object):
def build(self, developer_secret, address):
dev_keypair = Keypair.from_seed(developer_secret)
dev_account = AccountBuilder().build(dev_keypair)

user_keypair = Keypair.from_address(address)
user_account = AccountBuilder().build(user_keypair)

return App(dev_account,user_account)
Empty file.
63 changes: 63 additions & 0 deletions mobius_client_python/auth/challenge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from stellar_base.transaction_envelope import TransactionEnvelope
from stellar_base.operation import Operation, CreateAccount
from stellar_base.transaction import Transaction
from stellar_base.memo import TextMemo,NoneMemo
from stellar_base.keypair import Keypair
from stellar_base.builder import Builder
from stellar_base.asset import Asset

import math
import random
import datetime

# Helper object for timestamps
class Time(object):
def __init__(self, min_time=0, max_time=0):
self.minTime = int(min_time)
self.maxTime = int(max_time)

# Generates challenge transaction on developer's side.
class Challenge(object):
def __init__(self, developer_secret, expires_in):

if type(expires_in) is not datetime.datetime:
raise TypeError('expires_in must be a datetime.datetime object, not a %s' % type(expires_in))

self.developer_secret = developer_secret
self.expires_in = expires_in

def call(self):
keypair = self.keypair()

builder = Builder(address=keypair.address().decode(),
sequence=self.random_sequence())

builder.add_memo(self.memo())
builder.add_time_bounds(self.build_time_bounds())

builder.append_payment_op(source=Keypair.random(),
destination=keypair.address().decode(),
amount='0.000001')
builder.sign(keypair.seed())

te = builder.gen_te()

return te.xdr()

def keypair(self):
return Keypair.from_seed(self.developer_secret)

def random_sequence(self):
max_seq_number = 2 ** 128 - 1
limit = 65535
return max_seq_number - random.randint(1,limit)

def build_time_bounds(self):
min_time = datetime.datetime.now()
min_time = min_time.timestamp()
max_time = min_time + self.expires_in.timestamp()
return Time(min_time,max_time)

def memo(self):
# TODO : should return TextMemo('Mobius authentication') but for some reason it throws error.
return NoneMemo()
19 changes: 19 additions & 0 deletions mobius_client_python/auth/jwt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import jwt

class Jwt(object):
def __init__(self,secret):
self.ALG = 'HS512'
self.secret = secret

def encode(self, token):
payload = {
'hash': token.hash('hex'),
'public_key': token.address,
'min_time': token.bounds[0].minTime,
'max_time': token.bounds[0].maxTime,
}

return jwt.encode(payload,self.secret,self.ALG)

def decode(self, value):
return jwt.decode(value.decode(),self.secret,algorithms=[self.ALG])
36 changes: 36 additions & 0 deletions mobius_client_python/auth/sign.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from stellar_base.transaction_envelope import TransactionEnvelope
from stellar_base.transaction import Transaction
from stellar_base.keypair import Keypair
from stellar_base.builder import Builder

from ..utils.keypair import verify

# Signs challenge transaction on user's side.
class Sign(object):
def __init__(self, user_secret, te_xdr, address):
self.user_secret = user_secret # seed
self.xdr = te_xdr # 'TransactionEnvelope' object xdr
self.address = address # Developer public key

def call(self):
te = TransactionEnvelope.from_xdr(xdr=self.xdr)
keypair = self.keypair()
dev_keypair = self.dev_keypair()
self.validate(dev_keypair,te)

te.sign(keypair)

return te.xdr()

def keypair(self):
return Keypair.from_seed(self.user_secret)

def dev_keypair(self):
return Keypair.from_address(self.address)

def validate(self, keypair, te):
is_valid = verify(te,keypair)
if is_valid == False:
raise Exception("Wrong challenge transaction signature")
else:
return True
79 changes: 79 additions & 0 deletions mobius_client_python/auth/token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from stellar_base.transaction_envelope import TransactionEnvelope
from stellar_base.transaction import Transaction
from stellar_base.keypair import Keypair

from ..utils.keypair import verify
from ..client import Client

import datetime
import binascii

class Token(object):
def __init__(self, developer_secret, te_xdr, address):
self.developer_secret = developer_secret
self.te = TransactionEnvelope.from_xdr(te_xdr)
self.tx = self.te.tx
self.address = address
self.keypair = self.keypair()
self.user_keypair = self.user_keypair()

def time_bounds(self):
self.bounds = self.tx.time_bounds

if not self.bounds:
raise Exception("Malformed Transaction")

return self.bounds

def hash(self, format='binary'):
self.validate()

hash_meta = self.te.hash_meta()
if format == 'binary':
return hash_meta

return binascii.hexlify(hash_meta).decode()

def envelope(self, xdr):
return TransactionEnvelope.from_xdr(xdr)

def validate(self, strict=True):
if not self.signed_correctly():
raise Exception('Wrong challenge transaction signature')

bounds = self.time_bounds()

if self.time_now_covers(bounds):
raise Exception('Challenge transaction expired')

if strict and self.too_old(bounds):
raise Exception('Challenge transaction expired')

return True

def keypair(self):
return Keypair.from_seed(self.developer_secret)

def user_keypair(self):
return Keypair.from_address(self.address)

def signed_correctly(self):
signed_by_dev = verify(self.te,self.keypair)
signed_by_user = verify(self.te,self.user_keypair)
return signed_by_dev and signed_by_user

def time_now_covers(self,time_bounds):
min_time = time_bounds[0].minTime
max_time = time_bounds[0].maxTime
now = datetime.datetime.now().timestamp()

if min_time > now and max_time < now:
return True
else:
return False

def too_old(self,time_bounds):
min_time = time_bounds[0].minTime
now = datetime.datetime.now() - datetime.timedelta(seconds=Client.strict_interval)
now = now.timestamp()
return now > min_time
Empty file.
66 changes: 66 additions & 0 deletions mobius_client_python/blockchain/account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from stellar_base.keypair import Keypair
from ..client import Client

class Account(object):
def __init__(self, account, keypair):
self.account = account
self.keypair = keypair
self.asset_issuers = []
self.client = Client()
self.client_instance = self.client.get_horizon_client()

def reload(self):
try:
self.account = None
public_key = self.keypair.address().decode()
account = self.client_instance.account(address=public_key)
self.account = account
except Exception as error:
return False

return True

def trust_line_exsists(self,asset=None):
if not asset:
asset = self.client.get_stellar_asset()
balance = dict(self.find_balance(asset))
return balance and float(balance['limit']) > 0

def next_sequence_value(self):
self.reload()
account = self.get_info()
return int(account['sequence']) + 1

def balance(self,asset=None):
if not asset:
asset = self.client.get_stellar_asset()
return float(self.find_balance(asset)['balance'])

def get_keypair(self):
return self.keypair

def get_info(self):
return self.account

def authorized(self,keypair):
return self.find_signer(public_key=keypair.address().decode())

def balance_match(self, asset, balance):
balance = dict(balance) # Bugfix for python 3.5
if asset.is_native():
return balance['asset_type'] == 'native'
else:
asset_code = balance['asset_code']
issuer = balance['asset_issuer']
asset_issuer_addr = Keypair.from_address(asset.issuer).address().decode()
return asset_code == asset.code and issuer == asset_issuer_addr

def find_balance(self,asset):
for item in self.account['balances']:
if self.balance_match(asset,item) == True:
return item

def find_signer(self,public_key):
for item in self.account['signers']:
if item['public_key'] == public_key:
return item
Loading

0 comments on commit 237f4cd

Please sign in to comment.