The Soneso open source stellar SDK for iOS & Mac provides APIs to build transactions and connect to Horizon.
CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:
$ gem install cocoapods
To integrate stellar SDK into your Xcode project using CocoaPods, specify it in your Podfile
:
use_frameworks!
target '<Your Target Name>' do
pod 'stellar-ios-mac-sdk', '~> 1.8.5'
end
Then, run the following command:
$ pod repo update
$ pod install
Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
You can install Carthage with Homebrew using the following command:
$ brew update
$ brew install carthage
To integrate stellar-ios-mac-sdk into your Xcode project using Carthage, specify it in your Cartfile
:
github "soneso/stellar-ios-mac-sdk" ~> 1.1.5
Run carthage update
to build the framework and drag the build stellar-ios-mac-sdk.framework
into your Xcode project.
Add the SDK project as a subproject, and having the SDK as a target dependencies. Here is a step by step that we recommend:
- Clone this repo (as a submodule or in a different directory, it's up to you);
- Drag
stellarsdk.xcodeproj
as a subproject; - In your main
.xcodeproj
file, select the desired target(s); - Go to Build Phases, expand Target Dependencies, and add
stellarsdk
for iOS andstellarsdk-macOS
for OSX; - In Swift,
import stellarsdk
and you are good to go!
// create a completely new and unique pair of keys.
let keyPair = try! KeyPair.generateRandomKeyPair()
print("Account Id: " + keyPair.accountId)
// GCFXHS4GXL6BVUCXBWXGTITROWLVYXQKQLF4YH5O5JT3YZXCYPAFBJZB
print("Secret Seed: " + keyPair.secretSeed)
// SAV76USXIJOBMEQXPANUOQM6F5LIOTLPDIDVRJBFFE2MDJXG24TAPUU7
The Stellar Ecosystem Proposal SEP-005 Key Derivation Methods for Stellar Accounts describes methods for key derivation for Stellar. This improves key storage and moving keys between wallets and apps.
Generate mnemonic
let mnemonic = Wallet.generate24WordMnemonic()
print("generated 24 words mnemonic: \(mnemonic)")
// bench hurt jump file august wise shallow faculty impulse spring exact slush thunder author capable act festival slice deposit sauce coconut afford frown better
Generate key pairs
let keyPair0 = try! Wallet.createKeyPair(mnemonic: mnemonic, passphrase: nil, index: 0)
let keyPair1 = try! Wallet.createKeyPair(mnemonic: mnemonic, passphrase: nil, index: 1)
print("key pair 0 accountId: \(keyPair0.accountId)")
// key pair 0 accountId: GC3MMSXBWHL6CPOAVERSJITX7BH76YU252WGLUOM5CJX3E7UCYZBTPJQ
print("key pair 0 secretSeed: \(keyPair0.secretSeed!)")
// key pair 0 secretSeed: SAEWIVK3VLNEJ3WEJRZXQGDAS5NVG2BYSYDFRSH4GKVTS5RXNVED5AX7
Generate key pairs with passphrase
let keyPair0 = try! Wallet.createKeyPair(mnemonic: mnemonic, passphrase: "p4ssphr4se", index: 0)
let keyPair1 = try! Wallet.createKeyPair(mnemonic: mnemonic, passphrase: "p4ssphr4se", index: 0)
BIP and master key generation
let bip39Seed = Mnemonic.createSeed(mnemonic: mnemonic)
let masterPrivateKey = Ed25519Derivation(seed: bip39Seed)
let purpose = masterPrivateKey.derived(at: 44)
let coinType = purpose.derived(at: 148)
let account0 = coinType.derived(at: 0)
let keyPair0 = try! KeyPair.init(seed: Seed(bytes: account0.raw.bytes))
let account1 = coinType.derived(at: 1)
let keyPair1 = try! KeyPair.init(seed: Seed(bytes: account1.raw.bytes))
After the key pair generation, you have already got the address, but it is not activated until someone transfers at least 1 lumen into it.
If you want to play in the Stellar test network, the sdk can ask Friendbot to create an account for you as shown below:
// To create a test account, sdk.accounts.createTestAccount will send Friendbot the public key you created
sdk.accounts.createTestAccount(accountId: keyPair.accountId) { (response) -> (Void) in
switch response {
case .success(let details):
print(details)
case .failure(let error):
print(error.localizedDescription)
}
}
On the other hand, if you would like to create an account in the public net, you should buy some Stellar Lumens from an exchange. When you withdraw the Lumens into your new account, the exchange will automatically create the account for you. However, if you want to create an account from another account of your own, you may run the following code:
// build the operation
let createAccount = CreateAccountOperation(sourceAccount: nil,
destination: destinationKeyPair,
startBalance: 2.0)
// build the transaction
let transaction = try Transaction(sourceAccount: accountResponse,
operations: [createAccount],
memo: Memo.none,
timeBounds:nil)
// sign the transaction
try transaction.sign(keyPair: sourceAccountKeyPair, network: Network.testnet)
// submit the transaction
try sdk.transactions.submitTransaction(transaction: transaction) { (response) -> (Void) in
switch response {
case .success(_):
//...
case .failure(let error):
// ...
}
}
After creating the account, we may check the basic information of the account.
sdk.accounts.getAccountDetails(accountId: keyPair.accountId) { (response) -> (Void) in
switch response {
case .success(let accountDetails):
// You can check the `balance`, `sequence`, `flags`, `signers`, `data` etc.
for balance in accountDetails.balances {
switch balance.assetType {
case AssetTypeAsString.NATIVE:
print("balance: \(balance.balance) XLM")
default:
print("balance: \(balance.balance) \(balance.assetCode!) issuer: \(balance.assetIssuer!)")
}
}
print("sequence number: \(accountDetails.sequenceNumber)")
for signer in accountDetails.signers {
print("signer public key: \(signer.publicKey)")
}
print("auth required: \(accountDetails.flags.authRequired)")
print("auth revocable: \(accountDetails.flags.authRevocable)")
for (key, value) in accountDetails.data {
print("data key: \(key) value: \(value.base64Decoded() ?? "")")
}
case .failure(let error):
print(error.localizedDescription)
}
}
You can check the most recent payments by:
sdk.payments.getPayments(order:Order.descending, limit:10) { response in
switch response {
case .success(let paymentsResponse):
for payment in paymentsResponse.records {
if let nextPayment = payment as? PaymentOperationResponse {
if (nextPayment.assetType == AssetTypeAsString.NATIVE) {
print("received: \(nextPayment.amount) lumen" )
} else {
print("received: \(nextPayment.amount) \(nextPayment.assetCode!)" )
}
print("from: \(nextPayment.from)" )
}
else if let nextPayment = payment as? AccountCreatedOperationResponse {
//...
}
}
case .failure(let error):
print(error.localizedDescription)
}
}
You can use the parameters:limit
, order
, and cursor
to customize the query. You can also get most recent payments for accounts, ledgers and transactions.
Horizon has SSE support for push data. You can use it like this:
first define your stream item somwhere to be able to hold the reference:
var streamItem:OperationsStreamItem? = nil
then create, assign and use it:
streamItem = sdk.payments.stream(for: .paymentsForAccount(account: destinationAccountKeyPair.accountId, cursor: nil))
streamItem?.onReceive { (response) -> (Void) in
switch response {
case .open:
break
case .response(let id, let operationResponse):
if let paymentResponse = operationResponse as? PaymentOperationResponse {
switch paymentResponse.assetType {
case AssetTypeAsString.NATIVE:
print("Payment of \(paymentResponse.amount) XLM from \(paymentResponse.sourceAccount) received - id \(id)" )
default:
print("Payment of \(paymentResponse.amount) \(paymentResponse.assetCode!) from \(paymentResponse.sourceAccount) received - id \(id)" )
}
}
case .error(let err):
print(err?.localizedDescription ?? "Error")
}
}
later you can close the stream item:
streamItem.close()
Just like payments, you you check assets
, transactions
, effects
, offers
, operations
, ledgers
etc. by:
sdk.assets.getAssets()
sdk.transactions.getTransactions()
sdk.effects.getEffects()
sdk.offers.getOffers()
sdk.operations.getOperations()
// add so on ...
Example "send payment":
// create the payment operation
let paymentOperation = PaymentOperation(sourceAccount: sourceAccountKeyPair,
destination: destinationAccountKeyPair,
asset: Asset(type: AssetType.ASSET_TYPE_NATIVE)!,
amount: 1.5)
// create the transaction containing the payment operation
let transaction = try Transaction(sourceAccount: accountResponse,
operations: [paymentOperation],
memo: Memo.none,
timeBounds:nil)
// sign the transaction
try transaction.sign(keyPair: sourceAccountKeyPair, network: Network.testnet)
// submit the transaction
try sdk.transactions.submitTransaction(transaction: transaction) { (response) -> (Void) in
switch response {
case .success(_):
// ...
case .failure(_):
// ...
}
}
Get a transaction envelope from an XDR string:
let xdrString = "AAAAAJ/Ax+axve53/7sXfQY0fI6jzBeHEcPl0Vsg1C2tqyRbAAAAZAAAAAAAAAAAAAAAAQAAAABb2L/OAAAAAFvYwPoAAAAAAAAAAQAAAAEAAAAAo7FW8r8Nj+SMwPPeAoL4aUkLob7QU68+9Y8CAia5k78AAAAKAAAAN0NJcDhiSHdnU2hUR042ZDE3bjg1ZlFGRVBKdmNtNFhnSWhVVFBuUUF4cUtORVd4V3JYIGF1dGgAAAAAAQAAAEDh/7kQjZbcXypISjto5NtGLuaDGrfL/F08apZQYp38JNMNQ9p/e1Fy0z23WOg/Ic+e91+hgbdTude6+1+i0V41AAAAAAAAAAGtqyRbAAAAQNeY1rEwPynWnVXaaE/XWeuRnOHS/479J+Eu7s5OplSlF41xB7E8u9WzEItaOs167xuOVcLZUKBCBF1fnfzMEQg="
do {
let envelope = try TransactionEnvelopeXDR(xdr:xdrString)
let envelopeString = envelope.xdrEncoded
} catch {
print("Invalid xdr string")
}
Get a transaction object from an XDR string:
let xdrString = "AAAAAJ/Ax+axve53/7sXfQY0fI6jzBeHEcPl0Vsg1C2tqyRbAAAAZAAAAAAAAAAAAAAAAQAAAABb2L/OAAAAAFvYwPoAAAAAAAAAAQAAAAEAAAAAo7FW8r8Nj+SMwPPeAoL4aUkLob7QU68+9Y8CAia5k78AAAAKAAAAN0NJcDhiSHdnU2hUR042ZDE3bjg1ZlFGRVBKdmNtNFhnSWhVVFBuUUF4cUtORVd4V3JYIGF1dGgAAAAAAQAAAEDh/7kQjZbcXypISjto5NtGLuaDGrfL/F08apZQYp38JNMNQ9p/e1Fy0z23WOg/Ic+e91+hgbdTude6+1+i0V41AAAAAA=="
do {
// Get the transaction object
let transaction = try Transaction(xdr:xdrString)
// Convert your transaction back to xdr
let transactionString = transaction.xdrEncoded
} catch {
print("Invalid xdr string")
}
The Stellar federation protocol defined in SEP-002 maps Stellar addresses to more information about a given user. It’s a way for Stellar client software to resolve email-like addresses such as:
name*yourdomain.com
into account IDs like:
GCCVPYFOHY7ZB7557JKENAX62LUAPLMGIWNZJAFV2MITK6T32V37KEJU
Stellar addresses provide an easy way for users to share payment details by using a syntax that interoperates across different domains and providers.
Get the federation of your domain:
Federation.forDomain(domain: "https://YOUR_DOMAIN") { (response) -> (Void) in
switch response {
case .success(let federation):
//use the federation object to map your infos
case .failure(_):
//something went wrong
}
}
Resolve your addresses:
let federation = Federation(federationAddress: "https://YOUR_FEDERATION_SERVER")
federation.resolve(address: "bob*YOUR_DOMAIN") { (response) -> (Void) in
switch response {
case .success(let federationResponse):
if let accountId = federationResponse.accountId {
// use the account id
} else {
// there is no account id corresponding to the given address
}
case .failure(_):
// something went wrong
}
}
The Stellar Ecosystem Proposal SEP-006 defines the standard way for anchors and wallets to interact on behalf of users. This improves user experience by allowing wallets and other clients to interact with anchors directly without the user needing to leave the wallet to go to the anchor's site.
This protocol requires anchors to implement endpoints on their TRANSFER_SERVER. An anchor must define the location of their transfer server in their stellar.toml. This is how a wallet knows where to find the anchor's server.
Example: TRANSFER_SERVER="https://api.example.com"
Get the TransferServerService for your domain:
TransferServerService.forDomain(domain: "https://YOUR_DOMAIN") { (response) -> (Void) in
switch response {
case .success(let transferServerService):
// use the transferServerService object to call other operations
case .failure(_):
// something went wrong
}
}
A deposit is when a user sends an external token (BTC via Bitcoin, USD via bank transfer, etc...) to an address held by an anchor. In turn, the anchor sends an equal amount of tokens on the Stellar network (minus fees) to the user's Stellar account. The deposit endpoint allows a wallet to get deposit information from an anchor, so a user has all the information needed to initiate a deposit. It also lets the anchor specify additional information (if desired) that the user must submit via the /customer endpoint to be able to deposit.
let request = DepositRequest(assetCode: "BTC", account: "GAK7I2E6PVBFF27NU5MRY6UXGDWAJT4PF2AH46NUWLFJFFVLOZIEIO4Q")
transferServerService.deposit(request: request) { (response) -> (Void) in
switch response {
case .success(let response):
// deposit was sent with success
case .failure(_):
// something went wrong
}
}
This operation allows a user to redeem an asset currently on the Stellar network for the real asset (BTC, USD, stock, etc...) via the anchor of the Stellar asset. The withdraw endpoint allows a wallet to get withdrawal information from an anchor, so a user has all the information needed to initiate a withdrawal. It also lets the anchor specify additional information (if desired) that the user must submit via the /customer endpoint to be able to withdraw.
let request = WithdrawRequest(type: "crypto", assetCode: "BTC", dest: "GAK7I2E6PVBFF27NU5MRY6UXGDWAJT4PF2AH46NUWLFJFFVLOZIEIO4Q")
transferServerService.withdraw(request: request) { (response) -> (Void) in
switch response {
case .success(let info):
// the withdraw operation completed successfully
case .failure(_):
// something went wrong
}
}
Allows an anchor to communicate basic info about what their TRANSFER_SERVER supports to wallets and clients.
transferServerService.info { (response) -> (Void) in
switch response {
case .success(let info):
// info returned successfully
case .failure(_):
// something went wrong
}
}
The transaction history endpoint helps anchors enable a better experience for users using an external wallet. With it, wallets can display the status of deposits and withdrawals while they process and a history of past transactions with the anchor. It's only for transactions that are deposits to or withdrawals from the anchor.
let request = AnchorTransactionsRequest(assetCode: "BTC", account: "GAK7I2E6PVBFF27NU5MRY6UXGDWAJT4PF2AH46NUWLFJFFVLOZIEIO4Q")
transferServerService.getTransactions(request: request) { (response) -> (Void) in
switch response {
case .success(let transactions):
// the past transactions returned successfully
case .failure(_):
// something went wrong
}
}
Delete all personal information that the anchor has stored about a given customer. [account] is the Stellar account ID (G...) of the customer to delete. This request must be authenticated (via SEP-10) as coming from the owner of the account that will be deleted.
transferServerService.deleteCustomerInfo(account: "GAK7I2E6PVBFF27NU5MRY6UXGDWAJT4PF2AH46NUWLFJFFVLOZIEIO4Q") { (response) -> (Void) in
switch response {
case .success:
// all information for the given account was deleted successfully
case .failure(_):
// something went wrong
}
}
The Stellar Ecosystem Proposal SEP-007 introduces a URI Scheme that can be used to generate a URI that will serve as a request to sign a transaction. The URI (request) will typically be signed by the user’s trusted wallet where she stores her secret key(s).
Generate a URI that will serve as a request to sign a transaction. The URI (request) will typically be signed by the user’s trusted wallet where he stores his secret key(s).
sdk.accounts.getAccountDetails(accountId: "GAK7I2E6PVBFF27NU5MRY6UXGDWAJT4PF2AH46NUWLFJFFVLOZIEIO4Q") { (response) -> (Void) in
switch response {
case .success(let data):
// create the payment operation
let paymentOperation = PaymentOperation(sourceAccount: sourceAccountKeyPair,
destination: destinationAccountKeyPair,
asset: Asset(type: AssetType.ASSET_TYPE_NATIVE)!,
amount: 1.5)
// create the transaction containing the payment operation
let transaction = try Transaction(sourceAccount: accountResponse,
operations: [paymentOperation],
memo: Memo.none,
timeBounds:nil)
// create the URIScheme object
let uriSchemeBuilder = URIScheme()
// get the URI with your transactionXDR
// more params can be added to the url, check method definition
let uriScheme = uriSchemeBuilder.getSignTransactionURI(transactionXDR: transaction.transactionXDR, callBack: "your_callback_api.com")
case .failure(let error):
//
}
}
Generate a URI that will serve as a request to pay a specific address with a specific asset, regardless of the source asset used by the payer.
let uriSchemeBuilder = URIScheme()
// more params can be added to the url, check method definition
let uriScheme = uriSchemeBuilder.getPayOperationURI(accountID: "GAK7I2E6PVBFF27NU5MRY6UXGDWAJT4PF2AH46NUWLFJFFVLOZIEIO4Q", amount: 100, assetCode: "BTC", callBack: "your_callback_api.com")
Signs a transaction from a URI and sends it to the callback url if present or to the stellar network otherwise.
uriBuilder.signTransaction(forURL: uri, signerKeyPair: keyPair, transactionConfirmation: { (transaction) -> (Bool) in
// here the transaction from the uri can be checked and confirmed if the signing should continue
return true
}) { (response) -> (Void) in
switch response {
case .success:
// the transaction was successfully signed
case .failure(error: let error):
// the transaction wasn't valid or it didn't pass the confirmation
}
This SEP defines the standard way for clients such as wallets or exchanges to create authenticated web sessions on behalf of a user who holds a Stellar account. A wallet may want to authenticate with any web service which requires a Stellar account ownership verification, for example, to upload KYC information to an anchor in an authenticated way as described in SEP-6. Stellar Web Authentication is described in SEP-0010.
Authenticate with a server and get a JWT token.
// Hold a strong reference to this to avoid being deallocated
let webAuthenticator = WebAuthenticator(authEndpoint: "https://your_api.stellar.org/auth", network: .testnet, serverSigningKey: "GBWMCCC3NHSKLAOJDBKKYW7SSH2PFTTNVFKWSGLWGDLEBKLOVP5JLBBP", serverHomeDomain: "yourserverhomedomain.com" )
if let keyPair = try? KeyPair(secretSeed: "SBAYNYLQFXVLVAHW4BXDQYNJLMDQMZ5NQDDOHVJD3PTBAUIJRNRK5LGX") {
webAuthenticator.jwtToken(forKeyPair: keyPair) { (response) -> (Void) in
switch response {
case .success(let jwtToken):
// use the token to do your calls
case .failure(let error):
// handle the error
}
}
}
}
Creates the WebAuthenticator by loading the web auth endpoint and the server signing key from the stellar.toml file of the given domain.
let webAuthenticator = WebAuthenticator.from(domain:"yourserverhomedomain.com", network: .testnet)
The Web Authenticator can now be used to get the JWT token (see: 8.1)
Txrep: human-readable low-level representation of Stellar transactions is described in SEP-0011.
For more details have a look to our Txrep examples
You can find more documentation and examples in the docs folder.
Our SDK is used by the open source LOBSTR Vault. You can find the LOBSTR Vault source code here.
Our SDK is also used by the LOBSTR Wallet.
- SEP-0001 - Stellar Toml
- SEP-0002 - Federation protocol
- SEP-0005 - Key Derivation Methods for Stellar Accounts
- SEP-0006 - Anchor/Client interoperability
- SEP-0007 - URI Scheme to facilitate delegated signing
- SEP-0009 - Standard KYC / AML fields
- SEP-0010 - Stellar Web Authentication
- SEP-0011 - Txrep
- SEP-0012 - Anchor/Client customer info transfer
Please read our Contribution Guide.
Then please sign the Contributor License Agreement.
stellar-ios-mac-sdk is licensed under an Apache-2.0 license. See the LICENSE file for details.
Send lumens to: GANSYJ64BTHYFM6BAJEWXYLVSZVSHYZFPW4DIQDWJL5BOT6O6GUE7JM5