Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion install.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os

os.system("pip install -r requirements.txt")
os.system("cp local_settings_default.py multiexplorer/multiexplorer/local_settings.py")
os.system(
"cp local_settings_default.py multiexplorer/multiexplorer/local_settings.py")
os.system("python multiexplorer/manage.py migrate")
os.system("python multiexplorer/manage.py createcachetable")
3 changes: 3 additions & 0 deletions multiexplorer/exchange/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.contrib import admin
from .models import ExchangeCurrency, ExchangeMasterKey, ExchangeAddress


class ExchangeMasterKeyAdmin(admin.ModelAdmin):
list_display = ("created", 'notes', 'balances')
readonly_fields = ('xpriv', )
Expand All @@ -16,6 +17,7 @@ def balances(self, master_key):
return balances
balances.allow_tags = True


class ExchangeCurrencyAdmin(admin.ModelAdmin):
list_display = ("get_currency_display", 'balance', 'deposit')
readonly_fields = ('balance', 'deposit_qrcodes')
Expand Down Expand Up @@ -45,6 +47,7 @@ def deposit_qrcodes(self, currency):
)
deposit_qrcodes.allow_tags = True


class ExchangeAddressAdmin(admin.ModelAdmin):
list_display = ('deposit_currency', 'withdraw_address')

Expand Down
60 changes: 40 additions & 20 deletions multiexplorer/exchange/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
from multiexplorer.utils import get_wallet_currencies
from multiexplorer.models import CachedTransaction

SUPPORTED_CURRENCIES = [(x['bip44'], "%(code)s - %(name)s" % x) for x in get_wallet_currencies()]
SUPPORTED_CURRENCIES = [(x['bip44'], "%(code)s - %(name)s" % x)
for x in get_wallet_currencies()]


class ExchangeMasterKey(models.Model):
created = models.DateTimeField(default=timezone.now)
Expand All @@ -25,7 +27,8 @@ class Meta:

def save(self, *args, **kwargs):
if not self.xpriv:
seed_words = self.from_passphrase and self.from_passphrase.encode('ascii')
seed_words = self.from_passphrase and self.from_passphrase.encode(
'ascii')
self.xpriv = bip32_master_key(seed=seed_words or os.urandom(100))
self.from_passphrase = ''

Expand All @@ -44,15 +47,17 @@ def cached_balance(self, currency):
for utxo in utxos:
balance += utxo['amount']

return balance / 1e8 # convert satoshis to currency units
return balance / 1e8 # convert satoshis to currency units

def get_unused_addresses(self, currency, length=5):
i = 0
addresses = []
while len(addresses) < length:
address, priv_key = currency.derive_deposit_address(i, self)
is_used1 = ManualDeposit.objects.filter(address=address, currency=currency).exists()
is_used2 = ExchangeAddress.objects.filter(deposit_address=address, deposit_currency__code=currency).exists()
is_used1 = ManualDeposit.objects.filter(
address=address, currency=currency).exists()
is_used2 = ExchangeAddress.objects.filter(
deposit_address=address, deposit_currency__code=currency).exists()

if not is_used1 and not is_used2:
addresses.append(address)
Expand All @@ -62,18 +67,22 @@ def get_unused_addresses(self, currency, length=5):

FIAT_CHOICES = [(x, x.upper()) for x in settings.WALLET_SUPPORTED_FIATS]


class ExchangeCurrency(models.Model):
currency = models.IntegerField(choices=SUPPORTED_CURRENCIES, primary_key=True)
currency = models.IntegerField(
choices=SUPPORTED_CURRENCIES, primary_key=True)
fee_percentage = models.FloatField(default=0.01)
code = models.CharField(max_length=5, null=True, blank=True)
max_fiat_deposit = models.FloatField(default=10)
max_fiat_currency = models.CharField(max_length=4, choices=FIAT_CHOICES, default='usd')
max_fiat_currency = models.CharField(
max_length=4, choices=FIAT_CHOICES, default='usd')

@classmethod
def get_active(cls, code):
obj = cls.objects.get(code=code)
if obj.balance == 0:
raise cls.DoesNotExist("%s is temporairly out of order" % code.upper())
raise cls.DoesNotExist(
"%s is temporairly out of order" % code.upper())
return obj

def __unicode__(self):
Expand Down Expand Up @@ -113,7 +122,8 @@ def get_bip44_master(self, master_key):
return bip32_ckd(bip32_ckd(master_key.xpriv, 44), self.currency)

def _derive_address(self, change_or_deposit, index, master_key):
xpriv = bip32_ckd(bip32_ckd(self.get_bip44_master(master_key), change_or_deposit), index)
xpriv = bip32_ckd(bip32_ckd(self.get_bip44_master(
master_key), change_or_deposit), index)
priv = bip32_extract_key(xpriv)
address = privtoaddr(priv, self.get_address_byte())
return address, priv
Expand Down Expand Up @@ -174,13 +184,16 @@ class ExchangeAddress(models.Model):
last_kick = models.DateTimeField(default=None, null=True)

# the user sends funds to this address (address generated by us)
deposit_currency = models.ForeignKey(ExchangeCurrency, related_name="incoming_exchanges")
deposit_currency = models.ForeignKey(
ExchangeCurrency, related_name="incoming_exchanges")
deposit_address = models.CharField(max_length=128)

exchange_rate = models.FloatField(null=True)

# we send the exchanged funds to this address (address supplied by the user)
withdraw_currency = models.ForeignKey(ExchangeCurrency, related_name="outgoing_exchanges")
# we send the exchanged funds to this address (address supplied by the
# user)
withdraw_currency = models.ForeignKey(
ExchangeCurrency, related_name="outgoing_exchanges")
withdraw_address = models.CharField(max_length=128, blank=True)

def __unicode__(self):
Expand All @@ -192,29 +205,35 @@ def seconds_to_needing_kick(self):
return (settings.EXCHANGE_KICK_INTERVAL_MINUTES * 60) - since_last_kick

def kick(self):
if self.last_kick: # already been kicked
if self.last_kick: # already been kicked
if self.seconds_to_needing_kick() < 0:
return # kick not needed
return # kick not needed

if not self.withdraw_address:
master_key = ExchangeMasterKey.objects.latest()
self.withdraw_address = master_key.get_unused_addresses(self.withdraw_currency, 1)[0]
self.withdraw_address = master_key.get_unused_addresses(
self.withdraw_currency, 1)[0]

if not self.deposit_address:
master_key = ExchangeMasterKey.objects.latest()
self.deposit_address = master_key.get_unused_addresses(self.deposit_currency, 1)[0]
self.deposit_address = master_key.get_unused_addresses(
self.deposit_currency, 1)[0]

self.last_kick = timezone.now()
self.exchange_rate = self.calculate_exchange_rate()
self.save()

def calculate_exchange_rate(self):
deposit_price = get_current_price(crypto=self.deposit_currency.code, fiat='usd')
withdraw_price = get_current_price(crypto=self.withdraw_currency.code, fiat='usd')
deposit_price = get_current_price(
crypto=self.deposit_currency.code, fiat='usd')
withdraw_price = get_current_price(
crypto=self.withdraw_currency.code, fiat='usd')
raw_rate = deposit_price / withdraw_price
rate_with_fee = raw_rate * (1 + (settings.EXCHANGE_FEE_PERCENTAGE / 100.0))
rate_with_fee = raw_rate * \
(1 + (settings.EXCHANGE_FEE_PERCENTAGE / 100.0))
return rate_with_fee


class ExchangeWithdraw(models.Model):
exchange = models.ForeignKey(ExchangeAddress)
deposit_txid = models.TextField()
Expand All @@ -230,6 +249,7 @@ def withdraw_amount(self):

def make_payment(self):
tx = Transaction(crypto=self.withdraw_currency.code)
tx.add_inputs(self.exchange.withdraw_currency.pop_utxos(self.withdraw_amount))
tx.add_inputs(self.exchange.withdraw_currency.pop_utxos(
self.withdraw_amount))
tx.add_output(self.withdraw_amount, to_address)
return tx.push_tx()
11 changes: 8 additions & 3 deletions multiexplorer/exchange/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
from django.contrib import admin
from django.views.generic import TemplateView

from views import (
home, create_exchange
)
try:
from views import (
home, create_exchange
)
except ImportError:
from .views import (
home, create_exchange
)

urlpatterns = [
url(r'^$', home, name="exchange"),
Expand Down
77 changes: 43 additions & 34 deletions multiexplorer/exchange/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,63 @@
from django import http
from django.conf import settings
from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import redirect

from models import ExchangeCurrency, ExchangeAddress
try:
from models import ExchangeCurrency, ExchangeAddress
except ImportError:
from .models import ExchangeCurrency, ExchangeAddress

from multiexplorer.utils import get_wallet_currencies

crypto_data = get_wallet_currencies()
crypto_data_json = json.dumps(crypto_data)


def home(request):
return TemplateResponse(request, "exchange_home.html", {
'supported_cryptos': ExchangeCurrency.objects.all(),
'crypto_data': crypto_data_json,
'ENABLE_EXCHANGE': settings.ENABLE_EXCHANGE
})


@csrf_exempt
def create_exchange(request):
deposit_code = request.POST['deposit_code']
withdraw_address = request.POST['withdraw_address']
withdraw_code = request.POST['withdraw_code']
error = None

try:
withdraw_currency = ExchangeCurrency.get_active(code=withdraw_code)
except ExchangeCurrency.DoesNotExist as exc:
error = 'withdraw'
error_msg = exc.__str__

try:
deposit_currency = ExchangeCurrency.get_active(code=deposit_code)
except ExchangeCurrency.DoesNotExist as exc:
error = 'deposit'
error_msg = exc.__str__

if error:
if request.method == "POST":
deposit_code = request.POST['deposit_code']
withdraw_address = request.POST['withdraw_address']
withdraw_code = request.POST['withdraw_code']
error = None

try:
withdraw_currency = ExchangeCurrency.get_active(code=withdraw_code)
except ExchangeCurrency.DoesNotExist as exc:
error = 'withdraw'
error_msg = exc.__str__

try:
deposit_currency = ExchangeCurrency.get_active(code=deposit_code)
except ExchangeCurrency.DoesNotExist as exc:
error = 'deposit'
error_msg = exc.__str__

if error:
return http.JsonResponse({
'error': error_msg
}, status=400)

exchange, created = ExchangeAddress.objects.get_or_create(
deposit_currency=deposit_currency,
withdraw_address=withdraw_address,
withdraw_currency=withdraw_currency
)
exchange.kick()

return http.JsonResponse({
'error': error_msg
}, status=400)

exchange, created = ExchangeAddress.objects.get_or_create(
deposit_currency=deposit_currency,
withdraw_address=withdraw_address,
withdraw_currency=withdraw_currency
)
exchange.kick()

return http.JsonResponse({
'deposit_address': exchange.deposit_address,
'exchange_rate': exchange.exchange_rate,
'seconds_left': exchange.seconds_to_needing_kick(),
})
'deposit_address': exchange.deposit_address,
'exchange_rate': exchange.exchange_rate,
'seconds_left': exchange.seconds_to_needing_kick(),
})
else:
return redirect("exchange")
1 change: 1 addition & 0 deletions multiexplorer/multiexplorer/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .models import CachedTransaction
from django.utils.safestring import mark_safe


class CachedTransactionAdmin(admin.ModelAdmin):
list_display = ("txid", 'crypto', "content_length")
readonly_fields = ('pretty_print', )
Expand Down
7 changes: 5 additions & 2 deletions multiexplorer/multiexplorer/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .utils import datetime_to_iso
from moneywagon import get_single_transaction, get_block


class CachedTransaction(models.Model):
txid = models.CharField(max_length=72, primary_key=True)
content = models.TextField()
Expand All @@ -17,8 +18,10 @@ def fetch_full_tx(cls, crypto, txid, existing_tx_data=None):
transaction = json.loads(tx.content)

if existing_tx_data and existing_tx_data.get('confirmations', None):
# update cached entry with updated confirmations if its available
transaction['confirmations'] = existing_tx_data['confirmations']
# update cached entry with updated confirmations if its
# available
transaction['confirmations'] = existing_tx_data[
'confirmations']
tx.content = json.dumps(transaction)
tx.save()
return transaction
Expand Down
23 changes: 16 additions & 7 deletions multiexplorer/multiexplorer/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@
from django.contrib import admin
from django.views.generic import TemplateView

from views import (
home, perform_lookup, single_address, block_lookup, api_docs, address_disambiguation,
onchain_exchange_rates, onchain_status, logout, single_tx
)
try:
from views import (
home, perform_lookup, single_address, block_lookup, api_docs, address_disambiguation,
onchain_exchange_rates, onchain_status, logout, single_tx
)
except ImportError:
from .views import (
home, perform_lookup, single_address, block_lookup, api_docs, address_disambiguation,
onchain_exchange_rates, onchain_status, logout, single_tx
)

admin.site.site_header = 'MultiExplorer Administration'

Expand All @@ -22,12 +28,15 @@
url(r'^tx/(?P<crypto>\w{3,5})/(?P<txid>\w+)', single_tx, name="single_tx"),

url(r'^api$', api_docs, name="api_docs"),
url(r'^api/onchain_exchange_rates', onchain_exchange_rates, name="onchain_exchange_rates"),
url(r'^api/onchain_exchange_rates',
onchain_exchange_rates, name="onchain_exchange_rates"),
url(r'^api/onchain_exchange_status', onchain_status, name="onchain_status"),

url(r'^api/(?P<service_mode>\w+)/(?P<service_id>\w+)', perform_lookup, name="perform_lookup"),
url(r'^api/(?P<service_mode>\w+)/(?P<service_id>\w+)',
perform_lookup, name="perform_lookup"),

url(r'^disambiguation/(?P<address>\w+)', address_disambiguation, name="address_disambiguation"),
url(r'^disambiguation/(?P<address>\w+)',
address_disambiguation, name="address_disambiguation"),

url(r'^wallet/', include('wallet.urls')),
url(r'^exchange/', include('exchange.urls')),
Expand Down
3 changes: 2 additions & 1 deletion multiexplorer/multiexplorer/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ def make_crypto_data_json():
del ret[currency]
continue

ret[currency]['address_version_byte'] = data.get('address_version_byte', None)
ret[currency]['address_version_byte'] = data.get(
'address_version_byte', None)

for mode in service_modes:
services = data.get('services', {}).get(mode, [])
Expand Down
Loading