diff --git a/.coveragerc b/.coveragerc index f1da7b22..ee4a8bda 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,5 +5,4 @@ omit = *settings*, *tests/*, *urls.py, - src/manage.py - src/app/wsgi.py + polaris/manage.py diff --git a/.env.example b/.env.example index 3a6fb24f..e49e4c53 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,6 @@ DJANGO_SECRET_KEY="secretkeykeysecret" DJANGO_DEBUG=True STELLAR_DISTRIBUTION_ACCOUNT_SEED="SCHTHF3N4SHEQM25M43FJ43UTCZP6OO3JKYVJCJBZ4YW6KVVAGC2OUCT" STELLAR_ISSUER_ACCOUNT_ADDRESS="GCTVATNFP4FYKZ7BXZ3EOPVKEL2DGDCB2AVBDUNLW7NYR7REF5PMKY4V" -REDIS_URL="redis://redis:6379" # STELLAR_NETWORK_PASSPHRASE can either be "Test SDF Network ; September 2015" or # "Public Global Stellar Network ; September 2015" or a custom passphrase diff --git a/.gitignore b/.gitignore index af729639..f4213f76 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ share/python-wheels/ MANIFEST *.DS_Store .vscode +.idea # PyInstaller # Usually these files are written by a python script from a template @@ -92,12 +93,6 @@ ipython_config.py # install all needed dependencies. #Pipfile.lock -# celery beat schedule file -celerybeat-schedule - -# celery pid -src/celerybeat.pid - # SageMath parsed files *.sage.py @@ -127,6 +122,3 @@ dmypy.json # Pyre type checker .pyre/ - -# Django static files -src/staticfiles diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 959944d3..00000000 --- a/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -# Python version -FROM python:3.7-alpine - -# Set environment variables -ENV PYTHONDONTWRITEBYTECODE 1 -ENV PYTHONUNBUFFERED 1 - -RUN apk update && apk add build-base postgresql-dev - -# Copy files to working directory -RUN mkdir /code -COPY . /code/ - -# Install dependencies -WORKDIR /code -RUN pip install pipenv -RUN pipenv update -RUN pipenv install - -CMD ["pipenv", "run", "python", "src/manage.py", "runserver", "0.0.0.0:8000"] diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..b7a1135f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +include LICENSE +include README.md +include Pipfile +include .env.example +recursive-include polaris/polaris/templates * +recursive-include polaris/polaris/static * diff --git a/Pipfile b/Pipfile index e3b829b8..e52b6958 100644 --- a/Pipfile +++ b/Pipfile @@ -11,6 +11,7 @@ pytest = "*" pytest-django = "*" pytest-cov = "*" coveralls = "*" +pipenv-setup = "*" [packages] django = "==2.2.4" @@ -20,9 +21,7 @@ djangorestframework = "*" gunicorn = "*" whitenoise = "*" psycopg2-binary = "*" -stellar-sdk = "==2.0.0b3" -celery = "*" -redis = "*" +stellar-sdk = "==2.0.0b4" django-cors-headers = "*" toml = "*" pyjwt = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 52571392..8d1f82d9 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "aa594718c17daf2f474730473e3e91970e946ad7d29ad5a41d0eb66b3ca7ce2a" + "sha256": "13921a27f9b58a98fa2ba8d8ce1005c47f43fa3416b54dd17f6a36f714f813be" }, "pipfile-spec": 6, "requires": { @@ -37,13 +37,6 @@ ], "version": "==0.1.6" }, - "amqp": { - "hashes": [ - "sha256:19a917e260178b8d410122712bac69cb3e6db010d68f6101e7307508aded5e68", - "sha256:19d851b879a471fcfdcf01df9936cff924f422baa77653289f7095dedd5fb26a" - ], - "version": "==2.5.1" - }, "async-timeout": { "hashes": [ "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", @@ -58,21 +51,6 @@ ], "version": "==19.3.0" }, - "billiard": { - "hashes": [ - "sha256:01afcb4e7c4fd6480940cfbd4d9edc19d7a7509d6ada533984d0d0f49901ec82", - "sha256:b8809c74f648dfe69b973c8e660bcec00603758c9db8ba89d7719f88d5f01f26" - ], - "version": "==3.6.1.0" - }, - "celery": { - "hashes": [ - "sha256:821d11967f0f3f8fe24bd61ecfc7b6acbb5a926b719f1e8c4d5ff7bc09e18d81", - "sha256:ae4541fb3af5182bd4af749fee9b89c4858f2792d34bb5d034967e662cf9b55c" - ], - "index": "pypi", - "version": "==4.4.0rc3" - }, "certifi": { "hashes": [ "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", @@ -187,20 +165,6 @@ ], "version": "==2.8" }, - "importlib-metadata": { - "hashes": [ - "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", - "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" - ], - "version": "==0.23" - }, - "kombu": { - "hashes": [ - "sha256:31edb84947996fdda065b6560c128d5673bb913ff34aa19e7b84755217a24deb", - "sha256:c9078124ce2616b29cf6607f0ac3db894c59154252dee6392cdbbe15e5c4b566" - ], - "version": "==4.6.5" - }, "mnemonic": { "hashes": [ "sha256:4e37eb02b2cbd56a0079cabe58a6da93e60e3e4d6e757a586d9f23d96abea931", @@ -208,13 +172,6 @@ ], "version": "==0.19" }, - "more-itertools": { - "hashes": [ - "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", - "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" - ], - "version": "==7.2.0" - }, "multidict": { "hashes": [ "sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f", @@ -273,11 +230,13 @@ "sha256:84156313f258eafff716b2961644a4483a9be44a5d43551d554844d15d4d224e", "sha256:8578d6b8192e4c805e85f187bc530d0f52ba86c39172e61cd51f68fddd648103", "sha256:890167d5091279a27e2505ff0e1fb273f8c48c41d35c5b92adbf4af80e6b2ed6", + "sha256:98e10634792ac0e9e7a92a76b4991b44c2325d3e7798270a808407355e7bb0a1", "sha256:9aadff9032e967865f9778485571e93908d27dab21d0fdfdec0ca779bb6f8ad9", "sha256:9f24f383a298a0c0f9b3113b982e21751a8ecde6615494a3f1470eb4a9d70e9e", "sha256:a73021b44813b5c84eda4a3af5826dd72356a900bac9bd9dd1f0f81ee1c22c2f", "sha256:afd96845e12638d2c44d213d4810a08f4dc4a563f9a98204b7428e567014b1cd", "sha256:b73ddf033d8cd4cc9dfed6324b1ad2a89ba52c410ef6877998422fcb9c23e3a8", + "sha256:b8f490f5fad1767a1331df1259763b3bad7d7af12a75b950c2843ba319b2415f", "sha256:dbc5cd56fff1a6152ca59445178652756f4e509f672e49ccdf3d79c1043113a4", "sha256:eac8a3499754790187bb00574ab980df13e754777d346f85e0ff6df929bcd964", "sha256:eaed1c65f461a959284649e37b5051224f4db6ebdc84e40b5e65f2986f101a08" @@ -332,14 +291,6 @@ ], "version": "==2019.3" }, - "redis": { - "hashes": [ - "sha256:3613daad9ce5951e426f460deddd5caf469e08a3af633e9578fc77d362becf62", - "sha256:8d0fc278d3f5e1249967cba2eb4a5632d19e45ce5c09442b8422d15ee2c22cc2" - ], - "index": "pypi", - "version": "==3.3.11" - }, "requests": { "hashes": [ "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", @@ -369,11 +320,11 @@ }, "stellar-sdk": { "hashes": [ - "sha256:1e2d92a97e47f06d4a6f015c2cfd811116e6ee637a2156a2ff26f28f82c6d9da", - "sha256:837e106a46df496a2420e213baa0bfaca403b2db534e421ac648c02041ad5dc3" + "sha256:4d5f0a84971d0bb1778212d799ead93a8a17d38580e0b0a07760c7885c545759", + "sha256:9fcd51eb545f98fe137f8713aa7f6ae6841b3a1175795a7201a3d0060b44765a" ], "index": "pypi", - "version": "==2.0.0b3" + "version": "==2.0.0b4" }, "toml": { "hashes": [ @@ -393,17 +344,10 @@ }, "urllib3": { "hashes": [ - "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", - "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86" - ], - "version": "==1.25.6" - }, - "vine": { - "hashes": [ - "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87", - "sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af" + "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", + "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" ], - "version": "==1.3.0" + "version": "==1.25.7" }, "whitenoise": { "hashes": [ @@ -426,13 +370,6 @@ "sha256:e5d6b530bec5817be9383452728c116d59868bfbed650ccc7657b2ec9aa51c03" ], "version": "==1.4.0a11" - }, - "zipp": { - "hashes": [ - "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", - "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" - ], - "version": "==0.6.0" } }, "develop": { @@ -472,6 +409,19 @@ "index": "pypi", "version": "==19.10b0" }, + "cached-property": { + "hashes": [ + "sha256:3a026f1a54135677e7da5ce819b0c690f156f37976f3e30c5430740725203d7f", + "sha256:9217a59f14a5682da7c4b8829deadbfc194ac22e9908ccf7c8820234e80a1504" + ], + "version": "==1.5.1" + }, + "cerberus": { + "hashes": [ + "sha256:302e6694f206dd85cb63f13fd5025b31ab6d38c99c50c6d769f8fa0b0f299589" + ], + "version": "==1.3.2" + }, "certifi": { "hashes": [ "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", @@ -493,6 +443,13 @@ ], "version": "==7.0" }, + "colorama": { + "hashes": [ + "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", + "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48" + ], + "version": "==0.4.1" + }, "coverage": { "hashes": [ "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", @@ -538,12 +495,25 @@ "index": "pypi", "version": "==1.8.2" }, + "distlib": { + "hashes": [ + "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21" + ], + "version": "==0.3.0" + }, "docopt": { "hashes": [ "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" ], "version": "==0.6.2" }, + "first": { + "hashes": [ + "sha256:8d8e46e115ea8ac652c76123c0865e3ff18372aef6f03c22809ceefcea9dec86", + "sha256:ff285b08c55f8c97ce4ea7012743af2495c9f1291785f163722bd36f6af6d3bf" + ], + "version": "==2.0.2" + }, "idna": { "hashes": [ "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", @@ -556,6 +526,7 @@ "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" ], + "markers": "python_version < '3.8'", "version": "==0.23" }, "isort": { @@ -605,6 +576,13 @@ ], "version": "==7.2.0" }, + "orderedmultidict": { + "hashes": [ + "sha256:04070bbb5e87291cc9bfa51df413677faf2141c73c61d2a5f7b26bea3cd882ad", + "sha256:43c839a17ee3cdd62234c47deca1a8508a3f2ca1d0678a3bf791c87cf84adbf3" + ], + "version": "==1.0.1" + }, "packaging": { "hashes": [ "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", @@ -618,6 +596,43 @@ ], "version": "==0.6.0" }, + "pep517": { + "hashes": [ + "sha256:d283181fdb83fb698556cd3a4ebde5bb352b59242574e6ca56a95118774da374", + "sha256:fac83aa4c3b73adc84cb2a295f1f5bd5b9a13946ebd1339ba3b33ce287165c88" + ], + "version": "==0.7.0" + }, + "pip-shims": { + "hashes": [ + "sha256:0162d846bd60c7b1feb4e1336541a3e661eb6a1eff4b9ea0d759780f8e0a2936", + "sha256:d73372b9fa3a10e73f057fced70d39bb82df16a1ee3ded027fed0bb8d7c0ff97" + ], + "version": "==0.3.3" + }, + "pipenv-setup": { + "hashes": [ + "sha256:04eb6102f6ee1c9a2b22a6982e457161d1b46199f92f15f3b5ac1d788b78f275" + ], + "index": "pypi", + "version": "==2.2.0" + }, + "pipfile": { + "hashes": [ + "sha256:f7d9f15de8b660986557eb3cc5391aa1a16207ac41bc378d03f414762d36c984" + ], + "version": "==0.0.2" + }, + "plette": { + "extras": [ + "validation" + ], + "hashes": [ + "sha256:46402c03e36d6eadddad2a5125990e322dd74f98160c8f2dcd832b2291858a26", + "sha256:d6c9b96981b347bddd333910b753b6091a2c1eb2ef85bb373b4a67c9d91dca16" + ], + "version": "==0.2.3" + }, "pluggy": { "hashes": [ "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", @@ -634,11 +649,11 @@ }, "pylint": { "hashes": [ - "sha256:7b76045426c650d2b0f02fc47c14d7934d17898779da95288a74c2a7ec440702", - "sha256:856476331f3e26598017290fd65bebe81c960e806776f324093a46b76fb2d1c0" + "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd", + "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4" ], "index": "pypi", - "version": "==2.4.3" + "version": "==2.4.4" }, "pylint-django": { "hashes": [ @@ -657,10 +672,10 @@ }, "pyparsing": { "hashes": [ - "sha256:4acadc9a2b96c19fe00932a38ca63e601180c39a189a696abce1eaab641447e1", - "sha256:61b5ed888beab19ddccab3478910e2076a6b5a0295dffc43021890e136edf764" + "sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", + "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a" ], - "version": "==2.4.4" + "version": "==2.4.5" }, "pytest": { "hashes": [ @@ -711,6 +726,13 @@ ], "version": "==2.22.0" }, + "requirementslib": { + "hashes": [ + "sha256:50731ac1052473e4c7df59a44a1f3aa20f32e687110bc05d73c3b4109eebc23d", + "sha256:8b594ab8b6280ee97cffd68fc766333345de150124d5b76061dd575c3a21fe5a" + ], + "version": "==1.5.3" + }, "six": { "hashes": [ "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", @@ -726,6 +748,13 @@ "index": "pypi", "version": "==0.10.0" }, + "tomlkit": { + "hashes": [ + "sha256:32c10cc16ded7e4101c79f269910658cc2a0be5913f1252121c3cd603051c269", + "sha256:96e6369288571799a3052c1ef93b9de440e1ab751aa045f435b55e9d3bcd0690" + ], + "version": "==0.5.8" + }, "typed-ast": { "hashes": [ "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", @@ -754,10 +783,17 @@ }, "urllib3": { "hashes": [ - "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", - "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86" + "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", + "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" ], - "version": "==1.25.6" + "version": "==1.25.7" + }, + "vistir": { + "hashes": [ + "sha256:2166e3148a67c438c9e3edbba0cde153d42dec6e3bf5d8f4624feb27686c0990", + "sha256:3a0529b4b6c2e842fd19b5ceaa95b6c9201321314825c110406d4af3331a0709" + ], + "version": "==0.4.3" }, "wcwidth": { "hashes": [ @@ -766,6 +802,13 @@ ], "version": "==0.1.7" }, + "wheel": { + "hashes": [ + "sha256:10c9da68765315ed98850f8e048347c3eb06dd81822dc2ab1d4fde9dc9702646", + "sha256:f4da1763d3becf2e2cd92a14a7c920f0f00eca30fdde9ea992c836685b9faf28" + ], + "version": "==0.33.6" + }, "wrapt": { "hashes": [ "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" diff --git a/Procfile b/Procfile deleted file mode 100644 index 6b28f97c..00000000 --- a/Procfile +++ /dev/null @@ -1,4 +0,0 @@ -release: pipenv run python src/manage.py migrate -worker: celery worker --app app --beat --workdir src -l info -watcher: pipenv run python src/manage.py watch_transactions -web: gunicorn --pythonpath src app.wsgi diff --git a/README.md b/README.md index 6b300ef2..368dc6e4 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,62 @@ -# Stellar Anchor Server – Example Implementation - -[![CircleCI](https://circleci.com/gh/stellar/stellar-anchor-server.svg?style=shield)](https://circleci.com/gh/stellar/stellar-anchor-server) [![Coverage Status](https://coveralls.io/repos/github/stellar/stellar-anchor-server/badge.svg?branch=master)](https://coveralls.io/github/stellar/stellar-anchor-server?branch=master) +## Django-Polaris +This project is a WIP reusable django app implementing [SEP 24](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md). +It is intended to act as a reference implementation for prospective anchors or used in production within an existing Django project. +The [stellar-anchor-server](https://github.com/stellar/stellar-anchor-server) is an example Django project that uses this this package. IMPORTANT DISCLAIMER: This code should not be used in production without a thorough security audit. -This project is a WIP example implementation of a Stellar anchor server. - -Its goal is to provide a community example implementation of [SEP 24](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md) (and the related SEP [10](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md)). We hope to make it easier for anchors to integrate with the Stellar network, as they can refer to this sample implementation in their own development. Note that this implementation itself should not be utilized directly (i.e., forked) for handling real amounts of real money. - -Additionally, we want to enable wallets to seamlessly integrate with said anchor. This implementation will provide a reference server for wallets to implement their end of the above SEPs without having to collaborate with an anchor. - -You can check the project's roadmap [here](https://github.com/stellar/stellar-anchor-server/milestones). +## Installation +1. `pip install django-polaris` +1. Add `"polaris"` to `INSTALLED_APPS` in settings.py +1. Define `PROJECT_ROOT` in your project's `settings.py`. Polaris uses this to find your `.env` file. +1. Paste the text below into `PROJECT_ROOT/.env`. The [stellar-anchor-server](https://github.com/stellar/stellar-anchor-server) repository contains instructions for modifying this file to fit your use case. + ```.env + DJANGO_SECRET_KEY="secretkeykeysecret" + DJANGO_DEBUG=True + STELLAR_DISTRIBUTION_ACCOUNT_SEED="SCHTHF3N4SHEQM25M43FJ43UTCZP6OO3JKYVJCJBZ4YW6KVVAGC2OUCT" + STELLAR_ISSUER_ACCOUNT_ADDRESS="GCTVATNFP4FYKZ7BXZ3EOPVKEL2DGDCB2AVBDUNLW7NYR7REF5PMKY4V" + + # STELLAR_NETWORK_PASSPHRASE can either be "Test SDF Network ; September 2015" or + # "Public Global Stellar Network ; September 2015" or a custom passphrase + # if you're using a private network. + STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015" + # HORIZON_URI can point to a custom Horizon URI. It currently points + # to the testnet URL. + HORIZON_URI="https://horizon-testnet.stellar.org/" + SERVER_JWT_KEY="secret" + ``` +1. In your `urls.py`, add `path("", include(polaris.urls))` to `urlpatterns`. +1. Run migrations: `python manage.py migrate` +1. Run the server: `python manage.py runserver` ## Before running the project -Before running the project, follow the following steps to customize your environment. - -If you are simply testing the project out, it's fine to keep the variables as they are. All you need to do is set up the virtual environment using the following command: `$cp .env.example .env` - -If you are planning to run this in production, you need to follow the following steps to generate your own private/public keys for asset issuance and distribution. After copying the environment, do the below. +As an anchor, you need setup your stellar accounts for asset issuance and distribution and configure your server to use these accounts. The following instructions outline how to do this on the testnet. 1. Go to the [Stellar laboratory account creator](https://www.stellar.org/laboratory/#account-creator?network=test). 1. Click the button to "Generate keypair." This is the distribution account. 1. Fund the account: copy-paste the value of the Public Key (G...) into the Friendbot input box. 1. Click "Get test network lumens." You have now funded a Stellar account! -1. Open your virtual environment file, `stellar-anchor-server/.env`. +1. Open your virtual environment file, `PROJECT_ROOT/.env`. 1. Set `STELLAR_DISTRIBUTION_ACCOUNT_SEED` to the value of `Secret Key` from the Keypair Generator. 1. Go back to the account creator, and create and fund another Stellar account. This is the issuer account. 1. Set `STELLAR_ISSUER_ACCOUNT_ADDRESS` to the value of `Public Key` that you just funded. 1. Now, run [this script](https://github.com/msfeldstein/create-stellar-token), using the issuer seed and distribution seed of the accounts just created. You can decide the name of your asset and amount to issue. This will issue an asset and send some amount of that asset to the distribution account. 1. Finally, modify the `SERVER_JWT_KEY` to a more secure phrase for more secure SEP-10 authentication. -Note that the above steps are aimed at creating an environment plugged into the Stellar test network ("testnet"), rather than the public Stellar network ("mainnet"). If you want to run this application on the main network, you will also need to change the value of `STELLAR_NETWORK` in `.env` to `PUBLIC`, and the `HORIZON_URI` to a URL of a Horizon running on the public network (e.g., `https://horizon.stellar.org`). - -## Running the project locally with Docker -The project can be run via Docker Compose. We recommend this approach for easier use. -1. Install Docker Compose following the appropriate instructions [here](https://docs.docker.com/compose/install/) -1. You'll need a `.env` file (or the equivalent env vars defined). We provide a sample one, which you can copy and modify: `$ cp .env.example .env` -1. Modify the Stellar account in `.env` as below. -1. Modify the `SERVER_JWT_KEY` in `.env` to a more secure phrase, to allow for more secure SEP-10 authentication. -1. Build the Docker image, from the root directory: `docker-compose build` -1. Run the database migrations, from the root directory: `docker-compose run web pipenv run python src/manage.py migrate` -1. Set up the Django admin user, from the root directory: `docker-compose run web pipenv run python src/manage.py createsuperuser` -1. Run the Docker image, from the root directory: `docker-compose up` - -## Running the project locally without Docker - -This project was built using Pipenv. If you do not want to install Docker, here is another route, involving individually installing components. - -1. Install pipenv: `$ brew install pipenv` (on macOS) -1. Install redis: `$ brew install redis` (on macOS) -1. Inside the repo's root, install the project's dependencies: `$ pipenv install` -1. Run the database migrations: `$ pipenv run python src/manage.py migrate` -1. Set up the admin user: `$ pipenv run python src/manage.py createsuperuser`. Provide a username, email, and password of your choice. -1. Run the redis server in the background: `$ redis-server --daemonize yes` -1. Run celery: `$ pipenv run celery worker --app app --beat --workdir src -l info` -1. Run a script to stream withdrawal transactions from Horizon: `$ pipenv run python src/manage.py watch_transactions` -1. Run the project: `$ pipenv run python src/manage.py runserver` - -## Using the admin panel -Through Django's admin panel, you can create assets, monitor transaction status, and do other administrative tasks. -The above instructions for "Running the project locally" include the creation of an administrative user. -Once the project is running locally, navigate to `https://localhost:8000/admin` in a browser. Enter the username and password you set for the superuser above. You should then see the admin panel. -To create an asset, click `+ Add` in the `Assets` row of the `INFO` table. You can then edit the fields of an asset (its name, deposit values, withdrawal values) and save it. +## Contributing and Testing +To set up the development environment: +``` +pip install pipenv +git clone https://github.com/stellar/django-polaris.git +cd django-polaris +pipenv install --dev +``` +To test: +```.env +pipenv run python polaris/manage.py collectstatic --no-input +pipenv run pytest +``` +Note: `collectstatic` removes some files and generates others. Make sure these changes don't make it into your PR. You can remove the files generated using: +``` +pipenv run python polaris/manage.py collectstatic --clear +``` diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 628da6cb..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: "3" - -services: - redis: - image: "redis:alpine" - ports: - - "6379:6379" - server: - build: . - volumes: - - .:/code/ - ports: - - "8000:8000" - depends_on: - - redis - command: > - sh -c "pipenv run python src/manage.py migrate - && pipenv run python src/manage.py runserver 0.0.0.0:8000" - watch_transactions: - build: . - volumes: - - .:/code/ - depends_on: - - redis - command: > - sh -c "pipenv run python src/manage.py watch_transactions" - celery: - build: . - command: pipenv run celery worker --app app --beat --workdir src -l info - volumes: - - .:/code/ - depends_on: - - redis - - server diff --git a/src/manage.py b/polaris/manage.py old mode 100755 new mode 100644 similarity index 89% rename from src/manage.py rename to polaris/manage.py index 576403b7..5e00f750 --- a/src/manage.py +++ b/polaris/manage.py @@ -6,7 +6,7 @@ def main(): """Main function for executing arguments.""" - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "polaris.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/polaris/polaris/__init__.py b/polaris/polaris/__init__.py new file mode 100644 index 00000000..a6b3773e --- /dev/null +++ b/polaris/polaris/__init__.py @@ -0,0 +1,3 @@ +"""This module initializes the Django app.""" + +default_app_config = "polaris.apps.PolarisConfig" diff --git a/polaris/polaris/admin.py b/polaris/polaris/admin.py new file mode 100644 index 00000000..503aab61 --- /dev/null +++ b/polaris/polaris/admin.py @@ -0,0 +1,22 @@ +from django.contrib import admin +from polaris.models import Transaction, Asset + + +class TransactionAdmin(admin.ModelAdmin): + """ + This defines the admin view of a Transaction. + """ + + list_display = "id", "asset_name", "kind", "status", "started_at" + + +class AssetAdmin(admin.ModelAdmin): + """ + This defines the admin view of an Asset. + """ + + list_display = "code", "issuer", "deposit_enabled", "withdrawal_enabled" + + +admin.site.register(Transaction, TransactionAdmin) +admin.site.register(Asset, AssetAdmin) diff --git a/polaris/polaris/apps.py b/polaris/polaris/apps.py new file mode 100644 index 00000000..1f7b1058 --- /dev/null +++ b/polaris/polaris/apps.py @@ -0,0 +1,12 @@ +from django.apps import AppConfig + + +class PolarisConfig(AppConfig): + name = "polaris" + verbose_name = "Django Polaris" + + def ready(self): + """ + Initialize the app. Currently a no-op. + """ + pass diff --git a/src/deposit/__init__.py b/polaris/polaris/deposit/__init__.py similarity index 100% rename from src/deposit/__init__.py rename to polaris/polaris/deposit/__init__.py diff --git a/src/deposit/forms.py b/polaris/polaris/deposit/forms.py similarity index 100% rename from src/deposit/forms.py rename to polaris/polaris/deposit/forms.py diff --git a/src/deposit/urls.py b/polaris/polaris/deposit/urls.py similarity index 78% rename from src/deposit/urls.py rename to polaris/polaris/deposit/urls.py index acc21437..ec3755d8 100644 --- a/src/deposit/urls.py +++ b/polaris/polaris/deposit/urls.py @@ -1,6 +1,6 @@ """This module defines the URL patterns for the `/deposit` endpoint.""" from django.urls import path -from .views import deposit, interactive_deposit, confirm_transaction +from polaris.deposit.views import deposit, interactive_deposit, confirm_transaction urlpatterns = [ path("", deposit), diff --git a/src/deposit/views.py b/polaris/polaris/deposit/views.py similarity index 95% rename from src/deposit/views.py rename to polaris/polaris/deposit/views.py index e64d8955..1de3ad2c 100644 --- a/src/deposit/views.py +++ b/polaris/polaris/deposit/views.py @@ -10,29 +10,28 @@ import json from urllib.parse import urlencode -from django.conf import settings +from polaris import settings from django.shortcuts import render from django.urls import reverse from django.views.decorators.clickjacking import xframe_options_exempt +from django.core.management import call_command from rest_framework import status from rest_framework.decorators import api_view from rest_framework.response import Response from stellar_sdk.keypair import Keypair from stellar_sdk.exceptions import Ed25519PublicKeyInvalidError -from helpers import ( +from polaris.helpers import ( calc_fee, render_error_response, create_transaction_id, validate_jwt_request, validate_sep10_token, ) -from info.models import Asset -from transaction.models import Transaction -from transaction.serializers import TransactionSerializer +from polaris.models import Asset, Transaction +from polaris.transaction.serializers import TransactionSerializer -from deposit.tasks import create_stellar_deposit -from .forms import DepositForm +from polaris.deposit.forms import DepositForm def _construct_interactive_url(request, transaction_id): @@ -131,8 +130,8 @@ def confirm_transaction(request): transaction, context={"more_info_url": _construct_more_info_url(request)} ) - # Asynchronously launch the deposit Stellar transaction. - create_stellar_deposit.delay(transaction.id) + # launch the deposit Stellar transaction. + call_command("create_stellar_deposit", transaction.id) return Response({"transaction": serializer.data}) diff --git a/src/deposit/migrations/__init__.py b/polaris/polaris/fee/__init__.py similarity index 100% rename from src/deposit/migrations/__init__.py rename to polaris/polaris/fee/__init__.py diff --git a/src/fee/urls.py b/polaris/polaris/fee/urls.py similarity index 79% rename from src/fee/urls.py rename to polaris/polaris/fee/urls.py index 806b3e42..4b07c32f 100644 --- a/src/fee/urls.py +++ b/polaris/polaris/fee/urls.py @@ -1,5 +1,5 @@ """This module defines the URL patterns for the `/fee` endpoint.""" from django.urls import path -from .views import fee +from polaris.fee.views import fee urlpatterns = [path("", fee)] diff --git a/src/fee/views.py b/polaris/polaris/fee/views.py similarity index 93% rename from src/fee/views.py rename to polaris/polaris/fee/views.py index f92c42e5..edc69e5d 100644 --- a/src/fee/views.py +++ b/polaris/polaris/fee/views.py @@ -1,10 +1,10 @@ """This module defines the `/fee` view.""" -from django.conf import settings +from polaris import settings from rest_framework.decorators import api_view from rest_framework.response import Response -from helpers import calc_fee, render_error_response, validate_sep10_token -from info.models import Asset +from polaris.helpers import calc_fee, render_error_response, validate_sep10_token +from polaris.models import Asset OPERATION_DEPOSIT = settings.OPERATION_DEPOSIT OPERATION_WITHDRAWAL = settings.OPERATION_WITHDRAWAL diff --git a/src/helpers.py b/polaris/polaris/helpers.py similarity index 96% rename from src/helpers.py rename to polaris/polaris/helpers.py index 62adc91e..23c35231 100644 --- a/src/helpers.py +++ b/polaris/polaris/helpers.py @@ -3,14 +3,13 @@ import time import uuid -from django.conf import settings +from polaris import settings import jwt from rest_framework import status from rest_framework.response import Response -from info.models import Asset -from transaction.models import Transaction +from polaris.models import Asset, Transaction def calc_fee(asset: Asset, operation: str, amount: float) -> float: diff --git a/src/fee/__init__.py b/polaris/polaris/info/__init__.py similarity index 100% rename from src/fee/__init__.py rename to polaris/polaris/info/__init__.py diff --git a/src/info/admin.py b/polaris/polaris/info/admin.py similarity index 90% rename from src/info/admin.py rename to polaris/polaris/info/admin.py index d3277c90..b49b3983 100644 --- a/src/info/admin.py +++ b/polaris/polaris/info/admin.py @@ -1,6 +1,6 @@ """This module registers the models for the `info` endpoint with the admin.""" from django.contrib import admin -from .models import Asset +from polaris.models import Asset class AssetAdmin(admin.ModelAdmin): diff --git a/src/info/urls.py b/polaris/polaris/info/urls.py similarity index 78% rename from src/info/urls.py rename to polaris/polaris/info/urls.py index a9ca5755..1f817912 100644 --- a/src/info/urls.py +++ b/polaris/polaris/info/urls.py @@ -1,5 +1,5 @@ """This module defines the URL patterns for the `/info` endpoint.""" from django.urls import path -from .views import info +from polaris.info.views import info urlpatterns = [path("", info)] diff --git a/src/info/views.py b/polaris/polaris/info/views.py similarity index 95% rename from src/info/views.py rename to polaris/polaris/info/views.py index 41f0c6d1..95f2ead4 100644 --- a/src/info/views.py +++ b/polaris/polaris/info/views.py @@ -1,8 +1,10 @@ """This module defines the logic for the `/info` endpoint.""" +import json from rest_framework.decorators import api_view from rest_framework.response import Response -from .models import Asset +from polaris import settings +from polaris.models import Asset def _get_asset_deposit_info(asset: Asset): diff --git a/src/fee/migrations/__init__.py b/polaris/polaris/management/__init__.py similarity index 100% rename from src/fee/migrations/__init__.py rename to polaris/polaris/management/__init__.py diff --git a/src/info/__init__.py b/polaris/polaris/management/commands/__init__.py similarity index 100% rename from src/info/__init__.py rename to polaris/polaris/management/commands/__init__.py diff --git a/polaris/polaris/management/commands/check_trustlines.py b/polaris/polaris/management/commands/check_trustlines.py new file mode 100644 index 00000000..ef6e4550 --- /dev/null +++ b/polaris/polaris/management/commands/check_trustlines.py @@ -0,0 +1,56 @@ +import time +import logging + +from polaris import settings +from django.core.management.base import BaseCommand, CommandError +from django.core.management import call_command +from stellar_sdk.exceptions import BaseHorizonError + +from polaris.models import Transaction + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + """ + Create Stellar transaction for deposit transactions marked as pending trust, if a + trustline has been created. + """ + def add_arguments(self, parser): + parser.add_argument("--loop", "-l", action="store_true") + + def handle(self, *args, **options): + if options.get('loop'): + while True: + self.check_trustlines() + time.sleep(60) + else: + self.check_trustlines() + + def check_trustlines(self): + """ + Create Stellar transaction for deposit transactions marked as pending trust, if a + trustline has been created. + """ + transactions = Transaction.objects.filter(status=Transaction.STATUS.pending_trust) + server = settings.HORIZON_SERVER + for transaction in transactions: + try: + account = server.accounts().account_id(transaction.stellar_account).call() + except BaseHorizonError: + logger.debug("could not load account using provided horizon URL") + continue + try: + balances = account["balances"] + except KeyError: + logger.debug("horizon account response had no balances") + continue + for balance in balances: + try: + asset_code = balance["asset_code"] + except KeyError: + logger.debug("horizon balances had no asset_code") + continue + if asset_code == transaction.asset.code: + call_command('create_stellar_deposit', transaction.id) + diff --git a/polaris/polaris/management/commands/create_stellar_deposit.py b/polaris/polaris/management/commands/create_stellar_deposit.py new file mode 100644 index 00000000..049f2ada --- /dev/null +++ b/polaris/polaris/management/commands/create_stellar_deposit.py @@ -0,0 +1,132 @@ +import logging + +from django.utils.timezone import now +from django.core.management import BaseCommand + +from stellar_sdk.exceptions import BaseHorizonError +from stellar_sdk.transaction_builder import TransactionBuilder + +from polaris import settings +from polaris.models import Transaction + + +TRUSTLINE_FAILURE_XDR = "AAAAAAAAAGT/////AAAAAQAAAAAAAAAB////+gAAAAA=" +SUCCESS_XDR = "AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAA=" + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + """ + Create and submit the Stellar transaction for the deposit. + """ + def add_arguments(self, parser): + parser.add_argument("transaction_id") + + def handle(self, *args, **options): + self.create_stellar_deposit(options["transaction_id"]) + + def create_stellar_deposit(self, transaction_id): + """Create and submit the Stellar transaction for the deposit.""" + transaction = Transaction.objects.get(id=transaction_id) + + # We check the Transaction status to avoid double submission of a Stellar + # transaction. The Transaction can be either `pending_anchor` if the task + # is called from `GET deposit/confirm_transaction` or `pending_trust` if called + # from the `check_trustlines()`. + if transaction.status not in [ + Transaction.STATUS.pending_anchor, + Transaction.STATUS.pending_trust, + ]: + logger.debug( + "unexpected transaction status %s at top of create_stellar_deposit", + transaction.status, + ) + return + transaction.status = Transaction.STATUS.pending_stellar + transaction.save() + + # We can assume transaction has valid stellar_account, amount_in, and asset + # because this task is only called after those parameters are validated. + stellar_account = transaction.stellar_account + payment_amount = round(transaction.amount_in - transaction.amount_fee, 7) + asset = transaction.asset.code + + # If the given Stellar account does not exist, create + # the account with at least enough XLM for the minimum + # reserve and a trust line (recommended 2.01 XLM), update + # the transaction in our internal database, and return. + + server = settings.HORIZON_SERVER + starting_balance = settings.ACCOUNT_STARTING_BALANCE + server_account = server.load_account(settings.STELLAR_DISTRIBUTION_ACCOUNT_ADDRESS) + base_fee = server.fetch_base_fee() + builder = TransactionBuilder(source_account=server_account, + network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, + base_fee=base_fee) + try: + server.load_account(stellar_account) + except BaseHorizonError as address_exc: + # 404 code corresponds to Resource Missing. + if address_exc.status != 404: + logger.debug( + "error with message %s when loading stellar account", + address_exc.message, + ) + return + transaction_envelope = builder \ + .append_create_account_op(destination=stellar_account, + starting_balance=starting_balance, + source=settings.STELLAR_DISTRIBUTION_ACCOUNT_ADDRESS) \ + .build() + transaction_envelope.sign(settings.STELLAR_DISTRIBUTION_ACCOUNT_SEED) + try: + server.submit_transaction(transaction_envelope) + except BaseHorizonError as submit_exc: + logger.debug( + f"error with message {submit_exc.message} when submitting create account to horizon" + ) + return + transaction.status = Transaction.STATUS.pending_trust + transaction.save() + return + + # If the account does exist, deposit the desired amount of the given + # asset via a Stellar payment. If that payment succeeds, we update the + # transaction to completed at the current time. If it fails due to a + # trustline error, we update the database accordingly. Else, we do not update. + + transaction_envelope = builder \ + .append_payment_op(destination=stellar_account, + asset_code=asset, + asset_issuer=settings.STELLAR_ISSUER_ACCOUNT_ADDRESS, + amount=str(payment_amount)) \ + .build() + transaction_envelope.sign(settings.STELLAR_DISTRIBUTION_ACCOUNT_SEED) + try: + response = server.submit_transaction(transaction_envelope) + # Functional errors at this stage are Horizon errors. + except BaseHorizonError as exception: + if TRUSTLINE_FAILURE_XDR not in exception.result_xdr: + logger.debug( + "error with message %s when submitting payment to horizon, non-trustline failure", + exception.message, + ) + return + logger.debug("trustline error when submitting transaction to horizon") + transaction.status = Transaction.STATUS.pending_trust + transaction.save() + return + + # If this condition is met, the Stellar payment succeeded, so we + # can mark the transaction as completed. + if response["result_xdr"] != SUCCESS_XDR: + logger.debug("payment stellar transaction failed when submitted to horizon") + return + + transaction.stellar_transaction_id = response["hash"] + transaction.status = Transaction.STATUS.completed + transaction.completed_at = now() + transaction.status_eta = 0 # No more status change. + transaction.amount_out = payment_amount + transaction.save() diff --git a/src/transaction/management/commands/watch_transactions.py b/polaris/polaris/management/commands/watch_transactions.py similarity index 95% rename from src/transaction/management/commands/watch_transactions.py rename to polaris/polaris/management/commands/watch_transactions.py index 9f59a6fa..bc8362f4 100644 --- a/src/transaction/management/commands/watch_transactions.py +++ b/polaris/polaris/management/commands/watch_transactions.py @@ -1,12 +1,17 @@ """This module defines custom management commands for the app admin.""" -from django.conf import settings +import logging + from django.core.management.base import BaseCommand, CommandError from django.utils.timezone import now -from helpers import format_memo_horizon from stellar_sdk.exceptions import NotFoundError from stellar_sdk.transaction_envelope import TransactionEnvelope from stellar_sdk.xdr import Xdr -from transaction.models import Transaction + +from polaris import settings +from polaris.helpers import format_memo_horizon +from polaris.models import Transaction + +logger = logging.getLogger(__file__) def stream_transactions(): @@ -102,7 +107,7 @@ def handle(self, *args, **options): for withdrawal_transaction in pending_withdrawal_transactions: if process_withdrawal(response, withdrawal_transaction): envelope_xdr = response["envelope_xdr"] - print( + logger.info( f"successfully processed withdrawal for response with xdr {envelope_xdr}" ) break diff --git a/src/transaction/migrations/0001_squashed_0003_auto_20190812_1852.py b/polaris/polaris/migrations/0001_initial.py similarity index 59% rename from src/transaction/migrations/0001_squashed_0003_auto_20190812_1852.py rename to polaris/polaris/migrations/0001_initial.py index 9aa984f9..45e9a99a 100644 --- a/src/transaction/migrations/0001_squashed_0003_auto_20190812_1852.py +++ b/polaris/polaris/migrations/0001_initial.py @@ -1,22 +1,41 @@ -# Generated by Django 2.2.4 on 2019-08-15 16:14 +# Generated by Django 2.2.4 on 2019-11-01 22:46 import django.core.validators from django.db import migrations, models import django.db.models.deletion +import django.utils.timezone +import model_utils.fields import uuid class Migration(migrations.Migration): - replaces = [('transaction', '0001_initial'), ('transaction', '0002_auto_20190729_2236'), ('transaction', '0003_auto_20190812_1852')] - initial = True dependencies = [ - ('info', '0002_auto_20190726_2032'), ] operations = [ + migrations.CreateModel( + name='Asset', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('code', models.TextField(default='USD', validators=[django.core.validators.MinLengthValidator(1)])), + ('issuer', models.TextField(default='GCXEIFQV5CAFTMDWW3YYUBMRXHQEE3DWQCGQLJMC4PUK6GGWERICUTOQ', validators=[django.core.validators.MinLengthValidator(56)])), + ('deposit_enabled', models.BooleanField(default=True)), + ('deposit_fee_fixed', models.FloatField(blank=True, default=1.0)), + ('deposit_fee_percent', models.FloatField(blank=True, default=0.01)), + ('deposit_min_amount', models.FloatField(blank=True, default=10.0)), + ('deposit_max_amount', models.FloatField(blank=True, default=10000.0)), + ('withdrawal_enabled', models.BooleanField(default=True)), + ('withdrawal_fee_fixed', models.FloatField(blank=True, default=1.0)), + ('withdrawal_fee_percent', models.FloatField(blank=True, default=0.01)), + ('withdrawal_min_amount', models.FloatField(blank=True, default=10.0)), + ('withdrawal_max_amount', models.FloatField(blank=True, default=10000.0)), + ], + ), migrations.CreateModel( name='Transaction', fields=[ @@ -24,7 +43,7 @@ class Migration(migrations.Migration): ('stellar_account', models.TextField(validators=[django.core.validators.MinLengthValidator(1)])), ('kind', models.CharField(choices=[('deposit', 'deposit'), ('withdrawal', 'withdrawal')], default='deposit', max_length=20)), ('status', models.CharField(choices=[('completed', 'completed'), ('pending_external', 'pending_external'), ('pending_anchor', 'pending_anchor'), ('pending_stellar', 'pending_stellar'), ('pending_trust', 'pending_trust'), ('pending_user', 'pending_user'), ('pending_user_transfer_start', 'pending_user_transfer_start'), ('incomplete', 'incomplete'), ('no_market', 'no_market'), ('too_small', 'too_small'), ('too_large', 'too_large'), ('error', 'error')], default='pending_external', max_length=30)), - ('status_eta', models.IntegerField(blank=True, null=True)), + ('status_eta', models.IntegerField(blank=True, default=3600, null=True)), ('stellar_transaction_id', models.TextField(blank=True, null=True)), ('external_transaction_id', models.TextField(blank=True, null=True)), ('amount_in', models.FloatField(blank=True, null=True)), @@ -32,16 +51,16 @@ class Migration(migrations.Migration): ('amount_fee', models.FloatField(blank=True, null=True)), ('started_at', models.DateTimeField(auto_now_add=True)), ('completed_at', models.DateTimeField(null=True)), - ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='info.Asset')), - ('deposit_memo', models.TextField(blank=True, null=True)), - ('deposit_memo_type', models.CharField(choices=[('text', 'text'), ('id', 'id'), ('hash', 'hash')], default='text', max_length=10)), - ('external_extra', models.TextField(blank=True, null=True)), - ('external_extra_text', models.TextField(blank=True, null=True)), ('from_address', models.TextField(blank=True, null=True)), ('to_address', models.TextField(blank=True, null=True)), + ('external_extra', models.TextField(blank=True, null=True)), + ('external_extra_text', models.TextField(blank=True, null=True)), + ('deposit_memo', models.TextField(blank=True, null=True)), + ('deposit_memo_type', models.CharField(choices=[('text', 'text'), ('id', 'id'), ('hash', 'hash')], default='text', max_length=10)), ('withdraw_anchor_account', models.TextField(blank=True, null=True)), ('withdraw_memo', models.TextField(blank=True, null=True)), ('withdraw_memo_type', models.CharField(choices=[('text', 'text'), ('id', 'id'), ('hash', 'hash')], default='text', max_length=10)), + ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polaris.Asset')), ], options={ 'ordering': ('-started_at',), diff --git a/src/info/migrations/__init__.py b/polaris/polaris/migrations/__init__.py similarity index 100% rename from src/info/migrations/__init__.py rename to polaris/polaris/migrations/__init__.py diff --git a/src/transaction/models.py b/polaris/polaris/models.py similarity index 65% rename from src/transaction/models.py rename to polaris/polaris/models.py index 1d42514f..0a4d5abc 100644 --- a/src/transaction/models.py +++ b/polaris/polaris/models.py @@ -1,12 +1,43 @@ -"""This module defines the models for the transaction app.""" +"""This module defines the models for the polaris app.""" import uuid from django.contrib import admin +from polaris import settings from django.core.validators import MinLengthValidator from django.db import models +from model_utils.models import TimeStampedModel from model_utils import Choices +class Asset(TimeStampedModel): + """ + This defines an Asset, as described in the SEP-24 `info` endpoint. + See: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md#info + """ + + code = models.TextField(validators=[MinLengthValidator(1)], default="USD") + issuer = models.TextField( + validators=[MinLengthValidator(56)], default=settings.STELLAR_ISSUER_ACCOUNT_ADDRESS + ) + + # Deposit-related info + deposit_enabled = models.BooleanField(null=False, default=True) + deposit_fee_fixed = models.FloatField(default=1.0, blank=True) + deposit_fee_percent = models.FloatField(default=0.01, blank=True) + deposit_min_amount = models.FloatField(default=10.0, blank=True) + deposit_max_amount = models.FloatField(default=10000.0, blank=True) + + # Withdrawal-related info + withdrawal_enabled = models.BooleanField(null=False, default=True) + withdrawal_fee_fixed = models.FloatField(default=1.0, blank=True) + withdrawal_fee_percent = models.FloatField(default=0.01, blank=True) + withdrawal_min_amount = models.FloatField(default=10.0, blank=True) + withdrawal_max_amount = models.FloatField(default=10000.0, blank=True) + + class Meta: + app_label = "polaris" + + class Transaction(models.Model): """ This defines a Transaction, as described in the SEP-24 `transaction` endpoint. @@ -34,7 +65,7 @@ class Transaction(models.Model): # Stellar account to watch, and asset that is being transactioned # NOTE: these fields should not be publicly exposed stellar_account = models.TextField(validators=[MinLengthValidator(1)]) - asset = models.ForeignKey("info.Asset", on_delete=models.CASCADE) + asset = models.ForeignKey("Asset", on_delete=models.CASCADE) # These fields can be shown through an API: kind = models.CharField(choices=KIND, default=KIND.deposit, max_length=20) @@ -73,3 +104,4 @@ def asset_name(self): class Meta: ordering = ("-started_at",) + app_label = "polaris" \ No newline at end of file diff --git a/src/sep10auth/__init__.py b/polaris/polaris/sep10auth/__init__.py similarity index 100% rename from src/sep10auth/__init__.py rename to polaris/polaris/sep10auth/__init__.py diff --git a/src/sep10auth/urls.py b/polaris/polaris/sep10auth/urls.py similarity index 76% rename from src/sep10auth/urls.py rename to polaris/polaris/sep10auth/urls.py index a18041d8..ec0a5077 100644 --- a/src/sep10auth/urls.py +++ b/polaris/polaris/sep10auth/urls.py @@ -1,6 +1,6 @@ """This module defines the URL patterns for the `/auth` endpoint.""" from django.urls import path -from .views import auth +from polaris.sep10auth.views import auth urlpatterns = [path("", auth)] diff --git a/src/sep10auth/views.py b/polaris/polaris/sep10auth/views.py similarity index 99% rename from src/sep10auth/views.py rename to polaris/polaris/sep10auth/views.py index 5c0ea31b..109b478f 100644 --- a/src/sep10auth/views.py +++ b/polaris/polaris/sep10auth/views.py @@ -10,7 +10,7 @@ import time import jwt -from django.conf import settings +from polaris import settings from django.http import JsonResponse from rest_framework import status from rest_framework.decorators import api_view diff --git a/polaris/polaris/settings.py b/polaris/polaris/settings.py new file mode 100644 index 00000000..7d8e2b79 --- /dev/null +++ b/polaris/polaris/settings.py @@ -0,0 +1,128 @@ +""" +Django settings for app project. + +Generated by 'django-admin startproject' using Django 2.2.2. + +For more information on this file, see +https://docs.djangoproject.com/en/2.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/2.2/ref/settings/ +""" +# pylint: disable=invalid-name +import os +from shutil import copyfile + +import environ +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from stellar_sdk.server import Server +from stellar_sdk.keypair import Keypair + + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# Load environment variables from .env +env = environ.Env() + +# Use the directory of the django project that installed this app +# Or if undefined, use outer directory for this app +try: + PROJECT_ROOT = settings.PROJECT_ROOT +except ImproperlyConfigured: + PROJECT_ROOT = os.path.dirname(BASE_DIR) + copyfile(os.path.join(PROJECT_ROOT, ".env.example"), os.path.join(PROJECT_ROOT, ".env")) + +env_file = os.path.join(PROJECT_ROOT, ".env") +if os.path.exists(env_file): + environ.Env.read_env(str(env_file)) + +SECRET_KEY = env("DJANGO_SECRET_KEY") +DEBUG = env.bool("DJANGO_DEBUG", False) + +STELLAR_DISTRIBUTION_ACCOUNT_SEED = env("STELLAR_DISTRIBUTION_ACCOUNT_SEED") +STELLAR_DISTRIBUTION_ACCOUNT_ADDRESS = ( + Keypair.from_secret(STELLAR_DISTRIBUTION_ACCOUNT_SEED).public_key +) +STELLAR_ISSUER_ACCOUNT_ADDRESS = env("STELLAR_ISSUER_ACCOUNT_ADDRESS") +STELLAR_NETWORK_PASSPHRASE = env("STELLAR_NETWORK_PASSPHRASE", default="Test SDF Network ; September 2015") +HORIZON_URI = env("HORIZON_URI", default="https://horizon-testnet.stellar.org/") +HORIZON_SERVER = Server(horizon_url=HORIZON_URI) +SERVER_JWT_KEY = env("SERVER_JWT_KEY") +OPERATION_DEPOSIT = "deposit" +OPERATION_WITHDRAWAL = "withdraw" +ACCOUNT_STARTING_BALANCE = str(2.01) + +# Apps to add to parent project's INSTALLED_APPS +django_apps = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", +] +third_party_apps = ["rest_framework", "corsheaders"] +INSTALLED_APPS = django_apps + third_party_apps + ["polaris"] + +# Modules to add to parent project's MIDDLEWARE +MIDDLEWARE = [ + "corsheaders.middleware.CorsMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", +] + +APPEND_SLASH = False + +ROOT_URLCONF = "polaris.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join(BASE_DIR, "templates")], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ] + }, + } +] + +# Database +# https://docs.djangoproject.com/en/2.2/ref/settings/#databases + +DATABASES = { + "default": env.db( + "DATABASE_URL", default="sqlite:///" + os.path.join(PROJECT_ROOT, "db.sqlite3") + ) +} + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.2/howto/static-files/ + +STATIC_ROOT = os.path.join(BASE_DIR, "polaris/static") +STATIC_URL = "/polaris/static/" +STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" +STATICFILES_DIRS = ( + os.path.join(BASE_DIR, 'polaris/static'), +) + +# Django Rest Framework Settings: +# Attributes to add to parent project's REST_FRAMEWORK +REST_FRAMEWORK = { + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", + "PAGE_SIZE": 10, +} + +# API Config + +DEFAULT_PAGE_SIZE = 10 + +# CORS configuration + +CORS_ORIGIN_ALLOW_ALL = True diff --git a/src/static/base.css b/polaris/polaris/static/base.css similarity index 100% rename from src/static/base.css rename to polaris/polaris/static/base.css diff --git a/src/static/icon-refresh.svg b/polaris/polaris/static/icon-refresh.svg similarity index 100% rename from src/static/icon-refresh.svg rename to polaris/polaris/static/icon-refresh.svg diff --git a/src/static/leaf-bg.svg b/polaris/polaris/static/leaf-bg.svg similarity index 100% rename from src/static/leaf-bg.svg rename to polaris/polaris/static/leaf-bg.svg diff --git a/src/stellartoml/__init__.py b/polaris/polaris/stellartoml/__init__.py similarity index 100% rename from src/stellartoml/__init__.py rename to polaris/polaris/stellartoml/__init__.py diff --git a/src/stellartoml/urls.py b/polaris/polaris/stellartoml/urls.py similarity index 76% rename from src/stellartoml/urls.py rename to polaris/polaris/stellartoml/urls.py index 96843d94..cf5507bb 100644 --- a/src/stellartoml/urls.py +++ b/polaris/polaris/stellartoml/urls.py @@ -1,5 +1,5 @@ """This module defines the URL patterns for the `/.well-known/stellar.toml` endpoint.""" from django.urls import path -from .views import generate_toml +from polaris.stellartoml.views import generate_toml urlpatterns = [path("/stellar.toml", generate_toml)] diff --git a/src/stellartoml/views.py b/polaris/polaris/stellartoml/views.py similarity index 95% rename from src/stellartoml/views.py rename to polaris/polaris/stellartoml/views.py index bae437a1..ad5c92e3 100644 --- a/src/stellartoml/views.py +++ b/polaris/polaris/stellartoml/views.py @@ -8,9 +8,9 @@ import toml from django.http.response import HttpResponse -from django.conf import settings +from polaris import settings from rest_framework.decorators import api_view -from info.models import Asset +from polaris.models import Asset def generate_toml(request): diff --git a/src/templates/base.html b/polaris/polaris/templates/base.html similarity index 100% rename from src/templates/base.html rename to polaris/polaris/templates/base.html diff --git a/src/templates/deposit/form.html b/polaris/polaris/templates/deposit/form.html similarity index 100% rename from src/templates/deposit/form.html rename to polaris/polaris/templates/deposit/form.html diff --git a/src/templates/transaction/more_info.html b/polaris/polaris/templates/transaction/more_info.html similarity index 100% rename from src/templates/transaction/more_info.html rename to polaris/polaris/templates/transaction/more_info.html diff --git a/src/templates/withdraw/form.html b/polaris/polaris/templates/withdraw/form.html similarity index 100% rename from src/templates/withdraw/form.html rename to polaris/polaris/templates/withdraw/form.html diff --git a/src/tests/__init__.py b/polaris/polaris/tests/__init__.py similarity index 100% rename from src/tests/__init__.py rename to polaris/polaris/tests/__init__.py diff --git a/src/tests/auth_test.py b/polaris/polaris/tests/auth_test.py similarity index 97% rename from src/tests/auth_test.py rename to polaris/polaris/tests/auth_test.py index c8777a44..175b1021 100644 --- a/src/tests/auth_test.py +++ b/polaris/polaris/tests/auth_test.py @@ -1,11 +1,12 @@ """This module tests the endpoint.""" import json -from django.conf import settings + from stellar_sdk.keypair import Keypair from stellar_sdk.transaction_envelope import TransactionEnvelope from stellar_sdk.xdr import Xdr -from .conftest import STELLAR_ACCOUNT_1 +from polaris import settings +from polaris.tests.conftest import STELLAR_ACCOUNT_1 CLIENT_ADDRESS = "GDKFNRUATPH4BSZGVFDRBIGZ5QAFILVFRIRYNSQ4UO7V2ZQAPRNL73RI" CLIENT_SEED = "SDKWSBERDHP3SXW5A3LXSI7FWMMO5H7HG33KNYBKWH2HYOXJG2DXQHQY" diff --git a/src/tests/conftest.py b/polaris/polaris/tests/conftest.py similarity index 98% rename from src/tests/conftest.py rename to polaris/polaris/tests/conftest.py index 58393bc3..93647c36 100644 --- a/src/tests/conftest.py +++ b/polaris/polaris/tests/conftest.py @@ -5,8 +5,7 @@ import pytest from django.utils import timezone -from info.models import Asset -from transaction.models import Transaction +from polaris.models import Asset, Transaction STELLAR_ACCOUNT_1 = "GBCTKB22TYTLXHDWVENZGWMJWJ5YK2GTSF7LHAGMTSNAGLLSZVXRGXEW" STELLAR_ACCOUNT_2 = "GAB4FHP66SOQ4L22WQGW7BQCHGWRFWXQ6MWBZV2YRVTXSK3QPNFOTM3T" diff --git a/src/tests/deposit_test.py b/polaris/polaris/tests/deposit_test.py similarity index 90% rename from src/tests/deposit_test.py rename to polaris/polaris/tests/deposit_test.py index 20ed3b83..104e334e 100644 --- a/src/tests/deposit_test.py +++ b/polaris/polaris/tests/deposit_test.py @@ -7,26 +7,24 @@ from unittest.mock import patch import pytest -from deposit.tasks import ( - check_trustlines, - create_stellar_deposit, - SUCCESS_XDR, - TRUSTLINE_FAILURE_XDR, -) -from django.conf import settings from stellar_sdk import Keypair, TransactionEnvelope from stellar_sdk.client.response import Response from stellar_sdk.exceptions import BadRequestError -from transaction.models import Transaction - -from .helpers import mock_check_auth_success, mock_render_error_response, \ - mock_load_not_exist_account +from django.core.management import call_command + +from polaris import settings +from polaris.management.commands.create_stellar_deposit import ( + SUCCESS_XDR, + TRUSTLINE_FAILURE_XDR, +) +from polaris.models import Transaction +from polaris.tests.helpers import mock_check_auth_success, mock_render_error_response, mock_load_not_exist_account HORIZON_SUCCESS_RESPONSE = {"result_xdr": SUCCESS_XDR, "hash": "test_stellar_id"} @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_deposit_success(mock_check, client, acc1_usd_deposit_transaction_factory): """`GET /deposit` succeeds with no optional arguments.""" del mock_check @@ -40,7 +38,7 @@ def test_deposit_success(mock_check, client, acc1_usd_deposit_transaction_factor @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_deposit_success_memo(mock_check, client, acc1_usd_deposit_transaction_factory): """`GET /deposit` succeeds with valid `memo` and `memo_type`.""" del mock_check @@ -55,7 +53,7 @@ def test_deposit_success_memo(mock_check, client, acc1_usd_deposit_transaction_f assert content["type"] == "interactive_customer_info_needed" -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_deposit_no_params(mock_check, client): """`GET /deposit` fails with no required parameters.""" # Because this test does not use the database, the changed setting @@ -69,7 +67,7 @@ def test_deposit_no_params(mock_check, client): assert content == {"error": "`asset_code` and `account` are required parameters"} -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_deposit_no_account(mock_check, client): """`GET /deposit` fails with no `account` parameter.""" del mock_check @@ -81,7 +79,7 @@ def test_deposit_no_account(mock_check, client): @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_deposit_no_asset(mock_check, client, acc1_usd_deposit_transaction_factory): """`GET /deposit` fails with no `asset_code` parameter.""" del mock_check @@ -94,7 +92,7 @@ def test_deposit_no_asset(mock_check, client, acc1_usd_deposit_transaction_facto @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_deposit_invalid_account( mock_check, client, acc1_usd_deposit_transaction_factory ): @@ -112,7 +110,7 @@ def test_deposit_invalid_account( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_deposit_invalid_asset( mock_check, client, acc1_usd_deposit_transaction_factory ): @@ -129,7 +127,7 @@ def test_deposit_invalid_asset( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_deposit_invalid_memo_type( mock_check, client, acc1_usd_deposit_transaction_factory ): @@ -147,7 +145,7 @@ def test_deposit_invalid_memo_type( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_deposit_no_memo(mock_check, client, acc1_usd_deposit_transaction_factory): """`GET /deposit` fails with a valid `memo_type` and no `memo` provided.""" del mock_check @@ -163,7 +161,7 @@ def test_deposit_no_memo(mock_check, client, acc1_usd_deposit_transaction_factor @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_deposit_no_memo_type(mock_check, client, acc1_usd_deposit_transaction_factory): """`GET /deposit` fails with a valid `memo` and no `memo_type` provided.""" del mock_check @@ -179,7 +177,7 @@ def test_deposit_no_memo_type(mock_check, client, acc1_usd_deposit_transaction_f @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_deposit_invalid_hash_memo( mock_check, client, acc1_usd_deposit_transaction_factory ): @@ -261,16 +259,14 @@ def test_deposit_confirm_incorrect_amount(client, acc1_usd_deposit_transaction_f @pytest.mark.django_db @patch("stellar_sdk.server.Server.fetch_base_fee", return_value=100) @patch("stellar_sdk.server.Server.submit_transaction", return_value=HORIZON_SUCCESS_RESPONSE) -@patch("deposit.tasks.create_stellar_deposit.delay", side_effect=create_stellar_deposit) def test_deposit_confirm_success( - mock_delay, mock_submit, mock_base_fee, client, acc1_usd_deposit_transaction_factory, ): """`GET /deposit/confirm_transaction` succeeds with correct `amount` and `transaction_id`.""" - del mock_delay, mock_submit, mock_base_fee + del mock_submit, mock_base_fee deposit = acc1_usd_deposit_transaction_factory() amount = deposit.amount_in response = client.get( @@ -289,16 +285,14 @@ def test_deposit_confirm_success( @pytest.mark.django_db @patch("stellar_sdk.server.Server.fetch_base_fee", return_value=100) @patch("stellar_sdk.server.Server.submit_transaction", return_value=HORIZON_SUCCESS_RESPONSE) -@patch("deposit.tasks.create_stellar_deposit.delay", side_effect=create_stellar_deposit) def test_deposit_confirm_external_id( - mock_delay, mock_submit, mock_base_fee, client, acc1_usd_deposit_transaction_factory, ): """`GET /deposit/confirm_transaction` successfully stores an `external_id`.""" - del mock_delay, mock_submit, mock_base_fee + del mock_submit, mock_base_fee deposit = acc1_usd_deposit_transaction_factory() amount = deposit.amount_in external_id = "foo" @@ -328,7 +322,7 @@ def test_deposit_confirm_external_id( status_code=400, headers={}, url="", - text=json.dumps(dict(status=400, result_xdr=TRUSTLINE_FAILURE_XDR)), + text=json.dumps(dict(status=400, extras=dict(result_xdr=TRUSTLINE_FAILURE_XDR))), ) ), ) @@ -344,7 +338,7 @@ def test_deposit_stellar_no_trustline( deposit = acc1_usd_deposit_transaction_factory() deposit.status = Transaction.STATUS.pending_anchor deposit.save() - create_stellar_deposit(deposit.id) + call_command("create_stellar_deposit", deposit.id) assert ( Transaction.objects.get(id=deposit.id).status == Transaction.STATUS.pending_trust @@ -371,7 +365,7 @@ def test_deposit_stellar_no_account( deposit = acc1_usd_deposit_transaction_factory() deposit.status = Transaction.STATUS.pending_anchor deposit.save() - create_stellar_deposit(deposit.id) + call_command("create_stellar_deposit", deposit.id) assert ( Transaction.objects.get(id=deposit.id).status == Transaction.STATUS.pending_trust @@ -394,18 +388,16 @@ def test_deposit_stellar_success( deposit = acc1_usd_deposit_transaction_factory() deposit.status = Transaction.STATUS.pending_anchor deposit.save() - create_stellar_deposit(deposit.id) + call_command("create_stellar_deposit", deposit.id) assert Transaction.objects.get(id=deposit.id).status == Transaction.STATUS.completed @pytest.mark.django_db @patch("stellar_sdk.server.Server.fetch_base_fee", return_value=100) @patch("stellar_sdk.server.Server.submit_transaction", return_value=HORIZON_SUCCESS_RESPONSE) -@patch("deposit.tasks.create_stellar_deposit.delay", side_effect=create_stellar_deposit) -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_deposit_interactive_confirm_success( mock_check, - mock_delay, mock_submit, mock_base_fee, client, @@ -415,7 +407,7 @@ def test_deposit_interactive_confirm_success( `GET /deposit` and `GET /deposit/interactive_deposit` succeed with valid `account` and `asset_code`. """ - del mock_check, mock_delay, mock_submit, mock_base_fee + del mock_check, mock_submit, mock_base_fee deposit = acc1_usd_deposit_transaction_factory() response = client.get( f"/deposit?asset_code=USD&account={deposit.stellar_account}", follow=True @@ -484,23 +476,20 @@ def test_deposit_check_trustlines_success( Transaction.objects.get(id=deposit.id).status == Transaction.STATUS.pending_trust ) - check_trustlines() + call_command("check_trustlines") assert Transaction.objects.get(id=deposit.id).status == Transaction.STATUS.completed @pytest.mark.django_db @pytest.mark.skip -@patch("deposit.tasks.create_stellar_deposit.delay", side_effect=create_stellar_deposit) def test_deposit_check_trustlines_horizon( - mock_delay, client, acc1_usd_deposit_transaction_factory + client, acc1_usd_deposit_transaction_factory ): """ Tests the `check_trustlines` function's various logical paths. Note that the Stellar deposit is created synchronously. This makes Horizon calls, so it is skipped by the CI. """ - del mock_delay # Initiate a transaction with a new Stellar account. - print("Creating initial deposit.") deposit = acc1_usd_deposit_transaction_factory() keypair = Keypair.random() @@ -514,7 +503,6 @@ def test_deposit_check_trustlines_horizon( # Complete the interactive deposit. The transaction should be set # to pending_user_transfer_start, since wallet-side confirmation has not happened. - print("Completing interactive deposit.") transaction_id = content["id"] url = content["url"] amount = 20 @@ -543,10 +531,7 @@ def test_deposit_check_trustlines_horizon( # The Stellar account has not been registered, so # this should not change the status of the Transaction. - print( - "Check trustlines, try one. No trustline for account. Status should be pending_trust." - ) - check_trustlines() + call_command("check_trustlines") assert ( Transaction.objects.get(id=transaction_id).status == Transaction.STATUS.pending_trust @@ -557,7 +542,6 @@ def test_deposit_check_trustlines_horizon( from stellar_sdk.asset import Asset from stellar_sdk.transaction_builder import TransactionBuilder - print("Create trustline.") asset_code = deposit.asset.code asset_issuer = settings.STELLAR_DISTRIBUTION_ACCOUNT_ADDRESS Asset(code=asset_code, issuer=asset_issuer) @@ -572,8 +556,7 @@ def test_deposit_check_trustlines_horizon( response = server.submit_transaction(transaction) assert response["result_xdr"] == "AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAGAAAAAAAAAAA=" - print("Check trustlines, try three. Status should be completed.") - check_trustlines() + call_command("check_trustlines") completed_transaction = Transaction.objects.get(id=transaction_id) assert completed_transaction.status == Transaction.STATUS.completed assert ( @@ -619,7 +602,7 @@ def test_deposit_authenticated_success(client, acc1_usd_deposit_transaction_fact @pytest.mark.django_db -@patch("helpers.render_error_response", side_effect=mock_render_error_response) +@patch("polaris.helpers.render_error_response", side_effect=mock_render_error_response) def test_deposit_no_jwt(mock_render, client, acc1_usd_deposit_transaction_factory): """`GET /deposit` fails if a required JWT isn't provided.""" del mock_render diff --git a/src/tests/fee_test.py b/polaris/polaris/tests/fee_test.py similarity index 86% rename from src/tests/fee_test.py rename to polaris/polaris/tests/fee_test.py index e1d88a2b..47ea0d43 100644 --- a/src/tests/fee_test.py +++ b/polaris/polaris/tests/fee_test.py @@ -3,14 +3,14 @@ from unittest.mock import patch import pytest -from django.conf import settings from stellar_sdk.keypair import Keypair from stellar_sdk.transaction_envelope import TransactionEnvelope -from .helpers import mock_check_auth_success, mock_render_error_response +from polaris import settings +from polaris.tests.helpers import mock_check_auth_success, mock_render_error_response -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_fee_no_params(mock_check, client): """Fails with no params provided.""" del mock_check @@ -22,7 +22,7 @@ def test_fee_no_params(mock_check, client): @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_fee_wrong_asset_code(mock_check, client): """Fails with an invalid `asset_code`.""" del mock_check @@ -34,7 +34,7 @@ def test_fee_wrong_asset_code(mock_check, client): @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_fee_no_operation(mock_check, client, usd_asset_factory): """Fails with no `operation` provided.""" del mock_check @@ -47,7 +47,7 @@ def test_fee_no_operation(mock_check, client, usd_asset_factory): @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_fee_invalid_operation(mock_check, client, usd_asset_factory): """Fails with an invalid `operation` provided.""" del mock_check @@ -60,7 +60,7 @@ def test_fee_invalid_operation(mock_check, client, usd_asset_factory): @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_fee_no_amount(mock_check, client, usd_asset_factory): """Fails with no amount provided.""" del mock_check @@ -73,7 +73,7 @@ def test_fee_no_amount(mock_check, client, usd_asset_factory): @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_fee_invalid_amount(mock_check, client, usd_asset_factory): """Fails with a non-float amount provided.""" del mock_check @@ -88,7 +88,7 @@ def test_fee_invalid_amount(mock_check, client, usd_asset_factory): @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_fee_invalid_operation_type_deposit(mock_check, client, usd_asset_factory): """Fails if the specified deposit `operation` is not valid for `asset_code`.""" del mock_check @@ -103,7 +103,7 @@ def test_fee_invalid_operation_type_deposit(mock_check, client, usd_asset_factor @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_fee_invalid_operation_type_withdraw(mock_check, client, usd_asset_factory): """Fails if the specified withdraw `operation` is not enabled for `asset_code`.""" del mock_check @@ -118,7 +118,7 @@ def test_fee_invalid_operation_type_withdraw(mock_check, client, usd_asset_facto @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_fee_withdraw_disabled(mock_check, client, eth_asset_factory): """Fails if the withdraw `operation` is not enabled for the `asset_code`.""" del mock_check @@ -134,7 +134,7 @@ def test_fee_withdraw_disabled(mock_check, client, eth_asset_factory): @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_fee_deposit_disabled(mock_check, client, eth_asset_factory): """Fails if the deposit `operation` is not enabled for `asset_code`.""" del mock_check @@ -152,7 +152,7 @@ def test_fee_deposit_disabled(mock_check, client, eth_asset_factory): @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_fee_valid_deposit(mock_check, client, usd_asset_factory): """Succeeds for a valid deposit.""" del mock_check @@ -169,7 +169,7 @@ def test_fee_valid_deposit(mock_check, client, usd_asset_factory): # Fixed: 5.0 Percent = 1 @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_fee_valid_withdrawal(mock_check, client, usd_asset_factory): """Succeeds for a valid withdrawal.""" del mock_check @@ -185,7 +185,7 @@ def test_fee_valid_withdrawal(mock_check, client, usd_asset_factory): @pytest.mark.django_db -@patch("helpers.render_error_response", side_effect=mock_render_error_response) +@patch("polaris.helpers.render_error_response", side_effect=mock_render_error_response) def test_fee_authenticated_success(mock_render, client, usd_asset_factory): """Succeeds for a valid fee, with successful authentication.""" del mock_render @@ -225,7 +225,7 @@ def test_fee_authenticated_success(mock_render, client, usd_asset_factory): @pytest.mark.django_db -@patch("helpers.render_error_response", side_effect=mock_render_error_response) +@patch("polaris.helpers.render_error_response", side_effect=mock_render_error_response) def test_fee_no_jwt(mock_render, client, usd_asset_factory): """`GET /fee` fails if a required JWT is not provided.""" del mock_render diff --git a/src/tests/helpers.py b/polaris/polaris/tests/helpers.py similarity index 100% rename from src/tests/helpers.py rename to polaris/polaris/tests/helpers.py diff --git a/src/tests/info_test.py b/polaris/polaris/tests/info_test.py similarity index 100% rename from src/tests/info_test.py rename to polaris/polaris/tests/info_test.py diff --git a/src/tests/transaction_test.py b/polaris/polaris/tests/transaction_test.py similarity index 92% rename from src/tests/transaction_test.py rename to polaris/polaris/tests/transaction_test.py index 5a41474d..7bce1013 100644 --- a/src/tests/transaction_test.py +++ b/polaris/polaris/tests/transaction_test.py @@ -3,16 +3,16 @@ from unittest.mock import patch import pytest -from django.conf import settings from stellar_sdk.keypair import Keypair from stellar_sdk.transaction_envelope import TransactionEnvelope -from transaction.models import Transaction -from .helpers import mock_check_auth_success, mock_render_error_response +from polaris import settings +from polaris.models import Transaction +from polaris.tests.helpers import mock_check_auth_success, mock_render_error_response @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_transaction_requires_param( mock_check, client, @@ -32,7 +32,7 @@ def test_transaction_requires_param( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_transaction_id_filter_and_format( mock_check, client, @@ -87,7 +87,7 @@ def test_transaction_id_filter_and_format( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_transaction_stellar_filter( mock_check, client, @@ -113,7 +113,7 @@ def test_transaction_stellar_filter( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_transaction_external_filter( mock_check, client, @@ -142,7 +142,7 @@ def test_transaction_external_filter( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_transaction_multiple_filters( mock_check, client, @@ -175,7 +175,7 @@ def test_transaction_multiple_filters( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_transaction_filtering_no_result( mock_check, client, @@ -202,7 +202,7 @@ def test_transaction_filtering_no_result( @pytest.mark.django_db -@patch("helpers.render_error_response", side_effect=mock_render_error_response) +@patch("polaris.helpers.render_error_response", side_effect=mock_render_error_response) def test_transaction_authenticated_success( mock_render, client, @@ -258,7 +258,7 @@ def test_transaction_authenticated_success( @pytest.mark.django_db -@patch("helpers.render_error_response", side_effect=mock_render_error_response) +@patch("polaris.helpers.render_error_response", side_effect=mock_render_error_response) def test_transaction_no_jwt( mock_render, client, acc2_eth_withdrawal_transaction_factory ): diff --git a/src/tests/transactions_more_info_test.py b/polaris/polaris/tests/transactions_more_info_test.py similarity index 100% rename from src/tests/transactions_more_info_test.py rename to polaris/polaris/tests/transactions_more_info_test.py diff --git a/src/tests/transactions_test.py b/polaris/polaris/tests/transactions_test.py similarity index 92% rename from src/tests/transactions_test.py rename to polaris/polaris/tests/transactions_test.py index 32f283f0..db84093e 100644 --- a/src/tests/transactions_test.py +++ b/polaris/polaris/tests/transactions_test.py @@ -8,11 +8,11 @@ from stellar_sdk.keypair import Keypair from stellar_sdk.transaction_envelope import TransactionEnvelope -from .helpers import mock_check_auth_success, mock_render_error_response +from polaris.tests.helpers import mock_check_auth_success, mock_render_error_response @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_required_fields(mock_check, client, acc2_eth_withdrawal_transaction_factory): """Fails without required parameters.""" del mock_check @@ -26,7 +26,7 @@ def test_required_fields(mock_check, client, acc2_eth_withdrawal_transaction_fac @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_required_account(mock_check, client, acc2_eth_withdrawal_transaction_factory): """Fails without `account` parameter.""" del mock_check @@ -42,7 +42,7 @@ def test_required_account(mock_check, client, acc2_eth_withdrawal_transaction_fa @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_required_asset_code( mock_check, client, acc2_eth_withdrawal_transaction_factory ): @@ -60,7 +60,7 @@ def test_required_asset_code( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_transactions_format( mock_check, client, @@ -83,7 +83,7 @@ def test_transactions_format( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_transactions_order( mock_check, client, @@ -110,7 +110,7 @@ def test_transactions_order( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_transactions_content( mock_check, client, @@ -211,7 +211,7 @@ def test_transactions_content( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_paging_id( mock_check, client, @@ -240,7 +240,7 @@ def test_paging_id( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_kind_filter( mock_check, client, @@ -269,7 +269,7 @@ def test_kind_filter( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_kind_filter_no_500( mock_check, client, @@ -296,7 +296,7 @@ def test_kind_filter_no_500( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_limit( mock_check, client, @@ -322,7 +322,7 @@ def test_limit( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_invalid_limit( mock_check, client, @@ -348,7 +348,7 @@ def test_invalid_limit( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_negative_limit( mock_check, client, @@ -374,7 +374,7 @@ def test_negative_limit( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_no_older_than_filter( mock_check, client, @@ -405,7 +405,7 @@ def test_no_older_than_filter( @pytest.mark.django_db -@patch("helpers.render_error_response", side_effect=mock_render_error_response) +@patch("polaris.helpers.render_error_response", side_effect=mock_render_error_response) def test_transactions_authenticated_success( mock_render, client, @@ -458,7 +458,7 @@ def test_transactions_authenticated_success( @pytest.mark.django_db -@patch("helpers.render_error_response", side_effect=mock_render_error_response) +@patch("polaris.helpers.render_error_response", side_effect=mock_render_error_response) def test_transactions_no_jwt( mock_render, client, acc2_eth_withdrawal_transaction_factory ): diff --git a/src/tests/withdraw_test.py b/polaris/polaris/tests/withdraw_test.py similarity index 88% rename from src/tests/withdraw_test.py rename to polaris/polaris/tests/withdraw_test.py index 4b01c9fa..5db68af8 100644 --- a/src/tests/withdraw_test.py +++ b/polaris/polaris/tests/withdraw_test.py @@ -3,18 +3,18 @@ from unittest.mock import patch import pytest -from django.conf import settings -from helpers import format_memo_horizon from stellar_sdk.keypair import Keypair from stellar_sdk.transaction_envelope import TransactionEnvelope -from transaction.management.commands.watch_transactions import process_withdrawal -from transaction.models import Transaction -from .helpers import mock_check_auth_success, mock_render_error_response +from polaris import settings +from polaris.helpers import format_memo_horizon +from polaris.management.commands.watch_transactions import process_withdrawal +from polaris.models import Transaction +from polaris.tests.helpers import mock_check_auth_success, mock_render_error_response @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_withdraw_success(mock_check, client, acc1_usd_withdrawal_transaction_factory): """`GET /withdraw` succeeds with no optional arguments.""" del mock_check @@ -26,7 +26,7 @@ def test_withdraw_success(mock_check, client, acc1_usd_withdrawal_transaction_fa @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_withdraw_invalid_asset( mock_check, client, acc1_usd_withdrawal_transaction_factory ): @@ -40,7 +40,7 @@ def test_withdraw_invalid_asset( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_withdraw_no_asset(mock_check, client): """`GET /withdraw fails with no asset argument.""" del mock_check @@ -51,7 +51,7 @@ def test_withdraw_no_asset(mock_check, client): @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_withdraw_interactive_no_txid( mock_check, client, acc1_usd_withdrawal_transaction_factory ): @@ -67,7 +67,7 @@ def test_withdraw_interactive_no_txid( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_withdraw_interactive_no_asset( mock_check, client, acc1_usd_withdrawal_transaction_factory ): @@ -85,7 +85,7 @@ def test_withdraw_interactive_no_asset( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_withdraw_interactive_invalid_asset( mock_check, client, acc1_usd_withdrawal_transaction_factory ): @@ -108,10 +108,10 @@ def test_withdraw_interactive_invalid_asset( @pytest.mark.django_db @patch( - "transaction.management.commands.watch_transactions.stream_transactions", + "polaris.management.commands.watch_transactions.stream_transactions", return_value=[{}], ) -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_withdraw_interactive_failure_no_memotype( mock_check, mock_transactions, client, acc1_usd_withdrawal_transaction_factory ): @@ -139,10 +139,10 @@ def test_withdraw_interactive_failure_no_memotype( @pytest.mark.django_db @patch( - "transaction.management.commands.watch_transactions.stream_transactions", + "polaris.management.commands.watch_transactions.stream_transactions", return_value=[{"memo_type": "not_hash"}], ) -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_withdraw_interactive_failure_incorrect_memotype( mock_check, mock_transactions, client, acc1_usd_withdrawal_transaction_factory ): @@ -170,10 +170,10 @@ def test_withdraw_interactive_failure_incorrect_memotype( @pytest.mark.django_db @patch( - "transaction.management.commands.watch_transactions.stream_transactions", + "polaris.management.commands.watch_transactions.stream_transactions", return_value=[{"memo_type": "hash"}], ) -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_withdraw_interactive_failure_no_memo( mock_check, mock_transactions, client, acc1_usd_withdrawal_transaction_factory ): @@ -201,10 +201,10 @@ def test_withdraw_interactive_failure_no_memo( @pytest.mark.django_db @patch( - "transaction.management.commands.watch_transactions.stream_transactions", + "polaris.management.commands.watch_transactions.stream_transactions", return_value=[{"memo_type": "hash", "memo": "wrong_memo"}], ) -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_withdraw_interactive_failure_incorrect_memo( mock_check, mock_transactions, client, acc1_usd_withdrawal_transaction_factory ): @@ -231,7 +231,7 @@ def test_withdraw_interactive_failure_incorrect_memo( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_withdraw_interactive_success_transaction_unsuccessful( mock_check, client, acc1_usd_withdrawal_transaction_factory ): @@ -271,7 +271,7 @@ def test_withdraw_interactive_success_transaction_unsuccessful( @pytest.mark.django_db -@patch("helpers.check_auth", side_effect=mock_check_auth_success) +@patch("polaris.helpers.check_auth", side_effect=mock_check_auth_success) def test_withdraw_interactive_success_transaction_successful( mock_check, client, acc1_usd_withdrawal_transaction_factory ): @@ -345,7 +345,7 @@ def test_withdraw_authenticated_success( @pytest.mark.django_db -@patch("helpers.render_error_response", side_effect=mock_render_error_response) +@patch("polaris.helpers.render_error_response", side_effect=mock_render_error_response) def test_withdraw_no_jwt(mock_render, client, acc1_usd_withdrawal_transaction_factory): """`GET /withdraw` fails if a required JWT isn't provided.""" del mock_render diff --git a/src/transaction/__init__.py b/polaris/polaris/transaction/__init__.py similarity index 100% rename from src/transaction/__init__.py rename to polaris/polaris/transaction/__init__.py diff --git a/src/transaction/admin.py b/polaris/polaris/transaction/admin.py similarity index 87% rename from src/transaction/admin.py rename to polaris/polaris/transaction/admin.py index 06e740e0..3a96de79 100644 --- a/src/transaction/admin.py +++ b/polaris/polaris/transaction/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from .models import Transaction +from polaris.models import Transaction class TransactionAdmin(admin.ModelAdmin): diff --git a/src/transaction/serializers.py b/polaris/polaris/transaction/serializers.py similarity index 96% rename from src/transaction/serializers.py rename to polaris/polaris/transaction/serializers.py index d40152e6..ae6408f5 100644 --- a/src/transaction/serializers.py +++ b/polaris/polaris/transaction/serializers.py @@ -1,7 +1,7 @@ """This module defines a serializer for the transaction model.""" from rest_framework import serializers -from .models import Transaction +from polaris.models import Transaction class TransactionSerializer(serializers.ModelSerializer): diff --git a/src/transaction/urls.py b/polaris/polaris/transaction/urls.py similarity index 78% rename from src/transaction/urls.py rename to polaris/polaris/transaction/urls.py index 658465b5..74102681 100644 --- a/src/transaction/urls.py +++ b/polaris/polaris/transaction/urls.py @@ -1,6 +1,6 @@ """This module defines the URL patterns for the `/transaction(s)` endpoints.""" from django.urls import path -from .views import more_info, transaction, transactions +from polaris.transaction.views import more_info, transaction, transactions urlpatterns = [ path("transaction", transaction), diff --git a/src/transaction/views.py b/polaris/polaris/transaction/views.py similarity index 96% rename from src/transaction/views.py rename to polaris/polaris/transaction/views.py index 5744b471..482ec9ad 100644 --- a/src/transaction/views.py +++ b/polaris/polaris/transaction/views.py @@ -6,14 +6,14 @@ from rest_framework.response import Response from rest_framework import status -from django.conf import settings +from polaris import settings from django.shortcuts import render from django.urls import reverse from django.views.decorators.clickjacking import xframe_options_exempt -from helpers import render_error_response, validate_sep10_token, validate_jwt_request -from .models import Transaction -from .serializers import TransactionSerializer +from polaris.helpers import render_error_response, validate_sep10_token, validate_jwt_request +from polaris.models import Transaction +from polaris.transaction.serializers import TransactionSerializer def _validate_limit(limit): diff --git a/src/app/urls.py b/polaris/polaris/urls.py similarity index 65% rename from src/app/urls.py rename to polaris/polaris/urls.py index dade7ac1..8cc6fb17 100644 --- a/src/app/urls.py +++ b/polaris/polaris/urls.py @@ -17,12 +17,11 @@ from django.urls import path, include urlpatterns = [ - path("admin", admin.site.urls), - path("info", include("info.urls")), - path("fee", include("fee.urls")), - path("", include("transaction.urls")), - path("deposit", include("deposit.urls")), - path(".well-known", include("stellartoml.urls")), - path("withdraw", include("withdraw.urls")), - path("auth", include("sep10auth.urls")), + path("info", include("polaris.info.urls")), + path("fee", include("polaris.fee.urls")), + path("", include("polaris.transaction.urls")), + path("deposit", include("polaris.deposit.urls")), + path(".well-known", include("polaris.stellartoml.urls")), + path("withdraw", include("polaris.withdraw.urls")), + path("auth", include("polaris.sep10auth.urls")), ] diff --git a/src/transaction/management/__init__.py b/polaris/polaris/withdraw/__init__.py similarity index 100% rename from src/transaction/management/__init__.py rename to polaris/polaris/withdraw/__init__.py diff --git a/src/withdraw/forms.py b/polaris/polaris/withdraw/forms.py similarity index 100% rename from src/withdraw/forms.py rename to polaris/polaris/withdraw/forms.py diff --git a/src/withdraw/urls.py b/polaris/polaris/withdraw/urls.py similarity index 77% rename from src/withdraw/urls.py rename to polaris/polaris/withdraw/urls.py index 84c1bef0..38c9fa8b 100644 --- a/src/withdraw/urls.py +++ b/polaris/polaris/withdraw/urls.py @@ -1,6 +1,6 @@ """This module defines the URL patterns for the `/withdraw` endpoint.""" from django.urls import path -from .views import withdraw, interactive_withdraw +from polaris.withdraw.views import withdraw, interactive_withdraw urlpatterns = [ path("", withdraw), diff --git a/src/withdraw/views.py b/polaris/polaris/withdraw/views.py similarity index 96% rename from src/withdraw/views.py rename to polaris/polaris/withdraw/views.py index 5904f884..581690cf 100644 --- a/src/withdraw/views.py +++ b/polaris/polaris/withdraw/views.py @@ -6,7 +6,7 @@ import uuid from urllib.parse import urlencode -from django.conf import settings +from polaris import settings from django.shortcuts import render from django.urls import reverse from django.views.decorators.clickjacking import xframe_options_exempt @@ -14,16 +14,15 @@ from rest_framework.decorators import api_view from rest_framework.response import Response -from helpers import ( +from polaris.helpers import ( render_error_response, create_transaction_id, calc_fee, validate_sep10_token, ) -from info.models import Asset -from transaction.models import Transaction -from transaction.serializers import TransactionSerializer -from .forms import WithdrawForm +from polaris.models import Asset, Transaction +from polaris.transaction.serializers import TransactionSerializer +from polaris.withdraw.forms import WithdrawForm def _construct_interactive_url(request, transaction_id): diff --git a/pytest.ini b/pytest.ini index 49e1399a..20f71291 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] -DJANGO_SETTINGS_MODULE = app.settings +DJANGO_SETTINGS_MODULE = polaris.settings python_files = test.py test_*.py *_test.py -addopts = --rootdir src/ +addopts = --rootdir polaris/ diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..224a7795 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +description-file = README.md \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..759868db --- /dev/null +++ b/setup.py @@ -0,0 +1,66 @@ +from setuptools import setup, find_packages + + +with open("README.md") as f: + long_description = f.read() + +setup( + name="django-polaris", + version=0.9, + description="A SEP-24-compliant Django anchor server", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/stellar/django-polaris", + author="Jake Urban", + author_email="jake@stellar.org", + license="Apache license 2.0", + classifiers=[ + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Django :: 2.2", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.7", + ], + keywords=["stellar", "sdf", "anchor", "server", "polaris", "sep-24", "sep24"], + include_package_data=True, + package_dir={"": "polaris"}, + packages=find_packages("polaris"), + install_requires=[ + "aiohttp==4.0.0a1", + "aiohttp-sse-client==0.1.6", + "async-timeout==3.0.1", + "attrs==19.3.0", + "certifi==2019.9.11", + "cffi==1.13.2", + "chardet==3.0.4", + "crc16==0.1.1", + "django==2.2.4", + "django-cors-headers==3.1.1", + "django-environ==0.4.5", + "django-model-utils==3.2.0", + "djangorestframework==3.10.3", + "gunicorn==20.0.0", + "idna==2.8", + "mnemonic==0.19", + "multidict==4.5.2", + "psycopg2-binary==2.8.4", + "pycparser==2.19", + "pyjwt==1.7.1", + "pynacl==1.3.0", + "pytz==2019.3", + "requests==2.22.0", + "six==1.13.0", + "sqlparse==0.3.0", + "stellar-base-sseclient==0.0.21", + "stellar-sdk==2.0.0b4", + "toml==0.10.0", + "typing-extensions==3.7.4.1", + "urllib3==1.25.7", + "whitenoise==4.1.4", + "yarl==1.4.0a11", + ], + python_requires=">=3.7", +) diff --git a/src/app/__init__.py b/src/app/__init__.py deleted file mode 100644 index 931661fd..00000000 --- a/src/app/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""This module initializes the Django app.""" -from celery import app as celery_app - -__all__ = ("celery_app",) diff --git a/src/app/celery.py b/src/app/celery.py deleted file mode 100644 index faccec20..00000000 --- a/src/app/celery.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -This module sets up Celery for the Django application. -See: https://docs.celeryproject.org/en/latest/django/first-steps-with-django.html -""" -# pylint: disable=invalid-name -from __future__ import absolute_import -import os - -from celery import Celery -from django.conf import settings - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") -app = Celery("app") - -app.config_from_object("django.conf:settings", namespace="CELERY") - -app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) diff --git a/src/app/settings.py b/src/app/settings.py deleted file mode 100644 index 756d3183..00000000 --- a/src/app/settings.py +++ /dev/null @@ -1,203 +0,0 @@ -""" -Django settings for app project. - -Generated by 'django-admin startproject' using Django 2.2.2. - -For more information on this file, see -https://docs.djangoproject.com/en/2.2/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/2.2/ref/settings/ -""" -# pylint: disable=invalid-name -import os -import environ - -from stellar_sdk.server import Server -from stellar_sdk.keypair import Keypair - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) - -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -REPO_DIR = os.path.dirname(BASE_DIR) - - -# Load environment variables from .env -env = environ.Env() - -env_file = os.path.join(REPO_DIR, ".env") -if os.path.exists(env_file): - environ.Env.read_env(str(env_file)) - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = env("DJANGO_SECRET_KEY") - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = env.bool("DJANGO_DEBUG", False) - -ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=[]) - -STELLAR_DISTRIBUTION_ACCOUNT_SEED = env("STELLAR_DISTRIBUTION_ACCOUNT_SEED") -STELLAR_DISTRIBUTION_ACCOUNT_ADDRESS = ( - Keypair.from_secret(STELLAR_DISTRIBUTION_ACCOUNT_SEED).public_key -) -STELLAR_ISSUER_ACCOUNT_ADDRESS = env("STELLAR_ISSUER_ACCOUNT_ADDRESS") -STELLAR_NETWORK_PASSPHRASE = env("STELLAR_NETWORK_PASSPHRASE", default="Test SDF Network ; September 2015") -HORIZON_URI = env("HORIZON_URI", default="https://horizon-testnet.stellar.org/") -HORIZON_SERVER = Server(horizon_url=HORIZON_URI) -REDIS_URL = env("REDIS_URL", default=None) -SERVER_JWT_KEY = env("SERVER_JWT_KEY") -OPERATION_DEPOSIT = "deposit" -OPERATION_WITHDRAWAL = "withdraw" -ACCOUNT_STARTING_BALANCE = str(2.01) - -# Application definition -DJANGO_APPS = [ - "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.messages", - "django.contrib.staticfiles", -] - -THIRD_PARTY_APPS = ["rest_framework", "corsheaders"] - -CUSTOM_APPS = ["info", "transaction"] - -INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + CUSTOM_APPS - -MIDDLEWARE = [ - "django.middleware.security.SecurityMiddleware", - "corsheaders.middleware.CorsMiddleware", - "whitenoise.middleware.WhiteNoiseMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", - "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", -] - -APPEND_SLASH = False - -ROOT_URLCONF = "app.urls" - -TEMPLATES = [ - { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [os.path.join(BASE_DIR, "templates")], - "APP_DIRS": True, - "OPTIONS": { - "context_processors": [ - "django.template.context_processors.debug", - "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - ] - }, - } -] - -WSGI_APPLICATION = "app.wsgi.application" - - -# Database -# https://docs.djangoproject.com/en/2.2/ref/settings/#databases - -DATABASES = { - "default": env.db( - "DATABASE_URL", default="sqlite:///" + os.path.join(REPO_DIR, "db.sqlite3") - ) -} - - -# Password validation -# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" - }, - {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, - {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, - {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, -] - - -# Internationalization -# https://docs.djangoproject.com/en/2.2/topics/i18n/ - -LANGUAGE_CODE = "en-us" - -TIME_ZONE = "UTC" - -USE_I18N = True - -USE_L10N = True - -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/2.2/howto/static-files/ - -STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") -STATIC_URL = "/static/" -STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" -STATICFILES_DIRS = ( - os.path.join(BASE_DIR, 'static'), -) -# Django Rest Framework Settings: - -REST_FRAMEWORK = { - "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", - "PAGE_SIZE": 10, -} - - -# API Config - -DEFAULT_PAGE_SIZE = 10 - - -# Logging config - -LOGGING = { - "version": 1, - "disable_existing_loggers": False, - "formatters": { - "verbose": { - "format": ( - "%(asctime)s [%(process)d] [%(levelname)s] " - + "pathname=%(pathname)s lineno=%(lineno)s " - + "funcname=%(funcName)s %(message)s" - ), - "datefmt": "%Y-%m-%d %H:%M:%S", - }, - "simple": {"format": "%(levelname)s %(message)s"}, - }, - "handlers": { - "null": {"level": "DEBUG", "class": "logging.NullHandler"}, - "console": { - "level": "DEBUG", - "class": "logging.StreamHandler", - "formatter": "verbose", - }, - }, - "loggers": {"testlogger": {"handlers": ["console"], "level": "INFO"}}, -} - -# CORS configuration -CORS_ORIGIN_ALLOW_ALL = True - -# Celery config -CELERY_BROKER_URL = REDIS_URL -CELERY_RESULT_BACKEND = REDIS_URL -CELERY_ACCEPT_CONTENT = ["application/json"] -CELERY_TASK_SERIALIZER = "json" -CELERY_RESULT_SERIALIZER = "json" diff --git a/src/app/wsgi.py b/src/app/wsgi.py deleted file mode 100644 index 347ac44a..00000000 --- a/src/app/wsgi.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -WSGI config for app project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ -""" - -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") - -application = get_wsgi_application() diff --git a/src/deposit/apps.py b/src/deposit/apps.py deleted file mode 100644 index accafe16..00000000 --- a/src/deposit/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -"""This module configures the deposit app.""" -from django.apps import AppConfig - - -class DepositConfig(AppConfig): - """This stores metadata for the deposit app.""" - - name = "deposit" diff --git a/src/deposit/tasks.py b/src/deposit/tasks.py deleted file mode 100644 index 28cd1f24..00000000 --- a/src/deposit/tasks.py +++ /dev/null @@ -1,158 +0,0 @@ -"""This module defines the asynchronous tasks needed for deposits, run via Celery.""" -import logging - -from celery.task.schedules import crontab -from celery.decorators import periodic_task -from django.conf import settings -from django.utils.timezone import now - -from stellar_sdk.exceptions import BaseHorizonError -from stellar_sdk.transaction_builder import TransactionBuilder - -from app.celery import app -from transaction.models import Transaction - -TRUSTLINE_FAILURE_XDR = "AAAAAAAAAGT/////AAAAAQAAAAAAAAAB////+gAAAAA=" -SUCCESS_XDR = "AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAA=" - -logger = logging.getLogger(__name__) - - -@app.task -def create_stellar_deposit(transaction_id): - """Create and submit the Stellar transaction for the deposit.""" - transaction = Transaction.objects.get(id=transaction_id) - - # We check the Transaction status to avoid double submission of a Stellar - # transaction. The Transaction can be either `pending_anchor` if the task - # is called from `GET deposit/confirm_transaction` or `pending_trust` if called - # from the `check_trustlines()`. - if transaction.status not in [ - Transaction.STATUS.pending_anchor, - Transaction.STATUS.pending_trust, - ]: - logger.debug( - "unexpected transaction status %s at top of create_stellar_deposit", - transaction.status, - ) - return - transaction.status = Transaction.STATUS.pending_stellar - transaction.save() - - # We can assume transaction has valid stellar_account, amount_in, and asset - # because this task is only called after those parameters are validated. - stellar_account = transaction.stellar_account - payment_amount = round(transaction.amount_in - transaction.amount_fee, 7) - asset = transaction.asset.code - - # If the given Stellar account does not exist, create - # the account with at least enough XLM for the minimum - # reserve and a trust line (recommended 2.01 XLM), update - # the transaction in our internal database, and return. - - server = settings.HORIZON_SERVER - starting_balance = settings.ACCOUNT_STARTING_BALANCE - server_account = server.load_account(settings.STELLAR_DISTRIBUTION_ACCOUNT_ADDRESS) - base_fee = server.fetch_base_fee() - builder = TransactionBuilder(source_account=server_account, - network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE, - base_fee=base_fee) - try: - server.load_account(stellar_account) - except BaseHorizonError as address_exc: - # 404 code corresponds to Resource Missing. - if address_exc.status != 404: - logger.debug( - "error with message %s when loading stellar account", - address_exc.message, - ) - return - transaction_envelope = builder \ - .append_create_account_op(destination=stellar_account, - starting_balance=starting_balance, - source=settings.STELLAR_DISTRIBUTION_ACCOUNT_ADDRESS) \ - .build() - transaction_envelope.sign(settings.STELLAR_DISTRIBUTION_ACCOUNT_SEED) - try: - server.submit_transaction(transaction_envelope) - except BaseHorizonError as submit_exc: - logger.debug( - f"error with message {submit_exc.message} when submitting create account to horizon" - ) - return - transaction.status = Transaction.STATUS.pending_trust - transaction.save() - return - - # If the account does exist, deposit the desired amount of the given - # asset via a Stellar payment. If that payment succeeds, we update the - # transaction to completed at the current time. If it fails due to a - # trustline error, we update the database accordingly. Else, we do not update. - - transaction_envelope = builder \ - .append_payment_op(destination=stellar_account, - asset_code=asset, - asset_issuer=settings.STELLAR_ISSUER_ACCOUNT_ADDRESS, - amount=str(payment_amount)) \ - .build() - transaction_envelope.sign(settings.STELLAR_DISTRIBUTION_ACCOUNT_SEED) - try: - response = server.submit_transaction(transaction_envelope) - # Functional errors at this stage are Horizon errors. - except BaseHorizonError as exception: - if TRUSTLINE_FAILURE_XDR not in exception.result_xdr: - logger.debug( - "error with message %s when submitting payment to horizon, non-trustline failure", - exception.message, - ) - return - logger.debug("trustline error when submitting transaction to horizon") - transaction.status = Transaction.STATUS.pending_trust - transaction.save() - return - - # If this condition is met, the Stellar payment succeeded, so we - # can mark the transaction as completed. - if response["result_xdr"] != SUCCESS_XDR: - logger.debug("payment stellar transaction failed when submitted to horizon") - return - - transaction.stellar_transaction_id = response["hash"] - transaction.status = Transaction.STATUS.completed - transaction.completed_at = now() - transaction.status_eta = 0 # No more status change. - transaction.amount_out = payment_amount - transaction.save() - - -@periodic_task(run_every=(crontab(minute="*/1")), ignore_result=True) -def check_trustlines(): - """ - Create Stellar transaction for deposit transactions marked as pending trust, if a - trustline has been created. - """ - transactions = Transaction.objects.filter(status=Transaction.STATUS.pending_trust) - server = settings.HORIZON_SERVER - for transaction in transactions: - try: - account = server.accounts().account_id(transaction.stellar_account).call() - except BaseHorizonError as exc: - logger.debug("could not load account using provided horizon URL") - continue - try: - balances = account["balances"] - except KeyError: - logger.debug("horizon account response had no balances") - continue - for balance in balances: - try: - asset_code = balance["asset_code"] - except KeyError: - logger.debug("horizon balances had no asset_code") - continue - if asset_code == transaction.asset.code: - create_stellar_deposit(transaction.id) - - -if __name__ == "__main__": - app.worker_main() diff --git a/src/fee/apps.py b/src/fee/apps.py deleted file mode 100644 index 42df99da..00000000 --- a/src/fee/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -"""This module configures the fee app.""" -from django.apps import AppConfig - - -class FeeConfig(AppConfig): - """Stores metadata for the fee app.""" - - name = "fee" diff --git a/src/info/apps.py b/src/info/apps.py deleted file mode 100644 index 2a3dd90c..00000000 --- a/src/info/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -"""This module configures the info app.""" -from django.apps import AppConfig - - -class InfoConfig(AppConfig): - """This stores metadata for the info app.""" - - name = "info" diff --git a/src/info/migrations/0001_initial.py b/src/info/migrations/0001_initial.py deleted file mode 100644 index 84ba4eb2..00000000 --- a/src/info/migrations/0001_initial.py +++ /dev/null @@ -1,68 +0,0 @@ -# Generated by Django 2.2.3 on 2019-07-01 14:39 - -from django.db import migrations, models -import django.utils.timezone -import model_utils.fields - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='InfoField', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('name', models.TextField()), - ('description', models.TextField()), - ('optional', models.BooleanField(default=False)), - ('choices', models.TextField(blank=True, null=True)), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='WithdrawalType', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('name', models.TextField()), - ('fields', models.ManyToManyField(to='info.InfoField')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='Asset', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('name', models.TextField(unique=True)), - ('deposit_enabled', models.BooleanField(default=True)), - ('deposit_fee_fixed', models.FloatField()), - ('deposit_fee_percent', models.FloatField()), - ('deposit_min_amount', models.FloatField()), - ('deposit_max_amount', models.FloatField()), - ('withdrawal_enabled', models.BooleanField(default=True)), - ('withdrawal_fee_fixed', models.FloatField()), - ('withdrawal_fee_percent', models.FloatField()), - ('withdrawal_min_amount', models.FloatField()), - ('withdrawal_max_amount', models.FloatField()), - ('deposit_fields', models.ManyToManyField(to='info.InfoField')), - ('withdrawal_types', models.ManyToManyField(related_name='withdrawal_info', to='info.WithdrawalType')), - ], - options={ - 'abstract': False, - }, - ), - ] diff --git a/src/info/migrations/0002_auto_20190726_2032.py b/src/info/migrations/0002_auto_20190726_2032.py deleted file mode 100644 index e970210d..00000000 --- a/src/info/migrations/0002_auto_20190726_2032.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2.3 on 2019-07-26 20:32 - -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('info', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='asset', - name='name', - field=models.TextField(unique=True, validators=[django.core.validators.MinLengthValidator(1)]), - ), - migrations.AlterField( - model_name='infofield', - name='name', - field=models.TextField(validators=[django.core.validators.MinLengthValidator(1)]), - ), - migrations.AlterField( - model_name='withdrawaltype', - name='name', - field=models.TextField(validators=[django.core.validators.MinLengthValidator(1)]), - ), - ] diff --git a/src/info/migrations/0003_auto_20190826_2353.py b/src/info/migrations/0003_auto_20190826_2353.py deleted file mode 100644 index 78972eeb..00000000 --- a/src/info/migrations/0003_auto_20190826_2353.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 2.2.4 on 2019-08-26 23:53 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('info', '0002_auto_20190726_2032'), - ] - - operations = [ - migrations.RemoveField( - model_name='withdrawaltype', - name='fields', - ), - migrations.RemoveField( - model_name='asset', - name='deposit_fields', - ), - migrations.RemoveField( - model_name='asset', - name='withdrawal_types', - ), - migrations.DeleteModel( - name='InfoField', - ), - migrations.DeleteModel( - name='WithdrawalType', - ), - ] diff --git a/src/info/migrations/0004_auto_20190829_2106.py b/src/info/migrations/0004_auto_20190829_2106.py deleted file mode 100644 index c7246504..00000000 --- a/src/info/migrations/0004_auto_20190829_2106.py +++ /dev/null @@ -1,51 +0,0 @@ -# Generated by Django 2.2.4 on 2019-08-29 21:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [("info", "0003_auto_20190826_2353")] - - operations = [ - migrations.AlterField( - model_name="asset", - name="deposit_fee_fixed", - field=models.FloatField(blank=True, default=1.0), - ), - migrations.AlterField( - model_name="asset", - name="deposit_fee_percent", - field=models.FloatField(blank=True, default=0.01), - ), - migrations.AlterField( - model_name="asset", - name="deposit_max_amount", - field=models.FloatField(blank=True, default=10000.0), - ), - migrations.AlterField( - model_name="asset", - name="deposit_min_amount", - field=models.FloatField(blank=True, default=10.0), - ), - migrations.AlterField( - model_name="asset", - name="withdrawal_fee_fixed", - field=models.FloatField(blank=True, default=1.0), - ), - migrations.AlterField( - model_name="asset", - name="withdrawal_fee_percent", - field=models.FloatField(blank=True, default=0.01), - ), - migrations.AlterField( - model_name="asset", - name="withdrawal_max_amount", - field=models.FloatField(blank=True, default=10000.0), - ), - migrations.AlterField( - model_name="asset", - name="withdrawal_min_amount", - field=models.FloatField(blank=True, default=10.0), - ), - ] diff --git a/src/info/migrations/0005_auto_20191014_2330.py b/src/info/migrations/0005_auto_20191014_2330.py deleted file mode 100644 index f36dc8bb..00000000 --- a/src/info/migrations/0005_auto_20191014_2330.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-14 23:30 -# Manually modified to RenameField and add a setting as default - -import django.core.validators -from django.db import migrations, models -from django.conf import settings - - -class Migration(migrations.Migration): - - dependencies = [("info", "0004_auto_20190829_2106")] - - operations = [ - migrations.RenameField(model_name="asset", old_name="name", new_name="code"), - migrations.AlterField( - model_name="asset", - name="code", - field=models.TextField( - default="USD", validators=[django.core.validators.MinLengthValidator(1)] - ), - ), - migrations.AddField( - model_name="asset", - name="issuer", - field=models.TextField( - default=settings.STELLAR_ISSUER_ACCOUNT_ADDRESS, - validators=[django.core.validators.MinLengthValidator(56)], - ), - ), - ] diff --git a/src/info/models.py b/src/info/models.py deleted file mode 100644 index 2631da25..00000000 --- a/src/info/models.py +++ /dev/null @@ -1,32 +0,0 @@ -"""This module defines the models for the info app.""" -from django.conf import settings -from django.core.validators import MinLengthValidator -from django.db import models -from model_utils.models import TimeStampedModel - - -class Asset(TimeStampedModel): - """ - This defines an Asset, as described in the SEP-24 `info` endpoint. - See: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md#info - """ - - code = models.TextField(validators=[MinLengthValidator(1)], default="USD") - issuer = models.TextField( - validators=[MinLengthValidator(56)], default=settings.STELLAR_ISSUER_ACCOUNT_ADDRESS - ) - - # Deposit-related info - deposit_enabled = models.BooleanField(null=False, default=True) - deposit_fee_fixed = models.FloatField(default=1.0, blank=True) - deposit_fee_percent = models.FloatField(default=0.01, blank=True) - deposit_min_amount = models.FloatField(default=10.0, blank=True) - deposit_max_amount = models.FloatField(default=10000.0, blank=True) - - # Withdrawal-related info - withdrawal_enabled = models.BooleanField(null=False, default=True) - withdrawal_fee_fixed = models.FloatField(default=1.0, blank=True) - withdrawal_fee_percent = models.FloatField(default=0.01, blank=True) - withdrawal_min_amount = models.FloatField(default=10.0, blank=True) - withdrawal_max_amount = models.FloatField(default=10000.0, blank=True) - diff --git a/src/sep10auth/apps.py b/src/sep10auth/apps.py deleted file mode 100644 index 982074ae..00000000 --- a/src/sep10auth/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -"""This module configures the authentication endpoint, as per SEP 10.""" -from django.apps import AppConfig - - -class AuthConfig(AppConfig): - """This stores metadata for the authentication endpoint app.""" - - name = "auth" diff --git a/src/stellartoml/apps.py b/src/stellartoml/apps.py deleted file mode 100644 index cd156556..00000000 --- a/src/stellartoml/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -"""This module configures the Stellar TOML app.""" -from django.apps import AppConfig - - -class StellarTomlConfig(AppConfig): - """Stores metadata for the Stellar TOML app.""" - - name = "toml" diff --git a/src/transaction/apps.py b/src/transaction/apps.py deleted file mode 100644 index eabc45e4..00000000 --- a/src/transaction/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -"""This module configures the transaction app.""" -from django.apps import AppConfig - - -class TransactionConfig(AppConfig): - """This stores metadata for the transaction app.""" - - name = "transaction" diff --git a/src/transaction/management/commands/__init__.py b/src/transaction/management/commands/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/transaction/migrations/0002_auto_20190823_1804.py b/src/transaction/migrations/0002_auto_20190823_1804.py deleted file mode 100644 index 7ef0f9ae..00000000 --- a/src/transaction/migrations/0002_auto_20190823_1804.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.4 on 2019-08-23 18:04 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('transaction', '0001_squashed_0003_auto_20190812_1852'), - ] - - operations = [ - migrations.AlterField( - model_name='transaction', - name='status_eta', - field=models.IntegerField(blank=True, default=3600, null=True), - ), - ] diff --git a/src/transaction/migrations/__init__.py b/src/transaction/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/withdraw/__init__.py b/src/withdraw/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/withdraw/apps.py b/src/withdraw/apps.py deleted file mode 100644 index 0d8f24a8..00000000 --- a/src/withdraw/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -"""This module configures the withdraw app.""" -from django.apps import AppConfig - - -class WithdrawConfig(AppConfig): - """This stores metadata for the withdraw app.""" - - name = "withdraw"