SEP-38

SEP-38, or the Anchor Request for Quote API, is a standardize protocol that allows anchors to provide both firm and indicative quotes or exchange rates for a given on & off-chain asset pair. For example, a brazilian anchor can offer exchange rates for fiat (off-chain) Brazilian Real and Stellar (on-chain) USDC.

These quotes provided by the anchor can then be referenced when initiating transactions using other Stellar Ecosystem Protocols (SEPs), such as SEP-6 and SEP-31. SEP-24 can support exchanging different on & off-chain assets, but there is no need for the SEP-38 API in this protocol because the anchor can communicate all exchange rate and fee information in the interactive flow.

Configuration

To make the SEP-38 API available to clients, add "sep-38" to Polaris’ ACTIVE_SEPS environment variable or POLARIS_ACTIVE_SEPS django setting.

POLARIS_ACTIVE_SEPS = ["sep-1", "sep-10", "sep-31", ...]

For each Stellar Asset that can be exchanged with other off-chain assets, set sep38_enabled to True.

usdc = Asset.objects.filter(code="USDC").first()
usdc.sep38_enabled = True
usdc.save()

Data Model

There are four additional database models used for transactions involving SEP-38 quotes. Database entries for every model other than Quote must be created by the anchor before facilitating transactions using SEP-38.

Quote

Quote objects represent either firm or indicative quotes requested by the client application and provided by the anchor. Quote objects will be assigned to the Transaction.quote column by Polaris when requested via a SEP-6 or SEP-31 request. Anchors must create their own Quote objects when facilitating a SEP-24 transaction.

ExchangePair

Exchange pairs consist of an off-chain and on-chain asset that can be exchanged. Specifically, one of these assets can be sold by the client (sell_asset) and the other is bought by the client (buy_asset). ExchangePairs cannot consist of two off-chain assets or two on-chain assets. Note that two exchange pair objects must be created if each asset can be bought or sold for the other.

OffChainAsset

Off-chain assets represent the asset being exchanged with the Stellar asset. Each off-chain asset has a set of delivery methods by which the user can provide funds to the anchor and by which the anchor can deliver funds to the user.

DeliveryMethod

Delivery methods are the supported means of payment from the user to the anchor and from the anchor to the user. For example, an anchor may have retail stores that accept cash drop-off and pick-up, or only accept debit or credit card payments. The method used by the anchor to collect or deliver funds to the user may affect the rate or fees charged for facilitating the transaction.

Integrations

Each integration function required for SEP-38 corresponds to an endpoint specified in the API. The GET /info and GET /quote endpoints do not require integrations.

polaris.integrations.QuoteIntegration.get_prices()

Return a list of prices in the order of the buy_assets provided. Each price should be the value of one unit of the buy_asset in terms of sell_asset. The prices returned from this function are non-binding, meaning a Quote object will not be created and returned to the client as a result of this call.

The buy_assets list passed are the assets that have an ExchangePair database record with the sell_asset. Polaris will also ensure all buy_assets support the buy_delivery_method and country_code if specified by the client.

Polaris will also ensure that the sell_delivery_method is supported for sell_asset.

Raise a ValueError if the parameters are invalid in some way. Because Polaris validates all the parameters passed, the only reason to raise this exception _should_ be because the sell_amount is outside the minimum and maximum bounds for your service. Polaris will return a 400 Bad Request status code in this case.

Raise a RuntimeError if you cannot return prices for the provided sell_asset and buy_assets for any reason. For example, the service used by the anchor to source exchange prices could be down. Polaris will return a 503 Server Unavailable status code in this case.

Parameters
  • token – The SEP10Token object representing the authenticated session

  • request – The rest_framework.Request object representing the request

  • sell_asset – The asset the client would like to sell for buy_assets

  • buy_assets – The assets the client would like to buy using sell_asset

  • sell_amount – The amount the client would like to sell of sell_asset

  • sell_delivery_method – The method the client would like to use to deliver funds to the anchor.

  • buy_delivery_method – The method the client would like to use to receive or collect funds from the anchor.

  • country_code – The ISO 3166-1 alpha-3 code of the user’s current address

polaris.integrations.QuoteIntegration.get_price()

Return the price of one unit of buy_asset in terms of sell_asset. The price returned from this function is non-binding, meaning a Quote object will not be created and returned to the client as a result of this call.

Polaris will ensure that there is an ExchangePair database record for the sell_asset and buy_asset, that the specified sell_delivery_method or buy_delivery_method is supported by the off-chain asset, and that the anchor supports transacting the off-chain asset in the country_code, if specified.

Raise a ValueError if the parameters are invalid in some way. Because Polaris validates all the parameters passed, the only reason to raise this exception _should_ be because sell_amount or buy_amount is outside the minimum and maximum bounds for your service. Polaris will return a 400 Bad Request status code in this case.

Raise a RuntimeError if you cannot return prices for the provided sell_asset and buy_asset for any reason. For example, the service used by the anchor to source exchange prices could be down. Polaris will return a 503 Server Unavailable status code in this case.

Parameters
  • token – The SEP10Token object representing the authenticated session

  • request – The rest_framework.Request object representing the request

  • sell_asset – The asset the client would like to sell for buy_assets

  • sell_amount – The amount the client would like to sell of sell_asset

  • buy_asset – The asset the client would like to buy using sell_asset

  • buy_amount – The amount the client would like to purchase of buy_asset

  • sell_delivery_method – The method the client would like to use to deliver funds to the anchor.

  • buy_delivery_method – The method the client would like to use to receive or collect funds from the anchor.

  • country_code – The ISO 3166-1 alpha-3 code of the user’s current address

polaris.integrations.QuoteIntegration.post_quote()

Assign Quote.price and Quote.expire_at`` on the quote passed and return it. The anchor will be expected to honor the price set when the quote is used in a Transaction. Note that the Quote object passed is not yet saved to the database when this function is called. If no exception is raised, Polaris will calculate the amount of the asset not specified in the request using the price assigned and save the returned quote to the database. However, the anchor is free to save the quote to the database in this function if necessary.

Polaris will ensure that there is an ExchangePair database record for the sell_asset and buy_asset, that the specified sell_delivery_method or buy_delivery_method is supported by the off-chain asset, and that the anchor supports transacting the off-chain asset in the country_code, if specified.

Raise a ValueError if the parameters are invalid in some way. Because Polaris validates all the parameters passed, the only reason to raise this exception _should_ be because sell_amount or buy_amount is outside the minimum and maximum bounds for your service OR the requested expires_at value is not acceptable to the anchor. Polaris will return a 400 Bad Request status code in this case.

Raise a RuntimeError if you cannot return prices for the provided sell_asset and buy_asset for any reason. For example, the service used by the anchor to source exchange prices could be down. Polaris will return a 503 Server Unavailable status code in this case.

Parameters
  • token – The SEP10Token object representing the authenticated session

  • request – The rest_framework.Request object representing the request

  • quote – The Quote object representing the exchange rate to be offered

Using Quotes with SEP-6

Deposit and withdrawals can use different on and off-chain assets. For example, a brazilian anchor can accept fiat Brazilian Real and send USDC to the customer’s Stellar account. In the same way, a customer can send USDC on Stellar to the anchor and receive fiat Brazilian Real in their bank account.

SEP-6 supports this kind of transaction by adding the GET /deposit-exchange and GET /withdraw-exchange endpoints. Polaris will still use the appropriate process_sep6_request() integration function for these requests, but the parameters used by the client will include both the source_asset and destination_asset parameters, instead of the usual asset_code parameter. If the client already requested a firm quote using the POST /quote endpoint, the quote_id parameter will also be included.

For these requests, Polaris will assign a Quote object to Transaction.quote and pass the transaction to process_sep6_request(). If the quote_id parameter was not included in the request, the Quote will be indicative, meaning it will yet not be saved to the database or have Quote.price or Quote.expires_at assigned. If quote_id _was_ included in the request, the anchor has already commited to the price assigned to Quote.price and must deliver funds using this rate as long as the user has delivered funds to the anchor prior to Quote.expires_at.

For indicative quotes, the anchor must assign Quote.price in RailsIntegration.poll_pending_deposits() or RailsIntegration.execute_outgoing_transaction() depending on the type of transaction.

Using Quotes with SEP-24

SEP-24 does not have /deposit-exchange or /withdraw-exchange endpoints like SEP-6 does, nor does it use the SEP-38 API at all. Instead, the anchor is expected to collect and convey all relevant information during the interactive flow. Anchors may display an estimated exchange rate to the user during this flow or offer a firm rate the anchor will honor for a specified period of time.

Using Quotes with SEP-31

It is very common for cross-border payments to involve multiple assets. For example, a sending user in the United States can pay a remittance company US dollars to have the recipient paid in Brazilian Real. SEP-31 supports this kind of transaction by supporting the optional destination_asset and quote_id request parameters for its POST /transactions endpoint. When these parameters are included in requests, a firm or indicative Quote object will be assigned to the Transaction object passed to the process_post_request() integration function.

If the quote is indicative, a rate must be assigned to Quote.price in RailsIntegration.execute_outgoing_transaction(). If the quote is firm, the anchor has already committed to the rate and must honor it if the user delivers funds before the quote’s expiration.

Charging Fees

With SEP-38 support, two assets are involved in a transaction, and fees can be charged in units of either asset. Because of this, the anchor must assign Transaction.amount_fee, Transaction.amount_out, and Transaction.fee_asset appropriately.

These properties should be assigned values as soon as it is possible to calculate them. This will enable the client application to offer the best UX to its customers.

Also note that SEP-6’s and SEP-24’s GET /fee endpoint does not support calculating fees using multiple assets. If fees cannot be calculated solely as a function of the on-chain asset, this means client applications will be unable to communicate any fee information before initiating the transaction. Again, this makes assigning Transaction.amount_fee and the related properties as early as possible helpful.