SEP: 0008
Title: Regulated Assets
Author: Tomer Weller <@tomerweller>, Howard Chen <@howardtw>, Christian Peters (DSTOQ) <@shredding>, Leigh McCulloch <@leighmcculloch>
Status: Active
Created: 2018-08-22
Updated: 2022-02-21
Version 1.7.4
Regulated Assets are assets that require an issuer’s approval (or a delegated third party’s approval, such as a licensed securities exchange) on a per-transaction basis. It standardizes the identification of such assets as well as defines the protocol for performing compliance checks and requesting issuer approval.
Asset issuers, issuance platforms, wallet developers, and exchanges
Stellar is an ideal platform for issuing securities. Regulation sometimes requires asset issuers to monitor and approve each transaction involving issued assets and to enforce constraints. This is possible on the Stellar network today, however there exists no standard interface between wallets and issuers to negotiate approval.
Implementing a regulated asset requires these parts:
- Issuer account with appropriate authorization flags set.
- SEP-1 stellar.toml used for discovering Approval Server.
- Approval Server that validates client transactions according to the service's approval criteria. Validated transactions are signed by the asset's issuing account.
- Wallet creates and signs a transaction.
- Wallet resolves asset information and detects that it's an asset that requires authorization.
- Wallet can detect whether an asset requires authorization by checking the authorization flags of the asset issuer's account
- Wallet detects that it's a regulated asset.
- Wallet can detect whether an asset is a regulated asset by checking for an approval server via SEP-1 stellar.toml set up by the issuer
- Wallet sends the transaction to the approval server.
- Approval server determines whether the transaction should be approved.
- Wallet handles approval response:
- Success? Transaction has been approved and signed by the issuer.
- Revised? Transaction has been revised to be made compliant, and signed by the issuer. Wallet will show the changes to the user and ask them to sign the new transaction. It is important to understand that an approval service could respond with a different transaction.
- Pending? The issuer could not determine whether to approve this transaction at the moment. Wallet can re-send the same transaction to the approval server later (Return to 3).
- Action Required? Transaction requires a user action to be completed. Wallet will present the attached action url as a clickable link, along with the attached message and an option to resubmit once the action has been taken.
- Rejected? Wallet should display the associated error message to the user.
Regulated asset issuers must have both Authorization Required
and Authorization Revocable
flags set on their account. This allows the issuer to grant and revoke authorization to transact the asset at will.
Issuers advertise the existence of an Approval Service through their SEP-1 stellar.toml file. This is done in the [[CURRENCIES]]
section as different assets can have different requirements.
These fields are listed in the [[CURRENCIES]]
section:
regulated
is a boolean indicating whether or not this is a regulated asset. If missing,false
is assumed.approval_server
is the URL of an approval service that signs validated transactions.approval_criteria
is a human readable string that explains the issuer's requirements for approving transactions.
[[CURRENCIES]]
code="GOAT"
issuer="GD5T6IPRNCKFOHQWT264YPKOZAWUMMZOLZBJ6BNQMUGPWGRLBK3U7ZNP"
regulated=true
approval_server="https://goat.io/tx_approve"
approval_criteria="The goat approval server will ensure that transactions are compliant with NFO regulation"
A transaction that an approval server will approve consists of operations that authorize the clients' accounts, transact the regulated asset, and deauthorize the clients' accounts. If a transaction is built this way, there will be no point where the clients' accounts have open, authorized trustlines to run unapproved operations because of transaction atomocity. A transaction as described above can be built by wallets preemptively or by approval servers in order to make it compliant when the revised status is applicable.
Depending on whether issuers want to allow the clients' accounts to maintain offers, issuers can leave the accounts in the AUTHORIZE_TO_MAINTAIN_LIABILITIES_FLAG
state or completely deauthorize the accounts when completing the operation. See [CAP-18] for the fine-grained control of authorization.
An example of the transaction where the issuer allow the client's account to maintain offers:
Operation 1: AllowTrust op where issuer fully authorizes account A, asset X
Operation 2: Account A manages offer to buy or sell X
Operation 3: AllowTrust op where issuer sets account A, asset X to AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG state
An example of the transaction where the issuer do not allow the clients' accounts to maintain offers:
Operation 1: AllowTrust op where issuer fully authorizes account A, asset X
Operation 2: AllowTrust op where issuer fully authorizes account B, asset X
Operation 3: Payment from A to B
Operation 4: AllowTrust op where issuer fully deauthorizes account B, asset X
Operation 5: AllowTrust op where issuer fully deauthorizes account A, asset X
Approval server is a single endpoint that receives a signed transaction, checks for compliance, and signs it on success.
The specification of an approval server is defined as follows:
Cross-origin requests to the endpoints in this specification must be supported by the server so that web clients from other sites can use the endpoint(s). Valid CORS headers and requests that allow any domain origin to access the endpoints in this specification are necessary. The following HTTP header and support of preflight OPTIONS requests are the smallest change necessary. More complex headers configurations can be used.
Access-Control-Allow-Origin: *
Approval server accepts requests in the following Content-Type
s:
application/x-www-form-urlencoded
application/json
Approval server responds with the Content-Type
:
application/json
This endpoint accepts HTTP POST requests containing a single tx
parameter.
Name | Type | Description |
---|---|---|
tx |
string | A base64 encoded transaction envelope XDR signed by the user. This is the transaction that will be tested for compliance and signed on success. |
{
"tx": "AAAAAHAHhQtYBh5F2zA6..."
}
tx=AAAAAHAHhQtYBh5F2zA6...
All responses will contain a top level status
parameter that indicates the type of the result.
This response means that the transaction was found compliant and signed without being revised.
A success
response will have a 200
HTTP status code and success
as the status
value.
Parameters:
Name | Type | Description |
---|---|---|
status |
string | "success" |
tx |
string | Transaction envelope XDR, base64 encoded. This transaction will have both the original signature(s) from the request as well as one or multiple additional signatures from the issuer. |
message |
string | (optional) A human readable string containing information to pass on to the user. |
{
"status": "success",
"tx": "AAAAAHAHhQtYBh5F2zA6..."
}
This response means that the transaction was revised to be made compliant.
A revised transaction should only add operations to the transaction to make it compliant and should not change the intent of the operations that the user has included. If an operation the user has included cannot be made compliant by adding another operation, the rejected status should be returned. For example, if a user includes a payment operation to an account not authorized, the server can revise the transaction including allow trust operations to authorize the account. But, for example, if a user includes a payment operation for $1000, but amounts over $100 are not compliant, the transaction should not be revised lowering the amount and instead be rejected, because altering the payment amount would change the intent of the transaction.
A revised
response will have a 200
HTTP status code and revised
as the status
value.
It is important for the user to examine the revised transaction before re-signing it. More specifically, the user should make sure that the revised transaction does not contain any additional operations whose source account matches the source account of the original operation(s).
Parameters:
Name | Type | Description |
---|---|---|
status |
string | "revised" |
tx |
string | Transaction envelope XDR, base64 encoded. This transaction is a revised compliant version of the original request transation, signed by the issuer. |
message |
string | A human readable string explaining the modifications made to the transaction to make it compliant. |
{
"status": "revised",
"tx": "AAAAAHAHhQtYBh5F2zA6...",
"message": "Authorization and deauthorization operations were added."
}
This response means that the issuer could not determine whether to approve the transaction at the time of receiving it. Wallet can re-submit the same transaction at a later point in time.
A pending
response will have a 200
HTTP status code and pending
as the status
value.
Parameters:
Name | Type | Description |
---|---|---|
status |
string | "pending" |
timeout |
integer | Number of milliseconds to wait before submitting the same transaction again. Use 0 if the wait time cannot be determined. |
message |
string | (optional) A human readable string containing information to pass on to the user. |
{
"status": "pending",
"message": "Futher verifications are required due to..."
}
This response means that the user must complete an action before this transaction can be approved. The approval service will provide a URL that facilitates the action. Upon completion, the user will resubmit the transaction.
The action the user must complete is intended to be displayed in a browser. The client may be able to skip displaying the URL in the browser if the server supports the POST
action_method
, the client can supply the requested details, and the server's final response to the POST
indicates no further action is required.
An action_required
response will have a 200
HTTP status code and action_required
as the status
value.
Parameters:
Name | Type | Description |
---|---|---|
status |
string | "action_required" |
message |
string | A human readable string containing information regarding the action required. |
action_url |
string | A URL that allows the user to complete the actions required to have the transaction approved. |
action_method |
string | (optional) GET or POST , indicating the type of request that should be made to the action_url . If not provided, GET is assumed. |
action_fields |
string[] | (optional) An array of additional fields defined by SEP-9 Standard KYC / AML fields that the client may optionally provide to the approval service when sending the request to the action_url so as to circumvent the need for the user to enter the information manually. |
{
"status": "action_required",
"message": "Please provide an email address and mobile phone number.",
"action_url": "https://...",
"action_method": "POST",
"action_fields": ["email_address", "mobile_number"]
}
If the action_method
field is provided, the client uses the method specified when making the request to the action_url
.
If the action_fields
field is provided, the client may optionally supply the fields if they already have the information so as to reduce the user's need to re-enter the information. If the requested fields contain sensitive or personal information, the server should use action_method
POST
so that sensitive information is not transmitted in a URL.
When no further action is to be taken by the user, the client can re-submit the transaction to the approval server. However, there is no guarantee to receive a "success"
status in the following call. Clients should expect to receive a response with any of the status.
There are two possible values for action_method
.
-
In the case that
action_method
isGET
, or not defined:Fields requested may be passed as query parameters in the
action_url
. The URL should be opened in a browser. Any fields passed should allow the server to proceed without collecting user information, or at the least pre-fill the provided information. -
In the case that
action_method
isPOST
:Fields requested may be passed as JSON in the request body, using content type
application/json
. The server should respond to thePOST
with a HTTP response with status code200
, content-typeapplication/json
. The body should contain either an acknowledgement that thePOST
was sufficient and no further action is required, or the next URL if action is still required. If a next URL is provided, it should be loaded in a browser for the user to complete any action required.Request Parameters:
Any fields defined by SEP-9 Standard KYC / AML fields.
Request Example:
{ "email_address": "[email protected]" }
Response Parameters when no further action required:
Name Type Description result
string no_further_action_required
.Response Example:
{ "result": "no_further_action_required" }
Response Parameters when further action required:
Name Type Description result
string follow_next_url
.next_url
string A URL where the user can complete the required actions with all the parameters included in the original POST pre-filled or already accepted. message
string (optional) A human readable string containing information regarding the further action required. Response Example:
{ "result": "follow_next_url", "next_url": "https://...", "message": "Please sign the terms of service." }
This response means that the transaction is not compliant and could not be revised to be made compliant.
A rejected
response will have a 400
HTTP status code and rejected
as the status
value.
Parameters:
Name | Type | Description |
---|---|---|
status |
string | "rejected" |
error |
string | A human readable string explaining why the transaction is not compliant and could not be made compliant. |
{
"status": "rejected",
"error": "The destination account is blocked."
}
- Issuers are encouraged to use the
revised
status to make a transaction complaint when possible, since Wallets may not know how to form a transaction that is compliant with the server's criteria. - If a transaction was revised, ALWAYS explain the changes that were made to a transaction through the
message
parameter. - Wallet should inspect revised transactions and alert the user if any of the original operations are changed.
- Core operations shouldn't be modified as that can be confusing and misleading. For example, if the user wishes to put an offer for 1000 GOATS but due to velocity limits they can only put an offer for 500 GOATS, it is better to error with a message than to change the amount.
- Adding an upper timebound to a transaction can help the issuer ensure that their view of the world does not get out of sync.
- Issuers can enforce additional fees by adding additional operations. For example, any transaction involving GOATs, will also send 0.1 GOAT to the issuer's account.
Ideally, no. Implementing Regulated Assets should only be used when absolutely necessary, such as for regulatory reasons. It comes with a substantial operational overhead, added complexity, and burden for users. Issuers should only go down this route if it is absolutely required.
- Separation of concerns between signing transactions and submitting them.
- Transactions might require more signatures from further regulated asset issuers.
- Wallets can handle the failure from transaction submission based on the type of errors and reconstruct the transaction.
- Scaling concerns as having the approval server submit transactions would require more infrastructure.
- A reference implementation of the SEP-8 Approval Server for Go can be found in https://github.com/stellar/go/tree/master/services/regulated-assets-approval-server.
v1.7.4
: Add link to the Approval Server reference implementation in Go. (#1135)