From a5c820acc23bc4b992326dedaf7b38981fd12ac9 Mon Sep 17 00:00:00 2001 From: Franciszek Job <54181625+franciszekjob@users.noreply.github.com> Date: Wed, 12 Jun 2024 17:10:34 +0200 Subject: [PATCH] Support batching multiple RPC requests (#187) --- .gitignore | 2 + .../Starknet/Accounts/StarknetAccount.swift | 56 ++++--- .../Accounts/StarknetAccountProtocol.swift | 62 ++++---- .../TransactionReceiptWrapper.swift | 2 +- .../Data/Transaction/TransactionWrapper.swift | 2 +- .../Network/HttpNetworkProvider.swift | 44 ++++-- .../Network/StarknetBatchRequest.swift | 30 ++++ .../Starknet/Network/StarknetRequest.swift | 25 +++ .../StarknetProvider/JsonRpcParams.swift | 55 +++++++ .../StarknetProvider/JsonRpcPayload.swift | 9 +- .../StarknetProvider/StarknetProvider.swift | 148 ++++++++---------- .../Providers/StarknetProviderProtocol.swift | 94 ++++++----- .../StarknetTests/Accounts/AccountTest.swift | 44 +++--- Tests/StarknetTests/Data/ExecutionTests.swift | 2 +- .../Data/JsonRpcResponseTests.swift | 35 +++++ .../Providers/ProviderTests.swift | 130 +++++++++------ .../Utils/DevnetClient/DevnetClient.swift | 4 +- 17 files changed, 468 insertions(+), 276 deletions(-) create mode 100644 Sources/Starknet/Network/StarknetBatchRequest.swift create mode 100644 Sources/Starknet/Network/StarknetRequest.swift diff --git a/.gitignore b/.gitignore index 56d0f2a2e..5ccc12599 100644 --- a/.gitignore +++ b/.gitignore @@ -103,3 +103,5 @@ iOSInjectionProject/ .swiftpm/config/registries.json .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata .netrc + +docs/* diff --git a/Sources/Starknet/Accounts/StarknetAccount.swift b/Sources/Starknet/Accounts/StarknetAccount.swift index 7f03e2ef5..b1a922c4b 100644 --- a/Sources/Starknet/Accounts/StarknetAccount.swift +++ b/Sources/Starknet/Accounts/StarknetAccount.swift @@ -86,100 +86,100 @@ public class StarknetAccount: StarknetAccountProtocol { return makeDeployAccountTransactionV3(classHash: classHash, salt: salt, calldata: calldata, signature: signature, params: params, forFeeEstimation: forFeeEstimation) } - public func executeV1(calls: [StarknetCall], params: StarknetOptionalInvokeParamsV1) async throws -> StarknetInvokeTransactionResponse { + public func executeV1(calls: [StarknetCall], params: StarknetOptionalInvokeParamsV1) async throws -> StarknetRequest { var nonce: Felt var maxFee: Felt if let paramsNonce = params.nonce { nonce = paramsNonce } else { - nonce = try await getNonce() + nonce = try await getNonce().send() } if let paramsMaxFee = params.maxFee { maxFee = paramsMaxFee } else { - let feeEstimate = try await estimateFeeV1(calls: calls, nonce: nonce) + let feeEstimate = try await estimateFeeV1(calls: calls, nonce: nonce).send()[0] maxFee = feeEstimate.toMaxFee() } let params = StarknetInvokeParamsV1(nonce: nonce, maxFee: maxFee) let signedTransaction = try signV1(calls: calls, params: params, forFeeEstimation: false) - return try await provider.addInvokeTransaction(signedTransaction) + return provider.addInvokeTransaction(signedTransaction) } - public func executeV3(calls: [StarknetCall], params: StarknetOptionalInvokeParamsV3) async throws -> StarknetInvokeTransactionResponse { + public func executeV3(calls: [StarknetCall], params: StarknetOptionalInvokeParamsV3) async throws -> StarknetRequest { var nonce: Felt var resourceBounds: StarknetResourceBoundsMapping if let paramsNonce = params.nonce { nonce = paramsNonce } else { - nonce = try await getNonce() + nonce = try await getNonce().send() } if let paramsResourceBounds = params.resourceBounds { resourceBounds = paramsResourceBounds } else { - let feeEstimate = try await estimateFeeV3(calls: calls, nonce: nonce) + let feeEstimate = try await estimateFeeV3(calls: calls, nonce: nonce).send()[0] resourceBounds = feeEstimate.toResourceBounds() } let params = StarknetInvokeParamsV3(nonce: nonce, l1ResourceBounds: resourceBounds.l1Gas) let signedTransaction = try signV3(calls: calls, params: params, forFeeEstimation: false) - return try await provider.addInvokeTransaction(signedTransaction) + return provider.addInvokeTransaction(signedTransaction) } - public func executeV1(calls: [StarknetCall], estimateFeeMultiplier: Double) async throws -> StarknetInvokeTransactionResponse { - let nonce = try await getNonce() - let feeEstimate = try await estimateFeeV1(calls: calls, nonce: nonce) + public func executeV1(calls: [StarknetCall], estimateFeeMultiplier: Double) async throws -> StarknetRequest { + let nonce = try await getNonce().send() + let feeEstimate = try await estimateFeeV1(calls: calls, nonce: nonce).send()[0] let maxFee = feeEstimate.toMaxFee(multiplier: estimateFeeMultiplier) let params = StarknetInvokeParamsV1(nonce: nonce, maxFee: maxFee) let signedTransaction = try signV1(calls: calls, params: params, forFeeEstimation: false) - return try await provider.addInvokeTransaction(signedTransaction) + return provider.addInvokeTransaction(signedTransaction) } - public func executeV3(calls: [StarknetCall], estimateAmountMultiplier: Double, estimateUnitPriceMultiplier: Double) async throws -> StarknetInvokeTransactionResponse { - let nonce = try await getNonce() - let feeEstimate = try await estimateFeeV3(calls: calls, nonce: nonce) + public func executeV3(calls: [StarknetCall], estimateAmountMultiplier: Double, estimateUnitPriceMultiplier: Double) async throws -> StarknetRequest { + let nonce = try await getNonce().send() + let feeEstimate = try await estimateFeeV3(calls: calls, nonce: nonce).send()[0] let resourceBounds = feeEstimate.toResourceBounds(amountMultiplier: estimateAmountMultiplier, unitPriceMultiplier: estimateUnitPriceMultiplier) let params = StarknetInvokeParamsV3(nonce: nonce, l1ResourceBounds: resourceBounds.l1Gas) let signedTransaction = try signV3(calls: calls, params: params, forFeeEstimation: false) - return try await provider.addInvokeTransaction(signedTransaction) + return provider.addInvokeTransaction(signedTransaction) } - public func estimateFeeV1(calls: [StarknetCall], nonce: Felt, skipValidate: Bool) async throws -> StarknetFeeEstimate { + public func estimateFeeV1(calls: [StarknetCall], nonce: Felt, skipValidate: Bool) async throws -> StarknetRequest<[StarknetFeeEstimate]> { let params = StarknetInvokeParamsV1(nonce: nonce, maxFee: .zero) let signedTransaction = try signV1(calls: calls, params: params, forFeeEstimation: true) - return try await provider.estimateFee(for: signedTransaction, simulationFlags: skipValidate ? [.skipValidate] : []) + return provider.estimateFee(for: signedTransaction, simulationFlags: skipValidate ? [.skipValidate] : []) } - public func estimateFeeV3(calls: [StarknetCall], nonce: Felt, skipValidate: Bool) async throws -> StarknetFeeEstimate { + public func estimateFeeV3(calls: [StarknetCall], nonce: Felt, skipValidate: Bool) async throws -> StarknetRequest<[StarknetFeeEstimate]> { let params = StarknetInvokeParamsV3(nonce: nonce, l1ResourceBounds: .zero) let signedTransaction = try signV3(calls: calls, params: params, forFeeEstimation: true) - return try await provider.estimateFee(for: signedTransaction, simulationFlags: skipValidate ? [.skipValidate] : []) + return provider.estimateFee(for: signedTransaction, simulationFlags: skipValidate ? [.skipValidate] : []) } - public func estimateDeployAccountFeeV1(classHash: Felt, calldata: StarknetCalldata, salt: Felt, nonce: Felt, skipValidate: Bool) async throws -> StarknetFeeEstimate { + public func estimateDeployAccountFeeV1(classHash: Felt, calldata: StarknetCalldata, salt: Felt, nonce: Felt, skipValidate: Bool) async throws -> StarknetRequest<[StarknetFeeEstimate]> { let params = StarknetDeployAccountParamsV1(nonce: nonce, maxFee: 0) let signedTransaction = try signDeployAccountV1(classHash: classHash, calldata: calldata, salt: salt, params: params, forFeeEstimation: true) - return try await provider.estimateFee(for: signedTransaction, simulationFlags: skipValidate ? [.skipValidate] : []) + return provider.estimateFee(for: signedTransaction, simulationFlags: skipValidate ? [.skipValidate] : []) } - public func estimateDeployAccountFeeV3(classHash: Felt, calldata: StarknetCalldata, salt: Felt, nonce: Felt, skipValidate: Bool) async throws -> StarknetFeeEstimate { + public func estimateDeployAccountFeeV3(classHash: Felt, calldata: StarknetCalldata, salt: Felt, nonce: Felt, skipValidate: Bool) async throws -> StarknetRequest<[StarknetFeeEstimate]> { let params = StarknetDeployAccountParamsV3(nonce: nonce, l1ResourceBounds: .zero) let signedTransaction = try signDeployAccountV3(classHash: classHash, calldata: calldata, salt: salt, params: params, forFeeEstimation: true) - return try await provider.estimateFee(for: signedTransaction, simulationFlags: skipValidate ? [.skipValidate] : []) + return provider.estimateFee(for: signedTransaction, simulationFlags: skipValidate ? [.skipValidate] : []) } public func sign(typedData: StarknetTypedData) throws -> StarknetSignature { @@ -197,7 +197,7 @@ public class StarknetAccount: StarknetAccountProtocol { ) do { - let result = try await provider.callContract(call) + let result = try await provider.callContract(call).send() guard result.count == 1 else { throw StarknetAccountError.invalidResponse @@ -219,9 +219,7 @@ public class StarknetAccount: StarknetAccountProtocol { // And we want to rethrow all other errors. } - public func getNonce() async throws -> Felt { - let result = try await provider.getNonce(of: address) - - return result + public func getNonce() async throws -> StarknetRequest { + provider.getNonce(of: address) } } diff --git a/Sources/Starknet/Accounts/StarknetAccountProtocol.swift b/Sources/Starknet/Accounts/StarknetAccountProtocol.swift index 88fc0aa55..3ef0a3b53 100644 --- a/Sources/Starknet/Accounts/StarknetAccountProtocol.swift +++ b/Sources/Starknet/Accounts/StarknetAccountProtocol.swift @@ -74,7 +74,7 @@ public protocol StarknetAccountProtocol { /// - params: additional params for a given transaction. /// /// - Returns: InvokeTransactionResponse, containing transaction hash of submitted transaction. - func executeV1(calls: [StarknetCall], params: StarknetOptionalInvokeParamsV1) async throws -> StarknetInvokeTransactionResponse + func executeV1(calls: [StarknetCall], params: StarknetOptionalInvokeParamsV1) async throws -> StarknetRequest /// Execute list of calls as invoke transaction v3 /// @@ -83,7 +83,7 @@ public protocol StarknetAccountProtocol { /// - params: additional params for a given transaction. /// /// - Returns: InvokeTransactionResponse, containing transaction hash of submitted transaction. - func executeV3(calls: [StarknetCall], params: StarknetOptionalInvokeParamsV3) async throws -> StarknetInvokeTransactionResponse + func executeV3(calls: [StarknetCall], params: StarknetOptionalInvokeParamsV3) async throws -> StarknetRequest /// Execute list of calls as invoke transaction v1 with automatically estimated fee that will be multiplied by the specified multiplier when max fee is calculated. /// @@ -92,7 +92,7 @@ public protocol StarknetAccountProtocol { /// - estimateFeeMultiplier: multiplier for the estimated fee. /// /// - Returns: InvokeTransactionResponse, containing transaction hash of submitted transaction. - func executeV1(calls: [StarknetCall], estimateFeeMultiplier: Double) async throws -> StarknetInvokeTransactionResponse + func executeV1(calls: [StarknetCall], estimateFeeMultiplier: Double) async throws -> StarknetRequest /// Execute list of calls as invoke transaction v3 with automatically estimated fee that will be multiplied by the specified multipliers when resource bounds are calculated. /// @@ -102,7 +102,7 @@ public protocol StarknetAccountProtocol { /// - estimateUnitPriceMultiplier: multiplier for the estimated unit price. /// /// - Returns: InvokeTransactionResponse, containing transaction hash of submitted transaction. - func executeV3(calls: [StarknetCall], estimateAmountMultiplier: Double, estimateUnitPriceMultiplier: Double) async throws -> StarknetInvokeTransactionResponse + func executeV3(calls: [StarknetCall], estimateAmountMultiplier: Double, estimateUnitPriceMultiplier: Double) async throws -> StarknetRequest /// Execute list of calls as invoke transaction v1 with automatically estimated fee /// @@ -110,7 +110,7 @@ public protocol StarknetAccountProtocol { /// - calls: list of calls to be executed. /// /// - Returns: InvokeTransactionResponse, containing transaction hash of submitted transaction. - func executeV1(calls: [StarknetCall]) async throws -> StarknetInvokeTransactionResponse + func executeV1(calls: [StarknetCall]) async throws -> StarknetRequest /// Execute list of calls as invoke transaction v3 with automatically estimated fee /// @@ -118,7 +118,7 @@ public protocol StarknetAccountProtocol { /// - calls: list of calls to be executed. /// /// - Returns: InvokeTransactionResponse, containing transaction hash of submitted transaction. - func executeV3(calls: [StarknetCall]) async throws -> StarknetInvokeTransactionResponse + func executeV3(calls: [StarknetCall]) async throws -> StarknetRequest /// Estimate fee for a list of calls as invoke transaction v1 /// @@ -128,7 +128,7 @@ public protocol StarknetAccountProtocol { /// - skipValidate: Flag indicating whether validation of the transaction should be skipped. /// /// - Returns: struct containing fee estimate - func estimateFeeV1(calls: [StarknetCall], nonce: Felt, skipValidate: Bool) async throws -> StarknetFeeEstimate + func estimateFeeV1(calls: [StarknetCall], nonce: Felt, skipValidate: Bool) async throws -> StarknetRequest<[StarknetFeeEstimate]> /// Estimate fee for a list of calls as invoke transaction v3 /// @@ -138,7 +138,7 @@ public protocol StarknetAccountProtocol { /// - skipValidate: Flag indicating whether validation of the transaction should be skipped. /// - Returns: struct containing fee estimate - func estimateFeeV3(calls: [StarknetCall], nonce: Felt, skipValidate: Bool) async throws -> StarknetFeeEstimate + func estimateFeeV3(calls: [StarknetCall], nonce: Felt, skipValidate: Bool) async throws -> StarknetRequest<[StarknetFeeEstimate]> /// Estimate fee for a deploy account transaction v1 /// @@ -150,7 +150,7 @@ public protocol StarknetAccountProtocol { /// - skipValidate: flag indicating whether validation of the transaction should be skipped /// /// - Returns: struct containing fee estimate - func estimateDeployAccountFeeV1(classHash: Felt, calldata: StarknetCalldata, salt: Felt, nonce: Felt, skipValidate: Bool) async throws -> StarknetFeeEstimate + func estimateDeployAccountFeeV1(classHash: Felt, calldata: StarknetCalldata, salt: Felt, nonce: Felt, skipValidate: Bool) async throws -> StarknetRequest<[StarknetFeeEstimate]> /// Estimate fee for a deploy account transaction v3 /// @@ -162,12 +162,12 @@ public protocol StarknetAccountProtocol { /// - skipValidate: flag indicating whether validation of the transaction should be skipped /// /// - Returns: struct containing fee estimate - func estimateDeployAccountFeeV3(classHash: Felt, calldata: StarknetCalldata, salt: Felt, nonce: Felt, skipValidate: Bool) async throws -> StarknetFeeEstimate + func estimateDeployAccountFeeV3(classHash: Felt, calldata: StarknetCalldata, salt: Felt, nonce: Felt, skipValidate: Bool) async throws -> StarknetRequest<[StarknetFeeEstimate]> /// Get current nonce of the account /// /// - Returns: current nonce, as felt value. - func getNonce() async throws -> Felt + func getNonce() async throws -> StarknetRequest } public extension StarknetAccountProtocol { @@ -252,7 +252,7 @@ public extension StarknetAccountProtocol { /// - calls: list of calls to be executed. /// /// - Returns: InvokeTransactionResponse, containing transaction hash of submitted transaction. - func executeV1(calls: [StarknetCall]) async throws -> StarknetInvokeTransactionResponse { + func executeV1(calls: [StarknetCall]) async throws -> StarknetRequest { try await executeV1(calls: calls, params: StarknetOptionalInvokeParamsV1()) } @@ -262,7 +262,7 @@ public extension StarknetAccountProtocol { /// - calls: list of calls to be executed. /// /// - Returns: InvokeTransactionResponse, containing transaction hash of submitted transaction. - func executeV3(calls: [StarknetCall]) async throws -> StarknetInvokeTransactionResponse { + func executeV3(calls: [StarknetCall]) async throws -> StarknetRequest { try await executeV3(calls: calls, params: StarknetOptionalInvokeParamsV3()) } @@ -273,7 +273,7 @@ public extension StarknetAccountProtocol { /// - params: additional params for a given transaction /// /// - Returns: InvokeTransactionResponse, containing transaction hash of submitted transaction. - func executeV1(call: StarknetCall, params: StarknetOptionalInvokeParamsV1) async throws -> StarknetInvokeTransactionResponse { + func executeV1(call: StarknetCall, params: StarknetOptionalInvokeParamsV1) async throws -> StarknetRequest { try await executeV1(calls: [call], params: params) } @@ -284,7 +284,7 @@ public extension StarknetAccountProtocol { /// - params: additional params for a given transaction /// /// - Returns: InvokeTransactionResponse, containing transaction hash of submitted transaction. - func executeV3(call: StarknetCall, params: StarknetOptionalInvokeParamsV3) async throws -> StarknetInvokeTransactionResponse { + func executeV3(call: StarknetCall, params: StarknetOptionalInvokeParamsV3) async throws -> StarknetRequest { try await executeV3(calls: [call], params: params) } @@ -295,7 +295,7 @@ public extension StarknetAccountProtocol { /// - estimateFeeMultiplier: multiplier for the estimated fee. /// /// - Returns: InvokeTransactionResponse, containing transaction hash of submitted transaction. - func executeV1(call: StarknetCall, estimateFeeMultiplier: Double) async throws -> StarknetInvokeTransactionResponse { + func executeV1(call: StarknetCall, estimateFeeMultiplier: Double) async throws -> StarknetRequest { try await executeV1(calls: [call], estimateFeeMultiplier: estimateFeeMultiplier) } @@ -307,7 +307,7 @@ public extension StarknetAccountProtocol { /// - estimateUnitPriceMultiplier: multiplier for the estimated unit price. /// /// - Returns: InvokeTransactionResponse, containing transaction hash of submitted transaction. - func executeV3(call: StarknetCall, estimateAmountMultiplier: Double, estimateUnitPriceMultiplier: Double) async throws -> StarknetInvokeTransactionResponse { + func executeV3(call: StarknetCall, estimateAmountMultiplier: Double, estimateUnitPriceMultiplier: Double) async throws -> StarknetRequest { try await executeV3(calls: [call], estimateAmountMultiplier: estimateAmountMultiplier, estimateUnitPriceMultiplier: estimateUnitPriceMultiplier) } @@ -317,7 +317,7 @@ public extension StarknetAccountProtocol { /// - call: a call to be executed. /// /// - Returns: InvokeTransactionResponse, containing transaction hash of submitted transaction. - func executeV1(call: StarknetCall) async throws -> StarknetInvokeTransactionResponse { + func executeV1(call: StarknetCall) async throws -> StarknetRequest { try await executeV1(calls: [call]) } @@ -327,7 +327,7 @@ public extension StarknetAccountProtocol { /// - call: a call to be executed. /// /// - Returns: InvokeTransactionResponse, containing transaction hash of submitted transaction. - func executeV3(call: StarknetCall) async throws -> StarknetInvokeTransactionResponse { + func executeV3(call: StarknetCall) async throws -> StarknetRequest { try await executeV3(calls: [call]) } @@ -338,7 +338,7 @@ public extension StarknetAccountProtocol { /// - nonce: nonce of the account. /// - Returns: struct containing fee estimate - func estimateFeeV1(calls: [StarknetCall], nonce: Felt) async throws -> StarknetFeeEstimate { + func estimateFeeV1(calls: [StarknetCall], nonce: Felt) async throws -> StarknetRequest<[StarknetFeeEstimate]> { try await estimateFeeV1(calls: calls, nonce: nonce, skipValidate: false) } @@ -349,7 +349,7 @@ public extension StarknetAccountProtocol { /// - nonce: nonce of the account. /// - Returns: struct containing fee estimate - func estimateFeeV3(calls: [StarknetCall], nonce: Felt) async throws -> StarknetFeeEstimate { + func estimateFeeV3(calls: [StarknetCall], nonce: Felt) async throws -> StarknetRequest<[StarknetFeeEstimate]> { try await estimateFeeV3(calls: calls, nonce: nonce, skipValidate: false) } @@ -360,8 +360,8 @@ public extension StarknetAccountProtocol { /// - skipValidate: flag indicating whether validation of the transaction should be skipped. /// - Returns: struct containing fee estimate - func estimateFeeV1(calls: [StarknetCall], skipValidate: Bool = false) async throws -> StarknetFeeEstimate { - let nonce = try await getNonce() + func estimateFeeV1(calls: [StarknetCall], skipValidate: Bool = false) async throws -> StarknetRequest<[StarknetFeeEstimate]> { + let nonce = try await getNonce().send() return try await estimateFeeV1(calls: calls, nonce: nonce, skipValidate: skipValidate) } @@ -372,8 +372,8 @@ public extension StarknetAccountProtocol { /// - skipValidate: flag indicating whether validation of the transaction should be skipped. /// /// - Returns: struct containing fee estimate - func estimateFeeV3(calls: [StarknetCall], skipValidate: Bool = false) async throws -> StarknetFeeEstimate { - let nonce = try await getNonce() + func estimateFeeV3(calls: [StarknetCall], skipValidate: Bool = false) async throws -> StarknetRequest<[StarknetFeeEstimate]> { + let nonce = try await getNonce().send() return try await estimateFeeV3(calls: calls, nonce: nonce, skipValidate: skipValidate) } @@ -385,7 +385,7 @@ public extension StarknetAccountProtocol { /// - skipValidate: flag indicating whether validation of the transaction should be skipped. /// /// - Returns: struct containing fee estimate - func estimateFeeV1(call: StarknetCall, nonce: Felt, skipValidate: Bool = false) async throws -> StarknetFeeEstimate { + func estimateFeeV1(call: StarknetCall, nonce: Felt, skipValidate: Bool = false) async throws -> StarknetRequest<[StarknetFeeEstimate]> { try await estimateFeeV1(calls: [call], nonce: nonce, skipValidate: skipValidate) } @@ -397,7 +397,7 @@ public extension StarknetAccountProtocol { /// - skipValidate: flag indicating whether validation of the transaction should be skipped. /// /// - Returns: struct containing fee estimate - func estimateFeeV3(call: StarknetCall, nonce: Felt, skipValidate: Bool = false) async throws -> StarknetFeeEstimate { + func estimateFeeV3(call: StarknetCall, nonce: Felt, skipValidate: Bool = false) async throws -> StarknetRequest<[StarknetFeeEstimate]> { try await estimateFeeV3(calls: [call], nonce: nonce, skipValidate: skipValidate) } @@ -408,7 +408,7 @@ public extension StarknetAccountProtocol { /// - skipValidate: flag indicating whether validation of the transaction should be skipped. /// /// - Returns: struct containing fee estimate - func estimateFeeV1(call: StarknetCall, skipValidate: Bool = false) async throws -> StarknetFeeEstimate { + func estimateFeeV1(call: StarknetCall, skipValidate: Bool = false) async throws -> StarknetRequest<[StarknetFeeEstimate]> { try await estimateFeeV1(calls: [call], skipValidate: skipValidate) } @@ -419,7 +419,7 @@ public extension StarknetAccountProtocol { /// - skipValidate: flag indicating whether validation of the transaction should be skipped. /// /// - Returns: struct containing fee estimate - func estimateFeeV3(call: StarknetCall, skipValidate: Bool = false) async throws -> StarknetFeeEstimate { + func estimateFeeV3(call: StarknetCall, skipValidate: Bool = false) async throws -> StarknetRequest<[StarknetFeeEstimate]> { try await estimateFeeV3(calls: [call], skipValidate: skipValidate) } @@ -432,7 +432,7 @@ public extension StarknetAccountProtocol { /// - nonce: nonce of the account to be deployed /// /// - Returns: struct containing fee estimate - func estimateDeployAccountFeeV1(classHash: Felt, calldata: StarknetCalldata, salt: Felt, nonce: Felt = .zero) async throws -> StarknetFeeEstimate { + func estimateDeployAccountFeeV1(classHash: Felt, calldata: StarknetCalldata, salt: Felt, nonce: Felt = .zero) async throws -> StarknetRequest<[StarknetFeeEstimate]> { try await estimateDeployAccountFeeV1(classHash: classHash, calldata: calldata, salt: salt, nonce: nonce, skipValidate: false) } @@ -445,7 +445,7 @@ public extension StarknetAccountProtocol { /// - nonce: nonce of the account to be deployed /// /// - Returns: struct containing fee estimate - func estimateDeployAccountFeeV3(classHash: Felt, calldata: StarknetCalldata, salt: Felt, nonce: Felt = .zero) async throws -> StarknetFeeEstimate { + func estimateDeployAccountFeeV3(classHash: Felt, calldata: StarknetCalldata, salt: Felt, nonce: Felt = .zero) async throws -> StarknetRequest<[StarknetFeeEstimate]> { try await estimateDeployAccountFeeV3(classHash: classHash, calldata: calldata, salt: salt, nonce: nonce, skipValidate: false) } } diff --git a/Sources/Starknet/Data/Transaction/TransactionReceiptWrapper.swift b/Sources/Starknet/Data/Transaction/TransactionReceiptWrapper.swift index e8991ed06..6603136af 100644 --- a/Sources/Starknet/Data/Transaction/TransactionReceiptWrapper.swift +++ b/Sources/Starknet/Data/Transaction/TransactionReceiptWrapper.swift @@ -1,6 +1,6 @@ import Foundation -enum TransactionReceiptWrapper: Decodable { +public enum TransactionReceiptWrapper: Decodable { fileprivate enum Keys: String, CodingKey { case type } diff --git a/Sources/Starknet/Data/Transaction/TransactionWrapper.swift b/Sources/Starknet/Data/Transaction/TransactionWrapper.swift index 0e53d7fe5..40277f46d 100644 --- a/Sources/Starknet/Data/Transaction/TransactionWrapper.swift +++ b/Sources/Starknet/Data/Transaction/TransactionWrapper.swift @@ -1,7 +1,7 @@ import Foundation /// Transaction wrapper used for decoding polymorphic StarknetTransaction -enum TransactionWrapper: Decodable { +public enum TransactionWrapper: Decodable { fileprivate enum Keys: String, CodingKey { case type case version diff --git a/Sources/Starknet/Network/HttpNetworkProvider.swift b/Sources/Starknet/Network/HttpNetworkProvider.swift index f9d2a157f..e1dff05dc 100644 --- a/Sources/Starknet/Network/HttpNetworkProvider.swift +++ b/Sources/Starknet/Network/HttpNetworkProvider.swift @@ -47,10 +47,9 @@ class HttpNetworkProvider { return request } - func send(payload: some Encodable, config: Configuration, receive _: U.Type) async throws -> U where U: Decodable { - let encoded: Data + private func encodePayload(payload: any Encodable) throws -> Data { do { - encoded = try JSONEncoder().encode(payload) + return try JSONEncoder().encode(payload) } catch { if let encodingError = error as? EncodingError { throw HttpNetworkProviderError.encodingError(encodingError) @@ -58,6 +57,20 @@ class HttpNetworkProvider { throw HttpNetworkProviderError.unknownError } } + } + + private func handleResponseError(_ error: Error, _ response: URLResponse) throws -> Never { + if let response = response as? HTTPURLResponse, response.statusCode < 200 || response.statusCode > 299 { + throw HttpNetworkProviderError.requestRejected + } else if let decodingError = error as? DecodingError { + throw HttpNetworkProviderError.decodingError(decodingError) + } else { + throw HttpNetworkProviderError.unknownError + } + } + + func send(payload: JsonRpcPayload, config: Configuration, receive _: U.Type) async throws -> U where U: Decodable { + let encoded: Data = try encodePayload(payload: payload) let request = makeRequestWith(body: encoded, config: config) @@ -69,13 +82,24 @@ class HttpNetworkProvider { let result = try JSONDecoder().decode(U.self, from: data) return result } catch { - if let response = response as? HTTPURLResponse, response.statusCode < 200 || response.statusCode > 299 { - throw HttpNetworkProviderError.requestRejected - } else if let decodingError = error as? DecodingError { - throw HttpNetworkProviderError.decodingError(decodingError) - } else { - throw HttpNetworkProviderError.unknownError - } + try handleResponseError(error, response) + } + } + + func send(payload: [JsonRpcPayload], config: Configuration, receive _: [U.Type]) async throws -> [U] where U: Decodable { + let encoded: Data = try encodePayload(payload: payload) + + let request = makeRequestWith(body: encoded, config: config) + + guard let (data, response) = try? await session.data(for: request) else { + throw HttpNetworkProviderError.unknownError + } + + do { + let result = try JSONDecoder().decode([U].self, from: data) + return result + } catch { + try handleResponseError(error, response) } } } diff --git a/Sources/Starknet/Network/StarknetBatchRequest.swift b/Sources/Starknet/Network/StarknetBatchRequest.swift new file mode 100644 index 000000000..22214fd28 --- /dev/null +++ b/Sources/Starknet/Network/StarknetBatchRequest.swift @@ -0,0 +1,30 @@ +public struct StarknetBatchRequest { + let rpcPayloads: [JsonRpcPayload] + let config: HttpNetworkProvider.Configuration + let networkProvider: HttpNetworkProvider + + public func send() async throws -> [Result] { + let rpcResponses = try await networkProvider.send( + payload: rpcPayloads, + config: config, + receive: [JsonRpcResponse.self] + ) + + return orderRpcResults(rpcResponses: rpcResponses) + } +} + +func orderRpcResults(rpcResponses: [JsonRpcResponse]) -> [Result] { + var orderedRpcResults: [Result?] = Array(repeating: nil, count: rpcResponses.count) + for rpcResponse in rpcResponses { + if let result = rpcResponse.result { + orderedRpcResults[rpcResponse.id] = .success(result) + } else if let error = rpcResponse.error { + orderedRpcResults[rpcResponse.id] = .failure(StarknetProviderError.jsonRpcError(error.code, error.message, error.data)) + } else { + orderedRpcResults[rpcResponse.id] = .failure(StarknetProviderError.unknownError) + } + } + + return orderedRpcResults.compactMap { $0 } +} diff --git a/Sources/Starknet/Network/StarknetRequest.swift b/Sources/Starknet/Network/StarknetRequest.swift new file mode 100644 index 000000000..92b0d1932 --- /dev/null +++ b/Sources/Starknet/Network/StarknetRequest.swift @@ -0,0 +1,25 @@ +public struct StarknetRequest { + let method: JsonRpcMethod + let params: JsonRpcParams + let config: HttpNetworkProvider.Configuration + let networkProvider: HttpNetworkProvider + + public func send() async throws -> U { + let rpcPayload = JsonRpcPayload(method: method, params: params) + let response = try await networkProvider.send( + payload: rpcPayload, + config: config, + receive: JsonRpcResponse.self + ) + + if let error = response.error { + throw StarknetProviderError.jsonRpcError(error.code, error.message, error.data) + } + + guard let result = response.result else { + throw StarknetProviderError.unknownError + } + + return result + } +} diff --git a/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift b/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift index e62b50143..e8c3e4eeb 100644 --- a/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift +++ b/Sources/Starknet/Providers/StarknetProvider/JsonRpcParams.swift @@ -162,3 +162,58 @@ struct SimulateTransactionsParams: Encodable { case simulationFlags = "simulation_flags" } } + +enum JsonRpcParams { + case getNonce(GetNonceParams) + case addInvokeTransaction(AddInvokeTransactionParams) + case emptySequence(EmptySequence) + case empty(EmptyParams) + case call(CallParams) + case estimateFee(EstimateFeeParams) + case estimateMessageFee(EstimateMessageFeeParams) + case addDeployAccountTransaction(AddDeployAccountTransactionParams) + case getClassHashAt(GetClassHashAtParams) + case getEvents(GetEventsPayload) + case getTransactionByHash(GetTransactionByHashParams) + case getTransactionByBlockIdAndIndex(GetTransactionByBlockIdAndIndex) + case getTransactionReceipt(GetTransactionReceiptPayload) + case getTransactionStatus(GetTransactionStatusPayload) + case simulateTransactions(SimulateTransactionsParams) +} + +extension JsonRpcParams: Encodable { + func encode(to encoder: Encoder) throws { + switch self { + case let .getNonce(params): + try params.encode(to: encoder) + case let .addInvokeTransaction(params): + try params.encode(to: encoder) + case let .emptySequence(params): + try params.encode(to: encoder) + case let .empty(params): + try params.encode(to: encoder) + case let .call(params): + try params.encode(to: encoder) + case let .estimateFee(params): + try params.encode(to: encoder) + case let .estimateMessageFee(params): + try params.encode(to: encoder) + case let .addDeployAccountTransaction(params): + try params.encode(to: encoder) + case let .getClassHashAt(params): + try params.encode(to: encoder) + case let .getEvents(params): + try params.encode(to: encoder) + case let .getTransactionByHash(params): + try params.encode(to: encoder) + case let .getTransactionByBlockIdAndIndex(params): + try params.encode(to: encoder) + case let .getTransactionReceipt(params): + try params.encode(to: encoder) + case let .getTransactionStatus(params): + try params.encode(to: encoder) + case let .simulateTransactions(params): + try params.encode(to: encoder) + } + } +} diff --git a/Sources/Starknet/Providers/StarknetProvider/JsonRpcPayload.swift b/Sources/Starknet/Providers/StarknetProvider/JsonRpcPayload.swift index a5a78da5e..a268f8f60 100644 --- a/Sources/Starknet/Providers/StarknetProvider/JsonRpcPayload.swift +++ b/Sources/Starknet/Providers/StarknetProvider/JsonRpcPayload.swift @@ -1,15 +1,16 @@ import Foundation -struct JsonRpcPayload: Encodable { +struct JsonRpcPayload: Encodable { let version = "2.0" - let id = 0 + let id: Int let method: JsonRpcMethod - let params: T + let params: JsonRpcParams - init(method: JsonRpcMethod, params: T) { + init(method: JsonRpcMethod, params: JsonRpcParams, id: Int = 0) { self.method = method self.params = params + self.id = id } enum CodingKeys: String, CodingKey { diff --git a/Sources/Starknet/Providers/StarknetProvider/StarknetProvider.swift b/Sources/Starknet/Providers/StarknetProvider/StarknetProvider.swift index fa5958125..3e2d1ff4b 100644 --- a/Sources/Starknet/Providers/StarknetProvider/StarknetProvider.swift +++ b/Sources/Starknet/Providers/StarknetProvider/StarknetProvider.swift @@ -4,6 +4,7 @@ public enum StarknetProviderError: Error { case networkProviderError case unknownError case jsonRpcError(Int, String, String?) + case emptyBatchRequestError } public class StarknetProvider: StarknetProviderProtocol { @@ -34,163 +35,140 @@ public class StarknetProvider: StarknetProviderProtocol { self.init(url: url, urlSession: urlSession) } - private func makeRequest(method: JsonRpcMethod, params: some Encodable = EmptyParams(), receive _: U.Type) async throws -> U where U: Decodable { - let rpcPayload = JsonRpcPayload(method: method, params: params) - - var response: JsonRpcResponse - - let config = HttpNetworkProvider.Configuration(url: url, method: "POST", params: [ - (header: "Content-Type", value: "application/json"), - (header: "Accept", value: "application/json"), - ]) + private func buildRequest(method: JsonRpcMethod, params: JsonRpcParams) -> StarknetRequest { + let config = prepareHttpRequestConfiguration() + return StarknetRequest(method: method, params: params, config: config, networkProvider: networkProvider) + } - do { - response = try await networkProvider.send(payload: rpcPayload, config: config, receive: JsonRpcResponse.self) - } catch let error as HttpNetworkProviderError { - throw error - } catch { - throw StarknetProviderError.unknownError + public func batchRequests(requests: [StarknetRequest]) throws -> StarknetBatchRequest { + guard !requests.isEmpty else { + throw StarknetProviderError.emptyBatchRequestError } - if let result = response.result { - return result - } else if let error = response.error { - throw StarknetProviderError.jsonRpcError(error.code, error.message, error.data) - } else { - throw StarknetProviderError.unknownError + let rpcPayloads = requests.enumerated().map { index, request in + JsonRpcPayload(method: request.method, params: request.params, id: index) } + let config = prepareHttpRequestConfiguration() + + return StarknetBatchRequest(rpcPayloads: rpcPayloads, config: config, networkProvider: networkProvider) } - public func specVersion() async throws -> String { - let params = EmptySequence() + public func batchRequests(requests: StarknetRequest...) throws -> StarknetBatchRequest { + try batchRequests(requests: requests) + } - let result = try await makeRequest(method: .specVersion, params: params, receive: String.self) + public func getSpecVersion() -> StarknetRequest { + let params = EmptyParams() - return result + return buildRequest(method: .specVersion, params: .empty(params)) } - public func callContract(_ call: StarknetCall, at blockId: StarknetBlockId) async throws -> [Felt] { + public func callContract(_ call: StarknetCall, at blockId: StarknetBlockId) -> StarknetRequest<[Felt]> { let params = CallParams(request: call, blockId: blockId) - let result = try await makeRequest(method: .call, params: params, receive: [Felt].self) - - return result + return buildRequest(method: .call, params: .call(params)) } - public func estimateMessageFee(_ message: StarknetMessageFromL1, at blockId: StarknetBlockId) async throws -> StarknetFeeEstimate { + public func estimateMessageFee(_ message: StarknetMessageFromL1, at blockId: StarknetBlockId) -> StarknetRequest { let params = EstimateMessageFeeParams(message: message, blockId: blockId) - let result = try await makeRequest(method: .estimateMessageFee, params: params, receive: StarknetFeeEstimate.self) - - return result + return buildRequest(method: .estimateMessageFee, params: .estimateMessageFee(params)) } - public func estimateFee(for transactions: [any StarknetExecutableTransaction], at blockId: StarknetBlockId, simulationFlags: Set) async throws -> [StarknetFeeEstimate] { + public func estimateFee(for transactions: [any StarknetExecutableTransaction], at blockId: StarknetBlockId, simulationFlags: Set) -> StarknetRequest<[StarknetFeeEstimate]> { let params = EstimateFeeParams(request: transactions, simulationFlags: simulationFlags, blockId: blockId) - let result = try await makeRequest(method: .estimateFee, params: params, receive: [StarknetFeeEstimate].self) - return result + return buildRequest(method: .estimateFee, params: .estimateFee(params)) } - public func getNonce(of contract: Felt, at blockId: StarknetBlockId) async throws -> Felt { + public func getNonce(of contract: Felt, at blockId: StarknetBlockId) -> StarknetRequest { let params = GetNonceParams(contractAddress: contract, blockId: blockId) - let result = try await makeRequest(method: .getNonce, params: params, receive: Felt.self) - - return result + return buildRequest(method: .getNonce, params: .getNonce(params)) } - public func addInvokeTransaction(_ transaction: any StarknetExecutableInvokeTransaction) async throws -> StarknetInvokeTransactionResponse { + public func addInvokeTransaction(_ transaction: any StarknetExecutableInvokeTransaction) -> StarknetRequest { let params = AddInvokeTransactionParams(invokeTransaction: transaction) - let result = try await makeRequest(method: .invokeFunction, params: params, receive: StarknetInvokeTransactionResponse.self) - - return result + return buildRequest(method: .invokeFunction, params: .addInvokeTransaction(params)) } - public func addDeployAccountTransaction(_ transaction: any StarknetExecutableDeployAccountTransaction) async throws -> StarknetDeployAccountResponse { + public func addDeployAccountTransaction(_ transaction: any StarknetExecutableDeployAccountTransaction) -> StarknetRequest { let params = AddDeployAccountTransactionParams(deployAccountTransaction: transaction) - let result = try await makeRequest(method: .deployAccount, params: params, receive: StarknetDeployAccountResponse.self) - - return result + return buildRequest(method: .deployAccount, params: .addDeployAccountTransaction(params)) } - public func getClassHashAt(_ address: Felt, at blockId: StarknetBlockId) async throws -> Felt { + public func getClassHashAt(_ address: Felt, at blockId: StarknetBlockId) -> StarknetRequest { let params = GetClassHashAtParams(contractAddress: address, blockId: blockId) - let result = try await makeRequest(method: .getClassHashAt, params: params, receive: Felt.self) - - return result + return buildRequest(method: .getClassHashAt, params: .getClassHashAt(params)) } - public func getBlockNumber() async throws -> UInt64 { + public func getBlockNumber() -> StarknetRequest { let params = EmptySequence() - let result = try await makeRequest(method: .getBlockNumber, params: params, receive: UInt64.self) - return result + return buildRequest(method: .getBlockNumber, params: .emptySequence(params)) } - public func getBlockHashAndNumber() async throws -> StarknetBlockHashAndNumber { + public func getBlockHashAndNumber() -> StarknetRequest { let params = EmptySequence() - let result = try await makeRequest(method: .getBlockHashAndNumber, params: params, receive: StarknetBlockHashAndNumber.self) - return result + return buildRequest(method: .getBlockHashAndNumber, params: .emptySequence(params)) } - public func getEvents(filter: StarknetGetEventsFilter) async throws -> StarknetGetEventsResponse { + public func getEvents(filter: StarknetGetEventsFilter) -> StarknetRequest { let params = GetEventsPayload(filter: filter) - let result = try await makeRequest(method: .getEvents, params: params, receive: StarknetGetEventsResponse.self) - - return result + return buildRequest(method: .getEvents, params: .getEvents(params)) } - public func getTransactionBy(hash: Felt) async throws -> any StarknetTransaction { + public func getTransactionBy(hash: Felt) -> StarknetRequest { let params = GetTransactionByHashParams(hash: hash) - let result = try await makeRequest(method: .getTransactionByHash, params: params, receive: TransactionWrapper.self) - - return result.transaction + return buildRequest(method: .getTransactionByHash, params: .getTransactionByHash(params)) } - public func getTransactionBy(blockId: StarknetBlockId, index: UInt64) async throws -> any StarknetTransaction { + public func getTransactionBy(blockId: StarknetBlockId, index: UInt64) -> StarknetRequest { let params = GetTransactionByBlockIdAndIndex(blockId: blockId, index: index) - let result = try await makeRequest(method: .getTransactionByBlockIdAndIndex, params: params, receive: TransactionWrapper.self) - - return result.transaction + return buildRequest(method: .getTransactionByBlockIdAndIndex, params: .getTransactionByBlockIdAndIndex(params)) } - public func getTransactionReceiptBy(hash: Felt) async throws -> any StarknetTransactionReceipt { + public func getTransactionReceiptBy(hash: Felt) -> StarknetRequest { let params = GetTransactionReceiptPayload(transactionHash: hash) - let result = try await makeRequest(method: .getTransactionReceipt, params: params, receive: TransactionReceiptWrapper.self) - - return result.transactionReceipt + return buildRequest(method: .getTransactionReceipt, params: .getTransactionReceipt(params)) } - public func getTransactionStatusBy(hash: Felt) async throws -> StarknetGetTransactionStatusResponse { + public func getTransactionStatusBy(hash: Felt) -> StarknetRequest { let params = GetTransactionStatusPayload(transactionHash: hash) - let result = try await makeRequest(method: .getTransactionStatus, params: params, receive: StarknetGetTransactionStatusResponse.self) - - return result + return buildRequest(method: .getTransactionStatus, params: .getTransactionStatus(params)) } - public func getChainId() async throws -> StarknetChainId { + public func getChainId() -> StarknetRequest { let params = EmptySequence() - let result = try await makeRequest(method: .getChainId, params: params, receive: StarknetChainId.self) - - return result + return buildRequest(method: .getChainId, params: .emptySequence(params)) } - public func simulateTransactions(_ transactions: [any StarknetExecutableTransaction], at blockId: StarknetBlockId, simulationFlags: Set) async throws -> [StarknetSimulatedTransaction] { + public func simulateTransactions(_ transactions: [any StarknetExecutableTransaction], at blockId: StarknetBlockId, simulationFlags: Set) -> StarknetRequest<[StarknetSimulatedTransaction]> { let params = SimulateTransactionsParams(transactions: transactions, blockId: blockId, simulationFlags: simulationFlags) - let result = try await makeRequest(method: .simulateTransactions, params: params, receive: [StarknetSimulatedTransaction].self) + return buildRequest(method: .simulateTransactions, params: .simulateTransactions(params)) + } +} - return result +private extension StarknetProvider { + private func prepareHttpRequestConfiguration() -> HttpNetworkProvider.Configuration { + HttpNetworkProvider.Configuration( + url: url, + method: "POST", + params: [ + (header: "Content-Type", value: "application/json"), + (header: "Accept", value: "application/json"), + ] + ) } } diff --git a/Sources/Starknet/Providers/StarknetProviderProtocol.swift b/Sources/Starknet/Providers/StarknetProviderProtocol.swift index 7ff4e7fae..526297bd4 100644 --- a/Sources/Starknet/Providers/StarknetProviderProtocol.swift +++ b/Sources/Starknet/Providers/StarknetProviderProtocol.swift @@ -5,7 +5,7 @@ public protocol StarknetProviderProtocol { /// Get the version of the Starknet JSON-RPC specification being used by the node. /// /// - Returns: the version of the Starknet JSON-RPC specification being used. - func specVersion() async throws -> String + func getSpecVersion() -> StarknetRequest /// Call starknet contract. /// @@ -14,7 +14,7 @@ public protocol StarknetProviderProtocol { /// - blockId: hash, number, or tag of a block at which the call should be made. /// /// - Returns: Array of field elements, returned by called contract. - func callContract(_ call: StarknetCall, at blockId: StarknetBlockId) async throws -> [Felt] + func callContract(_ call: StarknetCall, at blockId: StarknetBlockId) -> StarknetRequest<[Felt]> /// Get nonce of given starknet contract /// @@ -23,7 +23,7 @@ public protocol StarknetProviderProtocol { /// - blockId: hash, number, or tag of a block at which the call should be made. /// /// - Returns: Felt value of contract's current nonce. - func getNonce(of contract: Felt, at blockId: StarknetBlockId) async throws -> Felt + func getNonce(of contract: Felt, at blockId: StarknetBlockId) -> StarknetRequest /// Estimate fee for a transaction. /// @@ -33,7 +33,7 @@ public protocol StarknetProviderProtocol { /// - simulationFlags: a set of simulation flags. /// /// - Returns: Array of fee estimates - func estimateFee(for transactions: [any StarknetExecutableTransaction], at blockId: StarknetBlockId, simulationFlags: Set) async throws -> [StarknetFeeEstimate] + func estimateFee(for transactions: [any StarknetExecutableTransaction], at blockId: StarknetBlockId, simulationFlags: Set) -> StarknetRequest<[StarknetFeeEstimate]> /// Estimate the L2 fee of a message sent on L1 /// @@ -42,7 +42,7 @@ public protocol StarknetProviderProtocol { /// - blockId: hash, numer, or tag of a block for which the estimation should be made. /// /// - Returns: the fee estimation - func estimateMessageFee(_ message: StarknetMessageFromL1, at blockId: StarknetBlockId) async throws -> StarknetFeeEstimate + func estimateMessageFee(_ message: StarknetMessageFromL1, at blockId: StarknetBlockId) -> StarknetRequest /// Invoke a function. /// @@ -52,7 +52,7 @@ public protocol StarknetProviderProtocol { /// - payload: invoke function payload. /// /// - Returns: transaction hash of invoked transaction. - func addInvokeTransaction(_ transaction: any StarknetExecutableInvokeTransaction) async throws -> StarknetInvokeTransactionResponse + func addInvokeTransaction(_ transaction: any StarknetExecutableInvokeTransaction) -> StarknetRequest /// Deploy account /// @@ -62,7 +62,7 @@ public protocol StarknetProviderProtocol { /// - transaction: deploy account transaction to be executed /// /// - Returns: transaction hash and contract address of deployed account - func addDeployAccountTransaction(_ transaction: any StarknetExecutableDeployAccountTransaction) async throws -> StarknetDeployAccountResponse + func addDeployAccountTransaction(_ transaction: any StarknetExecutableDeployAccountTransaction) -> StarknetRequest /// Get the contract class hash for the contract deployed at the given address. /// @@ -71,17 +71,17 @@ public protocol StarknetProviderProtocol { /// - blockId: id of the requested block /// /// - Returns: Class hash of the given contract - func getClassHashAt(_ address: Felt, at blockId: StarknetBlockId) async throws -> Felt + func getClassHashAt(_ address: Felt, at blockId: StarknetBlockId) -> StarknetRequest /// Get the most recent accepted block number. /// /// - Returns: Number of the most recent accepted block - func getBlockNumber() async throws -> UInt64 + func getBlockNumber() -> StarknetRequest /// Get the most recent accepted block hash and number. /// /// - Returns: Block hash and block number of the most recent accepted block - func getBlockHashAndNumber() async throws -> StarknetBlockHashAndNumber + func getBlockHashAndNumber() -> StarknetRequest /// Get all event objects matching the conditions in the provided filter /// @@ -89,7 +89,7 @@ public protocol StarknetProviderProtocol { /// - filter : the conditions used to filter the returned events /// /// - Returns: events matching the conditions in the provided filter and continuation token - func getEvents(filter: StarknetGetEventsFilter) async throws -> StarknetGetEventsResponse + func getEvents(filter: StarknetGetEventsFilter) -> StarknetRequest /// Get the details and status of a submitted transaction /// @@ -97,7 +97,7 @@ public protocol StarknetProviderProtocol { /// - hash: The hash of the requested transaction /// /// - Returns: Transaction found with provided hash - func getTransactionBy(hash: Felt) async throws -> any StarknetTransaction + func getTransactionBy(hash: Felt) -> StarknetRequest /// Get the details and status of a submitted transaction /// @@ -106,7 +106,7 @@ public protocol StarknetProviderProtocol { /// - index: index of transaction in the block /// /// - Returns: Transaction found with provided blockId and index. - func getTransactionBy(blockId: StarknetBlockId, index: UInt64) async throws -> any StarknetTransaction + func getTransactionBy(blockId: StarknetBlockId, index: UInt64) -> StarknetRequest /// Get transaction receipt of a submitted transaction /// @@ -114,7 +114,7 @@ public protocol StarknetProviderProtocol { /// - hash : the hash of the requested transaction /// /// - Returns: receipt of a transaction identified by given hash - func getTransactionReceiptBy(hash: Felt) async throws -> any StarknetTransactionReceipt + func getTransactionReceiptBy(hash: Felt) -> StarknetRequest /// Get the status of a submitted transaction. /// @@ -122,12 +122,12 @@ public protocol StarknetProviderProtocol { /// - hash: The hash of the requested transaction /// /// - Returns: The status(es) of a transaction - func getTransactionStatusBy(hash: Felt) async throws -> StarknetGetTransactionStatusResponse + func getTransactionStatusBy(hash: Felt) -> StarknetRequest /// Get the currently configured Starknet chain id /// /// - Returns: The Starknet chain id - func getChainId() async throws -> StarknetChainId + func getChainId() -> StarknetRequest /// Simulate running a given list of transactions, and generate the execution trace /// @@ -137,7 +137,23 @@ public protocol StarknetProviderProtocol { /// - simulationFlags: a set of simulation flags /// /// - Returns: array of simulated transactions - func simulateTransactions(_ transactions: [any StarknetExecutableTransaction], at blockId: StarknetBlockId, simulationFlags: Set) async throws -> [StarknetSimulatedTransaction] + func simulateTransactions(_ transactions: [any StarknetExecutableTransaction], at blockId: StarknetBlockId, simulationFlags: Set) -> StarknetRequest<[StarknetSimulatedTransaction]> + + /// Batch multiple calls into a single RPC request + /// + /// - Parameters + /// - requests: list of requests to be batched together. + /// + /// - Returns: batch request. + func batchRequests(requests: [StarknetRequest]) throws -> StarknetBatchRequest + + /// Batch multiple calls into a single RPC request + /// + /// - Parameters + /// - requests: requests to be batched together. + /// + /// - Returns: batch request. + func batchRequests(requests: StarknetRequest...) throws -> StarknetBatchRequest } let defaultBlockId = StarknetBlockId.tag(.pending) @@ -149,8 +165,8 @@ public extension StarknetProviderProtocol { /// - call: starknet call to be made. /// /// - Returns: Array of field elements, returned by called contract. - func callContract(_ call: StarknetCall) async throws -> [Felt] { - try await callContract(call, at: defaultBlockId) + func callContract(_ call: StarknetCall) -> StarknetRequest<[Felt]> { + callContract(call, at: defaultBlockId) } /// Estimate fee for a list of transactions with default flags in the pending block. @@ -159,8 +175,8 @@ public extension StarknetProviderProtocol { /// - transactions: transactions for which the fees should be estimated. /// /// - Returns: Array of fee estimates - func estimateFee(for transactions: [any StarknetExecutableTransaction]) async throws -> [StarknetFeeEstimate] { - try await estimateFee(for: transactions, at: defaultBlockId, simulationFlags: defaultSimulationFlagsForEstimateFee) + func estimateFee(for transactions: [any StarknetExecutableTransaction]) -> StarknetRequest<[StarknetFeeEstimate]> { + estimateFee(for: transactions, at: defaultBlockId, simulationFlags: defaultSimulationFlagsForEstimateFee) } /// Estimate fee for a list of transactions with default flags. @@ -170,8 +186,8 @@ public extension StarknetProviderProtocol { /// - blockId: hash, numer, or tag of a block for which the estimation should be made. /// /// - Returns: Array of fee estimates - func estimateFee(for transactions: [any StarknetExecutableTransaction], at blockId: StarknetBlockId) async throws -> [StarknetFeeEstimate] { - try await estimateFee(for: transactions, at: blockId, simulationFlags: defaultSimulationFlagsForEstimateFee) + func estimateFee(for transactions: [any StarknetExecutableTransaction], at blockId: StarknetBlockId) -> StarknetRequest<[StarknetFeeEstimate]> { + estimateFee(for: transactions, at: blockId, simulationFlags: defaultSimulationFlagsForEstimateFee) } /// Estimate fee for a list of transactions in the pending block.. @@ -181,8 +197,8 @@ public extension StarknetProviderProtocol { /// - simulationFlags: a set of simulation flags /// /// - Returns: Array of fee estimates - func estimateFee(for transactions: [any StarknetExecutableTransaction], simulationFlags: Set) async throws -> [StarknetFeeEstimate] { - try await estimateFee(for: transactions, at: defaultBlockId, simulationFlags: simulationFlags) + func estimateFee(for transactions: [any StarknetExecutableTransaction], simulationFlags: Set) -> StarknetRequest<[StarknetFeeEstimate]> { + estimateFee(for: transactions, at: defaultBlockId, simulationFlags: simulationFlags) } /// Estimate fee for a single transaction with default flags in the pending block. @@ -191,8 +207,8 @@ public extension StarknetProviderProtocol { /// - transaction: transaction for which the fee should be estimated. /// /// - Returns: Fee estimate - func estimateFee(for transaction: any StarknetExecutableTransaction) async throws -> StarknetFeeEstimate { - try await estimateFee(for: [transaction])[0] + func estimateFee(for transaction: any StarknetExecutableTransaction) -> StarknetRequest<[StarknetFeeEstimate]> { + estimateFee(for: [transaction]) } /// Estimate fee for a single transaction with default flags. @@ -202,8 +218,8 @@ public extension StarknetProviderProtocol { /// - blockId: hash, numer, or tag of a block for which the estimation should be made. /// /// - Returns: Fee estimate - func estimateFee(for transaction: any StarknetExecutableTransaction, at blockId: StarknetBlockId) async throws -> StarknetFeeEstimate { - try await estimateFee(for: [transaction], at: blockId)[0] + func estimateFee(for transaction: any StarknetExecutableTransaction, at blockId: StarknetBlockId) -> StarknetRequest<[StarknetFeeEstimate]> { + estimateFee(for: [transaction], at: blockId) } /// Estimate fee for a single transaction in the pending block.. @@ -213,8 +229,8 @@ public extension StarknetProviderProtocol { /// - simulationFlags: a set of simulation flags /// /// - Returns: Fee estimate - func estimateFee(for transaction: any StarknetExecutableTransaction, simulationFlags: Set) async throws -> StarknetFeeEstimate { - try await estimateFee(for: [transaction], simulationFlags: simulationFlags)[0] + func estimateFee(for transaction: any StarknetExecutableTransaction, simulationFlags: Set) -> StarknetRequest<[StarknetFeeEstimate]> { + estimateFee(for: [transaction], simulationFlags: simulationFlags) } /// Estimate the L2 fee of a message sent on L1 @@ -223,8 +239,8 @@ public extension StarknetProviderProtocol { /// - message: the message's parameters /// /// - Returns: Fee estimate - func estimateMessageFee(_ message: StarknetMessageFromL1) async throws -> StarknetFeeEstimate { - try await estimateMessageFee(message, at: defaultBlockId) + func estimateMessageFee(_ message: StarknetMessageFromL1) -> StarknetRequest { + estimateMessageFee(message, at: defaultBlockId) } /// Get nonce of given starknet contract in the pending block. @@ -233,8 +249,8 @@ public extension StarknetProviderProtocol { /// - contract: address of a contract, for which the nonce should be returned. /// /// - Returns: Felt value of contract's current nonce. - func getNonce(of contract: Felt) async throws -> Felt { - try await getNonce(of: contract, at: defaultBlockId) + func getNonce(of contract: Felt) -> StarknetRequest { + getNonce(of: contract, at: defaultBlockId) } /// Get the contract class hash for the contract deployed at the given address. @@ -243,8 +259,8 @@ public extension StarknetProviderProtocol { /// - address: address of the contract whose address will be returned /// /// - Returns: Class hash of the given contract - func getClassHashAt(_ address: Felt) async throws -> Felt { - try await getClassHashAt(address, at: defaultBlockId) + func getClassHashAt(_ address: Felt) -> StarknetRequest { + getClassHashAt(address, at: defaultBlockId) } /// Simulate running a given list of transactions in the latest block, and generate the execution trace @@ -254,7 +270,7 @@ public extension StarknetProviderProtocol { /// - simulationFlags: a set of simulation flags /// /// - Returns : array of simulated transactions - func simulateTransactions(_ transactions: [any StarknetExecutableTransaction], simulationFlags: Set) async throws -> [StarknetSimulatedTransaction] { - try await simulateTransactions(transactions, at: defaultBlockId, simulationFlags: simulationFlags) + func simulateTransactions(_ transactions: [any StarknetExecutableTransaction], simulationFlags: Set) -> StarknetRequest<[StarknetSimulatedTransaction]> { + simulateTransactions(transactions, at: defaultBlockId, simulationFlags: simulationFlags) } } diff --git a/Tests/StarknetTests/Accounts/AccountTest.swift b/Tests/StarknetTests/Accounts/AccountTest.swift index 22620ad48..3c9159800 100644 --- a/Tests/StarknetTests/Accounts/AccountTest.swift +++ b/Tests/StarknetTests/Accounts/AccountTest.swift @@ -24,7 +24,7 @@ final class AccountTests: XCTestCase { ethContractAddress = Self.devnetClient.constants.ethErc20ContractAddress let accountDetails = Self.devnetClient.constants.predeployedAccount1 signer = StarkCurveSigner(privateKey: accountDetails.privateKey)! - chainId = try await provider.getChainId() + chainId = try await provider.getChainId().send() account = StarknetAccount(address: accountDetails.address, signer: signer, provider: provider, chainId: chainId, cairoVersion: .one) } @@ -56,7 +56,7 @@ final class AccountTests: XCTestCase { let call = StarknetCall(contractAddress: ethContractAddress, entrypoint: starknetSelector(from: "transfer"), calldata: calldata) - let result = try await account.executeV1(call: call) + let result = try await account.executeV1(call: call).send() try await Self.devnetClient.assertTransactionSucceeded(transactionHash: result.transactionHash) } @@ -72,7 +72,7 @@ final class AccountTests: XCTestCase { let call = StarknetCall(contractAddress: ethContractAddress, entrypoint: starknetSelector(from: "transfer"), calldata: calldata) - let result = try await account.executeV3(calls: [call]) + let result = try await account.executeV3(calls: [call]).send() try await Self.devnetClient.assertTransactionSucceeded(transactionHash: result.transactionHash) } @@ -83,10 +83,10 @@ final class AccountTests: XCTestCase { let calldata: [Felt] = [recipientAddress, 1000, 0] let call = StarknetCall(contractAddress: ethContractAddress, entrypoint: starknetSelector(from: "transfer"), calldata: calldata) - let result = try await account.executeV1(call: call, estimateFeeMultiplier: 1.8) + let result = try await account.executeV1(call: call, estimateFeeMultiplier: 1.8).send() try await Self.devnetClient.assertTransactionSucceeded(transactionHash: result.transactionHash) - let result2 = try await account.executeV1(call: call, estimateFeeMultiplier: 0.9) + let result2 = try await account.executeV1(call: call, estimateFeeMultiplier: 0.9).send() try await Self.devnetClient.assertTransactionFailed(transactionHash: result2.transactionHash) } @@ -96,10 +96,10 @@ final class AccountTests: XCTestCase { let calldata: [Felt] = [recipientAddress, 1000, 0] let call = StarknetCall(contractAddress: ethContractAddress, entrypoint: starknetSelector(from: "transfer"), calldata: calldata) - let result = try await account.executeV3(call: call, estimateAmountMultiplier: 1.8, estimateUnitPriceMultiplier: 1.8) + let result = try await account.executeV3(call: call, estimateAmountMultiplier: 1.8, estimateUnitPriceMultiplier: 1.8).send() try await Self.devnetClient.assertTransactionSucceeded(transactionHash: result.transactionHash) - let result2 = try await account.executeV3(call: call, estimateAmountMultiplier: 0.9, estimateUnitPriceMultiplier: 1.0) + let result2 = try await account.executeV3(call: call, estimateAmountMultiplier: 0.9, estimateUnitPriceMultiplier: 1.0).send() try await Self.devnetClient.assertTransactionFailed(transactionHash: result2.transactionHash) } @@ -114,13 +114,13 @@ final class AccountTests: XCTestCase { let call = StarknetCall(contractAddress: ethContractAddress, entrypoint: starknetSelector(from: "transfer"), calldata: calldata) - let nonce = try await account.getNonce() - let feeEstimate = try await account.estimateFeeV1(call: call, nonce: nonce) + let nonce = try await account.getNonce().send() + let feeEstimate = try await account.estimateFeeV1(call: call, nonce: nonce).send()[0] let maxFee = feeEstimate.toMaxFee() let params = StarknetOptionalInvokeParamsV1(nonce: nonce, maxFee: maxFee) - let result = try await account.executeV1(call: call, params: params) + let result = try await account.executeV1(call: call, params: params).send() try await Self.devnetClient.assertTransactionSucceeded(transactionHash: result.transactionHash) } @@ -136,13 +136,13 @@ final class AccountTests: XCTestCase { let call = StarknetCall(contractAddress: ethContractAddress, entrypoint: starknetSelector(from: "transfer"), calldata: calldata) - let nonce = try await account.getNonce() - let feeEstimate = try await account.estimateFeeV3(call: call, nonce: nonce, skipValidate: false) + let nonce = try await account.getNonce().send() + let feeEstimate = try await account.estimateFeeV3(call: call, nonce: nonce, skipValidate: false).send()[0] let resourceBounds = feeEstimate.toResourceBounds() let params = StarknetOptionalInvokeParamsV3(nonce: nonce, l1ResourceBounds: resourceBounds.l1Gas) - let result = try await account.executeV3(calls: [call], params: params) + let result = try await account.executeV3(calls: [call], params: params).send() try await Self.devnetClient.assertTransactionSucceeded(transactionHash: result.transactionHash) } @@ -165,7 +165,7 @@ final class AccountTests: XCTestCase { let call1 = StarknetCall(contractAddress: ethContractAddress, entrypoint: starknetSelector(from: "transfer"), calldata: calldata1) let call2 = StarknetCall(contractAddress: AccountTests.devnetClient.constants.ethErc20ContractAddress, entrypoint: starknetSelector(from: "transfer"), calldata: calldata2) - let result = try await account.executeV1(calls: [call1, call2]) + let result = try await account.executeV1(calls: [call1, call2]).send() try await Self.devnetClient.assertTransactionSucceeded(transactionHash: result.transactionHash) } @@ -178,20 +178,20 @@ final class AccountTests: XCTestCase { try await Self.devnetClient.prefundAccount(address: newAccountAddress) - let nonce = await (try? newAccount.getNonce()) ?? .zero + let nonce = await (try? newAccount.getNonce().send()) ?? .zero - let feeEstimate = try await newAccount.estimateDeployAccountFeeV1(classHash: accountContractClassHash, calldata: [newPublicKey], salt: .zero, nonce: nonce, skipValidate: false) + let feeEstimate = try await newAccount.estimateDeployAccountFeeV1(classHash: accountContractClassHash, calldata: [newPublicKey], salt: .zero, nonce: nonce, skipValidate: false).send()[0] let maxFee = feeEstimate.toMaxFee() let params = StarknetDeployAccountParamsV1(nonce: nonce, maxFee: maxFee) let deployAccountTransaction = try newAccount.signDeployAccountV1(classHash: accountContractClassHash, calldata: [newPublicKey], salt: .zero, params: params, forFeeEstimation: false) - let response = try await provider.addDeployAccountTransaction(deployAccountTransaction) + let response = try await provider.addDeployAccountTransaction(deployAccountTransaction).send() try await Self.devnetClient.assertTransactionSucceeded(transactionHash: response.transactionHash) - let newNonce = try await newAccount.getNonce() + let newNonce = try await newAccount.getNonce().send() XCTAssertEqual(newNonce.value - nonce.value, Felt.one.value) } @@ -204,19 +204,19 @@ final class AccountTests: XCTestCase { try await Self.devnetClient.prefundAccount(address: newAccountAddress, unit: .fri) - let nonce = await (try? newAccount.getNonce()) ?? .zero + let nonce = await (try? newAccount.getNonce().send()) ?? .zero - let feeEstimate = try await newAccount.estimateDeployAccountFeeV3(classHash: accountContractClassHash, calldata: [newPublicKey], salt: .zero, nonce: nonce) + let feeEstimate = try await newAccount.estimateDeployAccountFeeV3(classHash: accountContractClassHash, calldata: [newPublicKey], salt: .zero, nonce: nonce).send()[0] let params = StarknetDeployAccountParamsV3(nonce: nonce, l1ResourceBounds: feeEstimate.toResourceBounds().l1Gas) let deployAccountTransaction = try newAccount.signDeployAccountV3(classHash: accountContractClassHash, calldata: [newPublicKey], salt: .zero, params: params, forFeeEstimation: false) - let response = try await provider.addDeployAccountTransaction(deployAccountTransaction) + let response = try await provider.addDeployAccountTransaction(deployAccountTransaction).send() try await Self.devnetClient.assertTransactionSucceeded(transactionHash: response.transactionHash) - let newNonce = try await newAccount.getNonce() + let newNonce = try await newAccount.getNonce().send() XCTAssertEqual(newNonce.value - nonce.value, Felt.one.value) } diff --git a/Tests/StarknetTests/Data/ExecutionTests.swift b/Tests/StarknetTests/Data/ExecutionTests.swift index f9813b10a..ba3385c55 100644 --- a/Tests/StarknetTests/Data/ExecutionTests.swift +++ b/Tests/StarknetTests/Data/ExecutionTests.swift @@ -20,7 +20,7 @@ final class ExecutionTests: XCTestCase { provider = StarknetProvider(url: Self.devnetClient.rpcUrl)! let accountDetails = ExecutionTests.devnetClient.constants.predeployedAccount1 signer = StarkCurveSigner(privateKey: accountDetails.privateKey)! - let chainId = try await provider.getChainId() + let chainId = try await provider.getChainId().send() account = StarknetAccount(address: accountDetails.address, signer: signer, provider: provider, chainId: chainId, cairoVersion: .one) balanceContractAddress = try await Self.devnetClient.declareDeployContract(contractName: "Balance").deploy.contractAddress } diff --git a/Tests/StarknetTests/Data/JsonRpcResponseTests.swift b/Tests/StarknetTests/Data/JsonRpcResponseTests.swift index c96422f4a..907095cdb 100644 --- a/Tests/StarknetTests/Data/JsonRpcResponseTests.swift +++ b/Tests/StarknetTests/Data/JsonRpcResponseTests.swift @@ -114,4 +114,39 @@ final class JsonRpcResponseTests: XCTestCase { let data = response.error!.data! XCTAssertEqual(data, "[\"More data about the execution failure.\",\"And even more data.\"]") } + + func testBatchResponseWithIncorrectOrder() throws { + let json = """ + [ + { + "id": 1, + "jsonrpc": "2.0", + "result": 222 + }, + { + "id": 2, + "jsonrpc": "2.0", + "result": 333 + }, + { + "id": 0, + "jsonrpc": "2.0", + "result": 111 + } + ] + """.data(using: .utf8)! + + let decoder = JSONDecoder() + let responses = try decoder.decode([JsonRpcResponse].self, from: json) + + XCTAssertNotNil(responses[0].result) + XCTAssertNotNil(responses[1].result) + XCTAssertNotNil(responses[2].result) + + let orderedResponses = orderRpcResults(rpcResponses: responses) + + XCTAssertEqual(try orderedResponses[0].get(), 111) + XCTAssertEqual(try orderedResponses[1].get(), 222) + XCTAssertEqual(try orderedResponses[2].get(), 333) + } } diff --git a/Tests/StarknetTests/Providers/ProviderTests.swift b/Tests/StarknetTests/Providers/ProviderTests.swift index 1c34785be..526bf16a1 100644 --- a/Tests/StarknetTests/Providers/ProviderTests.swift +++ b/Tests/StarknetTests/Providers/ProviderTests.swift @@ -35,11 +35,11 @@ final class ProviderTests: XCTestCase { let accountDetails = Self.devnetClient.constants.predeployedAccount2 signer = StarkCurveSigner(privateKey: accountDetails.privateKey)! - chainId = try await provider.getChainId() + chainId = try await provider.getChainId().send() account = StarknetAccount(address: accountDetails.address, signer: signer, provider: provider, chainId: chainId, cairoVersion: .one) } - func makeStarknetProvider(url: String) -> StarknetProviderProtocol { + func makeStarknetProvider(url: String) -> StarknetProvider { StarknetProvider(url: url)! } @@ -52,13 +52,15 @@ final class ProviderTests: XCTestCase { } func testGetChainId() async throws { - let chainId = try await provider.getChainId() + let chainId = try await provider.getChainId().send() XCTAssertEqual(chainId, .sepolia) } - func testSpecVersion() async throws { - let result = try await provider.specVersion() + func testGetSpecVersion() async throws { + let request = provider.getSpecVersion() + let result = try await request.send() + XCTAssertFalse(result.isEmpty) } @@ -70,7 +72,8 @@ final class ProviderTests: XCTestCase { ) do { - let result = try await provider.callContract(call) + let request = provider.callContract(call) + let result = try await request.send() XCTAssertEqual(result.count, 1) XCTAssertEqual(result[0], ProviderTests.devnetClient.constants.predeployedAccount1.publicKey) @@ -87,25 +90,25 @@ final class ProviderTests: XCTestCase { calldata: [Felt(2138)] ) - let result = try await provider.callContract(call) + let result = try await provider.callContract(call).send() XCTAssertEqual(result[0], Felt.zero) } func testGetNonce() async throws { - let nonce = try await provider.getNonce(of: ProviderTests.devnetClient.constants.predeployedAccount1.address) + let nonce = try await provider.getNonce(of: ProviderTests.devnetClient.constants.predeployedAccount1.address).send() print(nonce) } func testGetClassHash() async throws { - let classHash = try await provider.getClassHashAt(ethContractAddress) + let classHash = try await provider.getClassHashAt(ethContractAddress).send() print(classHash) } func testGetBlockNumber() async throws { - let blockNumber = try await provider.getBlockNumber() + let blockNumber = try await provider.getBlockNumber().send() print(blockNumber) } @@ -114,7 +117,7 @@ final class ProviderTests: XCTestCase { // Note to future developers experiencing failures in this test: // If there were no transactions, minting or other changes to the state of the network, // "Block not found" error is likely to occur - let result = try await provider.getBlockHashAndNumber() + let result = try await provider.getBlockHashAndNumber().send() print(result) } @@ -126,25 +129,25 @@ final class ProviderTests: XCTestCase { try await ProviderTests.devnetClient.assertTransactionSucceeded(transactionHash: invokeResult.transactionHash) let filter = StarknetGetEventsFilter(address: contract.deploy.contractAddress, keys: [["0x477e157efde59c5531277ede78acb3e03ef69508c6c35fde3495aa0671d227"]]) - let result = try await provider.getEvents(filter: filter) + let result = try await provider.getEvents(filter: filter).send() XCTAssertFalse(result.events.isEmpty) print(result) } func testGetTransactionByBlockIdAndHash() async throws { - let result = try await provider.getTransactionBy(blockId: .tag(.latest), index: 0) + let result = try await provider.getTransactionBy(blockId: .tag(.latest), index: 0).send() print(result) } func testGetTransactionByHash() async throws { - let previousResult = try await provider.getTransactionBy(blockId: .tag(.latest), index: 0) + let previousResult = try await provider.getTransactionBy(blockId: .tag(.latest), index: 0).send() - let _ = try await provider.getTransactionBy(hash: previousResult.hash!) + let _ = try await provider.getTransactionBy(hash: previousResult.transaction.hash!).send() do { - let _ = try await provider.getTransactionBy(hash: "0x123") + let _ = try await provider.getTransactionBy(hash: "0x123").send() XCTFail("Fetching transaction with nonexistent hash should fail") } catch {} } @@ -153,28 +156,28 @@ final class ProviderTests: XCTestCase { let contract = try await Self.devnetClient.declareDeployContract(contractName: "Balance", constructorCalldata: [1000]) let transactionHash = try await Self.devnetClient.invokeContract(contractAddress: contract.deploy.contractAddress, function: "increase_balance", calldata: [2137]).transactionHash - let result = try await provider.getTransactionBy(hash: transactionHash) - XCTAssertTrue(result.type == .invoke) + let result = try await provider.getTransactionBy(hash: transactionHash).send() + XCTAssertTrue(result.transaction.type == .invoke) } func testGetDeployAccountTransactionByHash() async throws { let account = try await ProviderTests.devnetClient.deployAccount(name: "provider_test") - let result = try await provider.getTransactionBy(hash: account.transactionHash) - XCTAssertTrue(result.type == .deployAccount) + let result = try await provider.getTransactionBy(hash: account.transactionHash).send() + XCTAssertTrue(result.transaction.type == .deployAccount) } func testGetDeclareTransactionByHash() async throws { let contract = try await ProviderTests.devnetClient.declareDeployContract(contractName: "Balance", constructorCalldata: [1000]) - let result = try await provider.getTransactionBy(hash: contract.declare.transactionHash) - XCTAssertTrue(result.type == .declare) + let result = try await provider.getTransactionBy(hash: contract.declare.transactionHash).send() + XCTAssertTrue(result.transaction.type == .declare) } func testGetTransactionStatus() async throws { let contract = try await ProviderTests.devnetClient.declareDeployContract(contractName: "Balance") - let status = try await provider.getTransactionStatusBy(hash: contract.declare.transactionHash) - let status2 = try await provider.getTransactionStatusBy(hash: contract.deploy.transactionHash) + let status = try await provider.getTransactionStatusBy(hash: contract.declare.transactionHash).send() + let status2 = try await provider.getTransactionStatusBy(hash: contract.deploy.transactionHash).send() XCTAssertEqual(status.finalityStatus, .acceptedL2) XCTAssertEqual(status2.finalityStatus, .acceptedL2) @@ -184,28 +187,28 @@ final class ProviderTests: XCTestCase { let contract = try await ProviderTests.devnetClient.declareDeployContract(contractName: "Balance", constructorCalldata: [1000]) let transactionHash = try await ProviderTests.devnetClient.invokeContract(contractAddress: contract.deploy.contractAddress, function: "increase_balance", calldata: [2137]).transactionHash - let receipt = try await provider.getTransactionReceiptBy(hash: transactionHash) - XCTAssertTrue(receipt.isSuccessful) + let result = try await provider.getTransactionReceiptBy(hash: transactionHash).send() + XCTAssertTrue(result.transactionReceipt.isSuccessful) } func testGetDeployAccountTransactionReceipt() async throws { let account = try await ProviderTests.devnetClient.deployAccount(name: "provider_test") - let receipt = try await provider.getTransactionReceiptBy(hash: account.transactionHash) - XCTAssertTrue(receipt.isSuccessful) + let result = try await provider.getTransactionReceiptBy(hash: account.transactionHash).send() + XCTAssertTrue(result.transactionReceipt.isSuccessful) } func testGetDeclareTransactionReceipt() async throws { let contract = try await ProviderTests.devnetClient.declareDeployContract(contractName: "Balance", constructorCalldata: [1000]) - let receipt = try await provider.getTransactionReceiptBy(hash: contract.declare.transactionHash) - XCTAssertTrue(receipt.isSuccessful) + let result = try await provider.getTransactionReceiptBy(hash: contract.declare.transactionHash).send() + XCTAssertTrue(result.transactionReceipt.isSuccessful) } func testEstimateInvokeV1Fee() async throws { let contractAddress = try await ProviderTests.devnetClient.declareDeployContract(contractName: "Balance", constructorCalldata: [1000]).deploy.contractAddress - let nonce = try await account.getNonce() + let nonce = try await account.getNonce().send() let call = StarknetCall(contractAddress: contractAddress, entrypoint: starknetSelector(from: "increase_balance"), calldata: [1000]) let call2 = StarknetCall(contractAddress: contractAddress, entrypoint: starknetSelector(from: "increase_balance"), calldata: [100_000_000_000]) @@ -216,17 +219,17 @@ final class ProviderTests: XCTestCase { let params2 = StarknetInvokeParamsV1(nonce: Felt(nonce.value + 1)!, maxFee: 0) let tx2 = try account.signV1(calls: [call, call2], params: params2, forFeeEstimation: true) - let _ = try await provider.estimateFee(for: [tx1, tx2], simulationFlags: []) + let _ = try await provider.estimateFee(for: [tx1, tx2], simulationFlags: []).send() let tx1WithoutSignature = StarknetInvokeTransactionV1(senderAddress: tx1.senderAddress, calldata: tx1.calldata, signature: [], maxFee: tx1.maxFee, nonce: nonce, forFeeEstimation: true) let tx2WithoutSignature = StarknetInvokeTransactionV1(senderAddress: tx2.senderAddress, calldata: tx2.calldata, signature: [], maxFee: tx2.maxFee, nonce: Felt(nonce.value + 1)!, forFeeEstimation: true) - let _ = try await provider.estimateFee(for: [tx1WithoutSignature, tx2WithoutSignature], simulationFlags: [.skipValidate]) + let _ = try await provider.estimateFee(for: [tx1WithoutSignature, tx2WithoutSignature], simulationFlags: [.skipValidate]).send() } func testEstimateInvokeV3Fee() async throws { let contractAddress = try await ProviderTests.devnetClient.declareDeployContract(contractName: "Balance", constructorCalldata: [1000]).deploy.contractAddress - let nonce = try await account.getNonce() + let nonce = try await account.getNonce().send() let call = StarknetCall(contractAddress: contractAddress, entrypoint: starknetSelector(from: "increase_balance"), calldata: [1000]) let call2 = StarknetCall(contractAddress: contractAddress, entrypoint: starknetSelector(from: "increase_balance"), calldata: [100_000_000_000]) @@ -237,12 +240,12 @@ final class ProviderTests: XCTestCase { let params2 = StarknetInvokeParamsV3(nonce: Felt(nonce.value + 1)!, l1ResourceBounds: .zero) let tx2 = try account.signV3(calls: [call, call2], params: params2, forFeeEstimation: true) - let _ = try await provider.estimateFee(for: [tx1, tx2], simulationFlags: []) + let _ = try await provider.estimateFee(for: [tx1, tx2], simulationFlags: []).send() let tx1WithoutSignature = StarknetInvokeTransactionV3(senderAddress: tx1.senderAddress, calldata: tx1.calldata, signature: [], l1ResourceBounds: tx1.resourceBounds.l1Gas, nonce: nonce, forFeeEstimation: true) let tx2WithoutSignature = StarknetInvokeTransactionV3(senderAddress: tx2.senderAddress, calldata: tx2.calldata, signature: [], l1ResourceBounds: tx2.resourceBounds.l1Gas, nonce: Felt(nonce.value + 1)!, forFeeEstimation: true) - let _ = try await provider.estimateFee(for: [tx1WithoutSignature, tx2WithoutSignature], simulationFlags: [.skipValidate]) + let _ = try await provider.estimateFee(for: [tx1WithoutSignature, tx2WithoutSignature], simulationFlags: [.skipValidate]).send() } func testEstimateDeployAccountV1Fee() async throws { @@ -253,17 +256,17 @@ final class ProviderTests: XCTestCase { try await Self.devnetClient.prefundAccount(address: newAccountAddress) - let nonce = await (try? newAccount.getNonce()) ?? .zero + let nonce = await (try? newAccount.getNonce().send()) ?? .zero let params = StarknetDeployAccountParamsV1(nonce: nonce, maxFee: .zero) let tx = try newAccount.signDeployAccountV1(classHash: accountContractClassHash, calldata: [newPublicKey], salt: .zero, params: params, forFeeEstimation: true) - let _ = try await provider.estimateFee(for: tx, simulationFlags: []) + let _ = try await provider.estimateFee(for: tx, simulationFlags: []).send() let txWithoutSignature = StarknetDeployAccountTransactionV1(signature: [], maxFee: tx.maxFee, nonce: tx.maxFee, contractAddressSalt: tx.contractAddressSalt, constructorCalldata: tx.constructorCalldata, classHash: tx.classHash, forFeeEstimation: true) - let _ = try await provider.estimateFee(for: txWithoutSignature, simulationFlags: [.skipValidate]) + let _ = try await provider.estimateFee(for: txWithoutSignature, simulationFlags: [.skipValidate]).send() } func testEstimateDeployAccountV3Fee() async throws { @@ -274,17 +277,17 @@ final class ProviderTests: XCTestCase { try await Self.devnetClient.prefundAccount(address: newAccountAddress) - let nonce = await (try? newAccount.getNonce()) ?? .zero + let nonce = await (try? newAccount.getNonce().send()) ?? .zero let params = StarknetDeployAccountParamsV3(nonce: nonce, l1ResourceBounds: .zero) let tx = try newAccount.signDeployAccountV3(classHash: accountContractClassHash, calldata: [newPublicKey], salt: .zero, params: params, forFeeEstimation: true) - let _ = try await provider.estimateFee(for: tx) + let _ = try await provider.estimateFee(for: tx).send() let txWithoutSignature = StarknetDeployAccountTransactionV3(signature: [], l1ResourceBounds: tx.resourceBounds.l1Gas, nonce: tx.nonce, contractAddressSalt: tx.contractAddressSalt, constructorCalldata: tx.constructorCalldata, classHash: tx.classHash, forFeeEstimation: true) - let _ = try await provider.estimateFee(for: txWithoutSignature, simulationFlags: [.skipValidate]) + let _ = try await provider.estimateFee(for: txWithoutSignature, simulationFlags: [.skipValidate]).send() } func testEstimateMessageFee() async throws { @@ -303,7 +306,7 @@ final class ProviderTests: XCTestCase { let feeEstimate = try await provider.estimateMessageFee( message, at: StarknetBlockId.tag(.pending) - ) + ).send() XCTAssertNotEqual(Felt.zero, feeEstimate.gasPrice) XCTAssertNotEqual(Felt.zero, feeEstimate.gasConsumed) @@ -314,14 +317,14 @@ final class ProviderTests: XCTestCase { func testSimulateTransactionsV1() async throws { let contract = try await ProviderTests.devnetClient.declareDeployContract(contractName: "Balance", constructorCalldata: [1000]) - let nonce = try await account.getNonce() + let nonce = try await account.getNonce().send() let call = StarknetCall(contractAddress: contract.deploy.contractAddress, entrypoint: starknetSelector(from: "increase_balance"), calldata: [1000]) let params = StarknetInvokeParamsV1(nonce: nonce, maxFee: 500_000_000_000_000) let invokeTx = try account.signV1(calls: [call], params: params, forFeeEstimation: false) - let accountClassHash = try await provider.getClassHashAt(account.address) + let accountClassHash = try await provider.getClassHashAt(account.address).send() let newSigner = StarkCurveSigner(privateKey: 1001)! let newPublicKey = newSigner.publicKey let newAccountAddress = StarknetContractAddressCalculator.calculateFrom(classHash: accountClassHash, calldata: [newPublicKey], salt: .zero) @@ -332,7 +335,7 @@ final class ProviderTests: XCTestCase { let newAccountParams = StarknetDeployAccountParamsV1(nonce: .zero, maxFee: 500_000_000_000_000) let deployAccountTx = try newAccount.signDeployAccountV1(classHash: accountClassHash, calldata: [newPublicKey], salt: .zero, params: newAccountParams, forFeeEstimation: false) - let simulations = try await provider.simulateTransactions([invokeTx, deployAccountTx], at: .tag(.pending), simulationFlags: []) + let simulations = try await provider.simulateTransactions([invokeTx, deployAccountTx], at: .tag(.pending), simulationFlags: []).send() XCTAssertEqual(simulations.count, 2) XCTAssertTrue(simulations[0].transactionTrace is StarknetInvokeTransactionTrace) @@ -342,7 +345,7 @@ final class ProviderTests: XCTestCase { let deployAccountWithoutSignature = StarknetDeployAccountTransactionV1(signature: [], maxFee: deployAccountTx.maxFee, nonce: deployAccountTx.nonce, contractAddressSalt: deployAccountTx.contractAddressSalt, constructorCalldata: deployAccountTx.constructorCalldata, classHash: deployAccountTx.classHash) - let simulations2 = try await provider.simulateTransactions([invokeWithoutSignature, deployAccountWithoutSignature], at: .tag(.pending), simulationFlags: [.skipValidate]) + let simulations2 = try await provider.simulateTransactions([invokeWithoutSignature, deployAccountWithoutSignature], at: .tag(.pending), simulationFlags: [.skipValidate]).send() XCTAssertEqual(simulations2.count, 2) XCTAssertTrue(simulations2[0].transactionTrace is StarknetInvokeTransactionTrace) @@ -352,7 +355,7 @@ final class ProviderTests: XCTestCase { func testSimulateTransactionsV3() async throws { let contract = try await ProviderTests.devnetClient.declareDeployContract(contractName: "Balance", constructorCalldata: [1000]) - let nonce = try await account.getNonce() + let nonce = try await account.getNonce().send() let call = StarknetCall(contractAddress: contract.deploy.contractAddress, entrypoint: starknetSelector(from: "increase_balance"), calldata: [1000]) @@ -362,7 +365,7 @@ final class ProviderTests: XCTestCase { let invokeTx = try account.signV3(calls: [call], params: params, forFeeEstimation: false) - let accountClassHash = try await provider.getClassHashAt(account.address) + let accountClassHash = try await provider.getClassHashAt(account.address).send() let newSigner = StarkCurveSigner(privateKey: 3003)! let newPublicKey = newSigner.publicKey let newAccountAddress = StarknetContractAddressCalculator.calculateFrom(classHash: accountClassHash, calldata: [newPublicKey], salt: .zero) @@ -374,7 +377,7 @@ final class ProviderTests: XCTestCase { let newAccountParams = StarknetDeployAccountParamsV3(nonce: 0, l1ResourceBounds: deployAccountL1Gas) let deployAccountTx = try newAccount.signDeployAccountV3(classHash: accountClassHash, calldata: [newPublicKey], salt: .zero, params: newAccountParams, forFeeEstimation: false) - let simulations = try await provider.simulateTransactions([invokeTx, deployAccountTx], at: .tag(.pending), simulationFlags: []) + let simulations = try await provider.simulateTransactions([invokeTx, deployAccountTx], at: .tag(.pending), simulationFlags: []).send() XCTAssertEqual(simulations.count, 2) XCTAssertTrue(simulations[0].transactionTrace is StarknetInvokeTransactionTrace) @@ -396,10 +399,35 @@ final class ProviderTests: XCTestCase { classHash: deployAccountTx.classHash ) - let simulations2 = try await provider.simulateTransactions([invokeWithoutSignature, deployAccountWithoutSignature], at: .tag(.pending), simulationFlags: [.skipValidate]) + let simulations2 = try await provider.simulateTransactions([invokeWithoutSignature, deployAccountWithoutSignature], at: .tag(.pending), simulationFlags: [.skipValidate]).send() XCTAssertEqual(simulations2.count, 2) XCTAssertTrue(simulations2[0].transactionTrace is StarknetInvokeTransactionTrace) XCTAssertTrue(simulations2[1].transactionTrace is StarknetDeployAccountTransactionTrace) } + + func testBatchGetTransactionByHash() async throws { + let previousResult = try await provider.getTransactionBy(blockId: .tag(.latest), index: 0).send() + + let transactionsResponse = try await provider.batchRequests(requests: + provider.getTransactionBy(hash: previousResult.transaction.hash!), + provider.getTransactionBy(hash: "0x123")).send() + + XCTAssertEqual(transactionsResponse.count, 2) + XCTAssertEqual(try transactionsResponse[0].get().transaction.hash, previousResult.transaction.hash) + + do { + let _ = try transactionsResponse[1].get().transaction.hash + XCTFail("Fetching transaction with nonexistent hash should fail") + } catch let error as StarknetProviderError { + switch error { + case let .jsonRpcError(_, message, _): + XCTAssertEqual(message, "Transaction hash not found", "Unexpected error message received") + default: + XCTFail("Expected JsonRpcError but received \(error)") + } + } catch { + XCTFail("Error was not a StarknetProviderError. Received error type: \(type(of: error))") + } + } } diff --git a/Tests/StarknetTests/Utils/DevnetClient/DevnetClient.swift b/Tests/StarknetTests/Utils/DevnetClient/DevnetClient.swift index ce7cf4c31..02c85bc3d 100644 --- a/Tests/StarknetTests/Utils/DevnetClient/DevnetClient.swift +++ b/Tests/StarknetTests/Utils/DevnetClient/DevnetClient.swift @@ -673,8 +673,8 @@ func makeDevnetClient() -> DevnetClientProtocol { } public func isTransactionSuccessful(transactionHash: Felt) async throws -> Bool { - let params = [transactionHash] - let rpcPayload = JsonRpcPayload(method: .getTransactionReceipt, params: params) + let params = GetTransactionByHashParams(hash: transactionHash) + let rpcPayload = JsonRpcPayload(method: .getTransactionReceipt, params: .getTransactionByHash(params)) let url = URL(string: rpcUrl)! let networkProvider = HttpNetworkProvider()