SEP-31¶
SEP-31 is a bilateral payment standard for one anchor’s user to make payments to another anchor’s user. Where SEP-6 and SEP-24 allow users to deposit and withdraw their funds on and off the Stellar network, users of SEP-31 anchors may not even know they are using Stellar. A user can simply send fiat or crypto to the sending anchor and have them send the same amount (minus fees) to another anchor who can deposit the off-chain funds into the receiving user’s account.
An anchor can use the integrations outlined below to implement a fully functional SEP-31 anchor.
Configuration¶
Add the SEP to POLARIS_ACTIVE_SEPS
in in your settings file.
POLARIS_ACTIVE_SEPS = ["sep-1", "sep-10", "sep-31", ...]
Integrations¶
Where SEP-6 and SEP-24 use DepositIntegration
and WithdrawalIntegration
,
SEP-31 uses SEP31ReceiverIntegration
and RailsIntegration
. Note that in future
releases, some SEP-6 and SEP-24 functions related to payment rails may be moved
from DepositIntegration
or WithdrawalIntegration
to RailsIntegration
.
SEP-31 Endpoints¶
-
polaris.integrations.SEP31ReceiverIntegration.
info
(self, asset: polaris.models.Asset, lang: str = None) → Dict[KT, VT]¶ Return a dictionary containing the
"fields"
and “sep12” objects as described in the info response for the given asset. Polaris will provide the rest of the fields documented in the info response.Descriptions should be in the lang passed if supported.
return { "fields": { "transaction":{ "routing_number":{ "description": "routing number of the destination bank account" }, "account_number":{ "description": "bank account number of the destination" }, "type":{ "description": "type of deposit to make", "choices":[ "SEPA", "SWIFT" ] } } }, "sep12": { "sender": { "types": { "sep31-sender": { "description": "the only SEP-12 type for SEP-31 sending customers" } } }, "receiver": { "types": { "sep31-receiver-cash-pickup": { "description": "recipients who will pick up cash at physical locations" }, "sep31-receiver-bank-transfer": { "description" : "recipients who would like to receive funds via direct bank transfer" } } } } }
Parameters: - asset – the
Asset
object for the field values returned - lang – the ISO 639-1 language code of the user
- asset – the
-
polaris.integrations.SEP31ReceiverIntegration.
process_post_request
(self, params: Dict[KT, VT], transaction: polaris.models.Transaction) → Optional[Dict[KT, VT]]¶ Use the params passed in the request to do any processing of the user and requested transaction necessary to facilitate the payment to the receiving user. If the arguments are valid, save
transaction
and link it to your other models. If the transaction is saved but an error response is returned Polaris will return a 500 response 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-31, it is encouraged to also populateTransaction.amount_fee
and ` Transaction.amount_out` here as well. 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 inRailsIntegration.execute_outgoing_transaction()
.Polaris validates that the request includes all the required fields returned by
SEP31ReceiverIntegration.info()
but cannot validate the values. ReturnNone
if the params passed are valid, otherwise return one of the error dictionaries outlined below.If the sender_id or receiver_id values are invalid or the information collected for these users is not sufficient to process this request, return a dictionary matching the customer-info-needed response schema.
return { "error": "customer_info_needed", "type": "sep31-large-amount-sender" }
For example, the above response could be used if the anchor requires additional information on the sender when the amount is large. The type key specifies the appropriate type value the client should use for the sender’s SEP-12 GET /customer request, and is optional.
If some optional fields from
info()
are missing but needed for this transaction, return a dictionary matching the schema described in the transaction-info-needed response.return { "error": "transaction_info_needed", "fields": { "transaction": { "sender_bank_account": { "description": ( "The bank account number used by the sender. " "Only required for large transactions." ), "optional": True } } } }
If some parameters passed are simply not acceptable, return a dictionary containing a single
"error"
key-value pair.return { "error": "invalid 'sender_bank_account' format" }
Parameters: - params – The parameters included in the /transaction request
- transaction – the
Transaction
object representing the transaction being processed
-
polaris.integrations.SEP31ReceiverIntegration.
process_patch_request
(self, params: Dict[KT, VT], transaction: polaris.models.Transaction)¶ 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_receiver
as well as clear therequired_info_updates
andrequired_info_message
fields.Once the transaction enters the
pending_receiver
status, the execute_outgoing_transactions process will attempt to send the payment to the receiving user. See theRailsIntegration.execute_outgoing_transaction
function for more information on the lifecycle of a transaction.Parameters: - params – the request body of the PATCH /transaction request
- transaction – the
Transaction
object that should be updated
-
polaris.integrations.SEP31ReceiverIntegration.
valid_sending_anchor
(self, public_key: str) → bool¶ Return
True
if public_key is a known anchor’s stellar account address, andFalse
otherwise. This function ensures that only registered sending anchors can make requests to protected endpoints.Parameters: public_key – the public key of the sending anchor’s stellar account
Payment Rails¶
-
polaris.integrations.RailsIntegration.
execute_outgoing_transaction
(self, transaction: polaris.models.Transaction) Send the amount of the off-chain asset specified by transaction minus fees to the user associated with transaction. This function is used for SEP-6 & SEP-24 withdraws as well as SEP-31 payments.
When this function is called,
transaction.amount_in
is the amount sent to the anchor, not the amount specified in a SEP-24 or SEP-31 API call. This matters because the amount actually sent to the anchor may differ from the amount specified in an API call. That is why you should always validatetransaction.amount_in
and calculatetransaction.amount_fee
here.If the amount is invalid in some way, the anchor must choose how to handle it. If you choose to refund the payment in its entirety, change
transaction.status
to “error”, assign an appropriate message totransaction.status_message
, and update transaction.refunded toTrue
.You could also refund a portion of the amount and continue processing the remaining amount. In this case, the
transaction.status
column should be assigned one of the expected statuses for this function, mentioned below, and theamount_in
field should be reassigned the value the anchor accepted.If the funds transferred to the user become available in the user’s off-chain account immediately, or the anchor cannot verify when funds have become available, update
Transaction.status
toTransaction.STATUS.completed
. If the transfer was simply initiated and is pending external systems, update the status toTransaction.STATUS.pending_external
.If an exception is raised, the transaction will be left in its current status and may be used again as a parameter to this function. To ensure the exception isn’t repeatedly re-raised, change the problematic transaction’s status to
Transaction.STATUS.error
.If
transaction.protocol == Transaction.PROTOCOL.sep31
and more information is required from the sending anchor or user to complete the transaction, update the status toTransaction.STATUS.pending_transaction_info_update
and save a JSON-serialized string containing the fields that need updating to theTransaction.required_info_update
column. The JSON string should be in the format returned fromSEP31ReceiverIntegration.info()
. You can also optionally save a human-readable message toTransaction.required_info_message
. Both fields will be included in the /transaction response requested by the sending anchor.If the SEP-31 transaction is waiting for an update, the sending anchor will eventually make a request to the PATCH /transaction endpoint with the information specified in
Transaction.required_info_update
. Once updated, this function will be called again with the updated transaction.Parameters: transaction – the Transaction
object associated with the payment this function should make
-
polaris.integrations.RailsIntegration.
poll_outgoing_transactions
(self, transactions: django.db.models.query.QuerySet) → List[polaris.models.Transaction]¶ Check the transactions that are still in a
pending_external
status and return the ones that are completed, meaning the user has received the funds.Polaris will update the transactions returned to
Transaction.STATUS.completed
.transactions is passed as a Django
QuerySet
in case there are many pending transactions. You may want to query batches ofTransaction
objects to avoid consuming large amounts of memory.Parameters: transactions – a QuerySet
ofTransaction
objects
Running the Service¶
In addition to the web server, SEP-31 requires three additional processes to be run in order to work. See the CLI Commands for more information on all Polaris commands.
Watch Transactions¶
Polaris’ watch_transactions
command line tool streams transactions from
every anchored asset’s distribution account and attempts to match every incoming
stellar payment with a Transaction object created by the sending anchor’s POST
/transaction request.
If it finds a match, it will update the transaction’s status to
pending_receiver
and update the Transaction.amount_in
field with the
amount actually sent in the stellar transaction.
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_receiver
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