SEP-6: Deposit and Withdrawal API
SEP-6 allows wallets and other clients to interact with anchors directly without the user needing to leave the wallet to go to the anchor’s site. In this integration, a user’s KYC information is gathered and handled by the wallet and submitted to the anchor on behalf of the user.
Find the anchor's TRANSFER_SERVER
Before we can ask anything about how to make a SEP-6 transfer, we have to figure out where to discover that information. Fortunately, the SEP-1 protocol describes standardized fields to find out what we need.
// Fetches and returns the endpoint used for SEP-6 transfer interactions.
export async function getTransferServerSep6(domain) {
let { TRANSFER_SERVER } = await fetchStellarToml(domain);
return TRANSFER_SERVER;
}
Source: https://github.com/stellar/basic-payment-app/blob/main/src/lib/stellar/sep1.js
Get /info
Now that we know where the transfer server is located, BasicPay needs to fetch the /info
endpoint from the anchor's transfer server to understand the supported transfer methods (deposit, withdraw, deposit-exchange, and withdraw-exchange) and available endpoints, as well as additional features that may be available during transfers.
At this time, BasicPay only supports the deposit
and withdraw
transfer methods. A future version of this tutorial will incorporate the *-exchange
transfer methods.
import { getTransferServerSep6 } from "$lib/stellar/sep1";
// Fetches and returns basic information about what the SEP-6 transfer server suppports.
export async function getSep6Info(domain) {
let transferServer = await getTransferServerSep6(domain);
let res = await fetch(`${transferServer}/info`);
let json = await res.json();
return json;
}
Source: https://github.com/stellar/basic-payment-app/blob/main/src/lib/stellar/sep6.js
Display interactive elements
Since many of the SEP-6 (and SEP-24) endpoints require authentication, we wait until our user is authenticated with SEP-10 before we display what kinds of transfers are available. When they have a valid authentication token, we can display some buttons the user can use to begin a transfer.
The user can then initiate one of the transfer methods (in BasicPay, only deposits and withdraws are supported) by clicking the “Deposit” or “Withdraw” button underneath a supported asset.
<script>
/* This <script> tag has been abbreviated for simplicity */
// We import things from external packages that will be needed
import { LogInIcon, LogOutIcon } from "svelte-feather-icons";
// We import some of our `$lib` functions
import { getSep6Info } from "$lib/stellar/sep6";
// The `open` Svelte context is used to open the confirmation modal
import { getContext } from "svelte";
const { open } = getContext("simple-modal");
/* ... */
</script>
<!-- HTML has been omitted from this tutorial. Please check the source file -->
Source: https://github.com/stellar/basic-payment-app/blob/main/src/routes/dashboard/transfers/+page.svelte
A special SEP-6 modal component
If you recall back to our confirmation modal section, we designed our modal component to be useful for anything we might require. Well, that's almost true. The fact is that SEP-6 interactions are just plain complex. To facilitate that complexity, we've created a purpose-built SEP-6 transfer modal. There is so much to it, that we couldn't possibly cover everything it does here. However, we'll cover the main bits and link to the relevant source files.
The modal component itself has been broken into several smaller Svelte components. Check out this source file to start looking through how we've put it together: https://github.com/stellar/basic-payment-app/blob/main/src/routes/dashboard/transfers/components/TransferModalSep6.svelte
Launching the SEP-6 modal
The above buttons will use the launchTransferModalSep6
function to display the modal to the user. Here's how it's defined in that same file.
<script>
/* This <script> tag has been abbreviated for simplicity */
// We import any Svelte components we will need
import TransferModalSep6 from "./components/TransferModalSep6.svelte";
// Launch the SEP-6 modal to begin the transfer process and gather information from the user.
const launchTransferModalSep6 = ({
homeDomain,
assetCode,
assetIssuer,
endpoint,
sep6Info,
}) => {
// Open the SEP-6 transfer modal, supplying the relevant props for our
// desired type of transfer.
open(TransferModalSep6, {
homeDomain: homeDomain,
assetIssuer: assetIssuer,
transferData: {
endpoint: endpoint,
},
formData: {
account: data.publicKey,
asset_code: assetCode,
},
sep6Info: sep6Info,
// This `submitPayment` function is described later on.
submitPayment: submitPayment,
});
};
</script>
<!-- HTML has been omitted from this tutorial. Please check the source file -->
Source: https://github.com/stellar/basic-payment-app/blob/main/src/routes/dashboard/transfers/+page.svelte
Once launched, the TransferModalSep6
will walk the user through a "wizard" to gather all the required information and ultimately create the transfer.
Modal step 1: Transfer details
BasicPay prompts the user to input additional information such as transfer type, destination, and amount. Some of this is prepopulated based on which button the user clicked. However, the user can change any of the fields if they so choose.
We'll spare the code sample in this section, since it's mostly Svelte things going on. You can view the source here: https://github.com/stellar/basic-payment-app/blob/main/src/routes/dashboard/transfers/components/TransferDetails.svelte
Modal step 2: Gather KYC information
To find out what infrastructure the anchor has made available for us to use, we need to query the anchor's SEP-1 stellar.toml
file for the KYC_SERVER
field. If this is not defined, BasicPay will fallback to using the TRANSFER_SERVER
for these requests.
// Fetches and returns the endpoint used for SEP-12 KYC interactions.
export async function getKycServer(domain) {
let { KYC_SERVER, TRANSFER_SERVER } = await fetchStellarToml(domain);
// If `KYC_SERVER` is undefined in the domain's TOML file, `TRANSFER_SERVER`
// will be used
return KYC_SERVER ?? TRANSFER_SERVER;
}
Source: https://github.com/stellar/basic-payment-app/blob/main/src/lib/stellar/sep1.js
Our SEP-6 modal then queries the anchor’s SEP-12 endpoint for the required KYC fields with a GET
request, and we present these fields for the user to complete.
import { getKycServer } from "$lib/stellar/sep1";
// Sends a `GET` request to query KYC status for a customer, returns current status of KYC submission
export async function getSep12Fields({ authToken, homeDomain }) {
let kycServer = await getKycServer(homeDomain);
let res = await fetch(`${kycServer}/customer`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${authToken}`,
},
});
let json = await res.json();
return json;
}
Source: https://github.com/stellar/basic-payment-app/blob/main/src/lib/stellar/sep12.js
Again, the presentation of the fields the user must complete is more on the Svelte side of things, so we won't share those details here. However, the source for this component is available here: https://github.com/stellar/basic-payment-app/blob/main/src/routes/dashboard/transfers/components/KYCInformation.svelte
Modal step 3: Put KYC fields and report status
Now that the user has provided the necessary information for the KYC requirements of the anchor, we can submit them to the anchor's KYC server with a PUT
request.
// Sends a `PUT` request to the KYC server, submitting the supplied fields for the customer's record.
export async function putSep12Fields({ authToken, fields, homeDomain }) {
let kycServer = await getKycServer(homeDomain);
let res = await fetch(`${kycServer}/customer`, {
method: "PUT",
mode: "cors",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${authToken}`,
},
body: JSON.stringify(fields),
});
let json = await res.json();
return json;
}
Source: https://github.com/stellar/basic-payment-app/blob/main/src/lib/stellar/sep12.js
BasicPay receives back from the anchor a status message for the user to see. Once the status message is ACCEPTED
, we can finally submit the actual transfer request!
This component of the SEP-6 modal, like most of them, is almost entirely Svelte-related. So as to keep this tutorial (somewhat) uncluttered, we'll refer you to the source for the component, which you can find here: https://github.com/stellar/basic-payment-app/blob/main/src/routes/dashboard/transfers/components/KYCStatus.svelte
Modal step 4: Submit transfer
BasicPay makes this request by taking all the fields that have been collected during this process and wrapping them into a URL that contains query parameters: example
We submit a GET
request to the URL with our authorization token in the headers, and the anchor takes it from there!
// Initiates a transfer using the SEP-6 protocol.
export async function initiateTransfer6({
authToken,
endpoint,
formData,
domain,
}) {
let transferServer = await getTransferServerSep6(domain);
let searchParams = new URLSearchParams(formData);
let res = await fetch(`${transferServer}/${endpoint}?${searchParams}`, {
method: "GET",
mode: "cors",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${authToken}`,
},
});
let json = await res.json();
if (!res.ok) {
throw error(res.status, {
message: json.error,
});
} else {
return json;
}
}
Source: https://github.com/stellar/basic-payment-app/blob/main/src/lib/stellar/sep12.js
We then store the details of the transfer in the transfersStore
store and display the transfer server's response to the user. We then wait for them to close the modal.
The only reason we're storing anything about the transfer in BasicPay is to help us keep track of which anchors the user has initiated transfers with. Otherwise, we wouldn't be able query for a transfer history (like on the /dashboard
page).
(Sometimes) Modal step 5: Send a Stellar payment
In a withdrawal transaction, BasicPay will also build and present to the user a Stellar transaction for them to sign with their pincode. Here we will finally get to move back to our "regular" modal that is so good at so many things!
<script>
/* This <script> tag has been abbreviated for simplicity */
// Define some component variables that will be used throughout the page
let paymentXDR = "";
let paymentNetwork = "";
// After a withdraw transaction has been presented to the user, and they've confirmed with the correct pincode, sign and submit the transaction to the Stellar network.
const onPaymentConfirm = async (pincode) => {
// Use the walletStore to sign the transaction
let signedTransaction = await walletStore.sign({
transactionXDR: paymentXDR,
network: paymentNetwork,
pincode: pincode,
});
// Submit the transaction to the Stellar network
await submit(signedTransaction);
};
// Builds a Stellar payment to present to the user which will complete a transfer to the Anchor.
let submitPayment = async ({
withdrawDetails,
assetCode,
assetIssuer,
amount,
}) => {
let { transaction, network_passphrase } = await createPaymentTransaction({
source: data.publicKey,
destination: withdrawDetails.account_id,
asset: `${assetCode}:${assetIssuer}`,
amount: amount,
memo: withdrawDetails.memo
? Buffer.from(withdrawDetails.memo, "base64")
: undefined,
});
// Set the component variables to hold the transaction details
paymentXDR = transaction;
paymentNetwork = network_passphrase;
// We close the SEP-6 modal, and open the regular confirmation modal
close();
open(ConfirmationModal, {
transactionXDR: paymentXDR,
transactionNetwork: paymentNetwork,
onConfirm: onPaymentConfirm,
});
};
/* ... */
</script>
<!-- HTML has been omitted from this tutorial. Please check the source file -->
Source: https://github.com/stellar/basic-payment-app/blob/main/src/routes/dashboard/transfers/+page.svelte