Skip to content

Commit 789940e

Browse files
Support tip estimation (#244)
1 parent bb1c8ed commit 789940e

File tree

3 files changed

+629
-0
lines changed

3 files changed

+629
-0
lines changed

Sources/Starknet/Helpers/Tip.swift

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import BigInt
2+
import Foundation
3+
4+
/// Estimates the transaction tip by taking the median of all V3 transaction tips in the latest block.
5+
///
6+
/// - Parameter provider: The provider used to interact with Starknet.
7+
/// - Returns: The estimated median tip.
8+
/// - Throws: An error if the RPC call fails or no transactions are found.
9+
public func estimateTip(provider: StarknetProviderProtocol) async throws -> UInt64AsHex {
10+
try await estimateTip(provider: provider, blockId: .tag(.latest))
11+
}
12+
13+
/// Estimates the transaction tip by taking the median of all V3 transaction tips in the latest block.
14+
///
15+
/// - Parameters:
16+
/// - provider: The provider used to interact with Starknet.
17+
/// - blockHash: The block hash to estimate tip for.
18+
/// - Returns: The estimated median tip.
19+
/// - Throws: An error if the RPC call fails or no transactions are found.
20+
public func estimateTip(provider: StarknetProviderProtocol, blockHash: Felt) async throws -> UInt64AsHex {
21+
try await estimateTip(provider: provider, blockId: .hash(blockHash))
22+
}
23+
24+
/// Estimates the transaction tip by taking the median of all V3 transaction tips in the latest block.
25+
///
26+
/// - Parameters:
27+
/// - provider: The provider used to interact with Starknet.
28+
/// - blockNumber: The block number to estimate tip for.
29+
/// - Returns: The estimated median tip.
30+
/// - Throws: An error if the RPC call fails or no transactions are found.
31+
public func estimateTip(provider: StarknetProviderProtocol, blockNumber: Int) async throws -> UInt64AsHex {
32+
try await estimateTip(provider: provider, blockId: .number(blockNumber))
33+
}
34+
35+
/// Estimates the transaction tip by taking the median of all V3 transaction tips in the latest block.
36+
///
37+
/// - Parameters:
38+
/// - provider: The provider used to interact with Starknet.
39+
/// - blockTag: The block tag to estimate tip for.
40+
/// - Returns: The estimated median tip.
41+
/// - Throws: An error if the RPC call fails or no transactions are found.
42+
public func estimateTip(provider: StarknetProviderProtocol, blockTag: StarknetBlockId.BlockTag) async throws -> UInt64AsHex {
43+
try await estimateTip(provider: provider, blockId: .tag(blockTag))
44+
}
45+
46+
/// Estimates the transaction tip by taking the median of all V3 transaction tips in the specified block.
47+
///
48+
/// - Parameters:
49+
/// - provider: The provider used to interact with Starknet.
50+
/// - blockId: The block identifier to estimate the tip for (hash, number, or tag).
51+
/// - Returns: The estimated median tip.
52+
/// - Throws: An error if the RPC call fails or no transactions are found.
53+
private func estimateTip(provider: StarknetProviderProtocol, blockId: StarknetBlockId) async throws -> UInt64AsHex {
54+
let request = RequestBuilder.getBlockWithTxs(blockId)
55+
let blockWithTxs = try await provider.send(request: request)
56+
57+
let transactions: [TransactionWrapper] = switch blockWithTxs {
58+
case let .processed(block): block.transactions
59+
case let .preConfirmed(block): block.transactions
60+
}
61+
62+
let tips = transactions.compactMap { transactionWrapper in
63+
switch transactionWrapper {
64+
case let .invokeV3(invokeV3): invokeV3.tip.value
65+
case let .deployAccountV3(deployAccountV3): deployAccountV3.tip.value
66+
case let .declareV3(declareV3): declareV3.tip.value
67+
default: nil
68+
}
69+
}
70+
71+
if tips.isEmpty {
72+
return UInt64AsHex.zero
73+
}
74+
75+
let sortedTips = tips.sorted()
76+
let count = sortedTips.count
77+
78+
let median = if count % 2 == 1 {
79+
sortedTips[count / 2]
80+
} else {
81+
(sortedTips[count / 2 - 1] + sortedTips[count / 2]) / 2
82+
}
83+
84+
if let median = median.toUInt64AsHex() {
85+
return median
86+
} else {
87+
fatalError("Failed to convert BigUInt to UInt64AsHex")
88+
}
89+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import Foundation
2+
3+
final class MockURLProtocol: URLProtocol {
4+
static var mockResponse: (statusCode: Int, body: Data)?
5+
6+
override class func canInit(with _: URLRequest) -> Bool {
7+
true
8+
}
9+
10+
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
11+
request
12+
}
13+
14+
override func startLoading() {
15+
guard let mock = MockURLProtocol.mockResponse else {
16+
fatalError("Mock response not set")
17+
}
18+
19+
let response = HTTPURLResponse(
20+
url: request.url!,
21+
statusCode: mock.statusCode,
22+
httpVersion: nil,
23+
headerFields: nil
24+
)!
25+
26+
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
27+
client?.urlProtocol(self, didLoad: mock.body)
28+
client?.urlProtocolDidFinishLoading(self)
29+
}
30+
31+
override func stopLoading() {}
32+
}
33+
34+
func makeMockedURLSession(
35+
statusCode: Int = 200,
36+
data: Data
37+
) -> URLSession {
38+
MockURLProtocol.mockResponse = (
39+
statusCode: statusCode,
40+
body: data
41+
)
42+
43+
let config = URLSessionConfiguration.ephemeral
44+
config.protocolClasses = [MockURLProtocol.self]
45+
46+
return URLSession(configuration: config)
47+
}

0 commit comments

Comments
 (0)