SPB Git

spb/os-vault Public

Self-custody, multi-chain crypto wallet for macOS. One recovery phrase, six chain families, zero API keys — nothing leaves your Mac.

Swift 96% Shell 3.4% Makefile 0.6%
10.0 KB

# SolanaSwift

Solana-blockchain client, written in pure swift.

Version License Platform Documentation Status

# Breaking changes

# v5.0

...

  • Remove deprecated typealias Mint, use SPLTokenMintState or Token2022MintState instead.
  • Remove deprecated typealias Wallet, use AccountBalance instead.
  • Support token 2022 via method getAccountBalances (See GetAccountBalancesTests).
  • Support token 2022 and Token2022Program. ... See more

# Features

  • Supported swift concurrency (from 2.0.0)
  • Key pairs generation
  • Solana JSON RPC API
  • Create, sign transactions
  • Send, simulate transactions
  • Solana token list
  • Socket communication
  • OrcaSwapSwift
  • RenVMSwift

# Example

To run the example project, clone the repo, and run pod install from the Example directory first. Demo wallet: p2p-wallet

# Requirements

  • iOS 13 or later

# Dependencies

  • TweetNacl
  • secp256k1.swift

# Installation

# Cocoapods

SolanaSwift is available through CocoaPods. To install it, simply add the following line to your Podfile:

ruby
pod 'SolanaSwift', '~> 5.0.0'

# Swift package manager

swift
...
dependencies: [
    ...
    .package(url: "https://github.com/p2p-org/solana-swift", from: "5.0.0")
],
...

# How to use

# Version 2.0 update anouncement

  • From v2.0.0 we officially omited Rx library and a lot of dependencies, thus we also adopt swift concurrency to solana-swift. What have been changed?
  • For those who still use SolanaSDK class, follow this link

# Import

swift
import SolanaSwift

# Logger

Create a logger that confirm to SolanaSwiftLogger

swift
import SolanaSwift

class MyCustomLogger: SolanaSwiftLogger {
    func log(event: String, data: String?, logLevel: SolanaSwiftLoggerLogLevel) {
        // Custom log goes here
    }
}

// AppDelegate or somewhere eles

let customLogger: SolanaSwiftLogger = MyCustomLogger()
SolanaSwift.Logger.setLoggers([customLogger])

# AccountStorage

Create an SolanaAccountStorage for saving account's keyPairs (public and private key), for example: KeychainAccountStorage for saving into Keychain in production, or InMemoryAccountStorage for temporarily saving into memory for testing. The "CustomAccountStorage" must conform to protocol SolanaAccountStorage, which has 2 requirements: function for saving save(_ account:) throws and computed property account: Account? { get thrrows } for retrieving user's account.

Example:

swift
import SolanaSwift
import KeychainSwift
struct KeychainAccountStorage: SolanaAccountStorage {
    let tokenKey = <YOUR_KEY_TO_STORE_IN_KEYCHAIN>
    func save(_ account: Account) throws {
        let data = try JSONEncoder().encode(account)
        keychain.set(data, forKey: tokenKey)
    }
    
    var account: Account? {
        guard let data = keychain.getData(tokenKey) else {return nil}
        return try JSONDecoder().decode(Account.self, from: data)
    }
}

struct InMemoryAccountStorage: SolanaAccountStorage {
    private var _account: Account?
    func save(_ account: Account) throws {
        _account = account
    }
    
    var account: Account? {
        _account
    }
}

# Create an account (keypair)

swift
let account = try await Account(network: .mainnetBeta)
// optional
accountStorage.save(account)

# Restore an account from a seed phrase (keypair)

swift
let account = try await Account(phrases: ["miracle", "hundred", ...], network: .mainnetBeta, derivablePath: ...)
// optional
accountStorage.save(account)

# Solana RPC Client

APIClient for Solana JSON RPC API. See Documentation

Example:

swift
import SolanaSwift

let endpoint = APIEndPoint(
    address: "https://api.mainnet-beta.solana.com",
    network: .mainnetBeta
)

// To get block height
let apiClient = JSONRPCAPIClient(endpoint: endpoint)
let result = try await apiClient.getBlockHeight()

// To get balance of the current account
guard let account = try? accountStorage.account?.publicKey.base58EncodedString else { throw UnauthorizedError }
let balance = try await apiClient.getBalance(account: account, commitment: "recent")

Wait for confirmation method.

swift
// Wait for confirmation
let signature = try await blockChainClient.sendTransaction(...)
try await apiClient.waitForConfirmation(signature: signature, ignoreStatus: true) // transaction will be mark as confirmed after timeout no matter what status is when ignoreStatus = true
let signature2 = try await blockchainClient.sendTransaction(/* another transaction that requires first transaction to be completed */)

Observe signature status. In stead of using socket to observe signature status, which is not really reliable (socket often returns signature status == finalized when it is not fully finalized), we observe its status by periodically sending getSignatureStatuses (with observeSignatureStatus method)

swift
// Observe signature status with `observeSignatureStatus` method
var statuses = [TransactionStatus]()
for try await status in apiClient.observeSignatureStatus(signature: "jaiojsdfoijvaij", timeout: 60, delay: 3) {
    print(status)
    statuses.append(status)
}
// statuses.last == .sending // the signature is not confirmed
// statuses.last?.numberOfConfirmations == x // the signature is confirmed by x nodes (partially confirmed)
// statuses.last == .finalized // the signature is confirmed by all nodes

Batch support

swift
// Batch request with different types
let req1: JSONRPCAPIClientRequest<AnyDecodable> = JSONRPCAPIClientRequest(method: "getAccountInfo", params: ["63ionHTAM94KaSujUCg23hfg7TLharchq5BYXdLGqia1"])
let req2: JSONRPCAPIClientRequest<AnyDecodable> = JSONRPCAPIClientRequest(method: "getBalance", params: ["63ionHTAM94KaSujUCg23hfg7TLharchq5BYXdLGqia1"])
let response = try await apiClient.batchRequest(with: [req1, req2])

// Batch request with same type
let balances: [Rpc<UInt64>?] = try await apiClient.batchRequest(method: "getBalance", params: [["63ionHTAM94KaSujUCg23hfg7TLharchq5BYXdLGqia1"], ["63ionHTAM94KaSujUCg23hfg7TLharchq5BYXdLGqia1"], ["63ionHTAM94KaSujUCg23hfg7TLharchq5BYXdLGqia1"]])

For the method that is not listed, use generic method request(method:params:) or request(method:) without params.

swift
let result: String = try await apiClient.request(method: "getHealth")
XCTAssertEqual(result, "ok")

# Solana Blockchain Client

Prepare, send and simulate transactions. See Documentation

Example:

swift
import SolanaSwift

let blockchainClient = BlockchainClient(apiClient: JSONRPCAPIClient(endpoint: endpoint))

/// Prepare any transaction, use any Solana program to create instructions, see section Solana program. 
let preparedTransaction = try await blockchainClient.prepareTransaction(
    instructions: [...],
    signers: [...],
    feePayer: ...
)

/// SPECIAL CASE: Prepare Sending Native SOL
let preparedTransaction = try await blockchainClient.prepareSendingNativeSOL(
    account: account,
    to: toPublicKey,
    amount: 0
)

/// SPECIAL CASE: Sending SPL Tokens
let preparedTransactions = try await blockchainClient.prepareSendingSPLTokens(
    account: account,
    mintAddress: <SPL TOKEN MINT ADDRESS>,  // USDC mint
    decimals: 6,
    from: <YOUR SPL TOKEN ADDRESS>, // Your usdc address
    to: destination,
    amount: <AMOUNT IN LAMPORTS>
)

/// Simulate or send

blockchainClient.simulateTransaction(
    preparedTransaction: preparedTransaction
)

blockchainClient.sendTransaction(
    preparedTransaction: preparedTransaction
)

# Solana Program

List of default programs and pre-defined method that live on Solana network:

  1. SystemProgram. See Documentation
  2. TokenProgram. See Documentation
  3. AssociatedTokenProgram. See Documentation
  4. OwnerValidationProgram. See Documentation
  5. TokenSwapProgram. See Documentation

# Solana Tokens Repository

Tokens repository usefull when you need to get a list of tokens. See Documentation

Example:

swift
let tokenRepository = TokensRepository(endpoint: endpoint)
let list = try await tokenRepository.getTokensList()

TokenRepository be default uses cache not to make extra calls, it can disabled manually .getTokensList(useCache: false)

# How to use OrcaSwap

OrcaSwap has been moved to new library OrcaSwapSwift

# How to use RenVM

RenVM has been moved to new library RenVMSwift

# How to use Serum swap (DEX) (NOT STABLE)

SerumSwap has been moved to new library SerumSwapSwift

# Contribution

  • Welcome to contribute, feel free to change and open a PR.

# Author

Chung Tran, chung.t@p2p.org

# License

SolanaSwift is available under the MIT license. See the LICENSE file for more info.