Enable Cross Border Payments

SEP-31 defines a standard for making cross-border payments. Using this standard, two organizations can provide remittance services between two distinct regulatory jurisdictions.

Polaris supports the receiving side of this standard, allowing organizations to receive remittance payments from SEP-31 sending anchors and payout off-chain funds to recipients.

Configure Settings

Activate SEP-31

Add SEP-31 as an active SEP in your .env file. SEP-31 requires SEP-12, so see the documentation on Support KYC Registration.

ACTIVE_SEPS=sep-1,sep-10,sep-12,sep-31
HOST_URL=http://localhost:8000
LOCAL_MODE=1
ENABLE_SEP_0023=1
SIGNING_SEED=S...
SERVER_JWT_KEY=...

Create a Stellar Asset

SEP-31 receiving anchors receive payments from sending anchors in the form of Stellar assets. You’ll either need to use an existing asset, such as USDC, or create your own. Using a reputable stablecoin such as USDC is highly recommended.

The process for creating a Stellar asset is the same regardless of which SEP you’re implementing. See the necessary steps in the Create a Stellar Asset section.

The one difference is to make sure that every Asset entry you create has sep31_enabled set to True.

Integrations

Defining Asset Info

Before sending anchors initiate transactions, they may want to gather information on the assets your service anchors and other related information. They do this by making a request to the GET /info endpoint.

Polaris generates most of the JSON returned in the response to these requests, but there are some pieces of information only anchor can provide. Specifically, SEP-31 receiving anchors must specify the different types of customers for which the anchor requires different KYC information, as well any per-transaction fields that must be sent when intiating a transaction. To fill these gaps, Polaris calls the info() method, which can be replaced by passing a subclass of SEP31Receiver to regisiter_integrations().

from typing import Dict, List
from polaris.integrations import SEP31Receiver
from polaris.models import Asset
from rest_framework.request import Request

class AnchorCrossBorderPayment(SEP31Receiver):
    def info(
        request: Request,
        asset: Asset,
        lang: str,
        *args: Dict,
        **kwargs: List
    ):
        return {
            "sep12": {
                "sender": {
                    "types": {
                        "sep31-sender": {
                            "description": "the basic type for sending customers"
                        }
                    }
                },
                "receiver": {
                    "types": {
                        "sep31-receiver": {
                            "description": "the basic type for receiving customers"
                        }
                    }
                },
            },
            "fields": {
                "transaction": {
                    "routing_number": {
                        "description": "routing number of the destination bank account"
                    },
                    "account_number": {
                        "description": "bank account number of the destination"
                    },
                },
            },
        }

Approving Sending Anchors

It is common for remittance receiving businesses to only accept payments from known sending businesses. In SEP-31, sending businesses or anchors are identitifed using a Stellar account. Polaris expects receiving anchor to implement valid_sending_anchor() so it can know whether or not to allow or reject transaction initiation requests from a specific account.

It is recommended to simply use an environment variable to pass the allowed Stellar accounts representing sending organizations and return True if the account passed in valid_sending_anchor() is present and False otherwise. Polaris may offer its own environment variable as a replacement for the valid_sending_anchor() method in the future.

Accepting Transaction Requests

Transaction information is passed to the anchor via a POST /transactions request. When these requests are made, Polaris calls process_post_request() with the transaction that will be created as a result of the request. Perform any actions necessary to initiate a transaction, and return a standard error message body if the any of the information provided is invalid.

...
from polaris.sep10.token import SEP10Token
from polaris.models import Transaction
from .users import user_for_id, verify_bank_account

class AnchorCrossBorderPayment(SEP31Receiver):
    ...
    def process_post_request(
        self,
        token: SEP10Token,
        request: Request,
        params: Dict,
        transaction: Transaction,
        *args: List,
        **kwargs: Dict,
    ):
        sending_user = user_for_id(params.get("sender_id"))
        receiving_user = user_for_id(params.get("receiver_id"))
        if not sending_user or not sending_user.kyc_approved:
            return {"error": "customer_info_needed", "type": "sep31-sender"}
        if not receiving_user or not receiving_user.kyc_approved:
            return {"error": "customer_info_needed", "type": "sep31-receiver"}
        transaction_fields = params.get("fields", {}).get("transaction")
        if not transaction_fields:
            return {
                "error": "transaction_info_needed",
                "fields": {
                    "transaction": {
                        "routing_number": {
                            "description": "routing number of the destination bank account"
                        },
                        "account_number": {
                            "description": "bank account number of the destination"
                        },
                    }
                }
            }
        try:
            verify_bank_account(
                transaction_fields.get("routing_number"),
                transaction_fields.get("account_number")
            )
        except ValueError:
            return {"error": "invalid routing or account number"}
        sending_user.add_transaction(transaction)
        receiving_user.add_transaction(transaction)

Updating Invalid Transaction Information

Lets say you do not have the valid_bank_account() function used in the previous example, and can only know if an account is valid when a bank transfer is initiated.

In this case, you may accept invalid banking information passed to process_post_request() and have to request updated information in the future. Guidance for requesting updated transaction information can be found in the execute_outgoing_transaction() documentation.

Once the sending anchor detects that updated transaction information is required, the sender will recollect that information from the sending customer and send it back to the receiving anchor using a PATCH /transactions request. Polaris passes this updated transaction information to the process_patch_request() integration method and expects anchors to validate and save it to their data model. If the data is valid, the anchor must also update their transaction’s status.

Register Integrations

you’ve implemented the integration functions, you need to register them via register_integrations(). Open your anchor/anchor/apps.py file.

from django.apps import AppConfig

class AnchorConfig(AppConfig):
    name = 'anchor'

    def ready(self):
        from polaris.integrations import register_integrations
        ...
        from .sep31 import AnchorCrossBorderPayment

        register_integrations(
            ...,
            sep31_receiver=AnchorCrossBorderPayment()
        )

Testing with the Demo Wallet

Start up the web server.

python anchor/manage.py runuserver --nostatic

You’ll also need to watch for incoming payments made to your distribution account.

python anchor/manage.py watch_transactions

Finally, you’ll need to be able to execute the off-chain payments to your receiving users.

python anchor/manage.py execute_outgoing_transactions --loop
python anchor/manage.py poll_outgoing_transactions --loop

See the CLI Commands and Connecting Off-Chain Payment Rails documentation if you are unfamiliar with these commands.

Go to https://demo-wallet.stellar.org, and import an account that has a balance of the asset you anchor. Then, select “SEP-31 Send” from the drop down menu and select “Start”. You should see the demo wallet ping your endpoints, authenticate, and open a widget for you to enter the KYC information you require from users of your service.

Once you enter the information, the demo wallet will register the customers with your service and initiate a transaction. Finally, it will make a payment to the address specified by your service and wait until the service receives and completes the transaction.