Skip to content

Commit 33df007

Browse files
committed
feat: waitForConfirmation
1 parent 3195f2f commit 33df007

File tree

3 files changed

+96
-13
lines changed

3 files changed

+96
-13
lines changed

Sources/SolanaSwift/APIClient/APIClient+Extension.swift

+24
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,28 @@ extension SolanaAPIClient {
183183
}
184184
return knownWallets + wallets
185185
}
186+
187+
/// Wait until transaction is confirmed, return even when there is one or more confirmations and request timed out
188+
/// - Parameters:
189+
/// - signature: signature of the transaction
190+
/// - ignoreStatus: ignore status and return true even when observation is timed out
191+
public func waitForConfirmation(signature: String, ignoreStatus: Bool, timeout: Int = 60, delay: Int = 2) async throws {
192+
var statuses = [TransactionStatus]()
193+
for try await status in observeSignatureStatus(signature: signature, timeout: timeout, delay: delay) {
194+
statuses.append(status)
195+
}
196+
197+
// if the status is important
198+
if !ignoreStatus {
199+
guard let lastStatus = statuses.last else {
200+
throw SolanaError.transactionHasNotBeenConfirmed
201+
}
202+
switch lastStatus {
203+
case .confirmed, .finalized:
204+
return
205+
default:
206+
throw SolanaError.transactionHasNotBeenConfirmed
207+
}
208+
}
209+
}
186210
}

Sources/SolanaSwift/Models/SolanaError.swift

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ public enum SolanaError: Swift.Error, Equatable {
4040
// Socket error
4141
case socket(Swift.Error)
4242

43+
// Transaction has not been confirmed
44+
case transactionHasNotBeenConfirmed
45+
4346
// Other
4447
case other(String)
4548
case unknown

Tests/SolanaSwiftUnitTests/APIClient/ObserveTransactionStatusTests.swift

+69-13
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,7 @@ class ObserveTransactionStatusTests: XCTestCase {
1515
var statuses: [TransactionStatus]!
1616

1717
override func setUpWithError() throws {
18-
let mock = NetworkManagerMock([
19-
.success(mockResponse(confirmations: 0, confirmationStatus: "processed")),
20-
.failure(CustomError.unknownNetworkError),
21-
.success(mockResponse(confirmations: 1, confirmationStatus: "confirmed")),
22-
.failure(CustomError.unknownNetworkError),
23-
.failure(SolanaError.unknown),
24-
.success(mockResponse(confirmations: 5, confirmationStatus: "confirmed")),
25-
.success(mockResponse(confirmations: 10, confirmationStatus: "confirmed")),
26-
.failure(SolanaError.unknown),
27-
.success(mockResponse(confirmations: nil, confirmationStatus: "finalized")),
28-
])
29-
apiClient = JSONRPCAPIClient(endpoint: endpoint, networkManager: mock)
30-
statuses = []
18+
resetAPIClient()
3119
}
3220

3321
override func tearDownWithError() throws {
@@ -60,6 +48,74 @@ class ObserveTransactionStatusTests: XCTestCase {
6048
XCTAssertEqual(statuses.last, .finalized)
6149
}
6250

51+
func testWaitForConfirmationIgnoreStatus() async throws {
52+
// return anyway after time out, even when transaction is not surely confimed
53+
let response: [Result<String, Error>] = [
54+
.failure(CustomError.unknownNetworkError),
55+
.failure(CustomError.unknownNetworkError),
56+
.failure(SolanaError.unknown),
57+
.success(mockResponse(confirmations: 5, confirmationStatus: "confirmed")),
58+
.success(mockResponse(confirmations: 10, confirmationStatus: "confirmed")),
59+
.failure(SolanaError.unknown),
60+
.success(mockResponse(confirmations: nil, confirmationStatus: "finalized")),
61+
]
62+
63+
resetAPIClient(customResponse: response)
64+
try await apiClient.waitForConfirmation(signature: "adfijidjfaisdf", ignoreStatus: true, timeout: 1, delay: 1)
65+
66+
resetAPIClient(customResponse: response)
67+
try await apiClient.waitForConfirmation(signature: "adfijidjfaisdf", ignoreStatus: true)
68+
}
69+
70+
func testWaitForConfirmationNotIgnoreStatus() async throws {
71+
// return only if transaction is confirmed or partially confirmed
72+
let response: [Result<String, Error>] = [
73+
.failure(CustomError.unknownNetworkError),
74+
.failure(CustomError.unknownNetworkError),
75+
.failure(SolanaError.unknown),
76+
.success(mockResponse(confirmations: 5, confirmationStatus: "confirmed")),
77+
.success(mockResponse(confirmations: 10, confirmationStatus: "confirmed")),
78+
.failure(SolanaError.unknown),
79+
.success(mockResponse(confirmations: nil, confirmationStatus: "finalized")),
80+
]
81+
82+
resetAPIClient(customResponse: response)
83+
do {
84+
try await apiClient.waitForConfirmation(signature: "adfijidjfaisdf", ignoreStatus: true, timeout: 1, delay: 1)
85+
} catch {
86+
XCTAssertTrue(error.isEqualTo(.transactionHasNotBeenConfirmed))
87+
}
88+
89+
resetAPIClient(customResponse: response)
90+
do {
91+
try await apiClient.waitForConfirmation(signature: "adfijidjfaisdf", ignoreStatus: true, timeout: 3, delay: 1)
92+
} catch {
93+
XCTAssertTrue(error.isEqualTo(.transactionHasNotBeenConfirmed))
94+
}
95+
96+
resetAPIClient(customResponse: response)
97+
try await apiClient.waitForConfirmation(signature: "adfijidjfaisdf", ignoreStatus: true, timeout: 7, delay: 1)
98+
99+
resetAPIClient(customResponse: response)
100+
try await apiClient.waitForConfirmation(signature: "adfijidjfaisdf", ignoreStatus: true, delay: 1)
101+
}
102+
103+
private func resetAPIClient(customResponse: [Result<String, Error>]? = nil) {
104+
let mock = NetworkManagerMock(customResponse ?? [
105+
.success(mockResponse(confirmations: 0, confirmationStatus: "processed")),
106+
.failure(CustomError.unknownNetworkError),
107+
.success(mockResponse(confirmations: 1, confirmationStatus: "confirmed")),
108+
.failure(CustomError.unknownNetworkError),
109+
.failure(SolanaError.unknown),
110+
.success(mockResponse(confirmations: 5, confirmationStatus: "confirmed")),
111+
.success(mockResponse(confirmations: 10, confirmationStatus: "confirmed")),
112+
.failure(SolanaError.unknown),
113+
.success(mockResponse(confirmations: nil, confirmationStatus: "finalized")),
114+
])
115+
apiClient = JSONRPCAPIClient(endpoint: endpoint, networkManager: mock)
116+
statuses = []
117+
}
118+
63119
// MARK: - Helpers
64120

65121
func mockResponse(confirmations: Int?, confirmationStatus: String) -> String {

0 commit comments

Comments
 (0)