diff --git a/README.md b/README.md index 4b01a9d..e288282 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ debugPrint(keyPair.address.address) import AptosSwift let nodeUrl = URL(string: "https://fullnode.devnet.aptoslabs.com")! -let provider = AptosRPCProvider(nodeUrl: nodeUrl) +let provider = AptosClientProvider(nodeUrl: nodeUrl) let healthy = try provider.healthy().wait() debugPrint(healthy) @@ -70,7 +70,7 @@ let resourceType = "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>" let accountResource = try provider.getAccountResource(address: address, resourceType: resourceType).wait() debugPrint(accountResource) -let coinStore = try accountResource.to(AptosRPC.AccountResourceData.CoinStore.self) +let coinStore = try accountResource.to(AptosClient.AccountResourceData.CoinStore.self) debugPrint(coinStore) let accountModules = try provider.getAccountModules(address: try AptosAddress("0x1")).wait() @@ -87,7 +87,7 @@ debugPrint(block) import AptosSwift let nodeUrl = URL(string: "https://fullnode.devnet.aptoslabs.com")! -let provider = AptosRPCProvider(nodeUrl: nodeUrl) +let provider = AptosClientProvider(nodeUrl: nodeUrl) let keyPair = try AptosKeyPairEd25519.randomKeyPair() let sequenceNumber = try provider.getAccount(address: keyPair.address).wait().sequenceNumber @@ -126,7 +126,7 @@ debugPrint(result) import AptosSwift let nodeUrl = URL(string: "https://fullnode.devnet.aptoslabs.com")! -let provider = AptosRPCProvider(nodeUrl: nodeUrl) +let provider = AptosClientProvider(nodeUrl: nodeUrl) let keyPair = try AptosKeyPairEd25519.randomKeyPair() let sequenceNumber = try provider.getAccount(address: keyPair.address).wait().sequenceNumber diff --git a/Sources/AptosSwift/Providers/AptosRPCModels.swift b/Sources/AptosSwift/Clients/AptosClient+Models.swift similarity index 98% rename from Sources/AptosSwift/Providers/AptosRPCModels.swift rename to Sources/AptosSwift/Clients/AptosClient+Models.swift index b6c0811..dfcd6e2 100644 --- a/Sources/AptosSwift/Providers/AptosRPCModels.swift +++ b/Sources/AptosSwift/Clients/AptosClient+Models.swift @@ -1,5 +1,5 @@ // -// AptosRPCModels.swift +// AptosClientModels.swift // // // Created by Forrest on 2022/8/15. @@ -8,7 +8,7 @@ import Foundation import AnyCodable -public struct AptosRPC { +extension AptosClient { public struct Error: Decodable { public let code: Int public let message: String diff --git a/Sources/AptosSwift/Providers/AptosRPCProvider+Methods.swift b/Sources/AptosSwift/Clients/AptosClient.swift similarity index 85% rename from Sources/AptosSwift/Providers/AptosRPCProvider+Methods.swift rename to Sources/AptosSwift/Clients/AptosClient.swift index 8dc3e1c..f26629f 100644 --- a/Sources/AptosSwift/Providers/AptosRPCProvider+Methods.swift +++ b/Sources/AptosSwift/Clients/AptosClient.swift @@ -1,5 +1,5 @@ // -// AptosRPCProvider+Methods.swift +// AptosClientProvider+Methods.swift // // // Created by mathwallet on 2022/8/15. @@ -8,13 +8,13 @@ import Foundation import PromiseKit -extension AptosRPCProvider { +public class AptosClient: AptosClientBase { /// Check basic node health /// - Parameter durationSecs: If the duration_secs param is provided, this endpoint will return a 200 if the following condition is true: /// server_latest_ledger_info_timestamp >= server_current_time_timestamp - duration_secs - /// - Returns: AptosRPC.Healthy - public func healthy(durationSecs: UInt32? = nil) -> Promise { + /// - Returns: AptosClient.Healthy + public func healthy(durationSecs: UInt32? = nil) -> Promise { var parameters: [String : Any]? if let secs = durationSecs { parameters = ["duration_secs": secs] @@ -23,8 +23,8 @@ extension AptosRPCProvider { } /// Get the latest ledger information, including data such as chain ID, role type, ledger versions, epoch, etc. - /// - Returns: AptosRPC.LedgerInfo - public func getLedgerInfo() -> Promise { + /// - Returns: AptosClient.LedgerInfo + public func getLedgerInfo() -> Promise { return GET(path: "/v1") } @@ -32,8 +32,8 @@ extension AptosRPCProvider { /// - Parameters: /// - blockHeight: Block Height /// - withTransactions: true,false,nil - /// - Returns: AptosRPC.Block - public func getBlock(_ blockHeight: UInt64, withTransactions: Bool? = nil) -> Promise { + /// - Returns: AptosClient.Block + public func getBlock(_ blockHeight: UInt64, withTransactions: Bool? = nil) -> Promise { var parameters: [String : Any]? if let wt = withTransactions { parameters = ["with_transactions": wt] @@ -44,14 +44,14 @@ extension AptosRPCProvider { /// Get account /// - Parameter address: Hex encoded 32 byte Aptos account address /// - Returns: high level information about an account such as its sequence number - public func getAccount(address: AptosAddress) -> Promise { + public func getAccount(address: AptosAddress) -> Promise { return GET(path: "/v1/accounts/\(address.address)") } /// Get account resources /// - Parameter address: Hex encoded 32 byte Aptos account address /// - Returns: all account resources - public func getAccountResources(address: AptosAddress) -> Promise<[AptosRPC.AccountResource]> { + public func getAccountResources(address: AptosAddress) -> Promise<[AptosClient.AccountResource]> { return GET(path: "/v1/accounts/\(address.address)/resources") } @@ -61,14 +61,14 @@ extension AptosRPCProvider { /// - resourceType: String representation of a MoveStructTag (on-chain Move struct type). /// This exists so you can specify MoveStructTags as path / query parameters, e.g. for get_events_by_event_handle. /// - Returns: the resource of a specific type - public func getAccountResource(address: AptosAddress, resourceType: String) -> Promise { + public func getAccountResource(address: AptosAddress, resourceType: String) -> Promise { return GET(path: "/v1/accounts/\(address.address)/resource/\(resourceType)") } /// Get account modules /// - Parameter address: Hex encoded 32 byte Aptos account address /// - Returns: All account modules at a given address at a specific ledger version (AKA transaction version) - public func getAccountModules(address: AptosAddress) -> Promise<[AptosRPC.AccountModule]> { + public func getAccountModules(address: AptosAddress) -> Promise<[AptosClient.AccountModule]> { return GET(path: "/v1/accounts/\(address.address)/modules") } @@ -77,14 +77,14 @@ extension AptosRPCProvider { /// - address: Hex encoded 32 byte Aptos account address /// - moduleName: Module name /// - Returns: the module with a specific name residing at a given account at a specified ledger version (AKA transaction version) - public func getAccountModule(address: AptosAddress, moduleName: String) -> Promise { + public func getAccountModule(address: AptosAddress, moduleName: String) -> Promise { return GET(path: "/v1/accounts/\(address.address)/module/\(moduleName)") } /// Submits a signed transaction to the the endpoint that takes BCS payload /// - Parameter signedTransaction: A BCS signed transaction /// - Returns: Transaction that is accepted and submitted to mempool - public func submitSignedTransaction(_ signedTransaction: AptosSignedTransaction) -> Promise { + public func submitSignedTransaction(_ signedTransaction: AptosSignedTransaction) -> Promise { let headers: [String: String] = ["Content-Type": "application/x.aptos.signed_transaction+bcs"] return POST(path: "/v1/transactions", body: try? BorshEncoder().encode(signedTransaction), headers: headers) } @@ -93,7 +93,7 @@ extension AptosRPCProvider { /// - Parameter rawTransaction AptosRawTransaction /// - Parameter publicKey AptosPublicKeyEd25519 /// - Returns: Simulation result in the form of UserTransaction - public func simulateTransaction(_ rawTransaction: AptosRawTransaction, publicKey: AptosPublicKeyEd25519) -> Promise<[AptosRPC.UserTransaction]> { + public func simulateTransaction(_ rawTransaction: AptosRawTransaction, publicKey: AptosPublicKeyEd25519) -> Promise<[AptosClient.UserTransaction]> { let signedTransaction = rawTransaction.simulate(publicKey) let headers: [String: String] = ["Content-Type": "application/x.aptos.signed_transaction+bcs"] return POST(path: "/v1/transactions/simulate", body: try? BorshEncoder().encode(signedTransaction), headers: headers) @@ -101,8 +101,8 @@ extension AptosRPCProvider { /// Get transaction by hash /// - Parameter txnHash: Transaction Hash - /// - Returns: AptosRPC.Block - public func getTransactionByHash(_ txnHash: String) -> Promise { + /// - Returns: AptosClient.Block + public func getTransactionByHash(_ txnHash: String) -> Promise { return GET(path: "/v1/transactions/by_hash/\(txnHash)") } } diff --git a/Sources/AptosSwift/Clients/AptosFaucetClient.swift b/Sources/AptosSwift/Clients/AptosFaucetClient.swift new file mode 100644 index 0000000..ea75774 --- /dev/null +++ b/Sources/AptosSwift/Clients/AptosFaucetClient.swift @@ -0,0 +1,25 @@ +// +// AptosFaucetClient.swift +// +// +// Created by mathwallet on 2022/8/22. +// + +import Foundation +import PromiseKit + +public class AptosFaucetClient: AptosClientBase { + + /// This creates an account if it does not exist and mints the specified amount of coins into that account + /// - Parameters: + /// - address: Aptos account address + /// - amount: Amount of tokens to mint + /// - Returns: Hashes of submitted transactions + public func fundAccount(address: AptosAddress, amount: UInt64) -> Promise<[String]> { + let queryParameters: [String: Any] = [ + "address": address.address, + "amount": amount + ] + return POST(path: "/mint", queryParameters: queryParameters) + } +} diff --git a/Sources/AptosSwift/Providers/AptosRPCProvider.swift b/Sources/AptosSwift/Clients/Base/AptosClientBase.swift similarity index 82% rename from Sources/AptosSwift/Providers/AptosRPCProvider.swift rename to Sources/AptosSwift/Clients/Base/AptosClientBase.swift index 0640254..e058dbd 100644 --- a/Sources/AptosSwift/Providers/AptosRPCProvider.swift +++ b/Sources/AptosSwift/Clients/Base/AptosClientBase.swift @@ -1,5 +1,5 @@ // -// AptosRPCProvider.swift +// AptosClientProvider.swift // // // Created by xgblin on 2022/8/2. @@ -9,25 +9,21 @@ import Foundation import PromiseKit import AnyCodable -public struct AptosRPCProvider { - public var nodeUrl: URL +public class AptosClientBase { + public var url: URL private var session: URLSession - public init(nodeUrl: URL) { - self.nodeUrl = nodeUrl - + public init(url: URL) { + self.url = url self.session = URLSession(configuration: .default) } -} - -extension AptosRPCProvider { public func GET(path: String, parameters: [String: Any]? = nil) -> Promise { let rp = Promise.pending() var task: URLSessionTask? = nil let queue = DispatchQueue(label: "aptos.get") queue.async { - let url = self.nodeUrl.appendPath(path).appendingQueryParameters(parameters) + let url = self.url.appendPath(path).appendingQueryParameters(parameters) // debugPrint("GET \(url)") var urlRequest = URLRequest(url: url, cachePolicy: URLRequest.CachePolicy.reloadIgnoringCacheData) urlRequest.httpMethod = "GET" @@ -57,7 +53,7 @@ extension AptosRPCProvider { if let resp = try? decoder.decode(T.self, from: data) { return resp } - if let errorResult = try? decoder.decode(AptosRPC.Error.self, from: data) { + if let errorResult = try? decoder.decode(AptosClient.Error.self, from: data) { throw AptosError.providerError(errorResult.message) } throw AptosError.providerError("Parameter error or received wrong message") @@ -66,16 +62,16 @@ extension AptosRPCProvider { public func POST(path: String, parameters: K? = nil) -> Promise { let body: Data? = (parameters != nil ? try? JSONEncoder().encode(parameters!) : nil) - return POST(path: path, body: body, headers: [:]) + return POST(path: path, queryParameters: nil, body: body, headers: [:]) } - public func POST(path: String, body: Data? = nil, headers: [String: String] = [:]) -> Promise { + public func POST(path: String, queryParameters: [String : Any]? = nil, body: Data? = nil, headers: [String: String] = [:]) -> Promise { let rp = Promise.pending() var task: URLSessionTask? = nil let queue = DispatchQueue(label: "aptos.post") queue.async { - let url = self.nodeUrl.appendPath(path) -// debugPrint("POST \(url)") + let url = self.url.appendPath(path).appendingQueryParameters(queryParameters) + debugPrint("POST \(url)") var urlRequest = URLRequest(url: url, cachePolicy: URLRequest.CachePolicy.reloadIgnoringCacheData) urlRequest.httpMethod = "POST" @@ -107,14 +103,14 @@ extension AptosRPCProvider { return rp.promise.ensure(on: queue) { task = nil }.map(on: queue){ (data: Data) throws -> T in -// debugPrint(String(data: data, encoding: .utf8) ?? "") + debugPrint(String(data: data, encoding: .utf8) ?? "") let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase if let resp = try? decoder.decode(T.self, from: data) { return resp } - if let errorResult = try? decoder.decode(AptosRPC.Error.self, from: data) { + if let errorResult = try? decoder.decode(AptosClient.Error.self, from: data) { throw AptosError.providerError(errorResult.message) } throw AptosError.providerError("Parameter error or received wrong message") diff --git a/Tests/AptosSwiftTests/AptosSwiftTests.swift b/Tests/AptosSwiftTests/AptosSwiftTests.swift index fb14773..23d0bed 100644 --- a/Tests/AptosSwiftTests/AptosSwiftTests.swift +++ b/Tests/AptosSwiftTests/AptosSwiftTests.swift @@ -4,6 +4,7 @@ import XCTest final class AptosSwiftTests: XCTestCase { let nodeUrl = URL(string: "https://fullnode.devnet.aptoslabs.com")! + let faucetUrl = URL(string: "https://faucet.devnet.aptoslabs.com")! func testKeyPair() throws { let keypair1 = try AptosKeyPairEd25519(mnemonics: "talk speak heavy can high immune romance language alarm sorry capable flame") @@ -71,43 +72,61 @@ final class AptosSwiftTests: XCTestCase { XCTAssertEqual(try BorshEncoder().encode(signedTx), Data(hex: "000000000000000000000000000000000000000000000000000000000a550c1800000000000000000126a11ceb0b030000000105000100000000050601000000000000000600000000000000001a0102010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00010002d0070000000000000000000000000000ffffffffffffffff040020b9c6ee1630ef3e711144a648db06bbb2284f7274cfbee53ffcee503cc1a4920040662b626455b62ca41ef35b34c74ef0b848c5b3679ae3cf32af47d10ef3372ed4060cfaaeee6ab71ab0034951c21e589d70512c8c536625f532ebf9f127867209")) } - func testProviderExamples() throws { + func testFaucetClientExamples() throws { let reqeustExpectation = expectation(description: "Tests") - let provider = AptosRPCProvider(nodeUrl: nodeUrl) + let faucetClient = AptosFaucetClient(url: faucetUrl) DispatchQueue.global().async { do { - let healthy = try provider.healthy().wait() + let address = try AptosAddress("0x689b6d1d3e54ebb582bef82be2e6781cccda150a6681227b4b0e43ab754834e5") + let hashs = try faucetClient.fundAccount(address: address, amount: 5000).wait() + debugPrint(hashs) + XCTAssertTrue(hashs.count > 0) + + reqeustExpectation.fulfill() + } catch { + reqeustExpectation.fulfill() + } + } + wait(for: [reqeustExpectation], timeout: 30) + } + + func testClientExamples() throws { + let reqeustExpectation = expectation(description: "Tests") + let client = AptosClient(url: nodeUrl) + DispatchQueue.global().async { + do { + let healthy = try client.healthy().wait() XCTAssertEqual(healthy.message, "aptos-node:ok") - let ledgerInfo = try provider.getLedgerInfo().wait() + let ledgerInfo = try client.getLedgerInfo().wait() XCTAssertTrue(ledgerInfo.chainId > 0) XCTAssertTrue((UInt64(ledgerInfo.blockHeight) ?? 0) > 0) let address = try AptosAddress("0x689b6d1d3e54ebb582bef82be2e6781cccda150a6681227b4b0e43ab754834e5") - let accountData = try provider.getAccount(address: address).wait() + let accountData = try client.getAccount(address: address).wait() XCTAssertEqual(accountData.authenticationKey, address.address) - let accountResources = try provider.getAccountResources(address: address).wait() + let accountResources = try client.getAccountResources(address: address).wait() XCTAssertTrue(!accountResources.isEmpty) let resourceType = "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>" - let accountResource = try provider.getAccountResource(address: address, resourceType: resourceType).wait() + let accountResource = try client.getAccountResource(address: address, resourceType: resourceType).wait() XCTAssertEqual(accountResource.type, resourceType) - let coinStore = try accountResource.to(AptosRPC.AccountResourceData.CoinStore.self) + let coinStore = try accountResource.to(AptosClient.AccountResourceData.CoinStore.self) XCTAssertTrue(!coinStore.coin.value.isEmpty) - let accountModules = try provider.getAccountModules(address: try AptosAddress("0x1")).wait() + let accountModules = try client.getAccountModules(address: try AptosAddress("0x1")).wait() XCTAssertTrue(!accountModules.isEmpty) - let accountModule = try provider.getAccountModule(address: try AptosAddress("0x1"), moduleName: "code").wait() + let accountModule = try client.getAccountModule(address: try AptosAddress("0x1"), moduleName: "code").wait() XCTAssertTrue(!accountModule.bytecode.isEmpty) - let block = try provider.getBlock(0).wait() + let block = try client.getBlock(0).wait() XCTAssertEqual(block.blockHeight, "0") - let transaction = try provider.getTransactionByHash("0xafa0af9bd0365114c53919a65d9e3b9c981023e454d8e1ea84253251eec7c201").wait() - XCTAssertEqual(transaction["hash"], "0xafa0af9bd0365114c53919a65d9e3b9c981023e454d8e1ea84253251eec7c201") + let transaction = try client.getTransactionByHash("0x3993463e2d17aca60d1114652c9c4ca4fe59b571ea343c16dd97e7080b3ad635").wait() + XCTAssertEqual(transaction["hash"], "0x3993463e2d17aca60d1114652c9c4ca4fe59b571ea343c16dd97e7080b3ad635") reqeustExpectation.fulfill() } catch { @@ -119,13 +138,13 @@ final class AptosSwiftTests: XCTestCase { func testSimulateTransactionExamples() throws { let reqeustExpectation = expectation(description: "Tests") - let provider = AptosRPCProvider(nodeUrl: nodeUrl) + let client = AptosClient(url: self.nodeUrl) DispatchQueue.global().async { do { let keyPair = try AptosKeyPairEd25519(privateKeyData: Data(hex: "0x105f0dd49fb8eb999efd01ee72def91c65d8a81ae4a4803c42a56df14ace864a")) - let sequenceNumber = try provider.getAccount(address: AptosAddress("0x689b6d1d3e54ebb582bef82be2e6781cccda150a6681227b4b0e43ab754834e5")).wait().sequenceNumber - let chainId = try provider.getLedgerInfo().wait().chainId + let sequenceNumber = try client.getAccount(address: AptosAddress("0x689b6d1d3e54ebb582bef82be2e6781cccda150a6681227b4b0e43ab754834e5")).wait().sequenceNumber + let chainId = try client.getLedgerInfo().wait().chainId let to = try AptosAddress("0xde1cbede2618446ed917826e79cc30d93c39eeeef635f76225f714dc2d7e26b6") let amount = UInt64(10) @@ -148,7 +167,7 @@ final class AptosSwiftTests: XCTestCase { expirationTimestampSecs: date, chainId: UInt8(chainId), payload: AptosTransactionPayload.EntryFunction(payload)) - let result1 = try provider.simulateTransaction(transaction, publicKey: keyPair.publicKey).wait() + let result1 = try client.simulateTransaction(transaction, publicKey: keyPair.publicKey).wait() debugPrint(result1) reqeustExpectation.fulfill() } catch let error { @@ -161,14 +180,14 @@ final class AptosSwiftTests: XCTestCase { func testSendBCSTransactionExamples() throws { let reqeustExpectation = expectation(description: "Tests") - let provider = AptosRPCProvider(nodeUrl: nodeUrl) + let client = AptosClient(url: self.nodeUrl) DispatchQueue.global().async { do { // Address[0x689b6d1d3e54ebb582bef82be2e6781cccda150a6681227b4b0e43ab754834e5] let keyPair = try AptosKeyPairEd25519(privateKeyData: Data(hex: "0x105f0dd49fb8eb999efd01ee72def91c65d8a81ae4a4803c42a56df14ace864a")) - let sequenceNumber = try provider.getAccount(address: keyPair.address).wait().sequenceNumber - let chainId = try provider.getLedgerInfo().wait().chainId + let sequenceNumber = try client.getAccount(address: keyPair.address).wait().sequenceNumber + let chainId = try client.getLedgerInfo().wait().chainId let to = try AptosAddress("0xde1cbede2618446ed917826e79cc30d93c39eeeef635f76225f714dc2d7e26b6") let amount = UInt64(10) @@ -192,7 +211,7 @@ final class AptosSwiftTests: XCTestCase { chainId: UInt8(chainId), payload: AptosTransactionPayload.EntryFunction(payload)) let signedtransaction = try transaction.sign(keyPair) - let result = try provider.submitSignedTransaction(signedtransaction).wait() + let result = try client.submitSignedTransaction(signedtransaction).wait() print(result) reqeustExpectation.fulfill()