Skip to content
78 changes: 78 additions & 0 deletions examples/high_volume_account_create/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package main

import (
"fmt"
"os"

hiero "github.com/hiero-ledger/hiero-sdk-go/v2/sdk"
)

func main() {
var client *hiero.Client
var err error

// Retrieving network type from environment variable HEDERA_NETWORK
client, err = hiero.ClientForName(os.Getenv("HEDERA_NETWORK"))
if err != nil {
panic(fmt.Sprintf("%v : error creating client", err))
}

// Retrieving operator ID from environment variable OPERATOR_ID
operatorAccountID, err := hiero.AccountIDFromString(os.Getenv("OPERATOR_ID"))
if err != nil {
panic(fmt.Sprintf("%v : error converting string to AccountID", err))
}

// Retrieving operator key from environment variable OPERATOR_KEY
operatorKey, err := hiero.PrivateKeyFromString(os.Getenv("OPERATOR_KEY"))
if err != nil {
panic(fmt.Sprintf("%v : error converting string to PrivateKey", err))
}

// Setting the client operator ID and key
client.SetOperator(operatorAccountID, operatorKey)

// Generate new key to use with new account
newKey, err := hiero.PrivateKeyGenerateEd25519()
if err != nil {
panic(fmt.Sprintf("%v : error generating PrivateKey", err))
}

// HIP-1313: opt in to high-volume throttles by setting the high-volume flag.
// During busy periods, dynamic pricing applies and the transaction fee may be
// multiplied — SetMaxTransactionFee caps the price the operator is willing to pay.
transactionResponse, err := hiero.NewAccountCreateTransaction().
SetKeyWithoutAlias(newKey.PublicKey()).
SetInitialBalance(hiero.NewHbar(1)).
SetHighVolume(true).
SetMaxTransactionFee(hiero.NewHbar(5)).
Execute(client)
if err != nil {
panic(fmt.Sprintf("%v : error executing high-volume account create", err))
}

receipt, err := transactionResponse.GetReceipt(client)
if err != nil {
panic(fmt.Sprintf("%v : error getting receipt", err))
}

fmt.Printf("account = %v\n", *receipt.AccountID)

// The high-volume pricing multiplier is reported on the transaction record.
// Value is divided by 1000 to get the actual multiplier (e.g. 1000 = 1.000x).
record, err := transactionResponse.GetRecord(client)
if err != nil {
panic(fmt.Sprintf("%v : error getting record", err))
}

fmt.Printf("transaction fee = %v\n", record.TransactionFee)
fmt.Printf("high-volume pricing multiplier = %s\n", formatMultiplier(record.HighVolumePricingMultiplier))
}

// formatMultiplier renders the high-volume pricing multiplier from the record.
func formatMultiplier(m *uint64) string {
if m == nil {
return "(not set)"
}
return fmt.Sprintf("%.3fx", float64(*m)/1000)
}
88 changes: 88 additions & 0 deletions sdk/hip_1313_e2e_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//go:build all || e2e

package hiero

// SPDX-License-Identifier: Apache-2.0

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestIntegrationHIP1313HighVolumeAccountCreate(t *testing.T) {
t.Parallel()
env := NewIntegrationTestEnv(t)
defer CloseIntegrationTestEnv(env, nil)

newKey, err := PrivateKeyGenerateEd25519()
require.NoError(t, err)

resp, err := NewAccountCreateTransaction().
SetKeyWithoutAlias(newKey).
SetNodeAccountIDs(env.NodeAccountIDs).
SetInitialBalance(NewHbar(1)).
SetHighVolume(true).
Execute(env.Client)
require.NoError(t, err)

receipt, err := resp.SetValidateStatus(true).GetReceipt(env.Client)
require.NoError(t, err)

accountID := *receipt.AccountID
assert.NotEqual(t, AccountID{}, accountID)

record, err := resp.GetRecord(env.Client)
require.NoError(t, err)
require.NotNil(t, record.HighVolumePricingMultiplier)
assert.GreaterOrEqual(t, *record.HighVolumePricingMultiplier, uint64(1000))
}

func TestIntegrationHIP1313HighVolumeWithMaxTransactionFee(t *testing.T) {
t.Parallel()
env := NewIntegrationTestEnv(t)
defer CloseIntegrationTestEnv(env, nil)

newKey, err := PrivateKeyGenerateEd25519()
require.NoError(t, err)

resp, err := NewAccountCreateTransaction().
SetKeyWithoutAlias(newKey).
SetNodeAccountIDs(env.NodeAccountIDs).
SetInitialBalance(NewHbar(1)).
SetHighVolume(true).
SetMaxTransactionFee(NewHbar(2)).
Execute(env.Client)
require.NoError(t, err)

receipt, err := resp.SetValidateStatus(true).GetReceipt(env.Client)
require.NoError(t, err)

accountID := *receipt.AccountID
assert.NotEqual(t, AccountID{}, accountID)

// Verify fee charged does not exceed the max transaction fee
record, err := resp.GetRecord(env.Client)
require.NoError(t, err)
assert.True(t, record.TransactionFee.AsTinybar() <= NewHbar(2).AsTinybar())
}

func TestIntegrationHIP1313HighVolumeInsufficientFee(t *testing.T) {
t.Parallel()
env := NewIntegrationTestEnv(t)
defer CloseIntegrationTestEnv(env, nil)

newKey, err := PrivateKeyGenerateEd25519()
require.NoError(t, err)

_, err = NewAccountCreateTransaction().
SetKeyWithoutAlias(newKey).
SetNodeAccountIDs(env.NodeAccountIDs).
SetInitialBalance(NewHbar(1)).
SetHighVolume(true).
SetMaxTransactionFee(HbarFromTinybar(1)).
Execute(env.Client)

require.ErrorContains(t, err, "exceptional precheck status INSUFFICIENT_TX_FEE")
}
19 changes: 19 additions & 0 deletions sdk/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type BaseTransaction struct {
transactionFee uint64
defaultMaxTransactionFee uint64
memo string
highVolume bool
transactionValidDuration *time.Duration
transactionID TransactionID

Expand Down Expand Up @@ -75,6 +76,7 @@ func _NewTransaction[T TransactionInterface](concreteTransaction T) *Transaction
BaseTransaction: &BaseTransaction{
defaultMaxTransactionFee: uint64(NewHbar(2).AsTinybar()),
transactionValidDuration: &duration,
highVolume: false,
transactions: _NewLockableSlice(),
signedTransactions: _NewLockableSlice(),
customFeeLimits: nil,
Expand Down Expand Up @@ -856,6 +858,7 @@ func (tx *Transaction[T]) _BuildTransaction(index int) (*services.Transaction, e
}

originalBody.Memo = tx.memo
originalBody.HighVolume = tx.highVolume
if tx.transactionFee != 0 {
originalBody.TransactionFee = tx.transactionFee
} else {
Expand Down Expand Up @@ -1236,6 +1239,20 @@ func (tx *Transaction[T]) GetBatchKey() Key {
return tx.batchKey
}

// GetHighVolume returns the high volume flag for this transaction.
func (tx *Transaction[T]) GetHighVolume() bool {
return tx.highVolume
}

// SetHighVolume sets the high volume flag for this transaction.
Comment thread
Dosik13 marked this conversation as resolved.
// Supported transactions: AccountCreate, ContractCreate, TokenCreate, TopicCreate,
// FileCreate, FileAppend, ScheduleCreate, TokenAirdrop, TokenAssociate,
// TokenClaimAirdrop, TokenMint, TransferTransaction, AccountAllowanceApprove, HookStore.
func (tx *Transaction[T]) SetHighVolume(highVolume bool) T {
tx.highVolume = highVolume
return tx.childTransaction
}

// Batchify method is used to mark a transaction as part of a batch transaction or make it so-called inner transaction.
// The Transaction will be frozen and signed by the operator of the client.
func (tx *Transaction[T]) Batchify(client *Client, batchKey Key) (T, error) {
Expand Down Expand Up @@ -1378,6 +1395,7 @@ func (tx *Transaction[T]) buildTransactionBody() *services.TransactionBody {
Memo: tx.memo,
TransactionValidDuration: _DurationToProtobuf(tx.GetTransactionValidDuration()),
TransactionID: tx.transactionID._ToProtobuf(),
HighVolume: tx.highVolume,
}
}

Expand Down Expand Up @@ -1934,6 +1952,7 @@ func setTransactionFields(body *services.TransactionBody, baseTx *Transaction[Tr
}

baseTx.memo = body.Memo
baseTx.highVolume = body.HighVolume
if body.TransactionFee != 0 {
baseTx.transactionFee = body.TransactionFee
}
Expand Down
67 changes: 39 additions & 28 deletions sdk/transaction_record.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,14 @@ type TransactionRecord struct {
// Deprecated
TokenAllowances []TokenAllowance
// Deprecated
TokenNftAllowances []TokenNftAllowance
EthereumHash []byte
PaidStakingRewards map[AccountID]Hbar
PrngBytes []byte
PrngNumber *int32
EvmAddress []byte
PendingAirdropRecords []PendingAirdropRecord
TokenNftAllowances []TokenNftAllowance
EthereumHash []byte
PaidStakingRewards map[AccountID]Hbar
PrngBytes []byte
PrngNumber *int32
EvmAddress []byte
PendingAirdropRecords []PendingAirdropRecord
HighVolumePricingMultiplier *uint64
}

// MarshalJSON returns the JSON representation of the TransactionRecord
Expand Down Expand Up @@ -220,6 +221,7 @@ func (record TransactionRecord) MarshalJSON() ([]byte, error) {
}
}
m["pendingAirdropRecords"] = pendingAirdropRecords
m["highVolumePricingMultiplier"] = record.HighVolumePricingMultiplier

receiptBytes, err := record.Receipt.MarshalJSON()
if err != nil {
Expand Down Expand Up @@ -354,27 +356,32 @@ func _TransactionRecordFromProtobuf(protoResponse *services.TransactionGetRecord
}

txRecord := TransactionRecord{
Receipt: _TransactionReceiptFromProtobuf(&services.TransactionGetReceiptResponse{Receipt: pb.GetReceipt()}, txID),
TransactionHash: pb.TransactionHash,
ConsensusTimestamp: _TimeFromProtobuf(pb.ConsensusTimestamp),
TransactionID: transactionID,
ScheduleRef: scheduleRef,
TransactionMemo: pb.Memo,
TransactionFee: HbarFromTinybar(int64(pb.TransactionFee)),
Transfers: accountTransfers,
TokenTransfers: tokenTransfers,
NftTransfers: nftTransfers,
CallResultIsCreate: true,
AssessedCustomFees: assessedCustomFees,
AutomaticTokenAssociations: tokenAssociation,
ParentConsensusTimestamp: _TimeFromProtobuf(pb.ParentConsensusTimestamp),
AliasKey: alias,
Duplicates: duplicateReceipts,
Children: childReceipts,
EthereumHash: pb.EthereumHash,
PaidStakingRewards: paidStakingRewards,
EvmAddress: pb.EvmAddress,
PendingAirdropRecords: pendingAirdropRecords,
Receipt: _TransactionReceiptFromProtobuf(&services.TransactionGetReceiptResponse{Receipt: pb.GetReceipt()}, txID),
TransactionHash: pb.TransactionHash,
ConsensusTimestamp: _TimeFromProtobuf(pb.ConsensusTimestamp),
TransactionID: transactionID,
ScheduleRef: scheduleRef,
TransactionMemo: pb.Memo,
TransactionFee: HbarFromTinybar(int64(pb.TransactionFee)),
Transfers: accountTransfers,
TokenTransfers: tokenTransfers,
NftTransfers: nftTransfers,
CallResultIsCreate: true,
AssessedCustomFees: assessedCustomFees,
AutomaticTokenAssociations: tokenAssociation,
ParentConsensusTimestamp: _TimeFromProtobuf(pb.ParentConsensusTimestamp),
AliasKey: alias,
Duplicates: duplicateReceipts,
Children: childReceipts,
EthereumHash: pb.EthereumHash,
PaidStakingRewards: paidStakingRewards,
EvmAddress: pb.EvmAddress,
PendingAirdropRecords: pendingAirdropRecords,
}

if pb.HighVolumePricingMultiplier != 0 {
multiplier := pb.HighVolumePricingMultiplier
txRecord.HighVolumePricingMultiplier = &multiplier
}

if w, ok := pb.Entropy.(*services.TransactionRecord_PrngBytes); ok {
Expand Down Expand Up @@ -490,6 +497,10 @@ func (record TransactionRecord) _ToProtobuf() (*services.TransactionGetRecordRes
EvmAddress: record.EvmAddress,
}

if record.HighVolumePricingMultiplier != nil {
tRecord.HighVolumePricingMultiplier = *record.HighVolumePricingMultiplier
}

if record.PrngNumber != nil {
tRecord.Entropy = &services.TransactionRecord_PrngNumber{PrngNumber: *record.PrngNumber}
} else if len(record.PrngBytes) > 0 {
Expand Down
3 changes: 3 additions & 0 deletions sdk/transaction_record_query_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ func TestUnitTransactionRecordQueryMarshalJSON(t *testing.T) {
record.PrngBytes = []byte{1, 2, 3, 4}
record.PrngNumber = &prngNumber
record.EvmAddress = evmAddressBytes
highVolumeMultiplier := uint64(1000)
record.HighVolumePricingMultiplier = &highVolumeMultiplier
record.AssessedCustomFees = []AssessedCustomFee{assessedCustomFee}
record.AutomaticTokenAssociations = []TokenAssociation{tokenAssociation}
record.PendingAirdropRecords = []PendingAirdropRecord{{pendingAirdropId: PendingAirdropId{&accID, &accID, &tokenID, nil}, pendingAirdropAmount: 789}}
Expand All @@ -329,6 +331,7 @@ func TestUnitTransactionRecordQueryMarshalJSON(t *testing.T) {
"duplicates":[],
"ethereumHash":"01020304",
"evmAddress":"deadbeef",
"highVolumePricingMultiplier":1000,
"nftTransfers":{"0.0.123":[{"sender":"0.0.1246","recipient":"0.0.1246","isApproved":true,"serial":123}]},
"paidStakingRewards":[
{"accountId":"0.0.1157","amount":"-1041694270","isApproved":false},
Expand Down
15 changes: 15 additions & 0 deletions sdk/transaction_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1545,6 +1545,21 @@ func createTransactionTests(txName string, nodeAccountIds []AccountID) []transac
require.Equal(t, "test memo", actual)
},
},
{
name: "TransactionHighVolume/" + txName,
set: func(transactionInterface TransactionInterface) (TransactionInterface, error) {
baseTx := transactionInterface.getBaseTransaction()
baseTx.SetHighVolume(true)
return transactionInterface, nil
},
get: func(transactionInterface TransactionInterface) (interface{}, error) {
baseTx := transactionInterface.getBaseTransaction()
return baseTx.GetHighVolume(), nil
},
assert: func(t *testing.T, actual interface{}) {
require.Equal(t, true, actual)
},
},
{
name: "TransactionMaxTransactionFee/" + txName,
set: func(transactionInterface TransactionInterface) (TransactionInterface, error) {
Expand Down
Loading