Skip to content

Commit

Permalink
Initialize client with Mirror Network (#411)
Browse files Browse the repository at this point in the history
  • Loading branch information
RickyLB authored Nov 22, 2024
1 parent cfb2cce commit 78cbce4
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 20 deletions.
33 changes: 14 additions & 19 deletions Examples/AddNftAllowance/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,17 @@ internal enum Program {
Data($0.utf8)
}

for cid in cids {
let mintReceipt = try await TokenMintTransaction()
.tokenId(nftTokenId)
.metadata(metadataArray)
.freezeWith(client)
.execute(client)
.getReceipt(client)

guard let serials = mintReceipt.serials, !serials.isEmpty else {
fatalError("Failed to mint NFTs")
}

for (index, serial) in serials.enumerated() {
print("Minted NFT (token ID: \(nftTokenId)) with serial: \(serials.first!)")
}

var nftMintReceipts: [TransactionReceipt] = []
for (offset, _) in cids.enumerated() {
nftMintReceipts.append(
try await TokenMintTransaction()
.tokenId(nftTokenId)
.metadata([metadataArray[offset]])
.freezeWith(client)
.execute(client)
.getReceipt(client))

print("Minted NFT (token ID: \(nftTokenId)) with serial: \(nftMintReceipts[offset].serials![0])")
}

// Step 3: Create spender and receiver accounts
Expand All @@ -100,15 +95,15 @@ internal enum Program {
print("Created spender account ID: \(spenderAccountId), receiver account ID: \(receiverAccountId)")

// Step 4: Associate spender and receiver with the NFT
try await TokenAssociateTransaction()
_ = try await TokenAssociateTransaction()
.accountId(spenderAccountId)
.tokenIds([nftTokenId])
.freezeWith(client)
.sign(spenderKey)
.execute(client)
.getReceipt(client)

try await TokenAssociateTransaction()
_ = try await TokenAssociateTransaction()
.accountId(receiverAccountId)
.tokenIds([nftTokenId])
.freezeWith(client)
Expand All @@ -119,7 +114,7 @@ internal enum Program {
print("Associated spender and receiver accounts with NFT")

// Step 5: Approve NFT allowance for spender
try await AccountAllowanceApproveTransaction()
_ = try await AccountAllowanceApproveTransaction()
.approveTokenNftAllowance(NftId(tokenId: nftTokenId, serial: 1), env.operatorAccountId, spenderAccountId)
.approveTokenNftAllowance(NftId(tokenId: nftTokenId, serial: 2), env.operatorAccountId, spenderAccountId)
.execute(client)
Expand Down
75 changes: 75 additions & 0 deletions Examples/InitializeClientWithMirrorNetwork/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* ‌
* 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 HederaExampleUtilities
import SwiftDotenv

@main
internal enum Program {
internal static func main() async throws {
let env = try Dotenv.load()

/*
* Step 0: Create and Configure the Client
*/
let client = try await Client.forMirrorNetwork(["testnet.mirrornode.hedera.com:443"])

// Payer and signer for all transactions
client.setOperator(env.operatorAccountId, env.operatorKey)

/*
* Step 1: Generate ed25519 keypair
*/
print("Generating ed25519 keypair...")
let privateKey = PrivateKey.generateEd25519()

/*
* Step 2: Create an account
*/
let aliceId = try await AccountCreateTransaction()
.key(.single(privateKey.publicKey))
.initialBalance(Hbar(5))
.execute(client)
.getReceipt(client)
.accountId!

print("Alice's account ID: \(aliceId)")
}
}

extension Environment {
/// Account ID for the operator to use in this example.
internal var operatorAccountId: AccountId {
AccountId(self["OPERATOR_ID"]!.stringValue)!
}

/// Private key for the operator to use in this example.
internal var operatorKey: PrivateKey {
PrivateKey(self["OPERATOR_KEY"]!.stringValue)!
}

/// The name of the hedera network this example should be ran against.
///
/// Testnet by default.
internal var networkName: String {
self["HEDERA_NETWORK"]?.stringValue ?? "testnet"
}
}
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ let exampleTargets = [
"TokenUpdateMetadata",
"NftUpdateMetadata",
"TokenAirdrop",
"InitializeClientWithMirrorNetwork",
].map { name in
Target.executableTarget(
name: "\(name)Example",
Expand Down
31 changes: 31 additions & 0 deletions Sources/Hedera/Client/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,24 @@ public final class Client: Sendable {
)
}

/// Set up the client from selected mirror network.
public static func forMirrorNetwork(_ mirrorNetworks: [String]) async throws -> Self {
let eventLoop = PlatformSupport.makeEventLoopGroup(loopCount: 1)
let client = Self(
network: .init(
primary: try .init(addresses: [:], eventLoop: eventLoop.next()),
mirror: .init(targets: mirrorNetworks, eventLoop: eventLoop)
),
ledgerId: nil,
eventLoop
)

let addressBook = try await NodeAddressBookQuery().execute(client)
client.setNetworkFromAddressBook(addressBook)

return client
}

/// Construct a Hedera client pre-configured for mainnet access.
public static func forMainnet() -> Self {
let eventLoop = PlatformSupport.makeEventLoopGroup(loopCount: 1)
Expand Down Expand Up @@ -358,6 +376,18 @@ public final class Client: Sendable {
return self
}

/// Replace all nodes in this Client with a new set of nodes from the given address book.
/// This preserves and makes appropriate updates to the existing Client.
@discardableResult
public func setNetworkFromAddressBook(_ addressBook: NodeAddressBook) -> Self {
_ = try? self.networkInner.primary.readCopyUpdate { old in
try Network.withAddresses(
old, Network.addressBookToNetwork(addressBook.nodeAddresses), eventLoop: self.eventLoop.next())
}

return self
}

public var networkUpdatePeriod: UInt64? {
networkUpdatePeriodInner.withLockedValue { $0 }
}
Expand All @@ -366,6 +396,7 @@ public final class Client: Sendable {
await self.networkUpdateTask.setUpdatePeriod(nanoseconds)
self.networkUpdatePeriodInner.withLockedValue { $0 = nanoseconds }
}

}

extension Client {
Expand Down
12 changes: 12 additions & 0 deletions Sources/Hedera/Client/Network.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ internal final class Network: Sendable, AtomicReference {
self.init(map: tmp.map, nodes: tmp.nodes, health: tmp.health, connections: tmp.connections)
}

internal static func addressBookToNetwork(_ addressBook: [NodeAddress]) -> [String: AccountId] {
var network = [String: AccountId]()

for nodeAddress in addressBook {
for endpoint in nodeAddress.serviceEndpoints {
network[endpoint.description] = nodeAddress.nodeAccountId
}
}

return network
}

internal static func withAddressBook(_ old: Network, _ eventLoop: EventLoop, _ addressBook: NodeAddressBook) -> Self
{
let addressBook = addressBook.nodeAddresses
Expand Down
5 changes: 4 additions & 1 deletion Sources/Hedera/NodeAddress.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ public struct SocketAddressV4: LosslessStringConvertible {
}

public var description: String {
"\(ip):\(port)"
guard !domainName.isEmpty else {
return "\(ip):\(port)"
}
return "\(domainName):\(port)"
}
}

Expand Down
34 changes: 34 additions & 0 deletions Tests/HederaE2ETests/Client.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* ‌
* 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 class ClientIntegrationTests: XCTestCase {
internal func testInitWithMirrorNetwork() async throws {
let mirrorNetworkString = "testnet.mirrornode.hedera.com:443"
let client = try await Client.forMirrorNetwork([mirrorNetworkString])
let mirrorNetwork = client.mirrorNetwork

XCTAssertEqual(mirrorNetwork.count, 1)
XCTAssertEqual(mirrorNetwork[0], mirrorNetworkString)
XCTAssertNotNil(client.network)
}
}

0 comments on commit 78cbce4

Please sign in to comment.