Skip to content

Commit c885f1a

Browse files
authored
Merge pull request #105 from wannabehero/feat/new-solana-messages
Adds new Solana RPC methods
2 parents 3e85bd3 + d61d4c9 commit c885f1a

File tree

7 files changed

+85
-0
lines changed

7 files changed

+85
-0
lines changed

Sources/SolanaSwift/APIClient/APIClient+Extension.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ public extension SolanaAPIClient {
2525
func getRecentBlockhash() async throws -> String {
2626
try await getRecentBlockhash(commitment: nil)
2727
}
28+
29+
func getLatestBlockhash() async throws -> String {
30+
try await getLatestBlockhash(commitment: "processed")
31+
}
2832

2933
func observeSignatureStatus(signature: String) -> AsyncStream<PendingTransactionStatus> {
3034
observeSignatureStatus(signature: signature, timeout: 60, delay: 2)

Sources/SolanaSwift/APIClient/Networking/JSONRPCAPIClient.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ public class JSONRPCAPIClient: SolanaAPIClient {
9494
let result: Rpc<Fee> = try await get(method: "getFees", params: [RequestConfiguration(commitment: commitment)])
9595
return result.value
9696
}
97+
98+
public func getFeeForMessage(message: String, commitment: Commitment? = nil) async throws -> Lamports {
99+
let result: Rpc<Lamports> = try await self.request(
100+
method: "getFeeForMessage",
101+
params: [message, RequestConfiguration(commitment: commitment)]
102+
)
103+
104+
return result.value
105+
}
97106

98107
public func getMinimumBalanceForRentExemption(dataLength: UInt64,
99108
commitment: Commitment? = "recent") async throws -> UInt64
@@ -112,6 +121,14 @@ public class JSONRPCAPIClient: SolanaAPIClient {
112121
}
113122
return blockhash
114123
}
124+
125+
public func getLatestBlockhash(commitment: Commitment? = nil) async throws -> String {
126+
let result: Rpc<LatestBlockhash> = try await get(
127+
method: "getLatestBlockhash",
128+
params: [RequestConfiguration(commitment: commitment)])
129+
130+
return result.value.blockhash
131+
}
115132

116133
public func getSignatureStatuses(signatures: [String],
117134
configs: RequestConfiguration? = nil) async throws -> [SignatureStatus?]

Sources/SolanaSwift/APIClient/SolanaAPIClient.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,18 @@ public protocol SolanaAPIClient {
120120
/// - SeeAlso https://docs.solana.com/developing/clients/jsonrpc-api#getfees
121121
///
122122
func getFees(commitment: Commitment?) async throws -> Fee
123+
124+
/// Get the fee the network will charge for a particular Message
125+
/// - Parameters:
126+
/// - message: Base-64 encoded Message
127+
/// - commitment: Optional
128+
/// - Throws: ApiClientError
129+
/// - Returns Fee corresponding to the message at the specified blockhash
130+
/// - SeeAlso https://solana.com/docs/rpc/http/getfeeformessage
131+
/// - Note: This method is only available in solana-core v1.9 or newer.
132+
/// Please use ``getFees(commitment:)`` for solana-core v1.8 and below.
133+
///
134+
func getFeeForMessage(message: String, commitment: Commitment?) async throws -> Lamports
123135

124136
/// Returns minimum balance required to make account rent exempt
125137
/// - Parameters:
@@ -287,6 +299,16 @@ public protocol SolanaAPIClient {
287299
/// - SeeAlso https://docs.solana.com/developing/clients/jsonrpc-api#getrecentblockhash
288300
///
289301
func getRecentBlockhash(commitment: Commitment?) async throws -> String
302+
303+
/// Returns the latest blockhash
304+
/// - Parameters:
305+
/// - commitment: (optional) Commitment
306+
/// - Throws: APIClientError
307+
/// - SeeAlso https://solana.com/docs/rpc/http/getlatestblockhash
308+
/// - Note: This method is only available in solana-core v1.9 or newer.
309+
/// Please use ``getRecentBlockhash(commitment:)`` for solana-core v1.8 and below.
310+
///
311+
func getLatestBlockhash(commitment: Commitment?) async throws -> String
290312

291313
/// Returns signatures for confirmed transactions that include the given address in their accountKeys list.
292314
/// Returns signatures backwards in time from the provided signature or most recent confirmed block

Sources/SolanaSwift/Models/Models.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ public struct Fee: Decodable {
104104
public let lastValidSlot: UInt64?
105105
}
106106

107+
public struct LatestBlockhash: Decodable {
108+
public let blockhash: String
109+
public let lastValidBlockHeight: UInt64
110+
}
111+
107112
public struct FeeCalculatorResponse: Decodable {
108113
public let lamportsPerSignature: Lamports
109114
}

Tests/SolanaSwiftUnitTests/APIClient/APIClientTests.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,17 @@ class APIClientTests: XCTestCase {
191191
XCTAssertEqual(result.feeCalculator?.lamportsPerSignature, 5000)
192192
XCTAssertEqual(result.blockhash, "7jvToPQ4ASj3xohjM117tMqmtppQDaWVADZyaLFnytFr")
193193
}
194+
195+
func testGetFeeForMessage() async throws {
196+
let mock = NetworkManagerMock(NetworkManagerMockJSON["getFeeForMessage"]!)
197+
let apiClient = JSONRPCAPIClient(endpoint: endpoint, networkManager: mock)
198+
let result = try! await apiClient.getFeeForMessage(
199+
message: Data([0x01]).base64EncodedString(),
200+
commitment: nil
201+
)
202+
XCTAssertNotNil(result)
203+
XCTAssertEqual(result, 1337)
204+
}
194205

195206
func testGetMinimumBalanceForRentExemption() async throws {
196207
let mock = NetworkManagerMock(NetworkManagerMockJSON["getMinimumBalanceForRentExemption"]!)
@@ -207,6 +218,14 @@ class APIClientTests: XCTestCase {
207218
XCTAssertNotNil(result)
208219
XCTAssertEqual(result, "63ionHTAM94KaSujUCg23hfg7TLharchq5BYXdLGqia1")
209220
}
221+
222+
func testGetLatestBlockhash() async throws {
223+
let mock = NetworkManagerMock(NetworkManagerMockJSON["getLatestBlockhash"]!)
224+
let apiClient = JSONRPCAPIClient(endpoint: endpoint, networkManager: mock)
225+
let result = try! await apiClient.getLatestBlockhash(commitment: nil)
226+
XCTAssertNotNil(result)
227+
XCTAssertEqual(result, "63ionHTAM94KaSujUCg23hfg7TLharchq5BYXdLGqia1")
228+
}
210229

211230
func testGetSignatureStatusses() async throws {
212231
let mock = NetworkManagerMock(NetworkManagerMockJSON["getSignatureStatuses"]!)
@@ -374,8 +393,10 @@ private var NetworkManagerMockJSON = [
374393
"getTransaction": "{\"jsonrpc\":\"2.0\",\"result\":{\"blockTime\":1647268521,\"meta\":{\"err\":null,\"fee\":10000,\"innerInstructions\":[{\"index\":0,\"instructions\":[{\"accounts\":[\"11111111111111111111111111111111\",\"5qePAZpkrsxkhPQrwmmzFmi84xBwV4Z2hBuHh2jFA1FA\",\"F2kK1Z55NTZcagih78suvreP3UjNfrVLP2UBcR3orNub\",\"GyvYVTFgrfCmkE4pHzw44xyELoaivkYDeP2P1TmeqFSs\",\"11111111111111111111111111111111\",\"HSqVcxpDaZzwkHxreLisDtR9bQsLaTCMzMATFVhDoeNe\",\"fjohsw9j8aBJaEE5ddzHk3AgMjdbQSXrMoPGrepNHrB\"],\"data\":\"12B5oCT463JwdTFKK1Xm3F2xL1NoDdJQGeMuRKappf2aRUXjfSUEeEPQYcW1cMis8VD\",\"programId\":\"namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX\"},{\"parsed\":{\"info\":{\"destination\":\"F2kK1Z55NTZcagih78suvreP3UjNfrVLP2UBcR3orNub\",\"lamports\":2227200,\"source\":\"5qePAZpkrsxkhPQrwmmzFmi84xBwV4Z2hBuHh2jFA1FA\"},\"type\":\"transfer\"},\"program\":\"system\",\"programId\":\"11111111111111111111111111111111\"},{\"parsed\":{\"info\":{\"account\":\"F2kK1Z55NTZcagih78suvreP3UjNfrVLP2UBcR3orNub\",\"space\":192},\"type\":\"allocate\"},\"program\":\"system\",\"programId\":\"11111111111111111111111111111111\"},{\"parsed\":{\"info\":{\"account\":\"F2kK1Z55NTZcagih78suvreP3UjNfrVLP2UBcR3orNub\",\"owner\":\"namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX\"},\"type\":\"assign\"},\"program\":\"system\",\"programId\":\"11111111111111111111111111111111\"},{\"parsed\":{\"info\":{\"destination\":\"6GxxbzceU62bMwRcrRAnDqgfCQD9Z1FLUJCdwnFpFUvX\",\"lamports\":974400,\"source\":\"5qePAZpkrsxkhPQrwmmzFmi84xBwV4Z2hBuHh2jFA1FA\"},\"type\":\"transfer\"},\"program\":\"system\",\"programId\":\"11111111111111111111111111111111\"},{\"parsed\":{\"info\":{\"account\":\"6GxxbzceU62bMwRcrRAnDqgfCQD9Z1FLUJCdwnFpFUvX\",\"space\":12},\"type\":\"allocate\"},\"program\":\"system\",\"programId\":\"11111111111111111111111111111111\"},{\"parsed\":{\"info\":{\"account\":\"6GxxbzceU62bMwRcrRAnDqgfCQD9Z1FLUJCdwnFpFUvX\",\"owner\":\"B59xBt3AVAcV5jiHGEbGHe93mbycA44EK3vA6E4VqKog\"},\"type\":\"assign\"},\"program\":\"system\",\"programId\":\"11111111111111111111111111111111\"}]}],\"logMessages\":[\"Program B59xBt3AVAcV5jiHGEbGHe93mbycA44EK3vA6E4VqKog invoke [1]\",\"Program namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX invoke [2]\",\"Program log: Entrypoint\",\"Program log: Beginning processing\",\"Program log: Instruction unpacked\",\"Program log: Instruction: Create\",\"Program 11111111111111111111111111111111 invoke [3]\",\"Program 11111111111111111111111111111111 success\",\"Program 11111111111111111111111111111111 invoke [3]\",\"Program 11111111111111111111111111111111 success\",\"Program 11111111111111111111111111111111 invoke [3]\",\"Program 11111111111111111111111111111111 success\",\"Program namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX consumed 20584 of 191569 compute units\",\"Program namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX success\",\"Program 11111111111111111111111111111111 invoke [2]\",\"Program 11111111111111111111111111111111 success\",\"Program 11111111111111111111111111111111 invoke [2]\",\"Program 11111111111111111111111111111111 success\",\"Program 11111111111111111111111111111111 invoke [2]\",\"Program 11111111111111111111111111111111 success\",\"Program B59xBt3AVAcV5jiHGEbGHe93mbycA44EK3vA6E4VqKog consumed 41308 of 200000 compute units\",\"Program B59xBt3AVAcV5jiHGEbGHe93mbycA44EK3vA6E4VqKog success\"],\"postBalances\":[443590671,100000000,2227200,974400,1,1141440,1009200,0,71353866,1001293440],\"postTokenBalances\":[],\"preBalances\":[446802271,100000000,0,0,1,1141440,1009200,0,71353866,1001293440],\"preTokenBalances\":[],\"rewards\":[],\"status\":{\"Ok\":null}},\"slot\":124916513,\"transaction\":{\"message\":{\"accountKeys\":[{\"pubkey\":\"5qePAZpkrsxkhPQrwmmzFmi84xBwV4Z2hBuHh2jFA1FA\",\"signer\":true,\"writable\":true},{\"pubkey\":\"fjohsw9j8aBJaEE5ddzHk3AgMjdbQSXrMoPGrepNHrB\",\"signer\":true,\"writable\":false},{\"pubkey\":\"F2kK1Z55NTZcagih78suvreP3UjNfrVLP2UBcR3orNub\",\"signer\":false,\"writable\":true},{\"pubkey\":\"6GxxbzceU62bMwRcrRAnDqgfCQD9Z1FLUJCdwnFpFUvX\",\"signer\":false,\"writable\":true},{\"pubkey\":\"11111111111111111111111111111111\",\"signer\":false,\"writable\":false},{\"pubkey\":\"namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX\",\"signer\":false,\"writable\":false},{\"pubkey\":\"SysvarRent111111111111111111111111111111111\",\"signer\":false,\"writable\":false},{\"pubkey\":\"GyvYVTFgrfCmkE4pHzw44xyELoaivkYDeP2P1TmeqFSs\",\"signer\":false,\"writable\":false},{\"pubkey\":\"HSqVcxpDaZzwkHxreLisDtR9bQsLaTCMzMATFVhDoeNe\",\"signer\":false,\"writable\":false},{\"pubkey\":\"B59xBt3AVAcV5jiHGEbGHe93mbycA44EK3vA6E4VqKog\",\"signer\":false,\"writable\":false}],\"instructions\":[{\"accounts\":[\"11111111111111111111111111111111\",\"namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX\",\"SysvarRent111111111111111111111111111111111\",\"5qePAZpkrsxkhPQrwmmzFmi84xBwV4Z2hBuHh2jFA1FA\",\"F2kK1Z55NTZcagih78suvreP3UjNfrVLP2UBcR3orNub\",\"6GxxbzceU62bMwRcrRAnDqgfCQD9Z1FLUJCdwnFpFUvX\",\"GyvYVTFgrfCmkE4pHzw44xyELoaivkYDeP2P1TmeqFSs\",\"11111111111111111111111111111111\",\"HSqVcxpDaZzwkHxreLisDtR9bQsLaTCMzMATFVhDoeNe\",\"fjohsw9j8aBJaEE5ddzHk3AgMjdbQSXrMoPGrepNHrB\"],\"data\":\"1RxqKETLww2ut33YFYhShTyYLvJhfTf7WPWUiLK5XGL58wSWFHKvt4YJUXDoL8JFGkzGzVbP\",\"programId\":\"B59xBt3AVAcV5jiHGEbGHe93mbycA44EK3vA6E4VqKog\"}],\"recentBlockhash\":\"U9hxJeX42n3rEG8FJychJpeQucN1QqKoMb56GigUdrm\"},\"signatures\":[\"3kNdBJeLhLQX8FsyHjAKrtfnq5L6NwjQ3Nm96Wyx1pk5GFicbE47mpu2CtiU8krZDVDk7Di5ELAoKtw91Yj89bQ\",\"4w7p5ZvAgzZ7THtxKxRbFqAuD69Uk82tnbuz9BT822MRiGZUypAkok8u4sTPizjHmjx65vpGcw4SwFXu8hgafCoh\"]}},\"id\":\"03194776-D570-4887-8A2A-84007EE79A66\"}\n",
375394
"getEpochInfo": "{\"jsonrpc\":\"2.0\",\"result\":{\"absoluteSlot\":131686768,\"blockHeight\":119443373,\"epoch\":304,\"slotIndex\":358768,\"slotsInEpoch\":432000,\"transactionCount\":71271072342},\"id\":\"AE699DFA-84E8-495C-8B06-F30DDFA6C56D\"}\n",
376395
"getFees": "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"slot\":131770081},\"value\":{\"blockhash\":\"7jvToPQ4ASj3xohjM117tMqmtppQDaWVADZyaLFnytFr\",\"feeCalculator\":{\"lamportsPerSignature\":5000},\"lastValidBlockHeight\":119512694,\"lastValidSlot\":131770381}},\"id\":\"3FF1AACE-812A-4106-8C34-6EF66237673C\"}\n",
396+
"getFeeForMessage": "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"slot\":5068},\"value\":1337},\"id\": \"FD486F1A-82F6-4295-B0B1-9E8905239A67\"}",
377397
"getMinimumBalanceForRentExemption": "{\"jsonrpc\":\"2.0\",\"result\":890880,\"id\":\"25423C5F-2FF3-4134-8CB3-9090BFCB2CE3\"}\n",
378398
"getRecentBlockhash": "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"slot\":131780453},\"value\":{\"blockhash\":\"63ionHTAM94KaSujUCg23hfg7TLharchq5BYXdLGqia1\",\"feeCalculator\":{\"lamportsPerSignature\":5000}}},\"id\":\"21D61199-F235-4CC9-9BE6-06745D3AC69E\"}\n",
399+
"getLatestBlockhash": "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"slot\":131780453},\"value\":{\"blockhash\":\"63ionHTAM94KaSujUCg23hfg7TLharchq5BYXdLGqia1\",\"lastValidBlockHeight\":3090}},\"id\":\"9C1116F2-688C-4A71-A7E4-FB436D9D9E44\"}\n",
379400
"getMultipleAccounts": "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"slot\":132420615},\"value\":[{\"data\":[\"APoAh5MDAAAAAAKLjuya35R64GfrOPbupmMcxJ1pmaH2fciYq9DxSQ88FioLlNul6FnDNF06/RKhMFBVI8fFQKRYcqukjYZitosKxZBjjg9hLR2AsDm2e/itloPtlrPeVDPIVdnO4+dmM2JiSZHdhsj7+Fn94OTNte9elt1ek0p487C2fLrFA9CvUPerjZvfP97EqlF9OXbPSzaGJzdmfWhk4jRnThsg5scAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAObFpMVhxY3CRrzEcywhYTa4a4SsovPp4wKPRTbTJVtzAfQBZAAAAABDU47UFrGnHMTsb0EaE1TBoVQGvCIHKJ4/EvpK3zvIfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsWQY44PYS0dgLA5tnv4rZaD7Zaz3lQzyFXZzuPnZjMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\",\"base64\"],\"executable\":false,\"lamports\":1345194,\"owner\":\"617jbWo616ggkDxvW1Le8pV38XLbVSyWY8ae6QUmGBAU\",\"rentEpoch\":306}]},\"id\":\"D5BCACBB-3CE7-44D6-8F66-C57470A90440\"}\n",
380401
"getMultipleMintDatas": "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"slot\":132426903},\"value\":[{\"data\":[\"AQAAABzjWe1aAS4E+hQrnHUaHF6Hz9CgFhuchf/TG3jN/Nj2dbn7oe7/EAAGAQEAAAAqnl7btTwEZ5CY/3sSZRcUQ0/AjFYqmjuGEQXmctQicw==\",\"base64\"],\"executable\":false,\"lamports\":122356825965,\"owner\":\"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA\",\"rentEpoch\":306},{\"data\":[\"AQAAAAXqnPFs5BGY8aSZN8iMNwqU1K//ibW6y470XmMku3j3J0UE6/G2BgAGAQEAAAAF6pzxbOQRmPGkmTfIjDcKlNSv/4m1usuO9F5jJLt49w==\",\"base64\"],\"executable\":false,\"lamports\":23879870146,\"owner\":\"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA\",\"rentEpoch\":306}]},\"id\":\"65766E4D-F678-489A-943B-8D70B5C6F1ED\"}\n",
381402
"simulateTransaction": "{\"jsonrpc\":\"2.0\",\"result\":{\"context\":{\"slot\":132581497},\"value\":{\"accounts\":null,\"err\":\"AccountNotFound\",\"logs\":[],\"unitsConsumed\":0}},\"id\":\"3916EC69-2924-40E7-8843-8CBA8C6DB14C\"}\n",

Tests/SolanaSwiftUnitTests/BlockchainClient/BlockchainClientWithNativeSOLTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ private class MockAPIClient: SolanaAPIClient {
183183
lastValidSlot: lastValidSlot
184184
)
185185
}
186+
187+
func getFeeForMessage(message _: String, commitment _: Commitment?) async throws -> Lamports {
188+
return 1337
189+
}
186190

187191
func getRecentBlockhash(commitment _: Commitment?) async throws -> String {
188192
switch testCase {
@@ -204,6 +208,10 @@ private class MockAPIClient: SolanaAPIClient {
204208
fatalError()
205209
}
206210
}
211+
212+
func getLatestBlockhash(commitment _: Commitment?) async throws -> String {
213+
return "Bc11qGhSE3Vham6cBWEUxhRVVSNtzkyisdGGXwh6hvnT"
214+
}
207215

208216
func getMinimumBalanceForRentExemption(dataLength _: UInt64, commitment _: Commitment?) async throws -> UInt64 {
209217
2_039_280

Tests/SolanaSwiftUnitTests/BlockchainClient/BlockchainClientWithTokenProgramTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ private class MockAPIClient: SolanaAPIClient {
220220
lastValidSlot: lastValidSlot
221221
)
222222
}
223+
224+
func getFeeForMessage(message _: String, commitment _: Commitment?) async throws -> Lamports {
225+
return 1337
226+
}
223227

224228
func getRecentBlockhash(commitment _: Commitment?) async throws -> String {
225229
switch testCase {
@@ -241,6 +245,10 @@ private class MockAPIClient: SolanaAPIClient {
241245
fatalError()
242246
}
243247
}
248+
249+
func getLatestBlockhash(commitment _: Commitment?) async throws -> String {
250+
return "Bc11qGhSE3Vham6cBWEUxhRVVSNtzkyisdGGXwh6hvnT"
251+
}
244252

245253
func getMinimumBalanceForRentExemption(dataLength _: UInt64, commitment _: Commitment?) async throws -> UInt64 {
246254
2_039_280

0 commit comments

Comments
 (0)