diff --git a/Examples/Schedule/main.swift b/Examples/Schedule/main.swift
index 0322fe1e..4be8165e 100644
--- a/Examples/Schedule/main.swift
+++ b/Examples/Schedule/main.swift
@@ -14,6 +14,13 @@ internal enum Program {
// by this account and be signed by this key
client.setOperator(env.operatorAccountId, env.operatorKey)
+ let key = PrivateKey.generateEd25519()
+ let accountId = try await AccountCreateTransaction()
+ .keyWithoutAlias(.single(key.publicKey))
+ .execute(client)
+ .getReceipt(client)
+ .accountId!
+
// Generate a Ed25519 private, public key pair
let key1 = PrivateKey.generateEd25519()
let key2 = PrivateKey.generateEd25519()
@@ -23,62 +30,45 @@ internal enum Program {
print("private key 2 = \(key2)")
print("public key 2 = \(key2.publicKey)")
- let newAccountId = try await AccountCreateTransaction()
- .keyWithoutAlias(.keyList([.single(key1.publicKey), .single(key2.publicKey)]))
- .initialBalance(.fromTinybars(1000))
+ var customFee = CustomFixedFee()
+ customFee.feeCollectorAccountId = accountId
+ customFee.amount = 10
+
+ let topicId = try await TopicCreateTransaction()
+ .feeScheduleKey(.single(key1.publicKey))
+ .addCustomFee(customFee)
.execute(client)
.getReceipt(client)
- .accountId!
+ .topicId!
- print("new account ID: \(newAccountId)")
+ print("new topic ID: \(topicId)")
- let tx = TransferTransaction()
- .hbarTransfer(newAccountId, -Hbar(1))
- .hbarTransfer(env.operatorAccountId, Hbar(1))
+ var customFeeLimitFee = CustomFixedFee()
+ customFeeLimitFee.amount = 5
+ let customFeeLimit = CustomFeeLimit(payerId: env.operatorAccountId, customFees: [customFeeLimitFee])
- let response =
- try await tx
+ let response = try await TopicMessageSubmitTransaction()
+ .topicId(topicId)
+ .message("hello from hashgraph".data(using: .utf8)!)
+ .addCustomFeeLimit(customFeeLimit)
.schedule()
- .expirationTime(.now + .days(1))
+ .expirationTime(.now + .seconds(3))
.isWaitForExpiry(true)
.execute(client)
- print("scheduled transaction ID = \(response.transactionId)")
+ let scheduledTransactionId = response.transactionId
+ print("scheduled transaction ID = \(scheduledTransactionId)")
let scheduleId = try await response.getReceipt(client).scheduleId!
print("schedule ID = \(scheduleId)")
- let record = try await response.getRecord(client)
- print("record = \(record)")
-
- _ = try await ScheduleSignTransaction()
- .scheduleId(scheduleId)
- .freezeWith(client)
- .sign(key1)
- .execute(client)
- .getReceipt(client)
-
- let info = try await ScheduleInfoQuery()
- .scheduleId(scheduleId)
- .execute(client)
-
- print("schedule info = \(info)")
-
- _ = try await ScheduleSignTransaction()
- .scheduleId(scheduleId)
- .freezeWith(client)
- .sign(key2)
- .execute(client)
- .getReceipt(client)
-
- let transactionId = response.transactionId
+ _ = try await Task.sleep(nanoseconds: 1_000_000_000 * 6)
- print("The following link should query the mirror node for the scheduled transaction:")
+ let scheduleInfo = try await ScheduleInfoQuery().scheduleId(scheduleId).execute(client)
- let transactionIdString =
- "\(transactionId.accountId)-\(transactionId.validStart.seconds)-\(transactionId.validStart.subSecondNanos)"
+ print("scheduleInfo = \(scheduleInfo)")
- print("https://\(env.networkName).mirrornode.hedera.com/api/v1/transactions/\(transactionIdString)")
+ print("scheduled transaction receipt = \(try await TransactionReceiptQuery().transactionId(scheduledTransactionId).execute(client))")
}
}
diff --git a/Sources/Hiero/Schedule/ScheduleCreateTransaction.swift b/Sources/Hiero/Schedule/ScheduleCreateTransaction.swift
index 914ab4a8..c2a5359c 100644
--- a/Sources/Hiero/Schedule/ScheduleCreateTransaction.swift
+++ b/Sources/Hiero/Schedule/ScheduleCreateTransaction.swift
@@ -171,6 +171,7 @@ extension ScheduleCreateTransaction: ToProtobuf {
Proto_SchedulableTransactionBody.with { proto in
proto.data = scheduledTransaction.toSchedulableTransactionData()
proto.memo = scheduledTransaction.transaction.transactionMemo
+ proto.maxCustomFees = scheduledTransaction.transaction.customFeeLimits.compactMap { $0.toProtobuf() }
let transactionFee =
scheduledTransaction.transaction.maxTransactionFee
diff --git a/Sources/HieroProtobufs/Generated/services/contract_types.pb.swift b/Sources/HieroProtobufs/Generated/services/contract_types.pb.swift
index 2359cb85..2c3bd1fd 100644
--- a/Sources/HieroProtobufs/Generated/services/contract_types.pb.swift
+++ b/Sources/HieroProtobufs/Generated/services/contract_types.pb.swift
@@ -56,7 +56,7 @@ public struct Proto_InternalCallContext: @unchecked Sendable {
}
///*
-/// Results of executing EVM transaction.
+/// Results of executing a EVM transaction.
public struct Proto_EvmTransactionResult: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
@@ -99,7 +99,8 @@ public struct Proto_EvmTransactionResult: @unchecked Sendable {
public var gasUsed: UInt64 = 0
///*
- /// If not already externalized, the context of the internal call producing this result.
+ /// If not already externalized in a transaction body, the context of the
+ /// internal call producing this result.
public var internalCallContext: Proto_InternalCallContext {
get {return _internalCallContext ?? Proto_InternalCallContext()}
set {_internalCallContext = newValue}
diff --git a/Sources/HieroProtobufs/Generated/services/node_update.pb.swift b/Sources/HieroProtobufs/Generated/services/node_update.pb.swift
index 58e12046..d39d992d 100644
--- a/Sources/HieroProtobufs/Generated/services/node_update.pb.swift
+++ b/Sources/HieroProtobufs/Generated/services/node_update.pb.swift
@@ -208,7 +208,9 @@ public struct Com_Hedera_Hapi_Node_Addressbook_NodeUpdateTransactionBody: Sendab
/// This endpoint MUST use a valid port and SHALL be reachable over TLS.
/// This field MAY be omitted if the node does not support gRPC-Web access.
/// This field MUST be updated if the gRPC-Web endpoint changes.
- /// This field SHALL enable frontend clients to avoid hard-coded proxy endpoints.
+ /// This field SHALL enable frontend clients to avoid hard-coded proxy endpoints.
+ /// This field MAY be set to `ServiceEndpoint.DEFAULT` to remove a previously-valid
+ /// web proxy.
public var grpcProxyEndpoint: Proto_ServiceEndpoint {
get {return _grpcProxyEndpoint ?? Proto_ServiceEndpoint()}
set {_grpcProxyEndpoint = newValue}
diff --git a/Sources/HieroProtobufs/Generated/services/response_code.pb.swift b/Sources/HieroProtobufs/Generated/services/response_code.pb.swift
index 23a7c499..5a07298e 100644
--- a/Sources/HieroProtobufs/Generated/services/response_code.pb.swift
+++ b/Sources/HieroProtobufs/Generated/services/response_code.pb.swift
@@ -1546,6 +1546,10 @@ public enum Proto_ResponseCodeEnum: SwiftProtobuf.Enum, Swift.CaseIterable {
/// The GRPC proxy endpoint is set in the NodeCreate or NodeUpdate transaction,
/// which the network does not support.
case grpcWebProxyNotSupported // = 399
+
+ ///*
+ /// An NFT transfers list referenced a token type other than NON_FUNGIBLE_UNIQUE.
+ case nftTransfersOnlyAllowedForNonFungibleUnique // = 400
case UNRECOGNIZED(Int)
public init() {
@@ -1912,6 +1916,7 @@ public enum Proto_ResponseCodeEnum: SwiftProtobuf.Enum, Swift.CaseIterable {
case 397: self = .throttleGroupLcmOverflow
case 398: self = .airdropContainsMultipleSendersForAToken
case 399: self = .grpcWebProxyNotSupported
+ case 400: self = .nftTransfersOnlyAllowedForNonFungibleUnique
default: self = .UNRECOGNIZED(rawValue)
}
}
@@ -2276,6 +2281,7 @@ public enum Proto_ResponseCodeEnum: SwiftProtobuf.Enum, Swift.CaseIterable {
case .throttleGroupLcmOverflow: return 397
case .airdropContainsMultipleSendersForAToken: return 398
case .grpcWebProxyNotSupported: return 399
+ case .nftTransfersOnlyAllowedForNonFungibleUnique: return 400
case .UNRECOGNIZED(let i): return i
}
}
@@ -2640,6 +2646,7 @@ public enum Proto_ResponseCodeEnum: SwiftProtobuf.Enum, Swift.CaseIterable {
.throttleGroupLcmOverflow,
.airdropContainsMultipleSendersForAToken,
.grpcWebProxyNotSupported,
+ .nftTransfersOnlyAllowedForNonFungibleUnique,
]
}
@@ -3006,5 +3013,6 @@ extension Proto_ResponseCodeEnum: SwiftProtobuf._ProtoNameProviding {
397: .same(proto: "THROTTLE_GROUP_LCM_OVERFLOW"),
398: .same(proto: "AIRDROP_CONTAINS_MULTIPLE_SENDERS_FOR_A_TOKEN"),
399: .same(proto: "GRPC_WEB_PROXY_NOT_SUPPORTED"),
+ 400: .same(proto: "NFT_TRANSFERS_ONLY_ALLOWED_FOR_NON_FUNGIBLE_UNIQUE"),
]
}
diff --git a/Sources/HieroProtobufs/Generated/services/schedulable_transaction_body.pb.swift b/Sources/HieroProtobufs/Generated/services/schedulable_transaction_body.pb.swift
index 3461ffcb..bc587e53 100644
--- a/Sources/HieroProtobufs/Generated/services/schedulable_transaction_body.pb.swift
+++ b/Sources/HieroProtobufs/Generated/services/schedulable_transaction_body.pb.swift
@@ -602,6 +602,17 @@ public struct Proto_SchedulableTransactionBody: @unchecked Sendable {
set {_uniqueStorage()._data = .tokenAirdrop(newValue)}
}
+ ///*
+ /// A list of maximum custom fees that the users are willing to pay.
+ ///
+ /// This field is OPTIONAL.
+ /// If left empty, the users are accepting to pay any custom fee.
+ /// If used with a transaction type that does not support custom fee limits, the transaction will fail.
+ public var maxCustomFees: [Proto_CustomFeeLimit] {
+ get {return _storage._maxCustomFees}
+ set {_uniqueStorage()._maxCustomFees = newValue}
+ }
+
public var unknownFields = SwiftProtobuf.UnknownStorage()
public enum OneOf_Data: Equatable, Sendable {
@@ -878,12 +889,14 @@ extension Proto_SchedulableTransactionBody: SwiftProtobuf.Message, SwiftProtobuf
46: .same(proto: "tokenCancelAirdrop"),
47: .same(proto: "tokenClaimAirdrop"),
48: .same(proto: "tokenAirdrop"),
+ 1001: .standard(proto: "max_custom_fees"),
]
fileprivate class _StorageClass {
var _transactionFee: UInt64 = 0
var _memo: String = String()
var _data: Proto_SchedulableTransactionBody.OneOf_Data?
+ var _maxCustomFees: [Proto_CustomFeeLimit] = []
#if swift(>=5.10)
// This property is used as the initial default value for new instances of the type.
@@ -901,6 +914,7 @@ extension Proto_SchedulableTransactionBody: SwiftProtobuf.Message, SwiftProtobuf
_transactionFee = source._transactionFee
_memo = source._memo
_data = source._data
+ _maxCustomFees = source._maxCustomFees
}
}
@@ -1519,6 +1533,7 @@ extension Proto_SchedulableTransactionBody: SwiftProtobuf.Message, SwiftProtobuf
_storage._data = .tokenAirdrop(v)
}
}()
+ case 1001: try { try decoder.decodeRepeatedMessageField(value: &_storage._maxCustomFees) }()
default: break
}
}
@@ -1724,6 +1739,9 @@ extension Proto_SchedulableTransactionBody: SwiftProtobuf.Message, SwiftProtobuf
}()
case nil: break
}
+ if !_storage._maxCustomFees.isEmpty {
+ try visitor.visitRepeatedMessageField(value: _storage._maxCustomFees, fieldNumber: 1001)
+ }
}
try unknownFields.traverse(visitor: &visitor)
}
@@ -1736,6 +1754,7 @@ extension Proto_SchedulableTransactionBody: SwiftProtobuf.Message, SwiftProtobuf
if _storage._transactionFee != rhs_storage._transactionFee {return false}
if _storage._memo != rhs_storage._memo {return false}
if _storage._data != rhs_storage._data {return false}
+ if _storage._maxCustomFees != rhs_storage._maxCustomFees {return false}
return true
}
if !storagesAreEqual {return false}
diff --git a/Sources/HieroProtobufs/Protos/services/contract_types.proto b/Sources/HieroProtobufs/Protos/services/contract_types.proto
index 7202ea7b..5d84f346 100644
--- a/Sources/HieroProtobufs/Protos/services/contract_types.proto
+++ b/Sources/HieroProtobufs/Protos/services/contract_types.proto
@@ -42,7 +42,7 @@ message InternalCallContext {
}
/**
- * Results of executing EVM transaction.
+ * Results of executing a EVM transaction.
*/
message EvmTransactionResult {
/**
@@ -73,7 +73,8 @@ message EvmTransactionResult {
uint64 gas_used = 5;
/**
- * If not already externalized, the context of the internal call producing this result.
+ * If not already externalized in a transaction body, the context of the
+ * internal call producing this result.
*/
InternalCallContext internal_call_context = 6;
}
diff --git a/Sources/HieroProtobufs/Protos/services/node_update.proto b/Sources/HieroProtobufs/Protos/services/node_update.proto
index d2a80350..7a0ceb28 100644
--- a/Sources/HieroProtobufs/Protos/services/node_update.proto
+++ b/Sources/HieroProtobufs/Protos/services/node_update.proto
@@ -162,7 +162,9 @@ message NodeUpdateTransactionBody {
* This endpoint MUST use a valid port and SHALL be reachable over TLS.
* This field MAY be omitted if the node does not support gRPC-Web access.
* This field MUST be updated if the gRPC-Web endpoint changes.
- * This field SHALL enable frontend clients to avoid hard-coded proxy endpoints.
+ * This field SHALL enable frontend clients to avoid hard-coded proxy endpoints.
+ * This field MAY be set to `ServiceEndpoint.DEFAULT` to remove a previously-valid
+ * web proxy.
*/
proto.ServiceEndpoint grpc_proxy_endpoint = 10;
}
diff --git a/Sources/HieroProtobufs/Protos/services/response_code.proto b/Sources/HieroProtobufs/Protos/services/response_code.proto
index 6607588d..6dadd9f1 100644
--- a/Sources/HieroProtobufs/Protos/services/response_code.proto
+++ b/Sources/HieroProtobufs/Protos/services/response_code.proto
@@ -1760,4 +1760,9 @@ enum ResponseCodeEnum {
* which the network does not support.
*/
GRPC_WEB_PROXY_NOT_SUPPORTED = 399;
+
+ /**
+ * An NFT transfers list referenced a token type other than NON_FUNGIBLE_UNIQUE.
+ */
+ NFT_TRANSFERS_ONLY_ALLOWED_FOR_NON_FUNGIBLE_UNIQUE = 400;
}
diff --git a/Sources/HieroProtobufs/Protos/services/schedulable_transaction_body.proto b/Sources/HieroProtobufs/Protos/services/schedulable_transaction_body.proto
index 7928e9a4..e9f6851e 100644
--- a/Sources/HieroProtobufs/Protos/services/schedulable_transaction_body.proto
+++ b/Sources/HieroProtobufs/Protos/services/schedulable_transaction_body.proto
@@ -70,6 +70,7 @@ import "services/token_airdrop.proto";
import "services/schedule_delete.proto";
import "services/util_prng.proto";
+import "services/custom_fees.proto";
import "services/node_create.proto";
import "services/node_update.proto";
@@ -403,4 +404,13 @@ message SchedulableTransactionBody {
*/
TokenAirdropTransactionBody tokenAirdrop = 48;
}
+
+ /**
+ * A list of maximum custom fees that the users are willing to pay.
+ *
+ * This field is OPTIONAL.
+ * If left empty, the users are accepting to pay any custom fee.
+ * If used with a transaction type that does not support custom fee limits, the transaction will fail.
+ */
+ repeated CustomFeeLimit max_custom_fees = 1001;
}
diff --git a/Tests/HieroTests/TopicMessageSubmitTransactionTests.swift b/Tests/HieroTests/TopicMessageSubmitTransactionTests.swift
index 6c265bd6..3da4f9e2 100644
--- a/Tests/HieroTests/TopicMessageSubmitTransactionTests.swift
+++ b/Tests/HieroTests/TopicMessageSubmitTransactionTests.swift
@@ -24,7 +24,7 @@ internal final class TopicMessageSubmitTransactionTests: XCTestCase {
internal func testSerialize() throws {
let tx = try Self.makeTransaction().makeProtoBody()
- assertSnapshot(matching: tx, as: .description)
+ assertSnapshot(of: tx, as: .description)
}
internal func testToFromBytes() throws {
@@ -128,4 +128,28 @@ internal final class TopicMessageSubmitTransactionTests: XCTestCase {
XCTAssertEqual(tx.customFeeLimits, [customFeeLimitToAdd])
}
+
+ internal func testScheduledCustomFeeLimits() throws {
+ let payerId = AccountId(3)
+ let amount: UInt64 = 4
+ let tokenId = TokenId(3)
+ let customFeeLimitToAdd = CustomFeeLimit(
+ payerId: payerId,
+ customFees: [
+ CustomFixedFee(amount, nil, tokenId)
+ ])
+
+ let tx = TopicMessageSubmitTransaction()
+ .addCustomFeeLimit(customFeeLimitToAdd)
+ .schedule()
+ .toProtobuf()
+
+ XCTAssertEqual(tx.scheduledTransactionBody.maxCustomFees.count, 1)
+ XCTAssertEqual(tx.scheduledTransactionBody.maxCustomFees[0].accountID.accountNum, Int64(payerId.num))
+ XCTAssertEqual(tx.scheduledTransactionBody.maxCustomFees[0].fees.count, 1)
+ XCTAssertEqual(tx.scheduledTransactionBody.maxCustomFees[0].fees[0].amount, Int64(amount))
+ XCTAssertEqual(
+ tx.scheduledTransactionBody.maxCustomFees[0].fees[0].denominatingTokenID.tokenNum, Int64(tokenId.num))
+
+ }
}
diff --git a/protobufs b/protobufs
index c4f62d04..e7db7cd7 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit c4f62d047ec898519710feab6882beebe70a124d
+Subproject commit e7db7cd74e1709ca719d1fcc9119aa062e82930f