SEP-6 & SEP-24
SEP-6 and SEP-24 are both protocols for transferring assets on and off the stellar network. The difference between the two is how they collect information for the transfer.
SEP-6 is non-interactive, meaning the server expects the client (usually a wallet application) to communicate with the server in a purely automated fashion via the API endpoints described in the proposal.
Comparatively, SEP-24 is interactive, meaning the server requires the user of the client application to input transfer information via a UI controlled by the anchor server instead of the client.
Using Polaris you can run one or both SEP implementations. Each have their pros and cons, so make sure you understand the proposals before choosing.
Configuration
Add the SEPs to POLARIS_ACTIVE_SEPS
in in your settings file.
POLARIS_ACTIVE_SEPS = ["sep-1", "sep-6", "sep-24", ...]
If you’re running SEP-24, add the following::
SESSION_COOKIE_SECURE = True
Polaris requires this setting to be True
for SEP-24 deployments if not in
LOCAL_MODE
.
Static Assets
Polaris comes with a UI for displaying forms and transaction information. While SEP-6 doesn’t use HTML forms, this is required in order to display transaction details available from the /more_info endpoint.
Make sure django.contrib.staticfiles
is listed in INSTALLED_APPS
.
INSTALLED_APPS = [
...,
"django.contrib.staticfiles",
]
Additionally, to serve static files in production, use the middleware provided by
whitenoise
, which comes with your installation of Polaris. It should be near the
top of the list for the best performance, but still under CorsMiddleware.
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
...,
]
Add the following to your settings.py as well:
STATIC_ROOT = os.path.join(BASE_DIR, "<your static root directory>")
STATIC_URL = "<your static url path>"
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
Since whitenoise
will now be serving your static files, use the --nostatic
flag
when using the runserver
command locally.
The last step is to collect the static files Polaris provides into your app:
python manage.py collectstatic --no-input
SEP-24 Configuration
SEP-24’s interactive flow uses a short-lived JWT to authenticate users, so add the follow environment variable.
SERVER_JWT_KEY="yoursupersecretjwtkey"
SessionMiddleware
is required for all SEP-24 deployments.
MIDDLEWARE = [
...,
'django.contrib.sessions.middleware.SessionMiddleware',
...
]
Add the following to your settings.py as well:
FORM_RENDERER = "django.forms.renderers.TemplatesSetting"
This allows Polaris to override django’s default HTML form widgets to provide a great UI out of the box. They can also be replaced with your own HTML widgets as described in the previous section.
Integrations
SEP-6 Integrations
The previous integrations are available for both SEP-6 and SEP-24 transactions, but the functions described below are only for SEP-6.
- polaris.integrations.DepositIntegration.process_sep6_request(self, token: polaris.sep10.token.SEP10Token, request: rest_framework.request.Request, params: Dict, transaction: polaris.models.Transaction, *args: List, **kwargs: Dict) Dict
This function is called during requests made to the SEP-6 deposit and deposit-exchange endpoints. The params object will contain the parameters included in the request. Note that Polaris will only call this function for deposit-exchange requests if SEP-38 is added to Polaris’
ACTIVE_SEPS
setting and the requested Stellar asset is enabled for SEP-38.If a request to the the deposit-exchange endpoint is made, a
Quote
object will be assigned to theTransaction
object passed.Process these parameters and return one of the following responses outlined below as a dictionary. Save transaction to the DB if you plan to return a success response. If transaction is saved to the DB but a failure response is returned, Polaris will return a 500 error to the user.
If you’d like the user to send
Transaction.amount_in
plus the fee amount, add the amount charged as a fee toTransaction.amount_in
andTransaction.amount_expected
. here. While not required per SEP-6, it is encouraged to also populateTransaction.amount_fee
andTransaction.amount_out
here as well. If this function is called for a deposit-exchange request,Transaction.fee_asset
should also be assigned. If not assigned here, these columns must be assigned before returning the transaction fromRailsIntegration.poll_pending_deposits()
.Note that the amount sent over the Stellar Network could differ from the amount specified in this API call, so fees and the amount delievered may have to be recalculated in
RailsIntegration.poll_pending_deposits()
for deposits andRailsIntegration.execute_outgoing_transaction()
for withdrawals.Polaris responds to requests with the standard status code according the SEP. However, if you would like to return a custom error code in the range of 400-599 you may raise an
rest_framework.exceptions.APIException
. For example, you could return a 503 status code by raising anAPIException("service unavailable", status_code=503)
.Deposit no additional information needed
The success response. Polaris creates most of the attributes described in this response. Simply return the ‘how’ and optionally ‘extra_info’ attributes. For example:
return { "how": "<your bank account address>", "extra_info": { "message": "Deposit the funds to the bank account specified in 'how'" } }
A failure response. Return the response as described in SEP.
return { "type": "non_interactive_customer_info_needed", "fields" : ["family_name", "given_name", "address", "tax_id"] }
A failure response. Return the ‘type’ and ‘status’ attributes. If
CustomerIntegration.more_info_url()
is implemented, Polaris will include the ‘more_info_url’ attribute in the response as well.return { "type": "customer_info_status", "status": "denied", }
- Parameters
token – the
SEP10Token
object representing the authenticated sessionrequest – a
rest_framwork.request.Request
objectparams – the request parameters as described in /deposit
transaction – an unsaved
Transaction
object representing the transaction to be processed
- polaris.integrations.WithdrawalIntegration.process_sep6_request(self, token: polaris.sep10.token.SEP10Token, request: rest_framework.request.Request, params: Dict, transaction: polaris.models.Transaction, *args: List, **kwargs: Dict) Dict
Same as
DepositIntegration.process_sep6_request
except for the case below. Specifically, thehow
attribute should not be included.Withdraw no additional information needed
A success response. Polaris populates most of the attributes for this response. Simply return an ‘extra_info’ attribute if applicable:
{ "extra_info": { "message": "Send the funds to the following stellar account including 'memo'" } }
In addition to this response, you may also return the Customer information needed and Customer Information Status responses as described in
DepositIntegration.process_sep6_request
.
- polaris.integrations.default_info_func(request: rest_framework.request.Request, asset: polaris.models.Asset, lang: Optional[str], exchange: bool, *args: List, **kwargs: Dict) Dict [source]
Replace this function with another by passing it to
register_integrations()
as described in Registering Integrations.Return a dictionary containing the fields and types key-value pairs described in the SEP-6 /info response for the asset passed. Raise a
ValueError()
if lang is not supported. For example,if asset.code == "USD": return { "fields": { "email_address" : { "description": "your email address for transaction status updates", "optional": True }, "amount" : { "description": "amount in USD that you plan to deposit" }, "type" : { "description": "type of deposit to make", "choices": ["SEPA", "SWIFT", "cash"] } }, "types": { "bank_account": { "fields": { "dest": {"description": "your bank account number" }, "dest_extra": { "description": "your routing number" }, "bank_branch": { "description": "address of your bank branch" }, "phone_number": { "description": "your phone number in case there's an issue" } } }, "cash": { "fields": { "dest": { "description": "your email address. Your cashout PIN will be sent here.", "optional": True } } } } }
- Parameters
request – a
rest_framework.request.Request
objectasset –
Asset
object for which to return the fields and types key-value pairslang – the language code the client requested for the description values in the response
exchange – whether or not the info returned will be used for a deposit-exchange or withdraw-exchange response object. Only relevant if SEP-38 is enabled.
- polaris.integrations.DepositIntegration.patch_transaction(self, token: polaris.sep10.token.SEP10Token, request: rest_framework.request.Request, params: Dict, transaction: polaris.models.Transaction, *args: List, **kwargs: Dict)
Currently only used for SEP-6 transactions.
The GET /info response contains a fields object that describes the custom fields an anchor requires in requests to the GET /deposit endpoint. If one or more of these fields were originally accepted but later discovered to be invalid in some way, an anchor can place the transaction in the
pending_transaction_info_update
status, save a JSON-serialized object describing the fields that need updating toTransaction.required_info_updates
, and save a human-readable message toTransaction.required_info_message
describing the reason the fields need to be updated. A client can make a request to GET /transactions to detect that updated information is needed.This function is called when SEP-6 PATCH /transactions requests are made by the client to provide the updated values described in the
Transaction.required_info_updates
. Use the params passed in the request to update transaction or any related data.Polaris validates that every field listed in
Transaction.required_info_updates
is present in params but cannot validate the values. If aValueError
is raised, Polaris will return a 400 response containing the exception message in the body.If no exception is raised, Polaris assumes the update was successful and will update the transaction’s status back to
pending_anchor
as well as clear therequired_info_updates
andrequired_info_message
fields.- Parameters
token – the
SEP10Token
object representing the authenticated sessionrequest – a
rest_framwork.request.Request
objectparams – The request parameters as described in the PATCH /transactions endpoint.
transaction – A
Transaction
object for which updated was provided.
- polaris.integrations.WithdrawalIntegration.patch_transaction(self, token: polaris.sep10.token.SEP10Token, request: rest_framework.request.Request, params: Dict, transaction: polaris.models.Transaction, *args: List, **kwargs: Dict)
Same as
DepositIntegration.patch_transaction
SEP-24 Integrations
Form Integrations
Polaris provides a set of integration functions that allow you to collect, validate, and process the information you need to collect from users with Django Forms.
Of course, you’ll need to collect the amount the user would like to deposit
or withdraw. Polaris provides a TransactionForm
that can be subclassed to
add additional fields to collects amounts of the Stellar Asset
the user would
like to deposit or withdraw. If the anchor wants to collect the amount of an
OffChainAsset
, the anchor must use a different form. See the
Forms documentation for more information.
The functions below facilitate the process of collecting the information needed.
- polaris.integrations.DepositIntegration.form_for_transaction(self, request: rest_framework.request.Request, transaction: polaris.models.Transaction, post_data: Optional[django.http.request.QueryDict] = None, amount: Optional[decimal.Decimal] = None, *args: List, **kwargs: Dict) Optional[django.forms.forms.Form]
This function should return the next form to render for the user given the state of the interactive flow.
For example, this function could return an instance of a
TransactionForm
subclass. Once the form is submitted, Polaris will detect the form used is aTransactionForm
subclass and updatetransaction.amount_in
with the amount specified in form.If post_data is passed, it must be used to initialize the form returned so Polaris can validate the data submitted. If amount is passed, it can be used to pre-populate a
TransactionForm
amount field to improve the user experience.def form_for_transaction(self, transaction, post_data = None, amount = None): if transaction.amount_in: return elif post_data: return TransactionForm(transaction, post_data) else: return TransactionForm(transaction, initial={"amount": amount})
After a form is submitted and validated, Polaris will call
DepositIntegration.after_form_validation
with the populated form and transaction. This is where developers should update their own state-tracking constructs or do any processing with the data submitted in the form.Finally, Polaris will call this function again to check if there is another form that needs to be rendered to the user. If you are collecting KYC data, you can return a
forms.Form
with the fields you need.This loop of submitting a form, validating & processing it, and checking for the next form will continue until this function returns
None
.When that happens, Polaris will check if
content_for_template()
also returnsNone
. If that is the case, Polaris assumes the anchor is finished collecting information and will update the Transaction status topending_user_transfer_start
.If
content_for_template()
returns a dictionary, Polaris will serve a page without a form. Anchors should do this when the user needs to take some action in order to continue, such as confirming their email address. Once the user is confirmed,form_for_transaction()
should return the next form.- Parameters
request – the
rest_framework.request.Request
objecttransaction – the
Transaction
database objectpost_data – the data sent in the POST request as a dictionary
amount – a
Decimal
object the wallet may pass in the GET request. Use it to pre-populate your TransactionForm along with any SEP-9 parameters.
- Returns
a dictionary containing various pieces of information to use when rendering the next page.
- polaris.integrations.DepositIntegration.content_for_template(self, request: rest_framework.request.Request, template: polaris.templates.Template, form: Optional[django.forms.forms.Form] = None, transaction: Optional[polaris.models.Transaction] = None, *args: List, **kwargs: Dict) Optional[Dict]
Return a dictionary containing the Django template variables to be passed to the template rendered.
The anchor may also pass a special key, template_name, which should be a file path relative your Django app’s /templates directory. Polaris will render the template specified by this key to the user instead of the default templates defined below. Note that all of the Django template variables defined below will still be passed to the template specified.
Polaris will pass one of the following
polaris.templates.Template
values to indicate the default template Polaris will use.Template.DEPOSIT
The template used for deposit flows
Template.WITHDRAW
The template used for withdraw flows
Template.MORE_INFO
The template used to show transaction details
The form parameter will always be
None
when template isTemplate.MORE_INFO
since that page does not display form content.If form is
None
and template_name is notTemplate.MORE_INFO
, returningNone
will signal to Polaris that the anchor is done collecting information for the transaction. Returning content will signal to Polaris that the user needs to take some action before receiving the next form, such as confirming their email. In this case, make sure to return an appropriate guidance message or return a custom template_name.Using this function, anchors pass key-value pairs to the template being rendered. Some of these key-value pairs are used by Polaris, but anchors are allowed and encouraged to extend Polaris’ templates and pass custom key-value pairs.
def content_for_template(template_name, form=None, transaction=None): ... return { "title": "Deposit Transaction Form", "guidance": "Please enter the amount you would like to deposit.", "show_fee_table": True, "symbol": "$", "icon_label": "Stellar Development Foundation", "icon_path": "images/company-icon.png", # custom field passed by the anchor "username": "John.Doe" }
Below are all the keys passed to the template rendered. If the dictionary returned has the same key, the default value Polaris uses will be overwritten.
For
Template.DEPOSIT
andTemplate.WITHDRAW
:form
The
django.forms.Form
instance returned fromform_for_transaction()
.post_url
The URL to make the POST request containing the form data to.
operation
Either deposit or withdraw.
asset
The
polaris.models.Asset
object of the Stellar asset being transacted.use_fee_endpoint
A boolean indicating whether or not Polaris should use the
GET /fee
endpoint when calculating fees and rendering the amounts on the page.org_logo_url
A URL for the default logo to render if the anchor has not specified their own via icon_path.
additive_fees_enabled
A boolean indicating whether or not to add fees to the amount entered in
TransactionForm
amount fields.False
by default, meaning fees are subtracted from the amounts entered.title
The browser tab’s title.
guidance
A text message displayed on the page that should help guide the user to take the appropriate action(s).
icon_label
The label for the image rendered on the page specified by
"icon_path"
.icon_path
The relative file path to the image you would like to use as the company icon in the UI. The file path should be relative to your Django app’s /static directory. If icon_path is not present, Polaris will use the image specified by your TOML integration function’s
ORG_LOGO
key. If neither are present, Polaris will use its default image. All images will be rendered in a 100 x 150px sized box as defined by the default stylesheet.show_fee_table
A boolean for whether the fee table in the default template should be visible on the page rendered to the user. This table is hidden by default unless a
TransactionForm
is returned fromform_for_transaction()
, in which case the fee table will be displayed. If the anchor instructs Polaris to display the fee table but aTransactionForm
is not present on the page, the anchor is responsible for updating the fee table with the appropriate values. This is useful when the anchor is collecting the amount of an off-chain asset, sinceTransactionForm
assumes the amount collected is for an on-chain asset.symbol
The character string that precedes the amounts shown on the fee table. It defaults to the Stellar
Asset.symbol
. Note that the symbol used in input fields must be passed separately using the field’s widget attributes.For
Template.MORE_INFO
tx_json
A JSON-serialized string matching the schema returned from GET /transaction
amount_in_asset
The string representation of the asset given to the anchor by the user, formatted using SEP-38 Asset Identification Format.
amount_out_asset
The string representation of the asset sent from the anchor to the user, formatted using SEP-38 Asset Identification Format.
amount_in
A string containing the amount to be displayed on the page as Amount Sent
amount_out
A string containing the amount to be displayed on the page as Amount Received
amount_fee
A string containing the amount to be displayed on the page as Fee
amount_in_symbol
Asset.symbol
orOffChainAsset.symbol
, depending on whether or not asset sent to the anchor is on or off chain. IfTransaction.quote
is null, the value will always matchAsset.symbol
.amount_fee_symbol
Asset.symbol
orOffChainAsset.symbol
, depending on the value ofTransaction.fee_asset
. IfTransaction.quote
is null, the value will always beAsset.symbol
.amount_out_symbol
Asset.symbol
orOffChainAsset.symbol
, depending on whether or not asset sent by the anchor is on or off chain. IfTransaction.quote
is null, the value will always matchAsset.symbol
.amount_in_significant_decimals
The number of decimals to display for amounts of
Transaction.amount_in
. Derived fromAsset.significant_decimals
orOffChainAsset.decimals
. IfTransaction.quote
is null, the value will always matchAsset.significant_decimals
.amount_fee_significant_decimals
The number of decimals to display for amounts of
Transaction.amount_fee
. Derived fromAsset.significant_decimals
orOffChainAsset.decimals
, depending on the value ofTransaction.fee_asset
.amount_out_significant_decimals
The number of decimals to display for amounts of
Transaction.amount_out
. Derived fromAsset.significant_decimals
orOffChainAsset.decimals
. IfTransaction.quote
is null, the value will always matchAsset.significant_decimals
.transaction
The
polaris.models.Transaction
object representing the transaction.asset
The
polaris.models.Asset
object representing the asset.offchain_asset
The
OffChainAsset
object used in theTransaction.quote
, if present.price
Transaction.quote.price
, if present.price_inversion
1 / Transaction.quote.price
, ifprice
is present. The default more_info.html template uses this number for displaying exchange rates when quotes are used.price_inversion_significant_decimals
The number of decimals to display for exchange rates. Polaris calculates this to ensure the rate displayed is always correct.
exchange_amount
If
Transaction.quote
is notNone
,exchange_amount
is the value ofTransaction.amount_out
expressed in units ofTransaction.amount_in
.exchanged_amount
If
Transaction.quote
is notNone
,exchanged_amount
is the value ofTransaction.amount_in
expressed in units ofTransaction.amount_out
.- Parameters
request – a
rest_framework.request.Request
instancetemplate – a
polaris.templates.Template
enum value for the template to be rendered in the responseform – the form to be rendered in the template
transaction – the transaction being processed
- polaris.integrations.DepositIntegration.after_form_validation(self, request: rest_framework.request.Request, form: django.forms.forms.Form, transaction: polaris.models.Transaction, *args: List, **kwargs: Dict)
Use this function to process the data collected with form and to update the state of the interactive flow so that the next call to
DepositIntegration.form_for_transaction()
returns the next form to render to the user.If you need to store some data to determine which form to return next when
DepositIntegration.form_for_transaction
is called, store this data in a model not used by Polaris.If form is the last form to be served to the user, Polaris will update the transaction status to
pending_user_transfer_start
, indicating that the anchor is waiting for the user to deliver off-chain funds to the anchor. If the KYC information collected is still being verified, update theTransaction.status
column topending_anchor
here. Make sure to save this change to the database before returning. In this case Polaris will detect the status change and will not update the status again. Polaris will wait until the anchor changes the transaction’s status topending_user_transfer_start
before including the transaction in calls toDepositIntegration.poll_pending_deposits()
.If the user is requesting a deposit or withdrawal of a Stellar asset in exchange for different off-chain asset, such as requesting a deposit of USDC using fiat mexican pesos, the anchor must assign a
Quote
object toTransaction.quote
before the end of the interactive flow. Polaris will check for aQuote
object on the transaction and adjust the UI of theMORE_INFO
template to display the exchange rate and other exchange-related information.Transaction.fee_asset
also must be populated with the asset in which fees will be collected, formatted using SEP-38 Asset Identification Format.- Parameters
request – the
rest_framework.request.Request
objectform – the completed
forms.Form
submitted by the usertransaction – the
Transaction
database object
- polaris.integrations.WithdrawalIntegration.form_for_transaction(self, request: rest_framework.request.Request, transaction: polaris.models.Transaction, post_data: Optional[django.http.request.QueryDict] = None, amount: Optional[decimal.Decimal] = None, *args: List, **kwargs: Dict) Optional[django.forms.forms.Form]
Same as
DepositIntegration.form_for_transaction
- Parameters
request – a
rest_framwork.request.Request
objecttransaction – the
Transaction
database objectpost_data – the data included in the POST request body as a dictionary
amount – a
Decimal
object the wallet may pass in the GET request. Use it to pre-populate your TransactionForm along with any SEP-9 parameters.
- polaris.integrations.WithdrawalIntegration.content_for_template(self, request: rest_framework.request.Request, template: polaris.templates.Template, form: Optional[django.forms.forms.Form] = None, transaction: Optional[polaris.models.Transaction] = None, *args: List, **kwargs: Dict) Optional[Dict]
Same as
DepositIntegration.content_for_template
.- Parameters
request – a
rest_framework.request.Request
instancetemplate – a
polaris.templates.Template
enum value for the template to be rendered in the responseform – the form to be rendered in the template
transaction – the transaction being processed
- polaris.integrations.WithdrawalIntegration.after_form_validation(self, request: rest_framework.request.Request, form: django.forms.forms.Form, transaction: polaris.models.Transaction, *args: List, **kwargs: Dict)
Same as
DepositIntegration.after_form_validation
, except transaction.to_address should be saved here when present in form.- Parameters
request – a
rest_framework.request.Request
instanceform – the completed
forms.Form
submitted by the usertransaction – the
Transaction
database object
Some wallets may pass fields documented in SEP-9 in the initial POST request for the anchor to use to pre-populate the forms presented to the user. Polaris provides an integration function to save and validate the fields passed.
- polaris.integrations.DepositIntegration.save_sep9_fields(self, token: polaris.sep10.token.SEP10Token, request: rest_framework.request.Request, stellar_account: str, fields: Dict, language_code: str, muxed_account: Optional[str] = None, account_memo: Optional[str] = None, account_memo_type: Optional[str] = None, *args: List, **kwargs: Dict)
DEPRECATED: stellar_account, account_memo, account_memo_type, and muxed_account parameters. Use the token object passed instead.
Save the fields passed for the user identified by stellar_account to pre-populate the forms returned from
form_for_transaction()
. Note that this function is called before the transaction is created.For example, you could save the user’s contact information with the model used for KYC information.
# Assuming you have a similar method and model if token.muxed_account: user_key = token.muxed_account elif token.memo: user_key = f"{token.account}:{token.memo}" else: user_key = token.account user = user_for_key(user_key) user.phone_number = fields.get('mobile_number') user.email = fields.get('email_address') user.save()
Then when returning a form to collect KYC information, also return the values saved in this method relevant to that form.
# In your form_for_transaction() implementation if token.muxed_account: user_key = token.muxed_account elif token.memo: user_key = f"{token.account}:{token.memo}" else: user_key = token.account user = user_for_key(user_key) form_args = { 'phone_number': format_number(user.phone_number), 'email': user.email_address } return KYCForm(initial=form_args),
If you’d like to validate the values passed in fields, you can perform any necessary checks and raise a
ValueError
in this function. Polaris will return the message of the exception in the response along with 400 HTTP status. The error message should be in the language specified by language_code if possible.
- polaris.integrations.WithdrawalIntegration.save_sep9_fields(self, token: polaris.sep10.token.SEP10Token, request: rest_framework.request.Request, stellar_account: str, fields: Dict, language_code: str, muxed_account: Optional[str] = None, account_memo: Optional[str] = None, account_memo_type: Optional[str] = None, *args: List, **kwargs: Dict)
Same as
DepositIntegration.save_sep9_fields
Using an External Application for the Interactive Flow
Polaris provides Form Integrations for collecting and processing information about a deposit or withdraw. However if you would rather use another application to collect that information, override this function to return the URL that should be requested to begin that process.
Note that if you choose to use another application or set of endpoints, you must redirect to the /transactions/<deposit or withdraw>/interactive/complete?transaction_id= endpoint for the relevant transaction when finished. This signals to the wallet that the anchor is done processing the transaction and may resume control. A callback parameter can also be included in the URL.
- polaris.integrations.DepositIntegration.interactive_url(self, request: rest_framework.request.Request, transaction: polaris.models.Transaction, asset: polaris.models.Asset, amount: Optional[decimal.Decimal], callback: Optional[str], *args: List, **kwargs: Dict) Optional[str]
Override this function to provide the wallet a non-Polaris endpoint to begin the interactive flow. If the amount or callback arguments are not
None
, make sure you include them in the URL returned.- Returns
a URL to be used as the entry point for the interactive deposit flow
- polaris.integrations.WithdrawalIntegration.interactive_url(self, request: rest_framework.request.Request, transaction: polaris.models.Transaction, asset: polaris.models.Asset, amount: Optional[decimal.Decimal], callback: Optional[str], *args: List, **kwargs: Dict) Optional[str]
Same as
DepositIntegration.interactive_url
- Returns
a URL to be used as the entry point for the interactive withdraw flow
Running the Service
In addition to the web server, SEP-6 and SEP-24 require four additional processes to be run in order to work. See the CLI Commands for more information on all Polaris commands.
Processing Pending Deposits
The process_pending_deposits
command processes deposits transactions in one of the states defined below.
You can invoke the command like so:
python manage.py process_pending_deposits --loop --interval 10
This process will continue indefinitely, calling the associated integration
function, sleeping for 10 seconds, and then calling it again. You can also configure a
job scheduling service such as Jenkins or CircleCI to periodically invoke the above
command without the --loop
and --interval
argument.
Waiting for the user to deliver funds off-chain
When a user initiates a deposit transaction, the anchor must wait for the user to send the deposit amount to the anchor’s bank account. When this happens, the anchor should notice and deposit the same amount of the tokenized asset into the user’s stellar account.
Polaris provides the DepositIntegration.poll_pending_deposits()
integration function
for this purpose. Polaris will query for transactions whose funds may have been delivered
to the anchor’s off-chain account and calls the integration function mentioned to receive the
transactions whose funds have indeed arrived off-chain. If the transaction is not in one of
the other states described below, it is then submitted to the Stellar network.
Waiting for the user to establish a trustline
Sometimes, a user will initiate a deposit to an account that does not exist yet,
or the user’s account won’t have a trustline to the asset’s issuer account. In
these cases, the transaction database object gets assigned the pending_trust
status.
This command then queries for these transactions and checks if a trustline has been established. If it has, and the transaction is not in the state desribed below, it is submitted to the Stellar network.
Waiting for the anchor to collect transaction signatures
Polaris provides support for distribution accounts with multisignature configurations.
If an asset’s distribution account requires multiple signatures, Polaris saves the
transaction envelope to Transaction.envelope_xdr
and sets Transaction.pending_signatures
to True
.
Anchors are expected to query for these transactions and add signatures to the envelope.
When all signatures required have been collected, the anchor must set
Transaction.pending_signatures
back to False
. Note that there is no integration
function for this, instead the anchor is expected to define their own process for detecting
and collecting signatures on these transactions.
Finally, this command will detect transactions that are no longer pending signatures and submits them to the network.
Watching for Withdrawals
When a user initiates a withdrawal transaction, the anchor must wait for the
user to send the tokenized amount to the anchor’s stellar account. Polaris’
watch_transactions
command line tool streams transactions from every
anchored asset’s distribution account and attempts to match every incoming
deposit with a pending withdrawal.
If it finds a match, it will update the transaction’s status to pending_anchor
,
signaling to Polaris that it needs to submit the transaction to the external rails
used by the anchor.
Run the process like so:
python manage.py watch_transactions
Executing Outgoing Transactions
The execute_outgoing_transactions
CLI tool polls the database for transactions
in the pending_anchor
status and passes them to the
RailsIntegration.execute_outgoing_transaction()
function for the anchor to
initiate the payment to the receiving user. See the integration function’s
documentation for more information about this step.
You can run the service like so:
python manage.py execute_outgoing_transactions --loop --interval 10
This process will continue indefinitely, calling the associated integration function, sleeping for 10 seconds, and then calling it again.
Poll Outgoing Transactions
And finally, once a payment to the user has been initiated by the anchor, this CLI tool
periodically calls RailsIntegration.poll_outgoing_transactions()
so the anchor can
return the transactions that have have completed, meaning the user has received the funds.
If your banking or payment rails do not provide the necessary information to check if the
user has received funds, do not run this process and simply mark each transaction
as Transaction.STATUS.completed
after initiating the payment in
RailsIntegration.execute_outgoing_transaction()
.
Run the process like so:
python manage.py poll_outgoing_transactions --loop --interval 60