@@ -12,7 +12,7 @@ status: Approved
1212last-call-date-time : 2025-06-06T07:00:00Z
1313created : 2025-02-19
1414discussions-to : https://github.com/hiero-ledger/hiero-improvement-proposals/discussions/1172
15- updated : 2025-09-02
15+ updated : 2025-10-03
1616---
1717
1818## Abstract
@@ -22,8 +22,8 @@ In principle, hooks could be programmed in any language, but we begin with **EVM
2222writing contracts in a language like Solidity that compiles to EVM bytecode. EVM hooks may have optimized variants in
2323the future, but we propose first a full-featured ** lambda** EVM hook. The name is chosen to evoke event-driven code
2424running in the cloud or call other external services. For any given entity, its user can create many hooks for that
25- entity with different 64-bit ** hook ids** . There is no limit on the number of hook ids than an entity can use; bu
26- tits storage footprint, and hence rent, will increase proportionally.
25+ entity with different 64-bit ** hook ids** . There is no limit on the number of hook ids than an entity can use; but
26+ its storage footprint, and hence rent, will increase proportionally.
2727
2828As a first Hiero extension point, we propose ** account allowance hooks** . Users can create these hooks on their
2929accounts, and a Hiero API (HAPI) ` CryptoTransfer ` transaction can then reference an allowance hook just as it does an
@@ -593,32 +593,27 @@ message EvmHookState {
593593 */
594594 proto.ContractID hook_contract_id = 4;
595595
596- /**
597- * True if the hook has been deleted.
598- */
599- bool deleted = 5;
600-
601596 /**
602597 * For a lambda, its first storage key.
603598 */
604- bytes first_contract_storage_key = 6 ;
599+ bytes first_contract_storage_key = 5 ;
605600
606601 /**
607602 * If set, the id of the hook preceding this one in the owner's
608603 * doubly-linked list of hooks.
609604 */
610- google.protobuf.Int64Value previous_hook_id = 7 ;
605+ google.protobuf.Int64Value previous_hook_id = 6 ;
611606
612607 /**
613608 * If set, the id of the hook following this one in the owner's
614609 * doubly-linked list of hooks.
615610 */
616- google.protobuf.Int64Value next_hook_id = 8 ;
611+ google.protobuf.Int64Value next_hook_id = 7 ;
617612
618613 /**
619614 * The number of storage slots a lambda is using.
620615 */
621- uint32 num_storage_slots = 9 ;
616+ uint32 num_storage_slots = 8 ;
622617}
623618
624619/**
@@ -718,7 +713,8 @@ If any transaction repeats a hook id in its `hook_creation_details` list, it wil
718713to create a hook with an id that is already occupied, it will fail with status ` INDEX_IN_USE ` . Similarly, if either
719714update transaction tries to delete a hook with an id not in use, it will fail with status ` HOOK_NOT_FOUND ` . Much as an
720715account can only be deleted when it has zero HTS token balances, a hook can only be deleted when it has zero storage
721- slots. Otherwise deletion will fail with ` HOOK_DELETION_REQUIRES_EMPTY_STORAGE ` .
716+ slots. Otherwise deletion will fail with ` HOOK_DELETION_REQUIRES_EMPTY_STORAGE ` . An account can only be deleted when it
717+ has zero hooks; otherwise deletion will fail with ` TRANSACTION_REQUIRES_ZERO_HOOKS ` .
722718
723719To support atomic hook updates for compliance reasons, we ** do** support deleting and recreating a hook with the same
724720id in a single update transaction. (That is, the ` hook_ids_to_delete ` list is processed first; then the
@@ -822,7 +818,7 @@ message NftTransfer {
822818Note that ` NftTransfer ` supports both sender and receiver transfer allowance hooks, since the transaction may
823819need to use the receiver hook to satisfy a ` receiver_sig_required=true ` setting.
824820
825- ### The transfer allowance ABI
821+ ### The account allowance ABI
826822
827823The account allowance EVM hook ABI is as follows,
828824
@@ -831,8 +827,6 @@ The account allowance EVM hook ABI is as follows,
831827pragma solidity >=0.4.9 <0.9.0;
832828pragma experimental ABIEncoderV2;
833829
834- import './IHederaTokenService.sol';
835-
836830/// The interface for a generic EVM hook.
837831interface IHieroHook {
838832 /// The context the hook is executing in
@@ -850,46 +844,48 @@ interface IHieroHook {
850844 }
851845}
852846
847+
848+ // SPDX-License-Identifier: Apache-2.0
849+ pragma solidity >=0.4.9 <0.9.0;
850+ pragma experimental ABIEncoderV2;
851+
852+ import './IHieroHook.sol';
853853/// The interface for an account allowance hook invoked once before a CryptoTransfer.
854854interface IHieroAccountAllowanceHook {
855- /// Combines HBAR and HTS asset transfers.
856- struct Transfers {
857- /// The HBAR transfers
858- IHederaTokenService.TransferList hbar ;
859- /// The HTS token transfers
860- IHederaTokenService.TokenTransferList[] tokens ;
861- }
855+ /// A single balance adjustment in the range of a Hiero native token
856+ struct AccountAmount {
857+ // The address of the account whose balance is changing
858+ address account ;
859+ // The amount in atomic units of the change
860+ int64 amount ;
861+ }
862862
863- /// Combines the full proposed transfers for a Hiero transaction,
864- /// including both its direct transfers and the implied HIP-18
865- /// custom fee transfers.
866- struct ProposedTransfers {
867- /// The transaction's direct transfers
868- Transfers direct ;
869- /// The transaction's assessed custom fees
870- Transfers customFee ;
871- }
863+ /// A single NFT ownership change
864+ struct NftTransfer {
865+ // The address of the sender
866+ address sender;
867+ // The address of the receiver
868+ address receiver ;
869+ // The serial number being transferred
870+ int64 serialNo ;
871+ }
872872
873- /// Decides if the proposed transfers are allowed, optionally in
874- /// the presence of additional context encoded by the transaction
875- /// payer in the extra calldata.
876- /// @param context The context of the hook call
877- /// @param proposedTransfers The proposed transfers
878- /// @return true If the proposed transfers are allowed, false or revert otherwise
879- function allow(
880- IHieroHook.HookContext calldata context,
881- ProposedTransfers memory proposedTransfers
882- ) external payable returns (bool);
883- }
873+ /// A zero-sum list of balance adjustments for a Hiero-native token
874+ struct TokenTransferList {
875+ // The Hiero token address
876+ address token;
877+ // For a fungible token, the zero-sum balance adjustments
878+ AccountAmount[] adjustments;
879+ // For a non-fungible token, the NFT ownership changes
880+ NftTransfer[] nftTransfers;
881+ }
884882
885- /// The interface for an account allowance hook invoked both before and after a CryptoTransfer.
886- interface IHieroAccountAllowancePrePostHook {
887883 /// Combines HBAR and HTS asset transfers.
888884 struct Transfers {
889- /// The HBAR transfers
890- IHederaTokenService.TransferList hbar ;
885+ /// A zero-sum list of balance adjustments for HBAR specifically
886+ AccountAmount[] hbarAdjustments ;
891887 /// The HTS token transfers
892- IHederaTokenService. TokenTransferList[] tokens;
888+ TokenTransferList[] tokens;
893889 }
894890
895891 /// Combines the full proposed transfers for a Hiero transaction,
@@ -902,6 +898,27 @@ interface IHieroAccountAllowancePrePostHook {
902898 Transfers customFee;
903899 }
904900
901+ /// Decides if the proposed transfers are allowed, optionally in
902+ /// the presence of additional context encoded by the transaction
903+ /// payer in the extra calldata.
904+ /// @param context The context of the hook call
905+ /// @param proposedTransfers The proposed transfers
906+ /// @return true If the proposed transfers are allowed, false or revert otherwise
907+ function allow(
908+ IHieroHook.HookContext calldata context,
909+ ProposedTransfers memory proposedTransfers
910+ ) external payable returns (bool);
911+ }
912+
913+ // SPDX-License-Identifier: Apache-2.0
914+ pragma solidity >=0.4.9 <0.9.0;
915+ pragma experimental ABIEncoderV2;
916+
917+ import './IHieroHook.sol';
918+ import './IHieroAccountAllowanceHook.sol';
919+
920+ /// The interface for an account allowance hook invoked both before and after a CryptoTransfer.
921+ interface IHieroAccountAllowancePrePostHook {
905922 /// Decides if the proposed transfers are allowed BEFORE the CryptoTransfer
906923 /// business logic is performed, optionally in the presence of additional
907924 /// context encoded by the transaction payer in the extra calldata.
@@ -910,9 +927,9 @@ interface IHieroAccountAllowancePrePostHook {
910927 /// @return true If the proposed transfers are allowed, false or revert otherwise
911928 function allowPre(
912929 IHieroHook.HookContext calldata context,
913- ProposedTransfers memory proposedTransfers
930+ IHieroAccountAllowanceHook. ProposedTransfers memory proposedTransfers
914931 ) external payable returns (bool);
915-
932+
916933 /// Decides if the proposed transfers are allowed AFTER the CryptoTransfer
917934 /// business logic is performed, optionally in the presence of additional
918935 /// context encoded by the transaction payer in the extra calldata.
@@ -921,11 +938,34 @@ interface IHieroAccountAllowancePrePostHook {
921938 /// @return true If the proposed transfers are allowed, false or revert otherwise
922939 function allowPost(
923940 IHieroHook.HookContext calldata context,
924- ProposedTransfers memory proposedTransfers
941+ IHieroAccountAllowanceHook. ProposedTransfers memory proposedTransfers
925942 ) external payable returns (bool);
926943}
927944```
928945
946+ ### Call order of ` ACCOUNT_ALLOWANCE_HOOK ` s
947+
948+ A ` CryptoTransferTransactionBody ` can execute multiple hooks, subject only to the global limit on the number of child
949+ records for a single transaction,
950+ ```
951+ consensus.handle.maxFollowingRecords=50
952+ ```
953+ The ` CryptoTransfer ` handler will execute hook calls in the following order:
954+ 1 . All ` pre_tx_allowance_hook ` and ` pre_post_tx_allowance_hook ` calls in the HBAR ` TransferList ` , in the order they
955+ appear in the ` CryptoTransferTransactionBody ` .
956+ 2 . All ` pre_tx_allowance_hook ` and ` pre_post_tx_allowance_hook ` calls in the fungible ` transfers ` of the ` tokenTransfers `
957+ list, in the order they appear in the ` CryptoTransferTransactionBody ` .
958+ 3 . All ` pre_tx_sender_allowance_hook ` , ` pre_post_sender_tx_allowance_hook ` , ` pre_tx_receiver_allowance_hook ` , and
959+ ` pre_post_tx_receiver_allowance_hook ` calls in the non-fungible ` nftTransfers ` of the ` tokenTransfers ` list, in the
960+ order they appear in the ` CryptoTransferTransactionBody ` . (_ Note:_ if both sender and receiver hooks are present, then
961+ the sender hook is executed first.)
962+ 4 . All ` pre_post ` hooks previously executed in steps (1) to (3), _ in the same order they were previously executed_ ;
963+ while the first call will be to the ` allowPre(HookContext, ProposedTransfers) ` method signature, the second call will
964+ be to the ` allowPost(HookContext, ProposedTransfers) ` signature.
965+
966+ A block stream or legacy record stream client can use this well-known ordering to identify which child ` ContractCall `
967+ to ` 0x16d ` corresponds to which ` HookCall ` in a ` CryptoTransferTransactionBody ` .
968+
929969### Examples
930970
931971Next we provide two examples of account allowance EVM hooks.
0 commit comments