diff --git a/oss_server/base/urls.py b/oss_server/base/urls.py index e33168a..ed4b21d 100644 --- a/oss_server/base/urls.py +++ b/oss_server/base/urls.py @@ -1,14 +1,6 @@ from django.conf.urls import url -from .v1.views import (CreateLicenseRawTxView, - CreateLicenseTransferRawTxView, - CreateMintRawTxView, - CreateSmartContractRawTxView, - CreateRawTxView, - GetBalanceView, - GetLicenseInfoView, - GetRawTxView, - SendRawTxView) +from .v1.views import * urlpatterns = [ url('^v1/balance/(?P
[a-zA-Z0-9]+)$', GetBalanceView.as_view()), @@ -22,5 +14,6 @@ url('^v1/smartcontract/send$', SendRawTxView.as_view()), url('^v1/transaction/prepare$', CreateRawTxView.as_view()), url('^v1/transaction/send$', SendRawTxView.as_view()), + url('^v1/exchange/prepare$', CreateExchangeRawTxView.as_view()), url('^v1/transaction/(?P[a-z0-9]+)$', GetRawTxView.as_view()), ] diff --git a/oss_server/base/v1/forms.py b/oss_server/base/v1/forms.py index 39ceea6..0acf1d3 100644 --- a/oss_server/base/v1/forms.py +++ b/oss_server/base/v1/forms.py @@ -129,3 +129,52 @@ class CreateLicenseTransferRawTxForm(forms.Form): 'min_value': '`color_id` should be greater than or equal to %(limit_value)s', 'max_value': '`color_id` should be less than or equal to %(limit_value)s' }) + + +class ExchangeRawTxForm(forms.Form): + fee_address = AddressField(error_messages={ + 'required': '`fee_address` is required', + 'invalid': '`fee_address` is not an address' + }) + address1 = AddressField(error_messages={ + 'required': '`address1` is required', + 'invalid': '`address1` is not an address' + }) + address2 = AddressField(error_messages={ + 'required': '`address2` is required', + 'invalid': '`address2` is not an address' + }) + color_id1 = ColorField(error_messages={ + 'required': '`color_id1` is required', + 'invalid': '`color_id1` is invalid', + 'min_value': '`color_id1` should be greater than or equal to %(limit_value)s', + 'max_value': '`color_id1` should be less than or equal to %(limit_value)s' + }) + color_id2 = ColorField(error_messages={ + 'required': '`color_id2` is required', + 'invalid': '`color_id2` is invalid', + 'min_value': '`color_id2` should be greater than or equal to %(limit_value)s', + 'max_value': '`color_id2` should be less than or equal to %(limit_value)s' + }) + amount1 = TxAmountField(error_messages={ + 'required': '`amount1` is required', + 'invalid': '`amount1` is invalid', + 'min_value': '`amount1` should be greater than or equal to %(limit_value)s', + 'max_value': '`amount1` should be less than or equal to %(limit_value)s', + 'max_decimal_places': '`amount1` only allow up to %(max)s decimal digits' + }) + amount2 = TxAmountField(error_messages={ + 'required': '`amount2` is required', + 'invalid': '`amount2` is invalid', + 'min_value': '`amount2` should be greater than or equal to %(limit_value)s', + 'max_value': '`amount2` should be less than or equal to %(limit_value)s', + 'max_decimal_places': '`amount2` only allow up to %(max)s decimal digits' + }) + + def clean(self): + address1 = self.cleaned_data.get('address1') + address2 = self.cleaned_data.get('address2') + + if address1 and address2: + if address1 == address2: + raise forms.ValidationError("`address1` and `address2` can't be the same") diff --git a/oss_server/base/v1/views.py b/oss_server/base/v1/views.py index 4eab34c..daf0469 100644 --- a/oss_server/base/v1/views.py +++ b/oss_server/base/v1/views.py @@ -11,9 +11,8 @@ from gcoinrpc import connect_to_remote from gcoinrpc.exceptions import InvalidAddressOrKey, InvalidParameter +from .forms import * from ..utils import balance_from_utxos, select_utxo, utxo_to_txin -from .forms import (CreateLicenseRawTxForm, CreateLicenseTransferRawTxForm, - CreateSmartContractRawTxForm, MintRawTxForm, RawTxForm) logger = logging.getLogger(__name__) @@ -315,3 +314,68 @@ def _get_license_utxo(self, utxos, color): if utxo['color'] == color: return utxo return None + + +class CreateExchangeRawTxView(View): + + def get(self, request, *args, **kwargs): + form = ExchangeRawTxForm(request.GET) + + if not form.is_valid(): + errors = ', '.join(reduce(lambda x, y: x + y, form.errors.values())) + response = {'error': errors} + return JsonResponse(response, status=httplib.BAD_REQUEST) + + fee_address = form.cleaned_data['fee_address'] + address1 = form.cleaned_data['address1'] + address2 = form.cleaned_data['address2'] + color_id1 = form.cleaned_data['color_id1'] + color_id2 = form.cleaned_data['color_id2'] + amount1 = form.cleaned_data['amount1'] + amount2 = form.cleaned_data['amount2'] + + ins = [] + outs = [] + fee_included = False + + for address, color_id, amount in [(address1, color_id1, amount1), (address2, color_id2, amount2)]: + utxos = get_rpc_connection().gettxoutaddress(address) + + inputs = select_utxo(utxos, color_id, amount) + if not inputs: + return JsonResponse({'error': 'insufficient funds'}, status=httplib.BAD_REQUEST) + + inputs_value = balance_from_utxos(inputs)[color_id] + change = inputs_value - amount + + if color_id == 1 and address == fee_address: + inputs = select_utxo(utxos, color_id, amount + 1) + if not inputs: + return JsonResponse({'error': 'insufficient fee'}, status=httplib.BAD_REQUEST) + fee_included = True + inputs_value = balance_from_utxos(inputs)[color_id] + change = inputs_value - amount - 1 + + if change: + outs.append({'address': address, + 'value': int(change * 10**8), 'color': color_id}) + + ins += [utxo_to_txin(utxo) for utxo in inputs] + + if not fee_included: + fee_address_utxos = get_rpc_connection().gettxoutaddress(fee_address) + inputs = select_utxo(fee_address_utxos, 1, 1) + if not inputs: + return JsonResponse({'error': 'insufficient fee in fee_address'}, status=httplib.BAD_REQUEST) + ins += [utxo_to_txin(utxo) for utxo in inputs] + inputs_value = balance_from_utxos(inputs)[1] + change = inputs_value - 1 + + if change: + outs.append({'address': fee_address, + 'value': int(change * 10**8), 'color': 1}) + + outs.append({'address': address1, 'value': int(amount2 * 10**8), 'color': color_id2}) + outs.append({'address': address2, 'value': int(amount1 * 10**8), 'color': color_id1}) + raw_tx = make_raw_tx(ins, outs) + return JsonResponse({'raw_tx': raw_tx}) diff --git a/oss_server/oss_server/settings/base.py b/oss_server/oss_server/settings/base.py index 25ac048..bf66da7 100644 --- a/oss_server/oss_server/settings/base.py +++ b/oss_server/oss_server/settings/base.py @@ -72,7 +72,7 @@ GCOIN_RPC = { 'user': '', - 'password': '', + 'password': '', 'host': '', 'port': '', } @@ -135,7 +135,7 @@ STATIC_URL = '/static/' -LOG_DIR = os.path.dirname(BASE_DIR) + '/log/' +LOG_DIR = os.path.dirname(BASE_DIR) + '/log/' LOGGING = { 'version': 1, 'disable_existing_loggers': False, diff --git a/requirements.txt b/requirements.txt index f14e024..8c118c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,5 @@ mock==1.0.1 MySQL-python==1.2.5 tornado==4.4.1 -git+ssh://git@github.com/OpenNetworking/gcoin-rpc.git@develop#egg=gcoin-python +git+ssh://git@github.com/OpenNetworking/gcoin-rpc.git@develop git+ssh://git@github.com/OpenNetworking/pygcointools@master