Integrations

Polaris does most of the work implementing SEP-24. However, some pieces of SEP-24 can only be implemented by the anchor.

Polaris provides a set of base classes that should be subclassed for processing transactions, DepositIntegration and WithdrawalIntegration. These subclasses, along with several other integration functions, should be registered with Polaris once implemented. See the Registering Integrations section for more information.

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 for this purpose. One TransactionForm should be rendered for every transaction processed. See the Forms documentation for more information.

The functions below facilitate the process of collecting the information needed.

polaris.integrations.DepositIntegration.content_for_transaction(transaction: polaris.models.Transaction, post_data: Optional[django.http.request.QueryDict] = None, amount: Optional[decimal.Decimal] = None) → Optional[Dict[KT, VT]]

This function should return a dictionary containing the next form class to render for the user given the state of the interactive flow.

For example, this function should return an instance of a TransactionForm subclass. Once the form is submitted, Polaris will detect the form used is a TransactionForm subclass and update transaction.amount_in with the amount specified in form.

The form will be rendered inside a django template that has several pieces of content that can be replaced by returning a dictionary containing the key-value pairs as shown below.

def content_for_transaction(cls, transaction, post_data = None, amount = None):
    if post_data:
        form = TransactionForm(transaction, post_data)
    else:
        form = TransactionForm(transaction, initial={"amount": amount})
    return {
        "form": form,
        "title": "Deposit Transaction Form",
        "guidance": "Please enter the amount you would like to deposit.",
        "icon_label": "Stellar Development Foundation"
    }

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.

Aside from the pieces of content returned from this function, the icon image displayed at the top of each web page can be replaced by adding a company-icon.svg in the top level of your app’s static files directory.

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, return a forms.Form with the fields you need.

You can also return a dictionary without a form key. You should do this if you are waiting on the user to take some action, like confirming their email. Once confirmed, the next call to this function should return the next form.

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 update the Transaction status to pending_user_transfer_start. Once the user makes the deposit to the anchor’s bank account, DepositIntegration.poll_pending_deposits should detect the event, and Polaris will submit the transaction to the stellar network, ultimately marking the transaction as complete upon success.

Parameters:
  • transaction – the Transaction database object
  • post_data – A django request.POST object
  • 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.after_form_validation(form: django.forms.forms.Form, transaction: polaris.models.Transaction)

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.content_for_transaction returns a dictionary containing the next form to render to the user, or returns None.

Keep in mind that if a TransactionForm is submitted, Polaris will update the amount_in and amount_fee with the information collected. There is no need to implement that yourself.

DO NOT update transaction.status here or in any other function for that matter. This column is managed by Polaris and is expected to have particular values at different points in the flow.

If you need to store some data to determine which form to return next when DepositIntegration.content_for_transaction is called, store this data in a model not used by Polaris.

Parameters:
  • form – the completed forms.Form submitted by the user
  • transaction – the Transaction database object
polaris.integrations.WithdrawalIntegration.content_for_transaction(transaction: polaris.models.Transaction, post_data: Optional[django.http.request.QueryDict] = None, amount: Optional[decimal.Decimal] = None) → Optional[Dict[KT, VT]]

Same as DepositIntegration.content_for_transaction

Parameters:
  • transaction – the Transaction database object
  • post_data – A django request.POST object
  • 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.WithdrawalIntegration.after_form_validation(form: polaris.integrations.forms.TransactionForm, transaction: polaris.models.Transaction)

Same as DepositIntegration.after_form_validation, except transaction.to_address should be saved here when present in form.

Parameters:
  • form – the completed forms.Form submitted by the user
  • transaction – 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(stellar_account: str, fields: Dict[KT, VT], language_code: str)

Save the fields passed for stellar_account to pre-populate the forms returned from content_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
user = user_for_account(stellar_account)
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 content_for_transaction() implementation
user = user_for_account(transaction.stellar_account)
form_args = {
    'phone_number': format_number(user.phone_number),
    'email': user.email_address
}
return {
    'form': KYCForm(initial=form_args),
    'title': "KYC Collection"
}

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(stellar_account: str, fields: Dict[KT, VT], language_code: str)

Same as DepositIntegration.save_sep9_fields

Use 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(request: rest_framework.request.Request, transaction: polaris.models.Transaction, asset: polaris.models.Asset, amount: Optional[decimal.Decimal], callback: Optional[str]) → 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(request: rest_framework.request.Request, transaction: polaris.models.Transaction, asset: polaris.models.Asset, amount: Optional[decimal.Decimal], callback: Optional[str]) → Optional[str]

Same as DepositIntegration.interactive_url

Returns:a URL to be used as the entry point for the interactive withdraw flow

Banking Rails

Polaris doesn’t have the information it needs to interface with an anchor’s partner financial entities. That is why Polaris provides a set of integration functions for anchors to implement.

polaris.integrations.DepositIntegration.poll_pending_deposits(pending_deposits: django.db.models.query.QuerySet) → List[polaris.models.Transaction]

This function should poll the appropriate financial entity for the state of all pending_deposits and return the ones that have externally completed. Make sure to save the transaction’s from_address field with the account number/address the funds originated from.

For every transaction that is returned, Polaris will submit it to the Stellar network. If a transaction was completed on the network, the overridable after_deposit function will be called, however implementing this function is optional.

If the Stellar network is unable to execute a transaction returned from this function, it’s status will be marked as error and its status_message attribute will be assigned a description of the problem that occurred. If the Stellar network is successful, the transaction will be marked as completed.

pending_deposits is a QuerySet of the form

Transactions.object.filter(
    kind=Transaction.KIND.deposit,
    status=Transaction.STATUS.pending_user_transfer_start
)

If you have many pending deposits, you may way want to batch the retrieval of these objects to improve query performance and memory usage.

Parameters:pending_deposits – a django Queryset for pending Transactions
Returns:a list of Transaction database objects which correspond to successful user deposits to the anchor’s account.
polaris.integrations.DepositIntegration.after_deposit(transaction: polaris.models.Transaction)

Use this function to perform any post-processing of transaction after its been executed on the Stellar network. This could include actions such as updating other django models in your project or emailing users about completed deposits. Overriding this function is not required.

Parameters:transaction – a Transaction that was executed on the Stellar network
polaris.integrations.WithdrawalIntegration.process_withdrawal(response: Dict[KT, VT], transaction: polaris.models.Transaction)

OVERRIDE REQUIRED

This method should implement the transfer of the amount of the anchored asset specified by transaction to the user who requested the withdrawal.

If an error is raised from this function, the transaction’s status will be changed to error and its status_message will be assigned to the message raised with the exception.

Parameters:
  • response – a response body returned from Horizon for the transactions for account endpoint
  • transaction – a Transaction instance to process

Registering Integrations

In order for Polaris to use the integration classes you’ve defined, you must register them.

polaris.integrations.register_integrations(deposit: polaris.integrations.transactions.DepositIntegration = None, withdrawal: polaris.integrations.transactions.WithdrawalIntegration = None, toml_func: Callable = None, scripts_func: Callable = None, fee_func: Callable = None)[source]

Registers instances of user-defined subclasses of WithdrawalIntegration and DepositIntegration with Polaris.

Call this function in the relevant Django AppConfig.ready() function:

from django.apps import AppConfig

class PolarisIntegrationApp(AppConfig):
    name = 'Polaris Integration'
    verbose_name = name

    def ready(self):
        from polaris.integrations import register_integrations
        from myapp.integrations import (MyDepositIntegration,
                                        MyWithdrawalIntegration)


        register_integrations(
            deposit=MyDepositIntegration(),
            withdrawal=MyWithdrawalIntegration()
        )

These integration classes provide a structured interface for implementing user-defined logic used by Polaris, specifically for deposit and withdrawal flows.

See the integration classes for more information on implementation.

Parameters:
  • deposit – the DepositIntegration subclass instance to be used by Polaris
  • withdrawal – the WithdrawalIntegration subclass instance to be used by Polaris
  • toml_func – a function that returns stellar.toml data as a dictionary
  • scripts_func – a function that returns a list of script tags as strings
  • fee_func – a function that returns the fee that would be charged
Raises:
  • ValueError – missing argument(s)
  • TypeError – arguments are not subclasses of DepositIntegration or Withdrawal

stellar.toml Integration

Every anchor must define a stellar.toml file to describe the anchors’s supported assets, any validators that are run, and other meta data. Polaris provides a default function that returns the assets supported by your server, but you’ll almost certainly need to replace this default to provide more detailed information.

polaris.integrations.get_stellar_toml()[source]

Returns the default info for stellar.toml as a dictionary. Replace this function with another by passing it to polaris.integrations.register_integrations like so:

from myapp.integrations import (
    get_toml_data,
    MyDepositIntegration,
    MyWithdrawalIntegration
)

register_integrations(
    deposit=MyDepositIntegration(),
    withdrawal=MyWithdrawalIntegration(),
    toml_func=get_toml_data
)

The function you pass to the toml_func parameter should return a dictionary containing any of the following top level keys: DOCUMENTATION - CURRENCIES - PRINCIPALS - VALIDATORS

You can also pass other top-level keys defined in the Account Info section such as VERSION, but many of these keys are pre-populated based on the variables defined in your .env file, so this isn’t strictly necessary.

See SEP-1 for more information on the stellar.toml format.

Returns:a dictionary containing the fields defined in SEP-1

Javascript Integration

polaris.integrations.scripts(page_content: Optional[Dict[KT, VT]]) → List[str][source]

Return a list of strings containing script tags that will be added to the bottom of the HTML body served for the current request. The scripts will be rendered like so:

{% for script in scripts %}
    {{ script|safe }}
{% endfor %}

page_content will be the return value from content_for_transaction() for requests during the interactive flow and None for requests to the /more_info endpoint. Anchors can use page_content to determine which scripts need to be rendered.

This gives anchors a great deal of freedom on the client side. The example reference server uses this functionality to inject Google Analytics into our deployment of Polaris, and to refresh the Confirm Email page every time the window is brought back into focus.

Replace this function with another by passing it to polaris.integrations.register_integrations like so:

from myapp.integrations import (
    scripts,
    MyDepositIntegration,
    MyWithdrawalIntegration
)

register_integrations(
    deposit=MyDepositIntegration(),
    withdrawal=MyWithdrawalIntegration(),
    scripts_func=scripts
)

Note that the scripts will be executed in the order in which they are returned.

Fee Integration

polaris.integrations.calculate_fee(fee_params: Dict[KT, VT]) → decimal.Decimal[source]

Calculate the fee to be charged for the transaction described by fee_params.

fee_params will always contain the following key-value pairs: amount, asset_code, and operation. Each of these key-value pairs correspond to the associated parameter for the /fee endpoint.

Additionally, fee_params may include a type key if this function is called from the /fee API view. If this function is called from an interactive flow’s TransactionForm submission, fee_params will also include any key-value pairs from form.cleaned_data. This allows anchors to use the fields collected via their TransactionForm in fee calculation.

Replace this function by registering another through register_integrations:

from myapp.integrations import (
    calculate_complex_fee,
    MyDepositIntegration,
    MyWithdrawalIntegration
)

register_integrations(
    deposit=MyDepositIntegration(),
    withdrawal=MyWithdrawalIntegration(),
    fee_func=calculate_complex_fee
)

If this function is replaced with your own, /info responses will no longer contain the fee_fixed and fee_percent attributes per-asset. This is because Polaris can no longer assume fees are determined using those attributes alone.

Note that any registered function must accept the same parameters and return the same type.

Deposit Instructions

polaris.integrations.DepositIntegration.instructions_for_pending_deposit(transaction: polaris.models.Transaction)

For pending deposits, its common to show instructions to the user for how to initiate the external transfer. Use this function to return text or HTML instructions to be rendered in response to /transaction/more_info.

Parameters:transaction – the transaction database object to be serialized and rendered in the response.
Returns:the text or HTML to render in the instructions template section