diff --git a/Sources/Network/FlowAccess.swift b/Sources/Network/FlowAccess.swift index 09ba1b2..cc85aa4 100644 --- a/Sources/Network/FlowAccess.swift +++ b/Sources/Network/FlowAccess.swift @@ -12,8 +12,8 @@ extension Flow: FlowAccessProtocol { return try await flow.accessAPI.ping() } - public func getLatestBlockHeader() async throws -> BlockHeader { - return try await flow.accessAPI.getLatestBlockHeader() + public func getLatestBlockHeader(blockStatus: Flow.BlockStatus = .final) async throws -> BlockHeader { + return try await flow.accessAPI.getLatestBlockHeader(blockStatus: blockStatus) } public func getBlockHeaderById(id: ID) async throws -> BlockHeader { @@ -24,8 +24,8 @@ extension Flow: FlowAccessProtocol { return try await flow.accessAPI.getBlockHeaderByHeight(height: height) } - public func getLatestBlock(sealed: Bool) async throws -> Block { - return try await flow.accessAPI.getLatestBlock(sealed: sealed) + public func getLatestBlock(blockStatus: Flow.BlockStatus = .final) async throws -> Block { + return try await flow.accessAPI.getLatestBlock(blockStatus: blockStatus) } public func getBlockById(id: ID) async throws -> Block { @@ -52,8 +52,8 @@ extension Flow: FlowAccessProtocol { return try await flow.accessAPI.getTransactionResultById(id: id) } - public func getAccountAtLatestBlock(address: Address) async throws -> Account { - return try await flow.accessAPI.getAccountAtLatestBlock(address: address) + public func getAccountAtLatestBlock(address: Address, blockStatus: Flow.BlockStatus = .final) async throws -> Account { + return try await flow.accessAPI.getAccountAtLatestBlock(address: address, blockStatus: blockStatus) } public func getAccountByBlockHeight(address: Address, height: UInt64) async throws -> Account { @@ -68,8 +68,8 @@ extension Flow: FlowAccessProtocol { return try await flow.accessAPI.getEventsForBlockIds(type: type, ids: ids) } - public func executeScriptAtLatestBlock(script: Script, arguments: [Argument]) async throws -> ScriptResponse { - return try await flow.accessAPI.executeScriptAtLatestBlock(script: script, arguments: arguments) + public func executeScriptAtLatestBlock(script: Script, arguments: [Argument], blockStatus: Flow.BlockStatus = .final) async throws -> ScriptResponse { + return try await flow.accessAPI.executeScriptAtLatestBlock(script: script, arguments: arguments, blockStatus: blockStatus) } public func getNetworkParameters() async throws -> ChainID { diff --git a/Sources/Network/FlowAccessProtocol.swift b/Sources/Network/FlowAccessProtocol.swift index 20afdb3..45f75d3 100644 --- a/Sources/Network/FlowAccessProtocol.swift +++ b/Sources/Network/FlowAccessProtocol.swift @@ -42,7 +42,7 @@ public protocol FlowAccessProtocol { /// Get latest block header /// - Returns: Most recent block header - func getLatestBlockHeader() async throws -> Flow.BlockHeader + func getLatestBlockHeader(blockStatus: Flow.BlockStatus) async throws -> Flow.BlockHeader /// Get block header by ID /// - Parameter id: Block identifier @@ -51,7 +51,7 @@ public protocol FlowAccessProtocol { func getBlockHeaderByHeight(height: UInt64) async throws -> Flow.BlockHeader - func getLatestBlock(sealed: Bool) async throws -> Flow.Block + func getLatestBlock(blockStatus: Flow.BlockStatus) async throws -> Flow.Block func getBlockById(id: Flow.ID) async throws -> Flow.Block @@ -65,13 +65,13 @@ public protocol FlowAccessProtocol { func getTransactionResultById(id: Flow.ID) async throws -> Flow.TransactionResult - func getAccountAtLatestBlock(address: Flow.Address) async throws -> Flow.Account + func getAccountAtLatestBlock(address: Flow.Address, blockStatus: Flow.BlockStatus) async throws -> Flow.Account func getAccountByBlockHeight(address: Flow.Address, height: UInt64) async throws -> Flow.Account - func executeScriptAtLatestBlock(script: Flow.Script, arguments: [Flow.Argument]) async throws -> Flow.ScriptResponse + func executeScriptAtLatestBlock(script: Flow.Script, arguments: [Flow.Argument], blockStatus: Flow.BlockStatus) async throws -> Flow.ScriptResponse - func executeScriptAtLatestBlock(script: Flow.Script, arguments: [Flow.Cadence.FValue]) async throws -> Flow.ScriptResponse + func executeScriptAtLatestBlock(script: Flow.Script, arguments: [Flow.Cadence.FValue], blockStatus: Flow.BlockStatus) async throws -> Flow.ScriptResponse func executeScriptAtBlockId(script: Flow.Script, blockId: Flow.ID, arguments: [Flow.Argument]) async throws -> Flow.ScriptResponse @@ -91,8 +91,17 @@ public protocol FlowAccessProtocol { } public extension FlowAccessProtocol { - func getAccountAtLatestBlock(address: String) async throws -> Flow.Account { - return try await getAccountAtLatestBlock(address: .init(hex: address.addHexPrefix())) + + func getLatestBlockHeader(blockStatus: Flow.BlockStatus = .final) async throws -> Flow.BlockHeader { + return try await getLatestBlockHeader(blockStatus: blockStatus) + } + + func getAccountAtLatestBlock(address: Flow.Address, blockStatus: Flow.BlockStatus = .final) async throws -> Flow.Account { + return try await getAccountAtLatestBlock(address: address, blockStatus: blockStatus) + } + + func getAccountAtLatestBlock(address: String, blockStatus: Flow.BlockStatus = .final) async throws -> Flow.Account { + return try await getAccountAtLatestBlock(address: .init(hex: address.addHexPrefix()), blockStatus: blockStatus) } func getTransactionById(id: String) async throws -> Flow.Transaction { @@ -104,24 +113,28 @@ public extension FlowAccessProtocol { } func getLatestBlock(sealed: Bool = true) async throws -> Flow.Block { - return try await getLatestBlock(sealed: sealed) + return try await getLatestBlock(blockStatus: .final) } - func executeScriptAtLatestBlock(cadence: String, arguments: [Flow.Argument] = []) async throws -> Flow.ScriptResponse { - return try await executeScriptAtLatestBlock(script: .init(text: cadence), arguments: arguments) + func executeScriptAtLatestBlock(cadence: String, arguments: [Flow.Argument] = [], blockStatus: Flow.BlockStatus = .final) async throws -> Flow.ScriptResponse { + return try await executeScriptAtLatestBlock(script: .init(text: cadence), arguments: arguments, blockStatus: blockStatus) } - func executeScriptAtLatestBlock(cadence: String, arguments: [Flow.Cadence.FValue] = []) async throws -> Flow.ScriptResponse { - return try await executeScriptAtLatestBlock(script: .init(text: cadence), arguments: arguments.map { $0.toArgument() }) + func executeScriptAtLatestBlock(cadence: String, arguments: [Flow.Cadence.FValue] = [], blockStatus: Flow.BlockStatus = .final) async throws -> Flow.ScriptResponse { + return try await executeScriptAtLatestBlock(script: .init(text: cadence), arguments: arguments, blockStatus: blockStatus) } - func executeScriptAtLatestBlock(script: Flow.Script) async throws -> Flow.ScriptResponse { + func executeScriptAtLatestBlock(script: Flow.Script, blockStatus: Flow.BlockStatus = .final) async throws -> Flow.ScriptResponse { let list: [Flow.Argument] = [] - return try await executeScriptAtLatestBlock(script: script, arguments: list) + return try await executeScriptAtLatestBlock(script: script, arguments: list, blockStatus: blockStatus) + } + + func executeScriptAtLatestBlock(script: Flow.Script, arguments: [Flow.Argument], blockStatus: Flow.BlockStatus = .final) async throws -> Flow.ScriptResponse { + return try await executeScriptAtLatestBlock(script: script, arguments: arguments, blockStatus: blockStatus) } - func executeScriptAtLatestBlock(script: Flow.Script, arguments: [Flow.Cadence.FValue]) async throws -> Flow.ScriptResponse { - return try await executeScriptAtLatestBlock(script: script, arguments: arguments.map { $0.toArgument() }) + func executeScriptAtLatestBlock(script: Flow.Script, arguments: [Flow.Cadence.FValue], blockStatus: Flow.BlockStatus = .final) async throws -> Flow.ScriptResponse { + return try await executeScriptAtLatestBlock(script: script, arguments: arguments.map { $0.toArgument() }, blockStatus: blockStatus) } func executeScriptAtBlockId(script: Flow.Script, blockId: Flow.ID, arguments: [Flow.Argument] = []) async throws -> Flow.ScriptResponse { diff --git a/Sources/Network/FlowBlockStatus.swift b/Sources/Network/FlowBlockStatus.swift new file mode 100644 index 0000000..9428ddc --- /dev/null +++ b/Sources/Network/FlowBlockStatus.swift @@ -0,0 +1,15 @@ +// +// File.swift +// Flow +// +// Created by Hao Fu on 7/5/2025. +// + +import Foundation + +extension Flow { + public enum BlockStatus: String, Codable { + case sealed + case final + } +} diff --git a/Sources/Network/HTTP/AccessEndpoint.swift b/Sources/Network/HTTP/AccessEndpoint.swift index b93b930..8e0c001 100644 --- a/Sources/Network/HTTP/AccessEndpoint.swift +++ b/Sources/Network/HTTP/AccessEndpoint.swift @@ -24,17 +24,17 @@ extension Flow { case getNetwork case getBlockHeaderByHeight(height: UInt64) case getBlockHeaderById(id: Flow.ID) - case getLatestBlockHeader - case getLatestBlock(sealed: Bool) + case getLatestBlockHeader(blockStatus: Flow.BlockStatus) + case getLatestBlock(blockStatus: Flow.BlockStatus) case getBlockById(id: Flow.ID) case getBlockByHeight(height: UInt64) case getCollectionById(id: Flow.ID) case sendTransaction(transaction: Flow.Transaction) case getTransactionById(id: Flow.ID) case getTransactionResultById(id: Flow.ID) - case getAccountAtLatestBlock(address: Flow.Address) + case getAccountAtLatestBlock(address: Flow.Address, blockStatus: Flow.BlockStatus) case getAccountByBlockHeight(address: Flow.Address, height: UInt64) - case executeScriptAtLatestBlock(script: Flow.Script, arguments: [Flow.Argument]) + case executeScriptAtLatestBlock(script: Flow.Script, arguments: [Flow.Argument], blockStatus: Flow.BlockStatus) case executeScriptAtBlockId(script: Flow.Script, blockId: Flow.ID, arguments: [Flow.Argument]) case executeScriptAtBlockHeight(script: Flow.Script, height: UInt64, arguments: [Flow.Argument]) case getEventsForHeightRange(type: String, range: ClosedRange) @@ -45,24 +45,26 @@ extension Flow { extension Flow.AccessEndpoint: TargetType { var task: Task { switch self { - case .ping, .getLatestBlockHeader: + case .ping: return .requestParameters(["height": "sealed"]) + case let .getLatestBlockHeader(blockStatus): + return .requestParameters(["height": blockStatus.rawValue]) case let .getBlockHeaderByHeight(height: height): return .requestParameters(["height": String(height)]) case .getBlockById: return .requestParameters(["expand": "payload"]) case let .getBlockByHeight(height): return .requestParameters(["height": String(height), "expand": "payload"]) - case let .getLatestBlock(sealed: sealed): - return .requestParameters(["height": sealed ? "sealed" : "finalized", "expand": "payload"]) - case .getAccountAtLatestBlock: - return .requestParameters(["block_height": "sealed", "expand": "contracts,keys"]) + case let .getLatestBlock(blockStatus): + return .requestParameters(["height": blockStatus.rawValue, "expand": "payload"]) + case let .getAccountAtLatestBlock(_, blockStatus): + return .requestParameters(["block_height": blockStatus.rawValue, "expand": "contracts,keys"]) case let .getAccountByBlockHeight(_, height): return .requestParameters(["block_height": String(height), "expand": "contracts,keys"]) case .getCollectionById: return .requestParameters(["expand": "transactions"]) - case let .executeScriptAtLatestBlock(script, arguments): - return .requestParameters(["block_height": "final"], body: Flow.ScriptRequest(script: script, arguments: arguments)) + case let .executeScriptAtLatestBlock(script, arguments, blockStatus): + return .requestParameters(["block_height": blockStatus.rawValue], body: Flow.ScriptRequest(script: script, arguments: arguments)) case let .executeScriptAtBlockHeight(script, height, arguments): return .requestParameters(["block_height": String(height)], body: Flow.ScriptRequest(script: script, arguments: arguments)) case let .executeScriptAtBlockId(script, id, arguments): @@ -109,7 +111,7 @@ extension Flow.AccessEndpoint: TargetType { return "/v1/blocks/\(id.hex)" case let .getBlockById(id): return "/v1/blocks/\(id.hex)" - case let .getAccountAtLatestBlock(address): + case let .getAccountAtLatestBlock(address, _): return "/v1/accounts/\(address.hex.stripHexPrefix())" case let .getAccountByBlockHeight(address, _): return "/v1/accounts/\(address.hex.stripHexPrefix())" diff --git a/Sources/Network/HTTP/FlowHTTPClient.swift b/Sources/Network/HTTP/FlowHTTPClient.swift index a4b76fa..9c2ec04 100644 --- a/Sources/Network/HTTP/FlowHTTPClient.swift +++ b/Sources/Network/HTTP/FlowHTTPClient.swift @@ -27,6 +27,7 @@ extension Flow { /// HTTP client implementation for Flow Access API /// Handles all network communication with Flow nodes class FlowHTTPAPI: FlowAccessProtocol { + /// Shared instance of the HTTP client static let client = FlowHTTPAPI() @@ -152,8 +153,8 @@ extension Flow { return result.chainId } - func getLatestBlockHeader() async throws -> Flow.BlockHeader { - let result: [Flow.BlockHeaderResponse] = try await request(Flow.AccessEndpoint.getLatestBlockHeader) + func getLatestBlockHeader(blockStatus: Flow.BlockStatus) async throws -> Flow.BlockHeader { + let result: [Flow.BlockHeaderResponse] = try await request(Flow.AccessEndpoint.getLatestBlockHeader(blockStatus: blockStatus)) guard let block = result.first else { throw FError.invaildResponse } @@ -176,8 +177,8 @@ extension Flow { return block.header } - func getLatestBlock(sealed: Bool) async throws -> Flow.Block { - let result: [Flow.BlockResponse] = try await request(Flow.AccessEndpoint.getLatestBlock(sealed: sealed)) + func getLatestBlock(blockStatus: Flow.BlockStatus) async throws -> Flow.Block { + let result: [Flow.BlockResponse] = try await request(Flow.AccessEndpoint.getLatestBlock(blockStatus: blockStatus)) guard let block = result.first else { throw FError.invaildResponse } @@ -217,17 +218,17 @@ extension Flow { return try await request(Flow.AccessEndpoint.getTransactionResultById(id: id)) } - func getAccountAtLatestBlock(address: Flow.Address) async throws -> Flow.Account { - return try await request(Flow.AccessEndpoint.getAccountAtLatestBlock(address: address)) + func getAccountAtLatestBlock(address: Flow.Address, blockStatus: Flow.BlockStatus = .final) async throws -> Flow.Account { + return try await request(Flow.AccessEndpoint.getAccountAtLatestBlock(address: address, blockStatus: blockStatus)) } func getAccountByBlockHeight(address: Flow.Address, height: UInt64) async throws -> Flow.Account { return try await request(Flow.AccessEndpoint.getAccountByBlockHeight(address: address, height: height)) } - func executeScriptAtLatestBlock(script: Flow.Script, arguments: [Flow.Argument]) async throws -> Flow.ScriptResponse { + func executeScriptAtLatestBlock(script: Flow.Script, arguments: [Flow.Argument], blockStatus: Flow.BlockStatus) async throws -> Flow.ScriptResponse { let resolvedScript = flow.addressRegister.resolveImports(in: script.text, for: chainID) - return try await request(Flow.AccessEndpoint.executeScriptAtLatestBlock(script: .init(text: resolvedScript), arguments: arguments)) + return try await request(Flow.AccessEndpoint.executeScriptAtLatestBlock(script: .init(text: resolvedScript), arguments: arguments, blockStatus: blockStatus)) } func executeScriptAtBlockId(script: Flow.Script, blockId: Flow.ID, arguments: [Flow.Argument]) async throws -> Flow.ScriptResponse { diff --git a/Tests/FlowAccessAPIOnTestnetTests.swift b/Tests/FlowAccessAPIOnTestnetTests.swift index 532e457..d725100 100644 --- a/Tests/FlowAccessAPIOnTestnetTests.swift +++ b/Tests/FlowAccessAPIOnTestnetTests.swift @@ -67,9 +67,9 @@ final class FlowAccessAPIOnTestnetTests: XCTestCase { let result = try! await flow.accessAPI.executeScriptAtLatestBlock(script: .init(text: """ import FlowFees from 0x912d5440f7e3769e - pub fun main(): FlowFees.FeeParameters { - return FlowFees.getFeeParameters() - } + access(all) fun main(): FlowFees.FeeParameters { + return FlowFees.getFeeParameters() + } """)) print(result) } @@ -90,19 +90,19 @@ final class FlowAccessAPIOnTestnetTests: XCTestCase { let accountKey = Flow.AccountKey(publicKey: Flow.PublicKey(hex: "bfa6d9893d4d9b5e53b0b9d79ac44b4e20f57b6443f02e5f12b366ed4e1fb4e7decca4e58b76308cee1a22a4c0c01f6fce698dc62c80120f65e8cdf57a0ffdff"), signAlgo: .ECDSA_P256, hashAlgo: .SHA2_256, - weight: 1000) + weight: 1001) var unsignedTx = try! await flow.buildTransaction { cadence { """ import Crypto transaction(publicKey: String, signatureAlgorithm: UInt8, hashAlgorithm: UInt8, weight: UFix64) { - prepare(signer: AuthAccount) { + prepare(signer: auth(BorrowValue | Storage) &Account) { let key = PublicKey( publicKey: publicKey.decodeHex(), signatureAlgorithm: SignatureAlgorithm(rawValue: signatureAlgorithm)! ) - let account = AuthAccount(payer: signer) + let account = Account(payer: signer) account.keys.add( publicKey: key, hashAlgorithm: HashAlgorithm(rawValue: hashAlgorithm)!, @@ -137,10 +137,18 @@ final class FlowAccessAPIOnTestnetTests: XCTestCase { } let signedTx = try! await unsignedTx.sign(signers: signer) - let txId = try! await flow.sendTransaction(signedTransaction: signedTx) - XCTAssertNotNil(txId) print("txid --> \(txId.hex)") + XCTAssertNotNil(txId) + + let result = try await txId.onceExecuted() + let address = result.getCreatedAddress()! + print("address --> \(address)") + XCTAssertNotNil(address) + + let accountInfo = try await flow.getAccountAtLatestBlock(address: .init(address)) + print("accountInfo --> \(accountInfo)") + XCTAssertNotNil(accountInfo) } func testMultipleSigner() async throws {