-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Your Name
committed
Jul 15, 2018
0 parents
commit 237f4cd
Showing
23 changed files
with
680 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# mobius-client-python |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.