Skip to main content

Build a SEP-31 Anchor on Testnet

Before setting up a server that connects to live banking rails and real money, you should build out a test rig connected to the Stellar test network. The testnet works just like the main Stellar network, but it uses test data and allows you to fund test accounts for free using a tool called Friendbot.

info

Note: the testnet is reset every three months, so when building on it, make sure you have a plan to recreate necessary accounts and other data. For more info, check out the best practices for using the testnet.

At the end of this section, you should have a sandboxed system capable of interfacing with the Stellar testnet that you can easily convert into a production-ready deployment on the main Stellar network. You should always keep a testnet deployment up and running — even once you have a production version — so that wallets can test your implementation without the risk of losing real user funds.

You can also find an outline of the basic steps anchors need to complete in the intro to SEP-31 section of the cross-border payment standard.

Prerequisites

Before you start building infrastructure to connect a Stellar-network token to banking rails, you need to:

  1. Issue an asset on the network – which you can find out how to do in the Issue Assets section — or find another trustworthy issuer and coordinate with them to broker their asset.
  2. Add meta-information about the asset (if you issue one) and define the location of your DIRECT_PAYMENT_SERVER in your stellar.toml so that wallets know where to find the server and relevant endpoints. You can consult this guide for help completing your stellar.toml.

Once you've taken those steps, you're ready to implement an /info endpoint.

Implementing the /info Endpoint

The /info endpoint allows receiving anchors to communicate basic information to sending anchors, exchange interfaces, and other Stellar apps. It responds to client queries with a JSON object detailing which currencies the anchor supports and specifically what pieces of information are required for the sender to collect and provide to the receiver in future requests.

For example, the SDF's reference server has the following /info response:

{
"receive": {
"SRT": {
"enabled": true,
"min_amount": 10.0,
"max_amount": 10000.0,
"sender_sep12_type": "sep31-sender",
"receiver_sep12_type": "sep31-receiver",
"fee_fixed": 1.0,
"fee_percent": 0.01,
"fields": {
"transaction": {
"routing_number": {
"description": "routing number of the destination bank account"
},
"account_number": {
"description": "bank account number of the destination"
}
}
}
}
}
}

It has a single SRT asset enabled with minimum and maximum amounts and both fixed rate and percentage fees taken from the amount received. If the receiving anchor has a complex fee structure that can't be expressed with the fee attributes from /info, sending anchors can support a /fee endpoint. Unfortunately, this endpoint is a pending change to the SEP that has not yet been approved. An update will be made when the /fee endpoint specification is finalized.

The sender_sep12_type and receiver_sep12_type keys signal to the to the SA application that SEP-12 customer registration is required for both the SU and RU. The values of these attributes are the values the SA should include in the GET /customer?type=<SEP-12 type value> request for each user. In this context, users are synonymous with customers.

The fields object contains the single transaction key-value pair, which contains the custom fields the RA requires from the SA. Typically, these attributes are off-chain rails-related attributes like routing and banking numbers. SAs should be aware of their partner RA's fields.transaction required attributes and should ideally validate the collected information from the SU prior to making the POST /transaction API call on the RA's server.

info

Note: Generally, RAs should require the KYC fields of the SU and RU to be sent via SEP-12 and per-transaction information via SEP-31's POST /transaction endpoint in the forms.transactions object. Be aware that SUs must be able to understand what fields to collect for your service and ideally how to validate. Vague descriptions or poorly named attributes will cause confusion.

Authentication

Stellar Web Authentication (SEP-10) is a protocol for verifying that a user controls a Stellar private key for a given account, and for creating a persistent session for that user. It relies on a variation of mutual challenge-response, and uses Stellar transactions to encode challenges and responses: the auth server provides a 'challenge' transaction; the client signs it on behalf of the user and returns it to the anchor; the anchor checks that the signature is valid, and if it is, issues a JWT.

In the context of SEP-31, the client and key holder is the sending anchor (SA). Receiving anchors (RAs) should have a list of approved sending anchors and their public keys used in SEP-10 authentication. If an authentication request is made with an unknown Stellar public key address, the receiving anchor should reject the request.

Once received, the JWT then acts as a reusable key for the SA to perform an action on behalf of a SU. It can contain an arbitrary amount of information, and is signed by the provider — in this case the asset anchor — to ensure validity.

Requesting a Challenge

To start the authentication flow, the client requests a challenge, which is a Stellar transaction with the sequence number set to 0. A transaction with a sequence number of 0 is, by definition, invalid, so it can't actually be submitted to the network. The issuer can, however, verify that the transaction is signed correctly (which is what happens in the next step).

The only information needed to request a challenge is the client's public key, passed as the account parameter. Here's an example: GET <AUTH_ENDPOINT>?account=GXXXXXX

Exchanging the Signed Challenge

Once the client signs the challenge transaction on behalf of the user using standard Stellar SDK tools, it sends the signed transaction back to the provider. Using those same Stellar SDKs, the provider then checks to see if the transaction is properly signed, and if it is, offers the client a JWT.

This JWT should be created with the claims that are appropriate given the account that signed the challenge. It can be created with any existing JWT library.

Here are the fields included in the JWT:

  • The sub key contains the account of the authenticated user
  • The exp key contains the expiration of the JWT. Some JWTs should be short-lived if the claims inside of it are expected to change, or if the JWT is used in less secure environments
  • Other keys can contain any type of claim or data the Anchor wishes.

Since the sub field contains the address of the authenticated user, it's kind of like a username. The JWT authenticates said user.

Since some tokens are used in less secure environments, such as as query parameters in URLs, you may want to create a short-lived or one-time use token to prevent a JWT from falling into the wrong hands.

Customer Registration

Once you have implemented an /info endpoint and set up SEP-10 authentication, your next step is to build out SEP-12. This SEP is a standard for communicating user KYC information between clients and servers running Stellar services.

In the context of SEP-31, the SA collects the information the RA specifies as required in the response to GET /customer?type=<sender/receiver SEP-12 type from /info> and makes a PUT /customer request containing that information back to the RA.

SEP-12 uses the standardized fields defined in SEP-9, so SAs should be familiar with the descriptions and formats of the fields used by the RA.

On testnet, KYC validation is discouraged in order to lower the barrier for other to test your service. However, if information passed to an RA turns out to be invalid after attempting to validate it on mainnet, specific customers (SUs or RUs) can be placed in a pending_customer_info_update status.

This signals to the SA that the fields should be updated by the SU and resent. The problematic fields can be identified using the GET /customer?id=<customer ID returned from the initial PUT call> API call. These fields should be included in the future PUT /customer request.

RAs should also respect the DELETE /customer requests made by the SA.

info

Note: Ideally, RAs validate KYC data passed by SAs during the request/response cycle instead of reporting KYC issues via the transaction's status later in the process.

SAs should poll the GET /transactions/:id endpoint(s) provided by their partner RA(s) and ensure the transactions initiated are always carried out to completion by fetching updated info from the SU when necessary.

The two diagram below outline a potential flow between a SA and RA including all possible SEP-12 states:

sequenceDiagram participant SU participant SA Client participant RA Server note right of SA Client: After SEP-10 auth SA Client->>RA Server: [SEP-31 GET /info] activate RA Server RA Server-->>SA Client: sender_sep12_type, receiver_sep12_type deactivate RA Server SA Client->>RA Server: [SEP-12 GET /customer?type={sender_sep12_type value}] activate RA Server RA Server-->>SA Client: NEEDS_INFO, fields deactivate RA Server SA Client->>RA Server: [SEP-12 GET /customer?type={receiver_sep12_type value}] activate RA Server RA Server-->>SA Client: NEEDS_INFO, fields deactivate RA Server SA Client->>SU: Requests fields returned from [SEP-12 GET]s activate SU SU-->>SA Client: Provides field values deactivate SU note right of SA Client: Register SU KYC SA Client->>RA Server: [SEP-12 PUT /customer] activate RA Server RA Server-->>SA Client: ID, 202 Accepted deactivate RA Server note right of SA Client: Register RU KYC SA Client->>RA Server: [SEP-12 PUT /customer] activate RA Server RA Server-->>SA Client: 400 Bad Request deactivate RA Server SA Client->>SU: Requests field(s) returned from [SEP-12 GET receiver] activate SU SU-->>SA Client: Provides field values deactivate SU note right of SA Client: Retry RU registration SA Client->>RA Server: [SEP-12 PUT /customer] activate RA Server RA Server-->>SA Client: ID, 202 Accepted deactivate RA Server

Now that both the SU and RU are registered with the RA, the SA must initiate the transaction request with the RA. This process is described in the section below. Assuming the SA made a successful POST /transaction request after collecting the KYC and transaction info, a potential update-flow could look something this:

sequenceDiagram participant SU participant SA Client participant RA Server participant RU note right of RA Server: After [SEP-31 POST /transaction] RA Server->>RU: [SEP-31 off-chain transfer attempt] activate RU RU-->>RA Server: transfer failed due to KYC field X, Y deactivate RU note right of SA Client: After failed transfer attempt SA Client->>RA Server: [SEP-31 GET /transaction/:id] activate RA Server RA Server-->>SA Client: receiver pending_customer_info_update deactivate RA Server SA Client->>RA Server: [SEP-12 GET /customer?id={receiver ID}] activate RA Server RA Server-->>SA Client: NEEDS_INFO, fields deactivate RA Server SA Client->>SU: Requests updated field values activate SU SU-->>SA Client: Provides updated values deactivate SU SA Client->>RA Server: [SEP-12 PUT /customer] activate RA Server RA Server-->>SA Client: ID, 202 Accepted deactivate RA Server RA Server->>RU: [SEP-31 off-chain transfer retry] activate RU RU-->>RA Server: transfer successful! note right of SA Client: Detect success & notify SU SA Client->>RA Server: [SEP-31 GET /transactions/:id] activate RA Server RA Server-->>SA Client: completed deactivate RA Server SA Client->>SU: transfer successful!

info

Note: It is perfectly acceptable for RAs to only require KYC information on the RU. To do this, simply omit the sender_sep12_type key from the GET /info response. This signals to the SA to make one GET and PUT for receiver fields and registration, respectively.

Transaction Processing

After registering the receiving and potentially sending user, but prior to any update flow, the SA must initiate the transaction creation process by making a POST /transactions request including the receiver_id (and sender_id) as well as the fields.transaction object containing the per-transaction level fields defined by the RA in /info.

On a successful response, the SA then makes the path payment on Stellar between the deposited asset from the SU to the asset anchored by RA. The transaction must contain a memo of type stellar_memo_type with a value of stellar_memo. These attributes are returned in the RA's POST /transactions response.

info

Note: the payment transaction can be a normal strict-receive payment transaction instead of a path payment. Typically, cross-border payments will involve a cross-currency transaction.

The RA should be polling or streaming incoming payment transactions on their Stellar distribution account. Once detected and matched with the transaction record created by the SA's POST /transactions request, the RA will attempt to transfer the value received off-chain to the associated RU.

The following diagram outlines the process for a successful transaction initiation request and an update flow that differs from the SEP-12 pending_customer_info_update process. Most of the time, SAs will not have to collect and provide updated transaction information. This is only in the case of data entry error by the SU.

info

Note: If the SU and RU are already registered, only the per-transaction information defined in the GET /info forms.transaction object needs to be collected. If SEP-12 registration has not happened or an update is required, it is recommended to collect both KYC and per-transaction field values from the SU at the same time.


Due to the separate documentation sections outlining the KYC and transaction API calls, we'll ask the SU for information a second time for per-transaction attributes.

sequenceDiagram participant SU participant SA Client participant Horizon participant RA Server participant RU note right of SA Client: After customer registration SA Client->>RA Server: [SEP-31 GET /info] activate RA Server RA Server-->>SA Client: fields.transaction descriptions deactivate RA Server SA Client->>SU: Request the transaction fields activate SU SU-->>SA Client: Provide transaction fields deactivate SU note right of SA Client: Make path payment SA Client->>Horizon: Stellar path payment including memo Horizon->>RA Server: Stream and match path payment RA Server->>RU: Off-chain transfer attempt activate RU RU-->RA Server: transfer failed due to TX fields X, Y deactivate RU SA Client->>RA Server: [SEP-31 GET /transactions/:id] activate RA Server RA Server-->>SA Client: pending_transaction_info_update, required_info_updates deactivate RA Server note left of SA Client: recollect required_info_updates fields SA Client->>SU: Request valid transaction X, Y values activate SU SU-->SA Client: Provide valid X, Y deactivate SU SA Client->>RA Server: [SEP-31 PATCH /transactions/:id] activate RA Server RA Server-->>SA Client: 200 Success deactivate RA Server RA Server->>RU: Off-chain transfer retry attempt activate RU RU-->>RA Server: transfer success! deactivate RU note right of SA Client: Detect and notify SU SA Client->>RA Server: [GET /transactions/:id] activate RA Server RA Server-->SA Client: pending_external deactivate RA Server note right of SA Client: After RA rails completion SA Client->>RA Server: [GET /transactions/:id] activate RA Server RA Server-->SA Client: completed deactivate RA Server SA Client->>SU: transaction success!

Transactions Endpoint

As demonstrated in the diagram from the previous section, the /transactions endpoint provides a way for SAs to initiate (POST), poll (GET :id), and update (PATCH :id) transactions with an RA.

Check out the SEP-31 standard for the API request and response specifications for the /transactions endpoint.

Streaming Incoming Path Payments

Receiving anchors (RAs) must be able to detect and match incoming Stellar path payment transactions with receiving users. This can be done one of two ways:

  • Polling: this option consists of periodically fetching the transactions submitted after an already-seen transaction's paging_id that should be updated after each iteration.
  • Streaming: this option consists of maintaining a persistent process that streams incoming transactions from the anchored asset's distribution account, which allows detection and transaction matching in close to real time.

For more information on path payment transactions, checkout the operation definition.