diff --git a/docs/anchoring-assets/overview.mdx b/docs/anchoring-assets/overview.mdx index 2a365e0df..b9ee74e84 100644 --- a/docs/anchoring-assets/overview.mdx +++ b/docs/anchoring-assets/overview.mdx @@ -13,7 +13,7 @@ Stellar has anchor services operating worldwide. View the [Anchor Directory](htt Anchors can issue their own assets on the Stellar network, or they can honor assets that already exist. To learn about issuing assets, check out the [Issue Assets section](https://developers.stellar.org/docs/category/issue-assets). -This documentation will instruct you on how to become an anchor. To understand how to integrate anchor services into your blockchain-based application, check out the [Build Apps section](https://developers.stellar.org/docs/category/deposit-and-withdraw-from-anchors). If you’re looking specifically for MoneyGram Access, see the [Integrate with MoneyGram Access tutorial](https://developers.stellar.org/docs/tutorials/moneygram-access-integration-guide). +This documentation will instruct you on how to become an anchor. To understand how to integrate anchor services into your blockchain-based application, check out the [Build Apps section](/docs/category/deposit-and-withdraw-from-anchors) and [Connect to Anchors section][/docs/category]. If you’re looking specifically for MoneyGram Access, see the [Integrate with MoneyGram Access tutorial](https://developers.stellar.org/docs/tutorials/moneygram-access-integration-guide). ## Stellar Ecosystem Proposals (SEPs) and interoperability diff --git a/docs/building-apps/wallet/_category_.json b/docs/building-apps/wallet/_category_.json new file mode 100644 index 000000000..1125fb8cc --- /dev/null +++ b/docs/building-apps/wallet/_category_.json @@ -0,0 +1,7 @@ +{ + "position": 45, + "label": "Building a Wallet", + "link": { + "type": "generated-index" + } +} diff --git a/docs/building-apps/wallet/intro.mdx b/docs/building-apps/wallet/intro.mdx new file mode 100644 index 000000000..3e73b4d26 --- /dev/null +++ b/docs/building-apps/wallet/intro.mdx @@ -0,0 +1,171 @@ +--- +title: Getting Started +sidebar_position: 20 +--- + +import { CodeExample } from "@site/src/components/CodeExample"; + +In this guide, we will use the Kotlin Wallet SDK to integrate with the Stellar blockchain and connect to anchors. + +## Installation + +First, you need to add the SDK dependency to your project. + + + +```kotlin +// gradle.kts +implementation("org.stellar:wallet-sdk:[version]") +``` + + + +You can get the latest available version on the [project GitHub page](https://github.com/stellar/kotlin-wallet-sdk). + +## Working with the SDK + +Let's start with the main class that provides all SDK functionality. It's advised to have a singleton wallet object shared across the application. Creating a wallet with a default configuration connected to Stellar's Testnet is simple: + + + +```kotlin +val wallet = Wallet(StellarConfiguration.Testnet) +``` + + + +The wallet instance can be further configured. For example, to connect to the public network: + + + +```kotlin +val walletMainnet = Wallet(StellarConfiguration(Network.PUBLIC, "https://horizon.stellar.org")) +``` + + + +There is one more available configuration for a wallet that allows it to configure internal logic of the SDK. For example, to test with local servers on an HTTP protocol, HTTP can be manually enabled. + + + +```kotlin +val walletCustom = Wallet( + StellarConfiguration.Testnet, + ApplicationConfiguration { defaultRequest { url { protocol = URLProtocol.HTTP } } } +) +``` + + + +### Configuring the Client + +The wallet SDK uses the [ktor client](https://ktor.io/docs/getting-started-ktor-client.html) for all network requests (excluding Horizon, where the Stellar SDK's HTTP client is used). Currently, the okhttp engine is configured to be used with the client. You can read more about how to configure the ktor client [here](https://ktor.io/docs/create-client.html#configure-client). + +For example, the client can be globally configured: + + + +```kotlin +val walletCustomClient = + Wallet( + StellarConfiguration.Testnet, + ApplicationConfiguration( + defaultClientConfig = { + engine { this.config { this.connectTimeout(Duration.ofSeconds(10)) } } + install(HttpRequestRetry) { + retryOnServerErrors(maxRetries = 5) + exponentialDelay() + } + } + ) + ) +``` + + + +This code will set the connect timeout to ten seconds via the [okhttp configuration](https://ktor.io/docs/http-client-engines.html#okhttp) and also installs the [retry plugin](https://ktor.io/docs/client-retry.html). You can also specify client configuration for specific wallet SDK classes. + +For example, to change connect timeout when connecting to some anchor server: + + + +```kotlin +val anchorCustomClient = + walletCustomClient.anchor("example.com") { + engine { this.config { this.connectTimeout(Duration.ofSeconds(30)) } } + } +``` + + + +### Closing Resources + +After the wallet class is no longer used, it's necessary to close all clients used by it. While in some applications it may not be required (e.g. the wallet lives for the whole lifetime of the application), in other cases it can be required. If your wallet class is short-lived, it's recommended to close client resources using a close function: + + + +```kotlin +fun closeWallet() { + wallet.close() +} +``` + + + +## Stellar Basics + +The wallet SDK provides some extra functionality on top of the existing [Horizon SDK]. For interaction with the Stellar network, the wallet SDK covers only the basics used in a typical wallet flow. For more advanced use cases, the underlying [Horizon SDK] should be used instead. + +To interact with the Horizon instance configured in the previous steps, simply do: + + + +```kotlin +val stellar = wallet.stellar() +``` + + + +This example will create a Stellar class that manages the connection to Horizon service. + +:::note + +Default configuration connects to the public Stellar Horizon instance. You can change this behavior as described [above](#working-with-the-sdk). + +::: + +You can read more about working with the Stellar network in the [respective section](./stellar.mdx). + +## Anchor Basics + +Primary use of the SDK is to provide an easy way to connect to anchors via sets of protocols known as SEPs. Let's look into connecting to the Stellar test anchor: + + + +```kotlin +val anchor = wallet.anchor("https://testanchor.stellar.org") +``` + + + +And the most basic interaction of fetching a [SEP-1]: Stellar Info File: + + + +```kotlin +suspend fun anchorToml(): TomlInfo { + return anchor.getInfo() +} +``` + + + +The anchor class also supports [SEP-10]: Stellar Authentication and [SEP-24]: Hosted Deposit and Withdrawal features. + +[sep-1]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0001.md +[sep-10]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md +[sep-12]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0012.md +[sep-24]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md +[sep-10]: ./sep10.mdx +[sep-24]: ./sep24.mdx +[horizon sdk]: /docs/tools-and-sdks#sdk-library diff --git a/docs/building-apps/wallet/overview.mdx b/docs/building-apps/wallet/overview.mdx new file mode 100644 index 000000000..8a5646c1e --- /dev/null +++ b/docs/building-apps/wallet/overview.mdx @@ -0,0 +1,42 @@ +--- +title: Overview +sidebar_position: 10 +--- + +import { CodeExample } from "@site/src/components/CodeExample"; + +## What is a Wallet? + +A wallet is an application that allows users to hold assets, such as Stellar's native asset (XLM), stablecoins (USDC), or any other asset issued on chain. Wallets can also help users exchange their assets, sometimes using entities called anchors. + +## Custodial versus Non-Custodial Wallets + +Some applications, such as centralized exchanges, are custodial, meaning the application has access to the private keys of the accounts holding its users’ funds on Stellar. Typically, custodial applications pool user funds into a smaller set of managed Stellar accounts, called pooled, shared, or omnibus accounts. + +Other applications are non-custodial, meaning the application does not have access to the private keys of the accounts holding its users' funds on Stellar. Typically, non-custodial applications create or import a pre-existing Stellar account for each user. + +These two approaches require minor but concrete differences in how applications integrate with an anchor. The sub-sections below will describe these differences, but the rest of this tutorial will assume the application is custodial. + +## What is an Anchor? + +An anchor is a Stellar-specific term for the on and off-ramps that connect the Stellar network to traditional financial rails, such as financial institutions or fintech companies. Anchors accept deposits of fiat currencies (such as the US dollar, Argentine peso, or Nigerian naira) via existing rails (such as bank deposits or cash-in points), then sends the user the equivalent digital tokens on the Stellar network. The equivalent digital tokens can either represent that same fiat currency or another digital token altogether. Alternatively, anchors allow token holders to redeem their tokens for the real-world assets they represent. + +Stellar has anchor services operating worldwide. View the [Anchor Directory](https://resources.stellar.org/anchors?) for more information on existing Stellar anchors. + +This documentation will instruct you on how to connect to the anchor as the wallet. + +## Stellar Ecosystem Proposals (SEPs) and Interoperability + +Stellar is an open-source network that is designed to interoperate with traditional financial institutions, various types of assets, and other networks. Network participants implement Stellar Ecosystem Proposals (SEPs) to ensure they can interoperate with other products and services on the network. SEPs are publicly created, open-source documents that live in a [GitHub repository](https://github.com/stellar/stellar-protocol/tree/master/ecosystem#stellar-ecosystem-proposals-seps) and they define how asset issuers, applications, exchanges, and other service providers should interact and interoperate. + +Read more about SEPs in the [SEPs section](https://developers.stellar.org/docs/fundamentals-and-concepts/stellar-ecosystem-proposals). + +As a wallet, the most important SEPs are [SEP-24]: Hosted Deposit and Withdrawal, and [SEP-31]: Cross Border Payments API. You’ll also work with [SEP-10]: Stellar Authentication, [SEP-12]: KYC API, and [SEP-38]: Anchor RFQ API. + +[sep-1]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0001.md +[sep-9]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0009.md +[sep-10]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md +[sep-12]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0012.md +[sep-24]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md +[sep-31]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0031.md +[sep-38]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0038.md diff --git a/docs/building-apps/wallet/sep10.mdx b/docs/building-apps/wallet/sep10.mdx new file mode 100644 index 000000000..0e66f1f63 --- /dev/null +++ b/docs/building-apps/wallet/sep10.mdx @@ -0,0 +1,172 @@ +--- +title: Stellar Authentication +sidebar_position: 40 +--- + +import { CodeExample } from "@site/src/components/CodeExample"; + +Wallets connect to anchors using a standard way of authentication via the Stellar network defined by the [SEP-10] standard. + +This guide will cover all ways to use SEP-10 to authenticate with an anchor. + +## Creating Authentication Key + +:::info Custodial wallets only + +::: + +First, let's create an authentication key. While you can use the same key for authentication and sending funds, it's recommended to split the responsibilities. In your application, you will have one or more fund keypairs (keypairs for the accounts that hold funds and initiate and receive transactions) and one authentication key. + +The authentication key is only used for authentication purposes and doesn't need to hold any funds. Note that you don't need to create an account for this keypair either. + +Go to the [Stellar Lab] and generate a keypair. The secret key must be handled securely, because it will be used for authentication. + +## Basic Authentication + +Let's do a basic authentication. In this example, we will use wallet SDK to create an authentication token. + +First, let's create an `anchor` object to work with the anchor you are integrating with. In this example, we will be using a reference anchor implementation with the home domain `testanchor.stellar.org` + + + +```kotlin +val anchor = wallet.anchor("https://testanchor.stellar.org") +``` + + + +Next, authenticate with the `authKey` created earlier: + + + +```kotlin +val authKey = SigningKeyPair.fromSecret("my secret key") + +suspend fun getAuthToken(): AuthToken { + return anchor.sep10().authenticate(authKey) +} +``` + + + +For non-custodial wallets, you want to use the user's private key as an `authKey`. + +## Home Domain (Optional) + +The home domain is the optional parameter for SEP-10 authentication, when a single auth server is shared between multiple domains. Some anchors may require you to provide this argument. The SDK automatically sets the `home_domain` parameter in all SEP-10 requests. + +## Client Domain (Optional) + +:::info Non-custodial wallets only + +::: + +:::caution + +Some anchors may require the `client_domain` to always be present as part of the request, even for non-custodial wallets. + +::: Client domain is used by anchors to verify the origin of user's request (which wallet this user is using?). This is particularly useful for anchors for integrating with non-custodial wallets. + +Supporting `client_domain` comes in two parts, the wallet's client and the wallet's server implementations. In this setup, we will have an extra authentication key. This key will be stored remotely on the server. Using the SEP-1 info file, the anchor will be able to query this key and verify the signature. As such, the anchor would be able to confirm that the request is coming from your wallet, belonging to wallet's `client_domain`. + +### Client Side + +First, let's implement the client side. In this example we will connect to a remote signer that signs transactions on the endpoint `https://demo-wallet-server.stellar.org/sign` for the client domain `demo-wallet-server.stellar.org`. + + + +```kotlin +val signer = WalletSigner.DomainSigner("https://demo-wallet-server.stellar.org/sign") {} + +suspend fun getAuthToken(): AuthToken { + return anchor + .sep10() + .authenticate(userKeyPair, signer, clientDomain = "demo-wallet-server.stellar.org") +} +``` + + + +:::danger + +The demo-wallet signing endpoint is not protected for anybody to use. Your production URL must be protected, otherwise anybody could impersonate your wallet's user. + +::: + +Let's add authentication with a bearer token. Simply update the request transformer: + + + +```kotlin +val signer = WalletSigner.DomainSigner("https://demo-wallet-server.stellar.org/sign") { bearerAuth("token") } +``` + + + +Finally, with the approach above we define the signer and client domain per request. If you want to define it once and use it for every authentication call your application is making, you can do so via changing the configuration: + + + +```kotlin +val appCfg = ApplicationConfiguration(WalletSigner.DomainSigner("https://my-domain.com/sign"), "my-domain.com") +``` + + + +This is particularly useful for integrating with multiple anchors. + +### Server Side + +Next, let's implement the server side. + +First, generate a new authentication key that will be used as a `client_domain` authentication key. + +Next, create a `SEP-1` toml file placed under `/.well-known/stellar.toml` with the following content: + + + +```toml +ACCOUNTS = [ "Authentication public key (address)" ] +VERSION = "0.1.0" +SIGNING_KEY = "Authentication public key (address)" +NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" +``` + + + +Don't forget to change the network passphrase for Mainnet deployment. + +Finally, let's add server implementation. This sample implementation uses express framework: + + + +```javascript +app.post("/sign", (req, res) => { + const envelope_xdr = req.body.transaction; + const network_passphrase = req.body.network_passphrase; + const transaction = new Transaction(envelope_xdr, network_passphrase); + + if (Number.parseInt(transaction.sequence, 10) !== 0) { + res.status(400); + res.send("transaction sequence value must be '0'"); + return; + } + + transaction.sign(Keypair.fromSecret(SERVER_SIGNING_KEY)); + + res.set("Access-Control-Allow-Origin", "*"); + res.status(200); + res.send({ + transaction: transaction.toEnvelope().toXDR("base64"), + network_passphrase: network_passphrase, + }); +}); +``` + + + +You can see full example [here](https://github.com/stellar/stellar-demo-wallet/blob/52071629f8d29470b61799bef9519776d9c252d2/packages/demo-wallet-server/src/index.ts). As mentioned before, this sample implementation doesn't have any protection against unauthorized requests, so you must add authorization checks as part of the request. + +[sep-1]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0001.md +[sep-10]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md +[stellar lab]: https://laboratory.stellar.org/ diff --git a/docs/building-apps/wallet/sep24.mdx b/docs/building-apps/wallet/sep24.mdx new file mode 100644 index 000000000..378224349 --- /dev/null +++ b/docs/building-apps/wallet/sep24.mdx @@ -0,0 +1,235 @@ +--- +title: Hosted Deposit and Withdrawal +sidebar_position: 50 +--- + +import { CodeExample } from "@site/src/components/CodeExample"; + +The [SEP-24] standard defines the standard way for anchors and wallets to interact on behalf of users. Wallets use this standard to facilitate exchanges between on-chain assets (such as stablecoins) and off-chain assets (such as fiat, or other network assets such as BTC). + +During the flow, a wallet makes several requests to the anchor, and finally receives an interactive URL to open in iframe. This URL is used by the user to provide an input (such as KYC) directly to the anchor. Finally, the wallet can fetch transaction information using query endpoints. + +## Get Anchor Information + +Let's start with getting an instance of `Interactive` class, responsible for all SEP-24 interactions: + + + +```kotlin +val sep24 = anchor.sep24() +``` + + + +First, let's get the information about the anchor's support for [SEP-24]. This request doesn't require authentication, and will return generic info, such as supported currencies, and features supported by the anchor. You can get a full list of returned fields in the [SEP-24 specification](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md#info). + + + +```kt +suspend fun getAnchorServices(): AnchorServiceInfo { + return sep24.getServicesInfo() +} +``` + + + +## Interactive Flows + +Before getting started, make sure you have connected to the anchor and received an authentication token, as described in the [Stellar Authentication] wallet guide. We will use the `token` object in the examples below as the [SEP-10] authentication token, obtained earlier. + +To initiate an operation, we need to know an asset. You may want to hard-code it, or get it dynamically from the anchor's info file, like shown above (for USDC): + + + +```kt +val asset = info.currencies.first { it.code == "USDC" }.assetId +``` + + + +:::info + +[//]: # "TODO: link to establish trustline guide" + +Before starting with the deposit flow, make sure that the user account has established a trustline for the asset you are working with. + +::: + +### Basic Flow + +Let's start with a basic deposit: + + + +```kt +val deposit = sep24.deposit(asset, token) +``` + + + +As a result, you will get an [interactive response](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md#deposit-and-withdraw-shared-responses) from the anchor. + +Open the received URL in an iframe and deposit the transaction ID for future reference: + + + +```kt +val url = deposit.url +val id = deposit.id +``` + + + +Similarly to the deposit flow, a basic withdrawal flow has the same method signature and repose type: + + + +```kt +val withdrawal = sep24.withdraw(asset, token) +val url = withdrawal.url +val id = withdrawal.id +``` + + + +### Providing KYC Info + +To improve the user experience, the [SEP-24] standard supports passing user KYC to the anchor via [SEP-9]. In turn, the anchor will pre-fill this information in the interactive popup. + +:::info + +While [SEP-9] supports passing binary data, the current version of the SDK doesn't offer such functionality. + +::: + +:::note + +At the time, accepted [SEP-9] is not strictly typed yet. Improved typing will be offered in future versions. + +::: + + + +```kt +val sep9 = mapOf("email_address" to "mail@example.com") + +val deposit = sep24.deposit(asset, token, sep9) +``` + + + +### Changing Stellar Transfer Account + +By default, the Stellar transfer will be sent to the authenticated account (with a memo) that initiated the deposit. + +While in most cases it's acceptable, some wallets may split their accounts. To do so, pass additional account (and optionally a memo): + + + +```kt +suspend fun depositDifferentAccount(): InteractiveFlowResponse { + val recipientAccount = "G..." + val memo = "my memo" to MemoType.TEXT + return sep24.deposit(asset, token, destinationAccount = recipientAccount, destinationMemo = memo) +} +``` + + + +Similarly, for a withdrawal, the origin account of the Stellar transaction could be changed: + + + +```kt +val originAccount = "G..." +val withdrawal = sep24.withdraw(asset, token, withdrawalAccount = originAccount) +``` + + + +## Getting Transaction Info + +On the typical flow, the wallet would get transaction data to notify users about status updates. This is done via the [SEP-24] `GET /transaction` and `GET /transactions` endpoint. + +Alternatively, some anchors support webhooks for notifications. Note that this feature is not widely adopted yet. + +### Tracking Transaction + +Let's look into how to use the wallet SDK to track transaction status changes. We will use `Watcher` class for this purpose. First, let's initialize watcher and start tracking a transaction. + + + +```kt +val watcher = sep24.watcher() +val result = watcher.watchOneTransaction(token, "transaction id") +``` + + + +Alternatively, we can track multiple transactions for the same asset. + + + +```kt +val watcher = sep24.watcher() +val result = watcher.watchAsset(getAuthToken(), asset) +``` + + + +Next, let's get the channel provided by `WatcherResult` to receive events. + + + +```kt +do { + val event = result.channel.receive() + when (event) { + is StatusChange -> + println("Status changed to ${event.status}. Transaction: ${event.transaction}") + is ExceptionHandlerExit -> println("Exception handler exited the job") + is ChannelClosed -> println("Channel closed. Job is done") + } +} while (event !is ChannelClosed) +``` + + + +This code example will consume all events coming from the channel until it's closed. There are three types of events: + +- `StatusChange`: indicates that transaction status has changed. +- `ExceptionHandlerExit`: indicates that the exception handler exited the processing loop. With default retry handler it happens when retries are exhausted. +- `ChannelClosed`: indicates that the channel is closed and no more events will be emitted. This event will always fire. If `ExceptionHandlerExit` happened, channel will close right after. Otherwise, (under normal circumstances) it will stop when all transactions reach terminal statuses. + +:::info + +Events are stored in the channel until they are received, and calling the `receive()` method will block the channel until a message is received. You can read more about how channels work in the [channel documentation].(https://kotlinlang.org/docs/coroutines-and-channels.html#channels). + +::: + +### Fetching Transaction + +While `Watcher` class offers powerful tracking capabilities, sometimes it's required to just fetch a transaction (or transactions) once. The `Anchor` class allows you to fetch a transaction by ID, Stellar transaction ID, or external transaction ID: + + + +```kt +val transaction = sep24.getTransactionBy(token, id = "transaction id") +``` + + + +It's also possible to fetch transaction by the asset + + + +```kt +val transactions = sep24.getTransactionsForAsset(asset, token) +``` + + + +[sep-9]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-000p.md +[sep-10]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md +[sep-24]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md +[stellar authentication]: ./sep10.mdx diff --git a/docs/building-apps/wallet/stellar.mdx b/docs/building-apps/wallet/stellar.mdx new file mode 100644 index 000000000..da13551e2 --- /dev/null +++ b/docs/building-apps/wallet/stellar.mdx @@ -0,0 +1,480 @@ +--- +title: Stellar Network +sidebar_position: 30 +--- + +import { CodeExample } from "@site/src/components/CodeExample"; + +In the previous section we learned how to create a wallet and a `Stellar` object that provides a connection to Horizon. In this section, we will look at the usages of this class. + +## Accounts + +The most basic entity on the Stellar network is an account. Let's look into AccountService that provides the capability to work with accounts: + + + +```kt +val account = wallet.stellar().account() +``` + + + +Now we can create a keypair: + + + +```kt +val accountKeyPair = account.createKeyPair() +``` + + + +## Build Transaction + +The transaction builder allows you to create various transactions that can be signed and submitted to the Stellar network. Some transactions can be sponsored. + +### Building Basic Transactions + +First, let's look into building basic transactions. + +#### Create Account + +The create account transaction activates/creates an account with a starting balance of XLM (1 XLM by default). + + + +```kotlin +suspend fun createAccount(): Transaction { + return stellar.transaction(sourceAccountKeyPair).createAccount(destinationAccountKeyPair).build() +} +``` + + + +#### Modify Account + +You can lock the master key of the account by setting its weight to 0. Use caution when locking the account's master key. Make sure you have set the correct signers and weights. Otherwise, you will lock the account irreversibly. + + + +```kotlin +suspend fun lockMasterKey(): Transaction { + return stellar.transaction(sourceAccountKeyPair).lockAccountMasterKey().build() +} +``` + + + +Add a new signer to the account. Use caution when adding new signers and make sure you set the correct signer weight. Otherwise, you will lock the account irreversibly. + + + +```kotlin +val newSignerKeyPair = account.createKeyPair() + +suspend fun addSigner(): Transaction { + return stellar.transaction(sourceAccountKeyPair).addAccountSigner(newSignerKeyPair, 10).build() +} +``` + + + +Remove a signer from the account. + + + +```kotlin +suspend fun removeSigner(): Transaction { + return stellar.transaction(sourceAccountKeyPair).removeAccountSigner(newSignerKeyPair).build() +} +``` + + + +Modify account thresholds (useful when multiple signers are assigned to the account). This allows you to restrict access to certain operations when the limit is not reached. + + + +```kotlin +suspend fun setThreshold(): Transaction { + return stellar.transaction(sourceAccountKeyPair).setThreshold(low = 1, medium = 10, high = 30).build() +} +``` + + + +#### Modify Assets (Trustlines) + +Add an asset (trustline) to the account. This allows the account to receive transfers of the asset. + + + +```kotlin +val asset = IssuedAssetId("USDC", "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5") + +suspend fun addAsset(): Transaction { + return stellar.transaction(sourceAccountKeyPair).addAssetSupport(asset).build() +} +``` + + + +Remove an asset from the account (the asset's balance must be 0). + + + +```kotlin +suspend fun removeAsset(): Transaction { + return stellar.transaction(sourceAccountKeyPair).removeAssetSupport(asset).build() +} +``` + + + +### Building Advanced Transactions + +In some cases a private key may not be known prior to forming a transaction. For example, a new account must be funded to exist and the wallet may not have the key for the account so may request the create account transaction to be sponsored by a third party. + + + +```kt +// Third-party key that will sponsor creating new account +val externalKeyPair = "MySponsorAddress".toPublicKeyPair() +val newKeyPair = account.createKeyPair() +``` + + + +First, the account must be created. + + + +```kotlin +suspend fun makeCreateTx(): Transaction { + return stellar.transaction(externalKeyPair).createAccount(newKeyPair).build() +} +``` + + + +This transaction must be sent to external signer (holder of `externalKeyPair`) to be signed. + + + +```kt +suspend fun remoteSignTransaction(transaction: Transaction) { + val xdrString = transaction.toEnvelopeXdrBase64() + // Send xdr encoded transaction to your backend server + val signedTransaction = sendTransactionToBackend(xdrString) + val decodedTransaction = stellar.decodeTransaction(xdrString) +} +``` + + + +:::note + +You can read more about passing XDR transaction to the server in the [chapter below](#using-xdr-to-send-transaction-data). + +::: + +Signed transaction can be submitted by the wallet. + + + +```kt +suspend fun submitCreateTx(signedCreateTx: Transaction) { + wallet.stellar().submitTransaction(signedCreateTx) +} + +``` + + + +Now, after the account is created, it can perform operations. For example, we can disable the master keypair and replace it with a new one (let's call it the device keypair) atomically in one transaction: + + + +```kotlin +suspend fun addDeviceKeyPair() { + val deviceKeyPair = account.createKeyPair() + + val modifyAccountTransaction = + stellar + .transaction(newKeyPair) + .addAccountSigner( + deviceKeyPair, + signerWeight = 1, + ) + .lockAccountMasterKey() + .build() + .sign(newKeyPair) + + wallet.stellar().submitTransaction(modifyAccountTransaction) +} +``` + + + +### Sponsoring Transactions + +#### Sponsor Operations + +Some operations, that modify account reserves can be [sponsored](https://developers.stellar.org/docs/encyclopedia/sponsored-reserves#sponsored-reserves-operations). For sponsored operations, the sponsoring account will be paying for the reserves instead of the account that being sponsored. This allows you to do some operations, even if account doesn't have enough funds to perform such operations. To sponsor a transaction, simply start a `sponsoring` block: + + + +```kotlin +suspend fun sponsorOperation() { + val transaction = + stellar + .transaction(sponsoredKeyPair) + .sponsoring(sponsorKeyPair) { addAssetSupport(asset) } + .build() + + transaction.sign(sponsorKeyPair).sign(sponsoredKeyPair) +} +``` + + + +:::info + +Only some operations can be sponsored, and a sponsoring block has a slightly different set of functions available compared to the regular `TransactionBuilder`. Note, that a transaction must be signed by both the sponsor account (`sponsoringKeyPair`) and the account being sponsored (`sponsoredKeyPair`). + +::: + +#### Sponsoring Account Creation + +One of the things that can be done via sponsoring is to create an account with a 0 starting balance. This account creation can be created by simply writing: + + + +```kt +suspend fun sponsorAccountCreation() { + val newKeyPair = account.createKeyPair() + + val transaction = + stellar + .transaction(sponsorKeyPair) + .sponsoring(sponsorKeyPair, sponsoredAccount = newKeyPair) { createAccount(newKeyPair) } + .build() + + transaction.sign(sponsorKeyPair).sign(newKeyPair) +} +``` + + + +Note how the transaction source account should be set to `sponsorKeyPair`. This time, we need to pass the sponsored account. In the example above, it was omitted and was defaulted to the transaction source account (`sponsoredKey`). + +However, this time, the sponsored account (freshly created) is different from the transaction source account. Therefore, it's necessary to specify it. Otherwise, the transaction will contain a malformed operation. As before, the transaction must be signed by both keys. + +#### Sponsoring Account Creation and Modification + +If you want to create an account and modify it in one transaction, it's possible to do so with passing a `sponsoredAccount` optional argument to the sponsored block. If this argument is present, all operations inside the sponsored block will be sourced by `sponsoredAccount`. (Except account creation, which is always sourced by the sponsor). + + + +```kotlin +suspend fun sponsorAccountCreationAndModification() { + val newKeyPair = account.createKeyPair() + val replaceWith = account.createKeyPair() + + val transaction = + stellar + .transaction(sponsorKeyPair) + .sponsoring(sponsorKeyPair, newKeyPair) { + createAccount(newKeyPair) + addAccountSigner(replaceWith, 1) + lockAccountMasterKey() + } + .build() + + transaction.sign(sponsorKeyPair).sign(newKeyPair) +} +``` + + + +### Fee-Bump Transaction + +If you wish to modify a newly created account with a 0 balance, it's also possible to do so via `FeeBump`. It can be combined with a sponsoring block to achieve the same result as in the example above. However, with `FeeBump` it's also possible to add more operations (that don't require sponsoring), such as a transfer. + +First, let's create a transaction that will replace the master key of an account with a new keypair. + + + +```kt +val replaceWith = account.createKeyPair() + +val transaction = + stellar + .transaction(sponsoredKeyPair) + .sponsoring(sponsorKeyPair) { + lockAccountMasterKey() + addAccountSigner(replaceWith, signerWeight = 1) + } + .build() + +``` + + + +Second, sign transaction with both keys. + + + +```kt +transaction.sign(sponsoredKeyPair).sign(sponsorKeyPair) +``` + + + +Next, create a fee bump, targeting the transaction. + + + +```kt +val feeBump = stellar.makeFeeBump(sponsorKeyPair, transaction) +feeBump.sign(sponsorKeyPair) +``` + + + +Finally, submit a fee-bump transaction. Executing this transaction will be fully covered by the `sponsorKeyPair` and `sponsoredKeyPair` and may not even have any XLM funds on its account. + + + +```kt +wallet.stellar().submitTransaction(feeBump) +``` + + + +### Using XDR to Send Transaction Data + +Note, that a wallet may not have a signing key for `sponsorKeyPair`. In that case, it's necessary to convert the transaction to XDR, send it to the server, containing `sponsorKey` and return the signed transaction back to the wallet. Let's use the previous example of sponsoring account creation, but this time with the sponsor key being unknown to the wallet. The first step is to define the public key of the sponsor keypair: + + + +```kt +val sponsorKeyPair = "SponsorAddress".toPublicKeyPair() +``` + + + +Next, create an account in the same manner as before and sign it with `newKeyPair`. This time, convert the transaction to XDR: + + + +```kt +suspend fun sponsorAccountCreation(): String { + val newKeyPair = account.createKeyPair() + + return stellar + .transaction(sponsorKeyPair) + .sponsoring(sponsorKeyPair) { createAccount(newKeyPair) } + .build() + .sign(newKeyPair) + .toEnvelopeXdrBase64() +} + +``` + + + +It can now be sent to the server. On the server, sign it with a private key for the sponsor address: + + + +```kt +// On the server +fun signTransaction(xdrString: String): String { + val sponsorPrivateKey = SigningKeyPair.fromSecret("MySecred") + + val signedTransaction = stellar.decodeTransaction(xdrString).sign(sponsorPrivateKey) + + return signedTransaction.toEnvelopeXdrBase64() +} +``` + + + +When the client receives the fully signed transaction, it can be decoded and sent to the Stellar network: + + + +```kt +suspend fun recoverSigned(xdrString: String) { + val signedTransaction = stellar.decodeTransaction(xdrString) + + stellar.submitTransaction(signedTransaction) +} + +``` + + + +## Submit Transaction + +:::info + +It's strongly recommended to use the wallet SDK transaction submission functions instead of Horizon alternatives. The wallet SDK gracefully handles timeout and out-of-fee exceptions. + +::: + +Finally, let's submit a signed transaction to the Stellar network. Note that a sponsored transaction must be signed by both the account and the sponsor. + +The transaction is automatically re-submitted on the Horizon 504 error (timeout), which indicates a sudden network activity increase. + + + +```kotlin +suspend fun signAndSubmit() { + val signedTxn = createAccount().sign(sourceAccountKeyPair) + wallet.stellar().submitTransaction(signedTxn) +} +``` + + + +However, the method above doesn't handle fee surge pricing in the network gracefully. If the required fee for a transaction to be included in the ledger becomes too high and transaction expires before making it into the ledger, this method will throw an exception. + +So, instead, the alternative approach is to: + + + +```kotlin +suspend fun submitWithFeeIncrease() { + wallet.stellar().submitWithFeeIncrease(sourceAccountKeyPair, Duration.ofSeconds(30), 100u) { + this.createAccount(destinationAccountKeyPair) + } +} +``` + + + +This will create and sign the transaction that originated from the `sourceAccountKeyPair`. Every 30 seconds this function will re-construct this transaction with a new fee (increased by 100 stroops), repeating signing and submitting. Once the transaction is successful, the function will return the transaction body. Note, that any other error will terminate the retry cycle and an exception will be thrown. + +## Accessing Horizon SDK + +It's very simple to use the Horizon SDK connecting to the same Horizon instance as a `Wallet` class. To do so, simply call: + + + +```kotlin +val server = wallet.stellar().server +``` + + + +And you can work with Horizon Server instance: + + + +```kt +val stellarTransaction = server.transactions().transaction("transaction_id") +``` + + diff --git a/docusaurus.config.js b/docusaurus.config.js index e2763f34f..28a8f6101 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -254,7 +254,8 @@ const config = { "toml", "json5", "python", - "docker" + "docker", + "kotlin" ], }, }), diff --git a/src/components/CodeExample.js b/src/components/CodeExample.js index 105891ee4..82e122460 100644 --- a/src/components/CodeExample.js +++ b/src/components/CodeExample.js @@ -10,6 +10,8 @@ const CODE_LANGS = { docker: 'Dockerfile', go: 'Go', html: 'html', + kotlin: 'Kotlin', + kt: 'Kotlin', java: 'Java', javascript: 'JavaScript', js: 'JavaScript',