Skip to content

This is a open source library on pure swift for Solana protocol.

License

Notifications You must be signed in to change notification settings

metaplex-foundation/Solana.Swift

Repository files navigation

⛓️ Solana.Swift

Swift MIT Licence Swift Package Manager compatible

Solana.Swift is a Swift library for signing transactions and interacting with Programs in the Solana Network.

Web3.swift supports iOS, macOS, tvOS, watchOS and Linux with Swift Package Manager. Solana.Swift was built with modularity, portability, speed and efficiency in mind.

Features

  • Sign and send transactions.
  • Key pair generation
  • RPC configuration.
  • SPM integration
  • Fewer libraries requirement (TweetNACL, Starscream, secp256k1).
  • Fully tested (53%)
  • Sockets
  • Await/Async Support
  • Bip39 seed phrase support

Requirements

  • iOS 11.0+ / macOS 10.12+ / tvOS 11.0+ / watchOS 3.0+
  • Swift 5.3+

Installation

Solana.Swift is compatible with Swift Package Manager v5 (Swift 5 and above). Simply add it to the dependencies in your Package.swift.

From Xcode, you can use Swift Package Manager to add Solana.swift to your project.

  • File > Swift Packages > Add Package Dependency
  • Add https://github.com/metaplex-foundation/Solana.Swift
  • Select "brach" with "master"
  • Select Solana

If you encounter any problem or have a question about adding the package to an Xcode project, I suggest reading the [Adding Package Dependencies to Your App guide article from Apple.

Should Look like this

dependencies: [
    .package(name: "Solana", url: "https://github.com/metaplex-foundation/Solana.Swift.git", branch: "2.0.1"),
]
targets: [
    .target(
        name: "MyProject",
        dependencies: ["Solana"]
    ),
    .testTarget(
        name: "MyProjectTests",
        dependencies: ["MyProject"])
]

Usage

Initialization

Set the NetworkingRouter and set up your environment. You can also pass your URLSession with your settings. Use this router to initialize the SDK.

let endpoint = RPCEndpoint.devnetSolana
let router = NetworkingRouter(endpoint: endpoint)
let solana = Solana(router: router)

Signers or Accounts

The library provides an Signer protocol that acts as the signer for any operation. This account allows any client to implement their Wallet architecture and storage. Keep in mind that the secretKey is not handled by the protocol that's up to the implementation.

public protocol Signer {
    var publicKey: PublicKey { get }
    func sign(serializedMessage: Data) throws -> Data
}

An example implementation can be a HotAccount. Solana.Swift comes with HotAccount which allows the creation and recovery from a standard Solana Mnemonic. This implementation does provide a secretKey object. The secretKey is held on a variable keep in mind that this might now be a secure way of permanent storage.

public struct HotAccount: Signer {
    public let phrase: [String]
    public let publicKey: PublicKey
    public let secretKey: Data
    ...
}

Create Hot Account.

let account = HotAccount()

Create Hot Account from the seed phrase.

let phrase12 = "miracle pizza supply useful steak border same again youth silver access hundred".components(separatedBy: " ")
let account12 = HotAccount(phrase: phrase12)

Create a HotAccount from bip32Deprecated("m/501'") seed phrase. Yes, we support Wallet Index and several accounts from the same Mnemonic. This is helpful for wallet creation.

let phrase24 = "hint begin crowd dolphin drive render finger above sponsor prize runway invest dizzy pony bitter trial ignore crop please industry hockey wire use side".components(separatedBy: " ")
let account24 = HotAccount(phrase: phrase24, derivablePath: DerivablePath( 
        type: .bip32Deprecated,
        walletIndex: 0,
        accountIndex: 0
    )
)

It also supports bip44, bip44Change("m/44'/501'")

Seed Phrase Generation

Solana.Swift comes with Bip39 support. Do not confuse a seed phrase with an account. The Seed Phrase is a way to construct back the Account from a set of words.

To create a new seed phrase only use Mnemonic(). It will create a 256 strength from an English Wordlist.

let phrase = Mnemonic()
let account = HotAccount(phrase: phrase)

RPC API calls

RPC requests are an application’s gateway to the Solana cluster. Solana.Swift can be configured to the default free clusters (devnet, mainnet, testnet and custom)

public static let mainnetBetaSerum = RPCEndpoint(
    url: URL(string: "https://solana-api.projectserum.com")!, 
    urlWebSocket: URL(string: "wss://solana-api.projectserum.com")!, 
    network: .mainnetBeta
)

public static let mainnetBetaSolana = RPCEndpoint(
    url: URL(string: "https://api.mainnet-beta.solana.com")!, 
    urlWebSocket: URL(string: "wss://api.mainnet-beta.solana.com")!, 
    network: .mainnetBeta
)

public static let devnetSolana = RPCEndpoint(
    url: URL(string: "https://api.devnet.solana.com")!, 
    urlWebSocket: URL(string: "wss://api.devnet.solana.com")!, 
    network: .devnet
)

public static let testnetSolana = RPCEndpoint(
    url: URL(string: "https://api.testnet.solana.com")!, 
    urlWebSocket: URL(string: "wss://api.testnet.solana.com")!, 
    network: .testnet
)

To set up a custom one set your url, urlWebSocket and network.

public static let mainnetBetaAnkr = RPCEndpoint(
    url: URL(string: "https://rpc.ankr.com/solana")!, 
    urlWebSocket: URL(string: "wss://rpc.ankr.com/solana")!,
    network: .mainnetBeta
)

To configure just set your router to the cluster endpoint you need.

let endpoint = RPCEndpoint.devnetSolana
let router = NetworkingRouter(endpoint: endpoint)
let solana = Solana(router: router)

Solana.Swift support 45 RPC API calls. This is the way we interact with the blockchain.

Gets Accounts info.

Example using await

let info: BufferInfo<AccountInfo> = try await solana.api.getAccountInfo(account: "So11111111111111111111111111111111111111112", decodedTo: AccountInfo.self)

Example using callback

solana.api.getAccountInfo(account: "So11111111111111111111111111111111111111112", decodedTo: AccountInfo.self) { result in
    // process result
}

Gets BlockCommitment

Example using await

let block = try await solana.api.getBlockCommitment(block: 82493733)

Example using callback

 solana.api.getBlockCommitment(block: 82493733) { result in
    // process result
 }

Get ProgramAccounts

Example using await

let block = try await solana.api.getProgramAccounts(publicKey: "SwaPpA9LAaLfeLi3a68M4DjnLqgtticKg6CnyNwgAC8", decodedTo: TokenSwapInfo.self)

Example using callback

 solana.api.getProgramAccounts(publicKey: "SwaPpA9LAaLfeLi3a68M4DjnLqgtticKg6CnyNwgAC8", decodedTo: TokenSwapInfo.self) { result in
    // process result
 }

Check the usage below or look through the repositories callback and Await/Async tests.

Serialization and Deserialization of accounts

One of the Key concepts of Solana is the ability to read and write. Solana is handled by writing and reading to Accounts. As you might see in the previous examples we are handling this by passing a target object to serialize. This object has to comply with BufferLayout. BufferLayout should implement how objects are serialized/deserialized.

In Metaplex we provide a custom Borsch Serialization and Deserialization library called Beet. We also provide a code generation tool for autogenerating all the annoying code from an IDL we code this library Solita.

Actions

Actions are predefined program interfaces that construct the required inputs for the most common tasks in Solana ecosystems. You can see them as a bunch of code that implements Solana tasks using RPC calls.

We support 12.

  • closeTokenAccount: Closes token account
  • getTokenWallets: get token accounts
  • createAssociatedTokenAccount: Opens associated token account
  • sendSOL: Sends SOL native token
  • createTokenAccount: Opens token account
  • sendSPLTokens: Sends tokens
  • findSPLTokenDestinationAddress: Finds the address of a token of an address
  • serializeAndSendWithFee: Serializes and signs the transaction. Then it sends it to the blockchain.
  • getMintData: Get mint data for token
  • serializeTransaction: Serializes transaction
  • getPools: Get all available pools. Very intensive
  • swap: Swaps 2 tokens from the pool.

Example

Create an account token

Using await / async

let account: (signature: String, newPubkey: String)? = try await solana.action.createTokenAccount( mintAddress: mintAddress, payer: account)

Using callback

solana.action.createTokenAccount( mintAddress: mintAddress) { result in
// process
}

Sending sol

Using await / async

let transactionId = try await solana.action.sendSOL(
    to: toPublicKey,
    from: account,
    amount: balance/10
)
let toPublicKey = "3h1zGmCwsRJnVk5BuRNMLsPaQu1y2aqXqXDWYCgrp5UG"
let transactionId = try! solana.action.sendSOL(
            to: toPublicKey,
            amount: 10
){ result in
 // process
}

More Resources

Acknowledgment

This was originally based on P2P-ORG, but currently is no longer compatible.