diff --git a/Sources/Hedera/Account/AccountBalanceQuery.swift b/Sources/Hedera/Account/AccountBalanceQuery.swift index e977e7f0..3de350d0 100644 --- a/Sources/Hedera/Account/AccountBalanceQuery.swift +++ b/Sources/Hedera/Account/AccountBalanceQuery.swift @@ -91,16 +91,16 @@ public final class AccountBalanceQuery: Query { let mirrorNodeGateway = try MirrorNodeGateway.forNetwork(context.mirrorNetworkNodes, context.ledgerId) let mirrorNodeService = MirrorNodeService.init(mirrorNodeGateway) - guard case .cryptogetAccountBalance(let proto) = response else { + guard case .cryptogetAccountBalance(var proto) = response else { throw HError.fromProtobuf("unexpected \(response) received, expected `cryptogetAccountBalance`") } - let accountId = try AccountId.fromProtobuf(proto.accountID) - let tokenBalanceProto = try await mirrorNodeService.getTokenBalancesForAccount(String(accountId.num)) + let tokenBalanceProto = try await mirrorNodeService.getTokenBalancesForAccount( + String(proto.accountID.accountNum)) - return AccountBalance( - accountId: accountId, hbars: .fromTinybars(Int64(proto.balance)), - tokensInner: .fromProtobuf(tokenBalanceProto)) + proto.tokenBalances = tokenBalanceProto + + return try .fromProtobuf(proto) } public override func validateChecksums(on ledgerId: LedgerId) throws { diff --git a/Sources/Hedera/Account/AccountId.swift b/Sources/Hedera/Account/AccountId.swift index 6b19e469..1bcedc83 100644 --- a/Sources/Hedera/Account/AccountId.swift +++ b/Sources/Hedera/Account/AccountId.swift @@ -135,15 +135,6 @@ public struct AccountId: Sendable, EntityId, ValidateChecksums { public func toBytes() -> Data { toProtobufBytes() } - - public func populateAccountIdNum(_ client: Client) async throws -> Self { - let mirrorNodeGateway = try MirrorNodeGateway.forClient(client) - let mirrorNodeService = MirrorNodeService(mirrorNodeGateway) - - let accountNumFromMirror = try await mirrorNodeService.getAccountNum(self.evmAddress!.toString()) - - return Self(shard: shard, realm: realm, num: accountNumFromMirror) - } } extension AccountId: TryProtobufCodable { diff --git a/Sources/Hedera/Account/AccountInfo.swift b/Sources/Hedera/Account/AccountInfo.swift index 18e99e19..c49a1f11 100644 --- a/Sources/Hedera/Account/AccountInfo.swift +++ b/Sources/Hedera/Account/AccountInfo.swift @@ -156,7 +156,7 @@ public struct AccountInfo: Sendable { /// Staking metadata for this account. public let staking: StakingInfo? - /// Staking metadata for this account. + /// Token relationships for this account. public let tokenRelationships: [TokenId: TokenRelationship] /// Decode `Self` from protobuf-encoded `bytes`. diff --git a/Sources/Hedera/Account/AccountInfoQuery.swift b/Sources/Hedera/Account/AccountInfoQuery.swift index 82f53f37..0670f84a 100644 --- a/Sources/Hedera/Account/AccountInfoQuery.swift +++ b/Sources/Hedera/Account/AccountInfoQuery.swift @@ -65,42 +65,16 @@ public final class AccountInfoQuery: Query { let mirrorNodeGateway = try MirrorNodeGateway.forNetwork(context.mirrorNetworkNodes, context.ledgerId) let mirrorNodeService = MirrorNodeService.init(mirrorNodeGateway) - guard case .cryptoGetInfo(let proto) = response else { + guard case .cryptoGetInfo(var proto) = response else { throw HError.fromProtobuf("unexpected \(response) received, expected `cryptoGetInfo`") } - let accountInfo = try AccountInfo.fromProtobuf(proto.accountInfo) let tokenRelationshipsProto = try await mirrorNodeService.getTokenRelationshipsForAccount( - String(describing: accountInfo.accountId.num)) + String(describing: try AccountId.fromProtobuf(proto.accountInfo.accountID).num)) - var tokenRelationships: [TokenId: TokenRelationship] = [:] + proto.accountInfo.tokenRelationships = tokenRelationshipsProto - for relationship in tokenRelationshipsProto { - tokenRelationships[.fromProtobuf(relationship.tokenID)] = try TokenRelationship.fromProtobuf(relationship) - } - - return AccountInfo( - accountId: accountInfo.accountId, - contractAccountId: accountInfo.contractAccountId, - isDeleted: accountInfo.isDeleted, - proxyAccountId: accountInfo.proxyAccountId, - proxyReceived: accountInfo.proxyReceived, - key: accountInfo.key, - balance: accountInfo.balance, - sendRecordThreshold: accountInfo.sendRecordThreshold, - receiveRecordThreshold: accountInfo.receiveRecordThreshold, - isReceiverSignatureRequired: accountInfo.isReceiverSignatureRequired, - expirationTime: accountInfo.expirationTime, - autoRenewPeriod: accountInfo.autoRenewPeriod, - accountMemo: accountInfo.accountMemo, - ownedNfts: accountInfo.ownedNfts, - maxAutomaticTokenAssociations: accountInfo.maxAutomaticTokenAssociations, - aliasKey: accountInfo.aliasKey, - ethereumNonce: accountInfo.ethereumNonce, - tokenRelationships: tokenRelationships, - ledgerId: accountInfo.ledgerId, - staking: accountInfo.staking - ) + return try .fromProtobuf(proto.accountInfo) } internal override func validateChecksums(on ledgerId: LedgerId) throws { diff --git a/Sources/Hedera/ChunkedTransaction.swift b/Sources/Hedera/ChunkedTransaction.swift index 10b7789b..47dad4e8 100644 --- a/Sources/Hedera/ChunkedTransaction.swift +++ b/Sources/Hedera/ChunkedTransaction.swift @@ -206,7 +206,10 @@ extension ChunkedTransaction.FirstChunkView: Execute { self.transaction.regenerateTransactionId } - internal func makeRequest(_ client: Client, _ transactionId: TransactionId?, _ nodeAccountId: AccountId) throws -> ( + internal func makeRequest( + _ ledgerId: LedgerId?, _ mirrorNodeNetworks: [String], _ transactionId: TransactionId?, + _ nodeAccountId: AccountId + ) throws -> ( GrpcRequest, Context ) { assert(transaction.isFrozen) @@ -265,7 +268,10 @@ extension ChunkedTransaction.ChunkView: Execute { self.transaction.regenerateTransactionId } - internal func makeRequest(_ client: Client, _ transactionId: TransactionId?, _ nodeAccountId: AccountId) throws -> ( + internal func makeRequest( + _ ledgerId: LedgerId?, _ mirrorNodeNetworks: [String], _ transactionId: TransactionId?, + _ nodeAccountId: AccountId + ) throws -> ( GrpcRequest, Context ) { assert(transaction.isFrozen) diff --git a/Sources/Hedera/Contract/ContractId.swift b/Sources/Hedera/Contract/ContractId.swift index c0cdadec..e1d83d99 100644 --- a/Sources/Hedera/Contract/ContractId.swift +++ b/Sources/Hedera/Contract/ContractId.swift @@ -119,17 +119,6 @@ public struct ContractId: EntityId { public func toBytes() -> Data { toProtobufBytes() } - - public func populateContractNum(_ client: Client) async throws -> Self { - let address = try EvmAddress.fromBytes(self.evmAddress!) - - let mirrorNodeGateway = try MirrorNodeGateway.forClient(client) - let mirrorNodeService = MirrorNodeService(mirrorNodeGateway) - - let contractNum = try await mirrorNodeService.getContractNum(address.toString()) - - return Self(shard: shard, realm: realm, num: contractNum) - } } #if compiler(>=5.7) diff --git a/Sources/Hedera/Contract/ContractInfo.swift b/Sources/Hedera/Contract/ContractInfo.swift index f84ae36b..a38ce300 100644 --- a/Sources/Hedera/Contract/ContractInfo.swift +++ b/Sources/Hedera/Contract/ContractInfo.swift @@ -63,7 +63,7 @@ public struct ContractInfo { /// The tokens associated to the contract /// - /// Query mirror node + /// Note: Query mirror node for token relationships. public let tokenRelationships: [TokenId: TokenRelationship] /// Ledger ID for the network the response was returned from. @@ -104,7 +104,7 @@ extension ContractInfo: TryProtobufCodable { self.init( contractId: try .fromProtobuf(proto.contractID), - accountId: try AccountId.fromProtobuf(proto.accountID), + accountId: try .fromProtobuf(proto.accountID), contractAccountId: proto.contractAccountID, adminKey: try .fromProtobuf(adminKey), expirationTime: .fromProtobuf(expirationTime), diff --git a/Sources/Hedera/Contract/ContractInfoQuery.swift b/Sources/Hedera/Contract/ContractInfoQuery.swift index 08a095cb..14e409de 100644 --- a/Sources/Hedera/Contract/ContractInfoQuery.swift +++ b/Sources/Hedera/Contract/ContractInfoQuery.swift @@ -42,7 +42,6 @@ public final class ContractInfoQuery: Query { } internal override func toQueryProtobufWith(_ header: Proto_QueryHeader) -> Proto_Query { - .with { proto in proto.contractGetInfo = .with { proto in proto.header = header @@ -62,37 +61,16 @@ public final class ContractInfoQuery: Query { let mirrorNodeGateway = try MirrorNodeGateway.forNetwork(context.mirrorNetworkNodes, context.ledgerId) let mirrorNodeService = MirrorNodeService.init(mirrorNodeGateway) - guard case .contractGetInfo(let proto) = response else { + guard case .contractGetInfo(var proto) = response else { throw HError.fromProtobuf("unexpected \(response) received, expected `contractGetInfo`") } - let contractInfo = try ContractInfo.fromProtobuf(proto.contractInfo) let tokenRelationshipsProto = try await mirrorNodeService.getTokenRelationshipsForAccount( - String(describing: contractInfo.contractId.num)) - - var tokenRelationships: [TokenId: TokenRelationship] = [:] + String(describing: proto.contractInfo.accountID.accountNum)) - for relationship in tokenRelationshipsProto { - tokenRelationships[.fromProtobuf(relationship.tokenID)] = try TokenRelationship.fromProtobuf(relationship) - } + proto.contractInfo.tokenRelationships = tokenRelationshipsProto - return ContractInfo( - contractId: contractInfo.contractId, - accountId: contractInfo.accountId, - contractAccountId: contractInfo.contractAccountId, - adminKey: contractInfo.adminKey, - expirationTime: contractInfo.expirationTime, - autoRenewPeriod: contractInfo.autoRenewPeriod, - storage: contractInfo.storage, - contractMemo: contractInfo.contractMemo, - balance: contractInfo.balance, - isDeleted: contractInfo.isDeleted, - autoRenewAccountId: contractInfo.autoRenewAccountId, - maxAutomaticTokenAssociations: contractInfo.maxAutomaticTokenAssociations, - tokenRelationships: tokenRelationships, - ledgerId: contractInfo.ledgerId, - stakingInfo: contractInfo.stakingInfo - ) + return try ContractInfo.fromProtobuf(proto.contractInfo) } internal override func validateChecksums(on ledgerId: LedgerId) throws { diff --git a/Sources/Hedera/Execute.swift b/Sources/Hedera/Execute.swift index 21551896..ed2aebe5 100644 --- a/Sources/Hedera/Execute.swift +++ b/Sources/Hedera/Execute.swift @@ -60,7 +60,10 @@ internal protocol Execute { /// /// A created request is cached per node until any request returns /// `TransactionExpired`; in which case, the request cache is cleared. - func makeRequest(_ client: Client, _ transactionId: TransactionId?, _ nodeAccountId: AccountId) throws -> ( + func makeRequest( + _ ledgerId: LedgerId?, _ mirrorNodeNetworks: [String], _ transactionId: TransactionId?, + _ nodeAccountId: AccountId + ) throws -> ( GrpcRequest, Context ) @@ -97,7 +100,8 @@ private struct ExecuteContext { fileprivate let network: Network fileprivate let backoffConfig: LegacyExponentialBackoff fileprivate let maxAttempts: Int - fileprivate let client: Client + fileprivate let ledgerId: LedgerId? + fileprivate let mirrorNodeNetworks: [String] // timeout for a single grpc request. fileprivate let grpcTimeout: Duration? } @@ -153,7 +157,8 @@ internal func executeAny( network: client.net, backoffConfig: backoffBuilder, maxAttempts: backoff.maxAttempts, - client: client, + ledgerId: client.ledgerId, + mirrorNodeNetworks: client.mirrorNetwork, grpcTimeout: nil ), executable: executable) @@ -189,7 +194,8 @@ private func executeAnyInner( } let (nodeAccountId, channel) = ctx.network.channel(for: nodeIndex) - let (request, context) = try executable.makeRequest(ctx.client, transactionId, nodeAccountId) + let (request, context) = try executable.makeRequest( + ctx.ledgerId, ctx.mirrorNodeNetworks, transactionId, nodeAccountId) let response: E.GrpcResponse do { @@ -313,7 +319,8 @@ private struct NodeIndexesGeneratorMap: AsyncSequence, AsyncIteratorProtocol { network: ctx.network, backoffConfig: ctx.backoffConfig, maxAttempts: ctx.maxAttempts, - client: ctx.client, + ledgerId: ctx.ledgerId, + mirrorNodeNetworks: ctx.mirrorNodeNetworks, grpcTimeout: ctx.grpcTimeout ), executable: request diff --git a/Sources/Hedera/HError.swift b/Sources/Hedera/HError.swift index e66aba50..31ee1ca9 100644 --- a/Sources/Hedera/HError.swift +++ b/Sources/Hedera/HError.swift @@ -32,6 +32,7 @@ public struct HError: Error, CustomStringConvertible { case queryPaymentPreCheckStatus(status: Status, transactionId: TransactionId) case queryNoPaymentPreCheckStatus(status: Status) case basicParse + case mirrorNodeQuery case keyParse case keyDerive case noPayerAccountOrTransactionId @@ -86,6 +87,10 @@ public struct HError: Error, CustomStringConvertible { Self(kind: .basicParse, description: description) } + internal static func mirrorNodeQuery(_ description: String) -> Self { + Self(kind: .mirrorNodeQuery, description: description) + } + internal static func keyParse(_ description: String) -> Self { Self(kind: .keyParse, description: "failed to parse a key: \(description)") } diff --git a/Sources/Hedera/MirrorNodeGateway.swift b/Sources/Hedera/MirrorNodeGateway.swift index 54a6b322..8a555bb5 100644 --- a/Sources/Hedera/MirrorNodeGateway.swift +++ b/Sources/Hedera/MirrorNodeGateway.swift @@ -45,7 +45,7 @@ internal struct MirrorNodeGateway { internal func getAccountInfo(_ idOrAliasOrEvmAddress: String) async throws -> [String: Any] { let fullApiUrl = MirrorNodeRouter.buildApiUrl( - self.mirrorNodeUrl, MirrorNodeRouter.accountsRoute, idOrAliasOrEvmAddress) + self.mirrorNodeUrl, MirrorNodeRouter.MirrorNodeRoute.accountInfoRoute, idOrAliasOrEvmAddress) let responseBody = try await queryFromMirrorNode(fullApiUrl) @@ -56,7 +56,7 @@ internal struct MirrorNodeGateway { internal func getContractInfo(_ idOrAliasOrEvmAddress: String) async throws -> [String: Any] { let fullApiUrl = MirrorNodeRouter.buildApiUrl( - self.mirrorNodeUrl, MirrorNodeRouter.contractsRoute, idOrAliasOrEvmAddress) + self.mirrorNodeUrl, MirrorNodeRouter.MirrorNodeRoute.contractInfoRoute, idOrAliasOrEvmAddress) let responseBody = try await queryFromMirrorNode(fullApiUrl) @@ -67,7 +67,7 @@ internal struct MirrorNodeGateway { internal func getAccountTokens(_ idOrAliasOrEvmAddress: String) async throws -> [String: Any] { let fullApiUrl = MirrorNodeRouter.buildApiUrl( - self.mirrorNodeUrl, MirrorNodeRouter.tokensAccountRoute, idOrAliasOrEvmAddress) + self.mirrorNodeUrl, MirrorNodeRouter.MirrorNodeRoute.tokenRelationshipsRoute, idOrAliasOrEvmAddress) let responseBody = try await queryFromMirrorNode(fullApiUrl) @@ -82,6 +82,7 @@ internal struct MirrorNodeGateway { try? httpClient.syncShutdown() } + // Delay is needed to fetch data from the mirror node. if apiUrl.contains("127.0.0.1:5551") { try await Task.sleep(nanoseconds: 1_000_000_000 * 3) } @@ -98,15 +99,11 @@ internal struct MirrorNodeGateway { func deserializeJson(_ responseBody: String) async throws -> [String: Any] { guard let jsonData = responseBody.data(using: .utf8) else { - throw NSError( - domain: "InvalidResponseError", code: -1, - userInfo: [NSLocalizedDescriptionKey: "Response body is not valid UTF-8"]) + throw HError.mirrorNodeQuery("Response body is not valid UTF-8") } guard let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] else { - throw NSError( - domain: "InvalidResponseError", code: -1, - userInfo: [NSLocalizedDescriptionKey: "Response body is not a valid JSON object"]) + throw HError.mirrorNodeQuery("Response body is not a valid JSON object") } return jsonObject diff --git a/Sources/Hedera/MirrorNodeRouter.swift b/Sources/Hedera/MirrorNodeRouter.swift index 841cf262..e55eb702 100644 --- a/Sources/Hedera/MirrorNodeRouter.swift +++ b/Sources/Hedera/MirrorNodeRouter.swift @@ -30,17 +30,11 @@ internal struct MirrorNodeRouter { static let localNodePort = "5551" - public static let accountsRoute = "accounts" - public static let contractsRoute = "contracts" - public static let tokensAccountRoute = "account_tokens" - - static let routes: [String: String] = [ - accountsRoute: "/accounts/%@", - contractsRoute: "/contracts/%@", - tokensAccountRoute: "/accounts/%@/tokens", - ] - - private func mirrorNodeRouter() {} + public enum MirrorNodeRoute: String { + case accountInfoRoute = "/accounts/%@" + case contractInfoRoute = "/contracts/%@" + case tokenRelationshipsRoute = "/accounts/%@/tokens" + } static func getMirrorNodeUrl(_ mirrorNetwork: [String], _ ledgerId: LedgerId?) throws -> String { var mirrorNodeAddress: String = "" @@ -62,7 +56,7 @@ internal struct MirrorNodeRouter { return fullMirrorNodeUrl } - static func buildApiUrl(_ mirrorNodeUrl: String, _ route: String, _ id: String) -> String { - return String("\(mirrorNodeUrl)\(apiVersion)\(String(format: "\(String(describing: routes[route]!))", id))") + static func buildApiUrl(_ mirrorNodeUrl: String, _ route: MirrorNodeRoute, _ id: String) -> String { + return String("\(mirrorNodeUrl)\(apiVersion)\(route.rawValue.replacingOccurrences(of: "%@", with: id))") } } diff --git a/Sources/Hedera/MirrorNodeService.swift b/Sources/Hedera/MirrorNodeService.swift index 1650df3e..83bb7a19 100644 --- a/Sources/Hedera/MirrorNodeService.swift +++ b/Sources/Hedera/MirrorNodeService.swift @@ -29,162 +29,87 @@ internal final class MirrorNodeService { self.mirrorNodeGateway = mirrorNodeGateway } - internal func getAccountNum(_ evmAddress: String) async throws -> UInt64 { - let accountInfoResponse = try await self.mirrorNodeGateway.getAccountInfo(evmAddress) - - guard let accountId = accountInfoResponse["account"] else { - throw NSError( - domain: "InvalidResponseError", code: -1, - userInfo: [NSLocalizedDescriptionKey: "Error while processing getAccountInfo mirror node query"]) - } - - let accountNum = AccountId(String(describing: accountId))?.num - - return accountNum! - } - - internal func getAccountEvmAddress(_ num: UInt64) async throws -> EvmAddress { - let accountInfoResponse = try await self.mirrorNodeGateway.getAccountInfo(String(describing: num)) - - guard let addressAny = accountInfoResponse["evm_address"] else { - fatalError("Error while processing getAccountEvmAddress mirror node query") - } - - let evmAddress = AccountId(String(describing: addressAny))?.evmAddress - - return evmAddress! - } - - internal func getContractNum(_ evmAddress: String) async throws -> UInt64 { - let contractInfoResponse = try await self.mirrorNodeGateway.getContractInfo(evmAddress) - - guard let contractId = contractInfoResponse["contract_id"] else { - throw NSError( - domain: "InvalidResponseError", code: -1, - userInfo: [ - NSLocalizedDescriptionKey: "Error while processing getContractNum mirror node query" - ]) - } - - let contractIdNum = ContractId(String(describing: contractId))?.num - - return contractIdNum! - } - - internal func getTokenBalancesForAccount(_ evmAddress: String) async throws -> [Proto_TokenBalance] { - let accountTokensResponse = try await self.mirrorNodeGateway.getAccountTokens(evmAddress) + internal func getTokenBalancesForAccount(_ idNumOrEvmAddress: String) async throws -> [Proto_TokenBalance] { + let accountTokensResponse = try await self.mirrorNodeGateway.getAccountTokens(idNumOrEvmAddress) guard let tokens = accountTokensResponse["tokens"] else { - throw NSError( - domain: "InvalidResponseError", code: -1, - userInfo: [ - NSLocalizedDescriptionKey: "Error while processing getAccountTokens mirror node query" - ]) + throw HError.mirrorNodeQuery("Error in fetching token relationships for account") } - var tokenBalances: [Proto_TokenBalance] = [] - guard let tokensList: [[String: Any]] = tokens as? [[String: Any]] else { - throw NSError( - domain: "InvalidResponseError", code: -1, - userInfo: [ - NSLocalizedDescriptionKey: "Error while processing getTokenBalancesForAccount mirror node query" - ]) + throw HError.mirrorNodeQuery("Error in converting tokens to array") } - tokensList.forEach { token in - var tokenId: String = "" - var balance: UInt64 = 0 - var decimals: UInt32 = 0 - if let id = token["token_id"] as? String { - tokenId = id + let tokenBalances = try tokensList.map { token in + guard let id = token["token_id"] as? String, let tokenId = TokenId(id) else { + throw HError.mirrorNodeQuery("Error while converting `token id` to TokenId") } - if let hbar = token["balance"] as? String { - balance = UInt64(hbar)! + guard let balance = token["balance"] as? UInt64 else { + throw HError.mirrorNodeQuery("Error while converting `balance` to unsigned int") } - if let dec = token["decimals"] as? String { - decimals = UInt32(dec)! + guard let decimals = token["decimals"] as? UInt32 else { + throw HError.mirrorNodeQuery("Error while converting `decimals` to unsigned int") } - let tokenBalanceProto = Proto_TokenBalance.with { proto in - proto.tokenID = TokenId(tokenId)!.toProtobuf() + return Proto_TokenBalance.with { proto in + proto.tokenID = tokenId.toProtobuf() proto.balance = balance proto.decimals = decimals } - - tokenBalances.append(tokenBalanceProto) } return tokenBalances } - internal func getTokenRelationshipsForAccount(_ evmAddress: String) async throws -> [Proto_TokenRelationship] { - let accountTokensResponse = try await self.mirrorNodeGateway.getAccountTokens(evmAddress) + internal func getTokenRelationshipsForAccount(_ idNumOrEvmAddress: String) async throws -> [Proto_TokenRelationship] { + let accountTokensResponse = try await self.mirrorNodeGateway.getAccountTokens(idNumOrEvmAddress) guard let tokens = accountTokensResponse["tokens"] else { - throw NSError( - domain: "InvalidResponseError", code: -1, - userInfo: [ - NSLocalizedDescriptionKey: - "Error while processing getTokenRelationshipsForAccount mirror node query" - ]) + throw HError.mirrorNodeQuery("Error in fetching token relationships for account") } - var tokenBalances: [Proto_TokenRelationship] = [] - guard let tokensList: [[String: Any]] = tokens as? [[String: Any]] else { - throw NSError( - domain: "InvalidResponseError", code: -1, - userInfo: [ - NSLocalizedDescriptionKey: "Error while processing getTokenBalancesForAccount mirror node query" - ]) + throw HError.mirrorNodeQuery("Error in converting tokens to array") } - try tokensList.forEach { token in - var tokenId: String = "" - var balance: UInt64 = 0 - var decimals: UInt32 = 0 - var kycStatus: String = "" - var freezeStatus: String = "" - var automaticAssociation: Bool = false - if let id = token["token_id"] as? String { - tokenId = id + + let tokenRelationships = try tokensList.map { token in + guard let id = token["token_id"] as? String, let tokenId = TokenId(id) else { + throw HError.mirrorNodeQuery("Error while converting `token id` to TokenId") } - if let hbar = token["balance"] as? String { - balance = UInt64(hbar)! + guard let balance = token["balance"] as? UInt64 else { + throw HError.mirrorNodeQuery("Error while converting `balance` to unsigned int") } - if let dec = token["decimals"] as? String { - decimals = UInt32(dec)! + guard let decimals = token["decimals"] as? UInt32 else { + throw HError.mirrorNodeQuery("Error while converting `decimals` to unsigned int") } - if let kyc = token["kyc_status"] as? String { - kycStatus = kyc + guard let kycStatus = token["kyc_status"] as? String else { + throw HError.mirrorNodeQuery("Error while converting `kyc status` to string") } - if let freeze = token["freeze_status"] as? String { - freezeStatus = freeze + guard let freezeStatus = token["freeze_status"] as? String else { + throw HError.mirrorNodeQuery("Error while processing freeze status as string") } - if let auto = token["automatic_association"] as? String { - automaticAssociation = Bool(auto)! + guard let automaticAssociation = token["automatic_association"] as? Bool else { + throw HError.mirrorNodeQuery("Error while processing automatic association from token relationship") } - let tokenRelationshipsProto = try Proto_TokenRelationship.with { proto in - proto.tokenID = TokenId(tokenId)!.toProtobuf() + return try Proto_TokenRelationship.with { proto in + proto.tokenID = tokenId.toProtobuf() proto.balance = balance proto.decimals = decimals proto.kycStatus = try getTokenKycStatusFromString(kycStatus) proto.freezeStatus = try getTokenFreezeStatusFromString(freezeStatus) proto.automaticAssociation = automaticAssociation } - - tokenBalances.append(tokenRelationshipsProto) } - return tokenBalances + return tokenRelationships } internal func getTokenKycStatusFromString(_ tokenKycStatusString: String) throws -> Proto_TokenKycStatus { @@ -192,7 +117,7 @@ internal final class MirrorNodeService { case "NOT_APPLICABLE": return Proto_TokenKycStatus.kycNotApplicable case "GRANTED": return Proto_TokenKycStatus.granted case "REVOKED": return Proto_TokenKycStatus.revoked - case _: fatalError("Invalid token KYC status: \(tokenKycStatusString)") + case _: throw HError.mirrorNodeQuery("Error while processing kyc status from token relationship") } } @@ -201,7 +126,7 @@ internal final class MirrorNodeService { case "NOT_APPLICABLE": return Proto_TokenFreezeStatus.freezeNotApplicable case "FROZEN": return Proto_TokenFreezeStatus.frozen case "UNFROZEN": return Proto_TokenFreezeStatus.unfrozen - case _: fatalError("Invalid token Freeze status: \(tokenFreezeStatusString)") + case _: throw HError.mirrorNodeQuery("Error while processing freeze status from token relationship") } } diff --git a/Sources/Hedera/PingQuery.swift b/Sources/Hedera/PingQuery.swift index a2e4040b..b2fef278 100644 --- a/Sources/Hedera/PingQuery.swift +++ b/Sources/Hedera/PingQuery.swift @@ -73,7 +73,10 @@ extension PingQuery: Execute { nil } - internal func makeRequest(_ client: Client, _ transactionId: TransactionId?, _ nodeAccountId: AccountId) throws -> ( + internal func makeRequest( + _ ledgerId: LedgerId?, _ mirrorNodeNetworks: [String], _ transactionId: TransactionId?, + _ nodeAccountId: AccountId + ) throws -> ( Proto_Query, () ) { let header = Proto_QueryHeader.with { $0.responseType = .answerOnly } diff --git a/Sources/Hedera/Query.swift b/Sources/Hedera/Query.swift index 048955c4..491ecf22 100644 --- a/Sources/Hedera/Query.swift +++ b/Sources/Hedera/Query.swift @@ -224,7 +224,10 @@ extension Query: Execute { payment.index } - internal func makeRequest(_ client: Client, _ transactionId: TransactionId?, _ nodeAccountId: AccountId) throws -> ( + internal func makeRequest( + _ ledgerId: LedgerId?, _ mirrorNodeNetworks: [String], _ transactionId: TransactionId?, + _ nodeAccountId: AccountId + ) throws -> ( Proto_Query, Context ) { let request = toQueryProtobufWith( @@ -232,13 +235,14 @@ extension Query: Execute { proto.responseType = .answerOnly if requiresPayment { - proto.payment = try payment.makeRequest(client, transactionId, nodeAccountId).0 + proto.payment = try payment.makeRequest(ledgerId, mirrorNodeNetworks, transactionId, nodeAccountId) + .0 } }) let context = MirrorNetworkContext( - ledgerId: client.ledgerId, - mirrorNetworkNodes: client.mirrorNetwork + ledgerId: ledgerId, + mirrorNetworkNodes: mirrorNodeNetworks ) return (request, context) diff --git a/Sources/Hedera/QueryCost.swift b/Sources/Hedera/QueryCost.swift index 8bde5c7d..fd27b1e8 100644 --- a/Sources/Hedera/QueryCost.swift +++ b/Sources/Hedera/QueryCost.swift @@ -67,7 +67,10 @@ extension QueryCost: Execute { internal var requiresTransactionId: Bool { false } - internal func makeRequest(_ client: Client, _ transactionId: TransactionId?, _ nodeAccountId: AccountId) throws -> ( + internal func makeRequest( + _ ledgerId: LedgerId?, _ mirrorNodeNetworks: [String], _ transactionId: TransactionId?, + _ nodeAccountId: AccountId + ) throws -> ( Proto_Query, () ) { let request = query.toQueryProtobufWith( diff --git a/Sources/Hedera/Token/TokenRelationship.swift b/Sources/Hedera/Token/TokenRelationship.swift index c5ae1735..7c728fd5 100644 --- a/Sources/Hedera/Token/TokenRelationship.swift +++ b/Sources/Hedera/Token/TokenRelationship.swift @@ -83,7 +83,7 @@ extension TokenRelationship: TryProtobufCodable { case .unfrozen: freezeStatus = false case .unrecognized(_): - fatalError("Unrecognized Freeze Status from Protobuf: \(proto.freezeStatus)") + throw HError.fromProtobuf("invalid freeze status from protobuf: \(proto.freezeStatus)") } switch proto.kycStatus { @@ -94,7 +94,7 @@ extension TokenRelationship: TryProtobufCodable { case .revoked: kycStatus = false case .unrecognized(_): - fatalError("Unrecognized KYC Status from protobuf: \(proto.kycStatus)") + throw HError.fromProtobuf("invalid kyc status from protobuf: \(proto.kycStatus)") } self.init( @@ -110,22 +110,22 @@ extension TokenRelationship: TryProtobufCodable { switch freezeStatus { case true: - protoFreezeStatus = Proto_TokenFreezeStatus.frozen + protoFreezeStatus = .frozen case false: - protoFreezeStatus = Proto_TokenFreezeStatus.unfrozen + protoFreezeStatus = .unfrozen case nil: - protoFreezeStatus = Proto_TokenFreezeStatus.freezeNotApplicable + protoFreezeStatus = .freezeNotApplicable case .some(_): fatalError("Unrecognized Freeze Status") } switch kycStatus { case true: - protoKycStatus = Proto_TokenKycStatus.granted + protoKycStatus = .granted case false: - protoKycStatus = Proto_TokenKycStatus.revoked + protoKycStatus = .revoked case nil: - protoKycStatus = Proto_TokenKycStatus.kycNotApplicable + protoKycStatus = .kycNotApplicable case .some(_): fatalError("Unrecognized KYC Status") } diff --git a/Sources/Hedera/Transaction.swift b/Sources/Hedera/Transaction.swift index bc84033a..99ae9c5e 100644 --- a/Sources/Hedera/Transaction.swift +++ b/Sources/Hedera/Transaction.swift @@ -593,7 +593,10 @@ extension Transaction: Execute { self.operator?.accountId } - internal func makeRequest(_ client: Client, _ transactionId: TransactionId?, _ nodeAccountId: AccountId) throws -> ( + internal func makeRequest( + _ ledgerId: LedgerId?, _ mirrorNodeNetworks: [String], _ transactionId: TransactionId?, + _ nodeAccountId: AccountId + ) throws -> ( GrpcRequest, TransactionHash ) { assert(isFrozen) diff --git a/Sources/Hedera/Transaction/TransactionSources.swift b/Sources/Hedera/Transaction/TransactionSources.swift index cc516120..b9460e6f 100644 --- a/Sources/Hedera/Transaction/TransactionSources.swift +++ b/Sources/Hedera/Transaction/TransactionSources.swift @@ -428,7 +428,10 @@ extension SourceTransactionExecuteView: Execute { nil } - internal func makeRequest(_ client: Client, _ transactionId: TransactionId?, _ nodeAccountId: AccountId) throws -> ( + internal func makeRequest( + _ ledgerId: LedgerId?, _ mirrorNodeNetworks: [String], _ transactionId: TransactionId?, + _ nodeAccountId: AccountId + ) throws -> ( GrpcRequest, Context ) { assert(transactionId == chunk.transactionId) diff --git a/Tests/HederaE2ETests/Account/AccountBalance.swift b/Tests/HederaE2ETests/Account/AccountBalance.swift index fb469c7a..fb230e79 100644 --- a/Tests/HederaE2ETests/Account/AccountBalance.swift +++ b/Tests/HederaE2ETests/Account/AccountBalance.swift @@ -22,7 +22,7 @@ import Hedera import XCTest internal final class AccountBalance: XCTestCase { - internal func testQuery() async throws { + internal func testQueryBoo() async throws { let testEnv = TestEnvironment.global guard let op = testEnv.operator else { diff --git a/Tests/HederaE2ETests/Contract/ContractIdPopulation.swift b/Tests/HederaE2ETests/Contract/ContractIdPopulation.swift deleted file mode 100644 index aeb0f3df..00000000 --- a/Tests/HederaE2ETests/Contract/ContractIdPopulation.swift +++ /dev/null @@ -1,59 +0,0 @@ -/* - * ‌ - * Hedera Swift SDK - * ​ - * Copyright (C) 2022 - 2024 Hedera Hashgraph, LLC - * ​ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ‍ - */ - -import Hedera -import XCTest - -internal final class ContractIdPopulation: XCTestCase { - internal let contractByteCode = - "608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506101cb806100606000396000f3fe608060405260043610610046576000357c01000000000000000000000000000000000000000000000000000000009004806341c0e1b51461004b578063cfae321714610062575b600080fd5b34801561005757600080fd5b506100606100f2565b005b34801561006e57600080fd5b50610077610162565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100b757808201518184015260208101905061009c565b50505050905090810190601f1680156100e45780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610160573373ffffffffffffffffffffffffffffffffffffffff16ff5b565b60606040805190810160405280600d81526020017f48656c6c6f2c20776f726c64210000000000000000000000000000000000000081525090509056fea165627a7a72305820ae96fb3af7cde9c0abfe365272441894ab717f816f07f41f07b1cbede54e256e0029" - .data(using: .utf8)! - - internal func testPopulateContractIdNum() async throws { - let testEnv = try TestEnvironment.nonFree - - let fileCreateReceipt = try await FileCreateTransaction() - .keys([.single(testEnv.operator.privateKey.publicKey)]) - .contents(self.contractByteCode) - .execute(testEnv.client) - .getReceipt(testEnv.client) - - let fileId = try XCTUnwrap(fileCreateReceipt.fileId) - - let contractCreateReceipt = try await ContractCreateTransaction() - .adminKey(.single(testEnv.operator.privateKey.publicKey)) - .gas(100000) - .constructorParameters(ContractFunctionParameters().addString("Hello from Hedera.")) - .contractMemo("[e2e::ContractIdPopulation]") - .bytecodeFileId(fileId) - .execute(testEnv.client) - .getReceipt(testEnv.client) - - let contractId = try XCTUnwrap(contractCreateReceipt.contractId) - try await Task.sleep(nanoseconds: 5 * 1_000_000_000) - - let contractInfo = try await ContractInfoQuery(contractId: contractId).execute(testEnv.client) - let contractIdMirror = try ContractId.fromEvmAddress(0, 0, contractInfo.contractAccountId) - - let newContractId = try await contractIdMirror.populateContractNum(testEnv.client) - - XCTAssertEqual(contractId.num, newContractId.num) - } -}