diff --git a/Sources/API/API.swift b/Sources/API/API.swift index 6b7f663..5ca40ea 100644 --- a/Sources/API/API.swift +++ b/Sources/API/API.swift @@ -1,8 +1,13 @@ import Foundation -struct API { - static var host: URL? - let route: Route +public struct API { + static let shared = API() + private init() {} + static var baseUrl = URL(string: "https://arweave.net")! + + func request(for route: Route) -> Request { + return Request(route: route) + } } enum Route { @@ -17,56 +22,58 @@ enum Route { } extension API { - var baseURL: URL { - API.host ?? URL(string: "https://arweave.net")! - } - var path: String { - switch route { - case .txAnchor: - return "/tx_anchor" - case let .transaction(id): - return "/tx/\(id)" - case let .transactionData(id): - return "/tx/\(id)/data" - case let .transactionStatus(id): - return "/tx/\(id)/status" - case let .lastTransactionId(walletAddress): - return "/wallet/\(walletAddress)/last_tx" - case let .walletBalance(walletAddress): - return "/wallet/\(walletAddress)/balance" - case let .reward(request): - var path = "/price/\(String(request.bytes))" - if let target = request.target { - path.append("/\(target.address)") + struct Request { + + var route: Route + + var path: String { + switch route { + case .txAnchor: + return "/tx_anchor" + case let .transaction(id): + return "/tx/\(id)" + case let .transactionData(id): + return "/tx/\(id)/data" + case let .transactionStatus(id): + return "/tx/\(id)/status" + case let .lastTransactionId(walletAddress): + return "/wallet/\(walletAddress)/last_tx" + case let .walletBalance(walletAddress): + return "/wallet/\(walletAddress)/balance" + case let .reward(request): + var path = "/price/\(String(request.bytes))" + if let target = request.target { + path.append("/\(target.address)") + } + return path + case .commit: + return "/tx" } - return path - case .commit: - return "/tx" } - } - - var url: URL { - baseURL.appendingPathComponent(path) - } - - var method: String { - if case Route.commit = route { - return "post" - } else { - return "get" + + var url: URL { + baseUrl.appendingPathComponent(path) } - } - - var body: Data? { - if case let Route.commit(transaction) = route { - return try? JSONEncoder().encode(transaction) - } else { - return nil + + var method: String { + if case Route.commit = route { + return "post" + } else { + return "get" + } + } + + var body: Data? { + if case let Route.commit(transaction) = route { + return try? JSONEncoder().encode(transaction) + } else { + return nil + } + } + + var headers: [String: String]? { + ["Content-type": "application/json"] } - } - - var headers: [String: String]? { - ["Content-type": "application/json"] } } diff --git a/Sources/API/HttpClient.swift b/Sources/API/HttpClient.swift index 6a1d417..58283de 100644 --- a/Sources/API/HttpClient.swift +++ b/Sources/API/HttpClient.swift @@ -9,7 +9,7 @@ struct HttpResponse { struct HttpClient { - static func request(_ target: API) async throws -> HttpResponse { + static func request(_ target: API.Request) async throws -> HttpResponse { var request = URLRequest(url: target.url) request.httpMethod = target.method diff --git a/Sources/Amount.swift b/Sources/Amount.swift index 1d97dca..687096e 100644 --- a/Sources/Amount.swift +++ b/Sources/Amount.swift @@ -2,10 +2,15 @@ import Foundation public struct Amount: Equatable { - @NonNegative var value: Double - var unit: Unit + @NonNegative public var value: Double + public var unit: Unit - func converted(to targetUnit: Unit) -> Amount { + public init(value: Double, unit: Amount.Unit) { + self.value = value + self.unit = unit + } + + public func converted(to targetUnit: Unit) -> Amount { guard unit != targetUnit else { return self } diff --git a/Sources/Helpers.swift b/Sources/Helpers.swift index 849e65b..e9dd5ed 100644 --- a/Sources/Helpers.swift +++ b/Sources/Helpers.swift @@ -1,15 +1,15 @@ import Foundation @propertyWrapper -struct NonNegative: Equatable { +public struct NonNegative: Equatable { var value: T - var wrappedValue: T { + public var wrappedValue: T { get { value } set { value = max(0, newValue) } } - init(wrappedValue: T) { + public init(wrappedValue: T) { self.value = max(0, wrappedValue) } } diff --git a/Sources/Transaction.swift b/Sources/Transaction.swift index b0e36b6..1b83e98 100644 --- a/Sources/Transaction.swift +++ b/Sources/Transaction.swift @@ -7,8 +7,14 @@ public typealias Base64EncodedString = String public extension Transaction { struct PriceRequest { - var bytes: Int = 0 - var target: Address? + + public init(bytes: Int = 0, target: Address? = nil) { + self.bytes = bytes + self.target = target + } + + public var bytes: Int = 0 + public var target: Address? } struct Tag: Codable { @@ -37,7 +43,7 @@ public struct Transaction: Codable { case id, last_tx, owner, tags, target, quantity, data, reward, signature } - var priceRequest: PriceRequest { + public var priceRequest: PriceRequest { PriceRequest(bytes: rawData.count, target: Address(address: target)) } @@ -76,7 +82,7 @@ public extension Transaction { throw "Missing signature on transaction." } - let commit = API(route: .commit(self)) + let commit = API.shared.request(for: .commit(self)) _ = try await HttpClient.request(commit) } @@ -98,20 +104,20 @@ public extension Transaction { public extension Transaction { static func find(_ txId: TransactionId) async throws -> Transaction { - let findEndpoint = API(route: .transaction(id: txId)) + let findEndpoint = API.shared.request(for: .transaction(id: txId)) let response = try await HttpClient.request(findEndpoint) return try JSONDecoder().decode(Transaction.self, from: response.data) } static func data(for txId: TransactionId) async throws -> Base64EncodedString { - let target = API(route: .transactionData(id: txId)) + let target = API.shared.request(for: .transactionData(id: txId)) let response = try await HttpClient.request(target) return String(decoding: response.data, as: UTF8.self) } static func status(of txId: TransactionId) async throws -> Transaction.Status { - let target = API(route: .transactionStatus(id: txId)) + let target = API.shared.request(for: .transactionStatus(id: txId)) let response = try await HttpClient.request(target) var status: Transaction.Status @@ -125,7 +131,7 @@ public extension Transaction { } static func price(for request: Transaction.PriceRequest) async throws -> Amount { - let target = API(route: .reward(request)) + let target = API.shared.request(for: .reward(request)) let response = try await HttpClient.request(target) let costString = String(decoding: response.data, as: UTF8.self) @@ -136,7 +142,7 @@ public extension Transaction { } static func anchor() async throws -> String { - let target = API(route: .txAnchor) + let target = API.shared.request(for: .txAnchor) let response = try await HttpClient.request(target) let anchor = String(decoding: response.data, as: UTF8.self) diff --git a/Sources/Wallet.swift b/Sources/Wallet.swift index 046d3b2..e353f6d 100644 --- a/Sources/Wallet.swift +++ b/Sources/Wallet.swift @@ -40,7 +40,7 @@ public struct Wallet: Codable, Hashable, Comparable { } public func balance() async throws -> Amount { - let target = API(route: .walletBalance(walletAddress: address)) + let target = API.shared.request(for: .walletBalance(walletAddress: address)) let response = try await HttpClient.request(target) let respString = String(decoding: response.data, as: UTF8.self) @@ -53,7 +53,7 @@ public struct Wallet: Codable, Hashable, Comparable { } public func lastTransactionId() async throws -> TransactionId { - let target = API(route: .lastTransactionId(walletAddress: address)) + let target = API.shared.request(for: .lastTransactionId(walletAddress: address)) let response = try await HttpClient.request(target) let lastTx = String(decoding: response.data, as: UTF8.self) @@ -89,7 +89,7 @@ public struct Address: Hashable, Codable, Equatable, Comparable, CustomStringCon } } -extension Address { +public extension Address { init(from modulus: String) { guard let data = Data(base64URLEncoded: modulus) else { preconditionFailure("Invalid base64 value for JWK public modulus (n) property.") diff --git a/Tests/WalletTests.swift b/Tests/WalletTests.swift index 8b3465c..1f9236e 100644 --- a/Tests/WalletTests.swift +++ b/Tests/WalletTests.swift @@ -28,6 +28,12 @@ final class WalletTests: XCTestCase { let balance = try await WalletTests.wallet?.balance() XCTAssertNotNil(balance?.value) } + + func testCheckWalletBalance_UsingCustomHost() async throws { + API.baseUrl = URL(string: "https://arweave.net:443")! + let balance = try await WalletTests.wallet?.balance() + XCTAssertNotNil(balance?.value) + } func testFetchLastTransactionId() async throws { let lastTxId = try await WalletTests.wallet?.lastTransactionId()