Build a minimal SEP-24 Polaris Anchor¶
This tutorial walks through each step of installing and configuring Polaris as well as implementing the necessary integrations to run a minimal SEP-24 anchor server on testnet. A live walk-through of the steps outlined below can also be found on the SDF’s Youtube. Note that the tutorial video uses Polaris version 0.12
, while this page has been updated for the current version.
Much of the content presented here can be found on other pages of the documentation, but its helpful to provide step-by-step instructions for a common use case.
SEP-24 is currently the most common SEP to implement using Polaris. It defines a standard protocol for allowing any wallet to request deposits or withdrawals on or off the Stellar network on behalf of it’s users.
Create a project¶
Assuming the project’s root directory has been created, the first step is to create the django project.
pip install django-polaris
django-admin startproject app
Django will create the app
project inside your project’s root directory. Inside will be another app
directory containing the django app source code and the manage.py
script.
Configure settings.py¶
Add the following to INSTALLED_APPS
in settings.py.
INSTALLED_APPS = [
...,
"corsheaders",
"rest_framework",
"app",
"polaris",
]
Add the following to your MIDDLEWARE
list. Make sure WhiteNoiseMiddleware
is below CorsMiddleware
.
MIDDLEWARE = [
...,
'corsheaders.middleware.CorsMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
...
]
PolarisSameSiteMiddleware can also be used if your anchor service should support wallets that use iframes to open interactive URL’s. Popups are the recommend strategy per SEP-24, but wallet application may still use iframes, so it can’t hurt to add it.
Polaris allows developers to specify their environment variables in a .env
file or through the Django settings file. By default Polaris looks in the same directory as your BASE_DIR
setting, but will use the POLARIS_ENV_PATH
if .env
cannot be found using BASE_DIR
. Alternatively, you may specify all environment variables directly in your settings file, but all Polaris-specific settings must be prepended with POLARIS_
. You can also use the environ package installed with Polaris to configure your settings.py variables with values stored in your environment.
import os
import environ
# Django defines BASE_DIR by default
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Optionally:
# POLARIS_ENV_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
# You can read your own settings from the environment
# This is useful when you don't want to check secrets into version control
env = environ.Env()
env_file = os.path.join(BASE_DIR, ".env")
if os.path.exists(env_file):
env.read_env(env_file)
SECRET_KEY = env("DJANGO_SECRET_KEY")
Add the SEPs we’re going to support in this server:
POLARIS_ACTIVE_SEPS = ["sep-1", "sep-10", "sep-24"]
And configure your static files. You should already have the staticfiles
app listed in INSTALLED_APPS
.
STATIC_ROOT = os.path.join(BASE_DIR, "collectstatic")
STATIC_URL = "/static"
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
Finally, allow Polaris to override Django’s default form widget HTML & CSS.
FORM_RENDERER = "django.forms.renderers.TemplatesSetting"
Add Polaris endpoints¶
Add Polaris’ endpoints to urls.py
in the app
inner directory:
from django.urls import path, include
import polaris.urls
urlpatterns = [
...,
path("", include(polaris.urls))
]
Note
Leaving admin.urls
in urlpatterns
will allow anyone using your service to access the admin menu login page.
By default, Django applications don’t have any User
models with usernames and passwords, but you may want to
remove the admin URLs to reduce risk regardless.
If you want to keep the admin panel for internal use, another option is to make the /admin
path only accessible
via a private network. This can be accomplished by configuring a proxy server accordingly or simply deploying a
second instance of the server process on a private network.
Specify environment variables¶
Write the following variables to a .env
file. If you’d rather define them in your settings file, you must prepend these settings with POLARIS_
.
STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
HORIZON_URI="https://horizon-testnet.stellar.org/"
HOST_URL="http://localhost:8000"
LOCAL_MODE=1
SERVER_JWT_KEY=<your secret string for encrypting JWTs>
SIGNING_SEED=<your Stellar secret key for signing SEP-10 challenges>
Many of these are self-explanatory, but LOCAL_MODE
ensures Polaris runs properly using HTTP. In production Polaris should run under HTTPS.
There is one more variable that must be added to .env
, but we’re going to wait until we issue the asset we intend to anchor.
Issue and add your asset¶
Use Polaris’ testnet issue
subcommand to create a token as well as setup issuer and distribution accounts for a fake asset we’re going to anchor.
python app/manage.py testnet issue --asset=TEST
It should output a public and secret key for both the issuer and distribution account.
Add the asset to the database¶
First, make sure you have configured your DATABASES
in settings.py
. We’ll place the DB file in a data
directory inside the project’s root directory.
DATABASES = {
'default': env.db(
"DATABASE_URL", default="sqlite:////" + os.path.join(os.path.dirname(BASE_DIR), "data/db.sqlite3")
)
}
Create the database with the schema defined for Polaris.
python app/manage.py migrate
Then, get into the python shell and create an Asset
object.
python app/manage.py shell
from polaris.models import Asset
Asset.objects.create(
code="TEST",
issuer=<issuer public key>,
distribution_seed=<distribution account secret key>,
sep24_enabled=True
)
Collect static assets¶
Now that your settings are configured correctly, we can collect the static assets our app will use into a single directory that whitenoise
can use.
python app/manage.py collectstatic --no-input
A collectstatic
directory should now be created in the outer app
directory containing the static files.
Run the server¶
You can now run the anchor server, although it doesn’t yet have the functionality to complete a SEP-24 deposit or withdraw.
python app/manage.py runserver
Use the SDF’s SEP-24 demo client to connect to your anchor service. You’ll see that it successfully makes a deposit request and opens the anchor’s interactive URL, but the client become stuck in polling loop after you complete the interactive web page. This is because we haven’t implemented our banking rails with Polaris.
Implement integrations¶
In order to let the demo client create a deposit or withdrawal transaction we have to implement some of Polaris’ integrations. There are many more integrations offered compared to the ones we will use in this tutorial, but the ones we use are required for a client to get though the entire flow on testnet.
Create an integrations.py
file within the inner app
directory. Technically, the only required integration functions for a SEP-24 testnet anchor are called from the registered RailsIntegration
subclass, specifically poll_pending_deposits()
and execute_outgoing_transactions()
.
from typing import List
from polaris.integrations import RailsIntegration
from polaris.models import Transaction
from django.db.models import QuerySet
class MyRailsIntegration(RailsIntegration):
def poll_pending_deposits(self, pending_deposits: QuerySet) -> List[Transaction]:
return list(pending_deposits)
def execute_outgoing_transaction(self, transaction: Transaction):
transaction.amount_fee = 0
transaction.status = Transaction.STATUS.completed
transaction.save()
Our poll_pending_deposits()
function returns every pending deposit transaction since users aren’t going to actually send the deposit amount when using testnet. Polaris then proceeds to submit stellar payment transactions to the network for each Transaction
object returned.
Since we won’t be sending users their withdrawn funds from testnet either, we simply update the amount_fee
and status
columns of the transaction. Its good form to always assign a fee value for the sake of readability, but Polaris will try to calculate amount_fee
if you have not registered a custom fee function and didn’t update the column from execute_outgoing_transaction()
.
Again, there are many more integrations Polaris provides, most notably those implemented by the DepositIntegration
and WithdrawalIntegration
classes. See the SEP-6 & 24 documentation to see what else Polaris offers. You’ll also likely want to add information to your SEP-1 TOML file.
Register integrations¶
Create an apps.py
file within the inner app
directory. We’ll subclass Django’s AppConfig
class and register our integrations from its ready()
function.
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = "app"
def ready(self):
from polaris.integrations import register_integrations
from .integrations import MyRailsIntegration
register_integrations(
rails=MyRailsIntegration()
)
Now we need to tell Django where to find our AppConfig subclass. Create or update the __init__.py
file within the inner app
directory and add the following:
default_app_config = "app.apps.MyAppConfig"
Polaris should now use your rails integrations, but these integration functions are not called from the web server process that we ran with the runserver
command.
Run the SEP-24 service¶
Polaris is a multi-process application, and poll_pending_deposits()
and execute_outgoing_transation()
are both called from their own process so that calling one is not delayed by calling the other. An easy way to run multi-process applications is with docker-compose.
First, create a requirements.txt
file in the project’s root directory:
pip freeze > requirements.txt
Now, lets write a simple Dockerfile
in the project’s root directory:
FROM python:3.7-slim-buster
RUN apt-get update && apt-get install -y build-essential
WORKDIR /home
RUN mkdir /home/data
COPY app /home/app/
COPY .env requirements.txt /home/
RUN pip install -r requirements.txt && python /home/app/manage.py collectstatic --no-input
CMD python /home/app/manage.py runserver --nostatic 0.0.0.0:8000
Write the following to a docker-compose.yml
file within the project’s root directory:
version: "3"
services:
server:
container_name: "test-server"
build: .
volumes:
- ./data:/home/data
ports:
- "8000:8000"
command: python app/manage.py runserver --nostatic 0.0.0.0:8000
execute_outgoing_transactions:
container_name: "test-execute_outgoing_transactions"
build: .
volumes:
- ./data:/home/data
command: python app/manage.py execute_outgoing_transactions --loop
check_trustlines:
container_name: "test-check_trustlines"
build: .
volumes:
- ./data:/home/data
command: python app/manage.py check_trustlines --loop
watch_transaction:
container_name: "test-watch_transactions"
build: .
volumes:
- ./data:/home/data
command: python app/manage.py watch_transactions
poll_pending_deposits:
container_name: "test-poll_pending_deposits"
build: .
volumes:
- ./data:/home/data
command: python app/manage.py poll_pending_deposits --loop
You’ll notice we’re also running the watch_transaction
process. This Polaris CLI command streams payment transactions from every anchored asset’s distribution account and updates the transaction’s status to pending_anchor
. The execute_outgoing_transactions
command then periodically queries for pending_anchor
transactions so the funds withdrawn from Stellar can be sent off-chain to the user.
Additionally, we’re going to run the check_trustlines
command. This Polaris command periodically checks the accounts that requested deposits but can’t receive our payment due to lacking a trustline to our asset.
Polaris comes with other commands that we won’t run in this tutorial. For example, the poll_outgoing_transactions
Polaris CLI command could periodically check if the funds sent off-chain were received by the user and update the status to completed
if so. You should do this on mainnet if your payment rails take some time before the user receives the funds sent off-chain.
Now that our multi-process application is defined, lets build and run the containers:
docker-compose build
docker-compose up
You should now be able to successfully deposit and withdraw funds on testnet using the SDF’s demo client via SEP-24.
What to read next¶
If you want to continue building your SEP-24 server, some useful sections of the documentation are listed below.
- Adding information to the SEP-1 TOML file
- Collection & validating KYC data
- Customizing Polaris’ static assets
- Customizing transaction fee calculation
Otherwise, check out the documentation page for each additional step you want to implement.