diff --git a/docs/building-apps/wallet/component/dart/configClient.mdx b/docs/building-apps/wallet/component/dart/configClient.mdx new file mode 100644 index 000000000..17963fc43 --- /dev/null +++ b/docs/building-apps/wallet/component/dart/configClient.mdx @@ -0,0 +1,27 @@ +import { CodeExample } from "@site/src/components/CodeExample"; + +### Configuring the Client + +The Flutter Wallet SDK uses the standard Client from the [http package](https://pub.dev/packages/http) for all network requests (excluding Horizon, where the Flutter Stellar SDK's HTTP client is used). + +Optionally, you can set your own client from [http package](https://pub.dev/packages/http) to be used across the app. + +The client can be globally configured: + + + +```dart +import 'package:http/http.dart'; +// ... + +// init and configure your HTTP client +// var myClient = ... + +// set as default HTTP client +var appConfig = ApplicationConfiguration(defaultClient: myClient); +var walletCustomClient = Wallet(StellarConfiguration.testNet, applicationConfiguration: appConfig); +``` + + + +Some [test cases](https://github.com/Soneso/stellar_wallet_flutter_sdk/tree/main/test) of this SDK use for example the `MockClient`. diff --git a/docs/building-apps/wallet/component/dart/install.mdx b/docs/building-apps/wallet/component/dart/install.mdx index 44f98df16..fbe2c97df 100644 --- a/docs/building-apps/wallet/component/dart/install.mdx +++ b/docs/building-apps/wallet/component/dart/install.mdx @@ -4,8 +4,8 @@ import { CodeExample } from "@site/src/components/CodeExample"; ```dart // pubspec.yaml -stellar_wallet_flutter_sdk: ^0.0.2 -stellar_flutter_sdk: ^1.6.9 +stellar_wallet_flutter_sdk: ^0.1.0 +stellar_flutter_sdk: ^1.7.1 ``` diff --git a/docs/building-apps/wallet/intro.mdx b/docs/building-apps/wallet/intro.mdx index b0d4044c0..abc2a432e 100644 --- a/docs/building-apps/wallet/intro.mdx +++ b/docs/building-apps/wallet/intro.mdx @@ -12,6 +12,7 @@ import DartInstall from "./component/dart/install.mdx"; import KtHttpConfig from "./component/kt/httpConfig.mdx"; import KtConfigClient from "./component/kt/configClient.mdx"; import TsConfigClient from "./component/ts/configClient.mdx"; +import DartConfigClient from "./component/dart/configClient.mdx";
@@ -40,7 +41,7 @@ let wallet = walletSdk.Wallet.TestNet(); ``` ```dart -var wallet = Wallet(StellarConfiguration.testNet); +var wallet = Wallet.testNet; ``` @@ -67,7 +68,11 @@ var wallet = Wallet(StellarConfiguration.publicNet); } /> -} ts={} /> +} + ts={} + dart={} +/> ## Stellar Basics @@ -85,6 +90,10 @@ val stellar = wallet.stellar() const stellar = wallet.stellar(); ``` +```dart +var stellar = wallet.stellar(); +``` + This example will create a Stellar class that manages the connection to Horizon service. diff --git a/docs/building-apps/wallet/sep30.mdx b/docs/building-apps/wallet/sep30.mdx index d06edc510..e828af4c9 100644 --- a/docs/building-apps/wallet/sep30.mdx +++ b/docs/building-apps/wallet/sep30.mdx @@ -28,6 +28,12 @@ const recoveryKp = wallet.stellar().account().createKeypair(); val recoveryKp = wallet.stellar().account().createKeyPair() ``` +```dart +var accountKp = wallet.stellar().account().createKeyPair(); +var deviceKp = wallet.stellar().account().createKeyPair(); +var recoveryKp = wallet.stellar().account().createKeyPair(); +``` + The `accountKp` is the wallet's main account. The `deviceKp` we will be adding to the wallet as a signer so a device (eg. a mobile device a wallet is hosted on) can take control of the account. And the `recoveryKp` will be used to identify the key with the recovery servers. @@ -65,6 +71,15 @@ val servers = mapOf(first to firstServer, second to secondServer) val recovery = wallet.recovery(servers) ``` +```dart +var first = RecoveryServerKey("first"); +var second = RecoveryServerKey("second"); +var firstServer = RecoveryServer("https://recovery.example1.com", "https://auth.example1.com", "recovery.example1.com"); +var secondServer = RecoveryServer("https://recovery.example2.com", "https://auth.example2.com", "recovery.example2.com"); +var servers = {first:firstServer, second:secondServer}; +var recovery = wallet.recovery(servers); +``` + Next, we need to define SEP-30 identities. In this example we are going to create an identity for both servers. Registering an identity tells the recovery server what identities are allowed to access the account. @@ -107,6 +122,19 @@ const identity2 = { ) ``` +```dart +var identity1 = [ + RecoveryAccountIdentity(RecoveryRole.owner, [ + RecoveryAccountAuthMethod(RecoveryType.stellarAddress, recoveryKp.address) + ]) +]; + +var identity2 = [ + RecoveryAccountIdentity(RecoveryRole.owner, + [RecoveryAccountAuthMethod(RecoveryType.email, "my-email@example.com")]) +]; +``` + Here, stellar key and email are used as recovery methods. Other recovery servers may support phone as a recovery method as well. @@ -141,6 +169,18 @@ const recoverableWallet = await recovery.createRecoverableWallet(config); ) ``` +```dart +var recoverableWallet = await recovery.createRecoverableWallet( + RecoverableWalletConfig( + accountKp, + deviceKp, + AccountThreshold(10, 10, 10), + {first: identity1, second: identity2}, + SignerWeight(10, 5) + ) +); +``` + With the given parameters, this function will create a transaction that will: @@ -166,6 +206,12 @@ await stellar.submitTransaction(recoverableWallet.transaction); wallet.stellar().submitTransaction(tx) ``` +```dart +var transaction = recoverableWallet.transaction; +transaction.sign(accountKp.keyPair, flutter_sdk.Network.TESTNET); +await wallet.stellar().submitTransaction(transaction); +``` + ## Get Account Info @@ -184,6 +230,11 @@ const authToken = await recovery val auth1 = recovery.sep10Auth(first).authenticate(recoveryKp) ``` +```dart +var sep10 = await recovery.sep10Auth(first); +var authToken = await sep10.authenticate(recoveryKp); +``` + Next, get account info using auth tokens: @@ -202,6 +253,10 @@ const accountResp = await recovery.getAccountInfo(accountKp, { println("Recoverable info: $accountInfo") ``` +```dart +var accountInfo = await recovery.getAccountInfo(accountKp, {first: authToken.jwt}); +``` + Our second identity uses an email as an auth method. For that we can't use a [SEP-10] auth token for that server. Instead we need to use a token that ties the email to the user. For example, Firebase tokens are a good use case for this. To use this, the recovery signer server needs to be prepared to handle these kinds of tokens. @@ -228,6 +283,10 @@ const accountResp = await recovery.getAccountInfo(accountKp, { println("Recoverable info: $accountInfo") ``` +```dart +var accountInfo = await recovery.getAccountInfo(accountKp, {second: }); +``` + ## Recover Wallet @@ -254,6 +313,14 @@ const firebaseToken = AuthToken.from() val firebaseToken = AuthToken.from() ``` +```dart +var sep10 = await recovery.sep10Auth(first); +var authToken = await sep10.authenticate(recoveryKp); + +var auth1 = authToken.jwt; +var auth2 = "..."; // get other token e.g. firebase token +``` + We need to know the recovery signer addresses that will be used to sign the transaction. You can get them from either the recoverable wallet object we created earlier (`recoverableWallet.signers`), or via fetching account info from recovery servers. @@ -269,6 +336,10 @@ const recoverySignerAddress2 = recoverableWallet.signers[1]; val recoverySigners = recoverableWallet.signers ``` +```dart +var recoverySigners = recoverableWallet.signers; +``` + Next, create a new device key and retrieve a signed transaction that replaces the device key: @@ -312,6 +383,16 @@ const recoverTxn = await recovery.replaceDeviceKey( ) ``` +```dart +var newKey = wallet.stellar().account().createKeyPair(); +var serverAuth = { + first: RecoveryServerSigning(recoverySigners[0], auth1), + second: RecoveryServerSigning(recoverySigners[1], auth2) +}; + +var signedReplaceKeyTx = await recovery.replaceDeviceKey(accountKp, newKey, serverAuth); +``` + Calling this function will create a transaction that locks the previous device key and replaces it with your new key (having the same weight as the old one). Both recovery signers will have signed the transaction. @@ -337,6 +418,10 @@ await stellar.submitTransaction(recoverTxn); wallet.stellar().submitTransaction(signedReplaceKeyTransaction) ``` +```dart +await wallet.stellar().submitTransaction(signedReplaceKeyTx); +``` + [sep-30]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0030.md diff --git a/docs/building-apps/wallet/stellar.mdx b/docs/building-apps/wallet/stellar.mdx index 5458239fc..24b891d16 100644 --- a/docs/building-apps/wallet/stellar.mdx +++ b/docs/building-apps/wallet/stellar.mdx @@ -26,6 +26,10 @@ val account = wallet.stellar().account() let account = wal.stellar().account(); ``` +```dart +var account = wallet.stellar().account(); +``` + Now we can create a keypair: @@ -40,6 +44,10 @@ val accountKeyPair = account.createKeyPair() let accountKeyPair = account.createKeypair(); ``` +```dart +var accountKeyPair = account.createKeyPair(); +``` + } /> @@ -71,6 +79,11 @@ const txBuilder = await stellar.transaction({ const tx = txBuilder.createAccount(destinationAccountKeyPair).build(); ``` +```dart +var txBuilder = await stellar.transaction(sourceAccountKeyPair); +var tx = txBuilder.createAccount(destinationAccountKeyPair).build(); +``` + #### Modify Account @@ -93,6 +106,11 @@ const txBuilder = await stellar.transaction({ const tx = txBuilder.lockAccountMasterKey().build(); ``` +```dart +var txBuilder = await stellar.transaction(sourceAccountKeyPair); +var tx = txBuilder.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. @@ -113,6 +131,13 @@ const newSignerKeyPair = account.createKeypair(); const tx = txBuilder.addAccountSigner(newSignerKeyPair, 10).build(); ``` +```dart +var newSignerKeyPair = account.createKeyPair(); + +var txBuilder = await stellar.transaction(sourceAccountKeyPair); +var tx = txBuilder.addAccountSigner(newSignerKeyPair, 10).build(); +``` + Remove a signer from the account. @@ -129,6 +154,11 @@ suspend fun removeSigner(): Transaction { const tx = txBuilder.removeAccountSigner(newSignerKeyPair).build(); ``` +```dart +var txBuilder = await stellar.transaction(sourceAccountKeyPair); +var tx = txBuilder.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. @@ -145,6 +175,11 @@ suspend fun setThreshold(): Transaction { const tx = txBuilder.setThreshold({ low: 1, medium: 10, high: 30 }).build(); ``` +```dart +var txBuilder = await stellar.transaction(sourceAccountKeyPair); +var tx = txBuilder.setThreshold(low: 1, medium: 10, high: 20).build(); +``` + #### Modify Assets (Trustlines) @@ -170,6 +205,15 @@ const asset = new IssuedAssetId( const tx = txBuilder.addAssetSupport(asset).build(); ``` +```dart +var asset = IssuedAssetId( + code: "USDC", + issuer: "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"); + +var txBuilder = await stellar.transaction(sourceAccountKeyPair); +var tx = txBuilder.addAssetSupport(asset).build(); +``` + Remove an asset from the account (the asset's balance must be 0). @@ -186,6 +230,11 @@ suspend fun removeAsset(): Transaction { const tx = txBuilder.removeAssetSupport(asset).build(); ``` +```dart +var txBuilder = await stellar.transaction(sourceAccountKeyPair); +var tx = txBuilder.removeAssetSupport(asset).build(); +``` + ### Building Advanced Transactions @@ -206,6 +255,12 @@ const externalKeyPair = new PublicKeypair.fromPublicKey("GC5GD..."); const newKeyPair = account.createKeypair(); ``` +```dart +// Third-party key that will sponsor creating new account +var externalKeyPair = PublicKeyPair.fromAccountId("GC5GD..."); +var newKeyPair = account.createKeyPair(); +``` + First, the account must be created. @@ -222,6 +277,11 @@ suspend fun makeCreateTx(): Transaction { const createTxn = txBuilder.createAccount(newKeyPair).build(); ``` +```dart +var txBuilder = await stellar.transaction(externalKeyPair); +var createTxn = txBuilder.createAccount(newKeyPair).build(); +``` + This transaction must be sent to external signer (holder of `externalKeyPair`) to be signed. @@ -248,6 +308,16 @@ const xdrStringFromBackend = await sendTransactionToBackend(xdrString); const signedTransaction = stellar.decodeTransaction(xdrStringFromBackend); ``` +```dart +var xdrString = createTxn.toEnvelopeXdrBase64(); + +// Send xdr encoded transaction to your backend server to sign +var xdrStringFromBackend = await sendTransactionToBackend(xdrString); + +// Decode xdr to get the signed transaction +var signedTransaction = stellar.decodeTransaction(xdrStringFromBackend); +``` + :::note @@ -271,6 +341,10 @@ suspend fun submitCreateTx(signedCreateTx: Transaction) { await wallet.stellar().submitTransaction(signedTransaction); ``` +```dart +bool success = await stellar.submitTransaction(signedTransaction); +``` + 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: @@ -309,6 +383,20 @@ newKeyPair.sign(modifyAccountTransaction); await wallet.stellar().submitTransaction(modifyAccountTransaction); ``` +```dart +var deviceKeyPair = account.createKeyPair(); + +var txBuilder = await stellar.transaction(newKeyPair); +var modifyAccountTransaction = txBuilder + .addAccountSigner(deviceKeyPair, 1) + .lockAccountMasterKey() + .build(); + +stellar.sign(modifyAccountTransaction, newKeyPair); + +bool success = await stellar.submitTransaction(modifyAccountTransaction); +``` + ### Sponsoring Transactions @@ -345,6 +433,16 @@ sponsoredKeyPair.sign(transaction); sponsorKeyPair.sign(transaction); ``` +```dart +var txBuilder = await stellar.transaction(sponsoredKeyPair); +var transaction = txBuilder + .sponsoring(sponsorKeyPair, (builder) => builder.addAssetSupport(asset)) + .build(); + +stellar.sign(transaction, sponsorKeyPair); +stellar.sign(transaction, sponsoredKeyPair); +``` + :::info @@ -387,6 +485,19 @@ newKeyPair.sign(transaction); sponsorKeyPair.sign(transaction); ``` +```dart +var newKeyPair = account.createKeyPair(); + +var txBuilder = await stellar.transaction(sponsorKeyPair); +var transaction = txBuilder + .sponsoring(sponsorKeyPair, sponsoredAccount: newKeyPair, + (builder) => builder.createAccount(newKeyPair)) + .build(); + +stellar.sign(transaction, sponsorKeyPair); +stellar.sign(transaction, newKeyPair); +``` + Note how in the first example the transaction source account is set to `sponsoredKeyPair`. Due to this, we did not need to pass a sponsored account value to the `sponsoring` block} ts={method} />. Since when ommitted, the sponsored account defaults to the transaction source account (`sponsoredKeyPair`). @@ -440,6 +551,25 @@ newKeyPair.sign(transaction); sponsorKeyPair.sign(transaction); ``` +```dart +var newKeyPair = account.createKeyPair(); +var replaceWith = account.createKeyPair(); + +var txBuilder = await stellar.transaction(sponsorKeyPair); +var transaction = txBuilder + .sponsoring( + sponsorKeyPair, + sponsoredAccount: newKeyPair, + (builder) => builder + .createAccount(newKeyPair) + .addAccountSigner(replaceWith, 1) + .lockAccountMasterKey()) + .build(); + +stellar.sign(transaction, sponsorKeyPair); +stellar.sign(transaction, newKeyPair); +``` + ### Fee-Bump Transaction @@ -478,6 +608,16 @@ const transaction = txBuilder .build(); ``` +```dart +var replaceWith = account.createKeyPair(); + +var txBuilder = await stellar.transaction(sponsoredKeyPair); +var transaction = txBuilder + .sponsoring(sponsorKeyPair, + (builder) => builder.lockAccountMasterKey().addAccountSigner(replaceWith, 1)) + .build(); +``` + Second, sign transaction with both keys. @@ -493,6 +633,11 @@ sponsorKeyPair.sign(transaction); sponsoredKeyPair.sign(transaction); ``` +```dart +stellar.sign(transaction, sponsorKeyPair); +stellar.sign(transaction, sponsoredKeyPair); +``` + Next, create a fee bump, targeting the transaction. @@ -512,6 +657,11 @@ const feeBump = stellar.makeFeeBump({ sponsorKeyPair.sign(feeBump); ``` +```dart +var feeBump = stellar.makeFeeBump(sponsorKeyPair, transaction); +stellar.sign(feeBump, 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. @@ -526,6 +676,10 @@ wallet.stellar().submitTransaction(feeBump) await wallet.stellar().submitTransaction(feeBump); ``` +```dart +bool success = await stellar.submitTransaction(feeBump); +``` + ### Using XDR to Send Transaction Data @@ -542,6 +696,10 @@ val sponsorKeyPair = "SponsorAddress".toPublicKeyPair() const sponsorKeyPair = new PublicKeypair.fromPublicKey("GC5GD..."); ``` +```dart +var sponsorKeyPair = PublicKeyPair.fromAccountId("GC5GD..."); +``` + Next, create an account in the same manner as before and sign it with `newKeyPair`. This time, convert the transaction to XDR: @@ -573,6 +731,21 @@ const transaction = txBuilder const xdrString = newKeyPair.sign(transaction).toXDR(); ``` +```dart +var newKeyPair = account.createKeyPair(); + +var txBuilder = await stellar.transaction(sponsorKeyPair); +var transaction = txBuilder + .sponsoring( + sponsorKeyPair, (builder) => builder.createAccount(newKeyPair), + sponsoredAccount: newKeyPair) + .build(); + +stellar.sign(transaction, newKeyPair); + +var xdrString = transaction.toEnvelopeXdrBase64(); +``` + It can now be sent to the server. On the server, sign it with a private key for the sponsor address: @@ -601,6 +774,17 @@ const signedTransaction = sponsorPrivateKey.sign( return signedTransaction.toXDR(); ``` +```dart +String signTransaction(String xdrString) { + var sponsorPrivateKey = SigningKeyPair.fromSecret("SD3LH4..."); + + var transaction = stellar.decodeTransaction(xdrString); + stellar.sign(transaction, sponsorPrivateKey); + + return transaction.toEnvelopeXdrBase64(); +} +``` + When the client receives the fully signed transaction, it can be decoded and sent to the Stellar network: @@ -622,6 +806,12 @@ const signedTransaction = stellar.decodeTransaction(xdrString); await wallet.stellar().submitTransaction(signedTransaction); ``` +```dart +var signedTransaction = stellar.decodeTransaction(xdrStringFromBackend); + +bool success = await stellar.submitTransaction(signedTransaction); +``` + ## Submit Transaction @@ -650,6 +840,11 @@ const signedTxn = transaction.sign(sourceAccountKeyPair); await wallet.stellar().submitTransaction(signedTxn); ``` +```dart +stellar.sign(transaction, sourceAccountKeyPair); +bool success = await stellar.submitTransaction(transaction); +``` + 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. @@ -678,6 +873,16 @@ await stellar.submitWithFeeIncrease({ }); ``` +```dart +bool success = await stellar.submitWithFeeIncrease( + sourceAddress: sourceAccountKeyPair, + timeout: const Duration(seconds: 30), + baseFeeIncrease: 100, + maxBaseFee: 2000, + buildingFunction: (builder) => + builder.transfer(destinationAccountKeyPair.address, NativeAssetId(), "10.0")); +``` + 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. @@ -696,6 +901,10 @@ val server = wallet.stellar().server const server = wallet.stellar().server; ``` +```dart +var server = wallet.stellar().server; +``` + And you can work with Horizon Server instance: @@ -713,4 +922,8 @@ const stellarTransaction = server .call(); ``` +```dart +var transactions = await server.transactions.forAccount("accountId").execute(); +``` +