Solana-blockchain client, written in pure swift.
- 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
To run the example project, clone the repo, and run pod install
from the Example directory first.
Demo wallet: p2p-wallet
- iOS 13 or later
- TweetNacl
- secp256k1.swift
SolanaSwift is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'SolanaSwift', '~> 4.0.0'
...
dependencies: [
...
.package(url: "https://github.com/p2p-org/solana-swift", from: "4.0.0")
],
...
- 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 SolanaSwift
Create a logger that confirm to SolanaSwiftLogger
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])
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:
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
}
}
let account = try await Account(network: .mainnetBeta)
// optional
accountStorage.save(account)
let account = try await Account(phrases: ["miracle", "hundred", ...], network: .mainnetBeta, derivablePath: ...)
// optional
accountStorage.save(account)
APIClient for Solana JSON RPC API. See Documentation
Example:
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.
// 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)
// 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
// 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.
let result: String = try await apiClient.request(method: "getHealth")
XCTAssertEqual(result, "ok")
Prepare, send and simulate transactions. See Documentation
Example:
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
)
List of default programs and pre-defined method that live on Solana network:
- SystemProgram. See Documentation
- TokenProgram. See Documentation
- AssociatedTokenProgram. See Documentation
- OwnerValidationProgram. See Documentation
- TokenSwapProgram. See Documentation
Tokens repository usefull when you need to get a list of tokens. See Documentation
Example:
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)
OrcaSwap has been moved to new library OrcaSwapSwift
RenVM has been moved to new library RenVMSwift
SerumSwap has been moved to new library SerumSwapSwift
- Welcome to contribute, feel free to change and open a PR.
Chung Tran, [email protected]
SolanaSwift is available under the MIT license. See the LICENSE file for more info.