Skip to content

Commit 6a6c2bc

Browse files
0xivanovemiliyankdependabot[bot]jjohannes
authored
feat: Hiero Hooks (#2538)
Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: emiliyank <e.kadiyski@gmail.com> Signed-off-by: Ivan Ivanov <ivanivanov.ii726@gmail.com> Signed-off-by: Emiliyan Kadiyski <e.kadiyski@gmail.com> Signed-off-by: Jendrik Johannes <jendrik.johannes@gmail.com> Co-authored-by: Emiliyan Kadiyski <e.kadiyski@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jendrik Johannes <jendrik.johannes@gmail.com>
1 parent d1744c0 commit 6a6c2bc

78 files changed

Lines changed: 7435 additions & 176 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ jobs:
179179
uses: hiero-ledger/hiero-solo-action@dd0048139ef1e40fd6067f01bf94eb42a67294f4 # v0.15
180180
with:
181181
installMirrorNode: true
182+
hieroVersion: v0.69.0-alpha.1
182183

183184
- name: Build SDK
184185
run: ./gradlew assemble
@@ -301,6 +302,7 @@ jobs:
301302
uses: hiero-ledger/hiero-solo-action@dd0048139ef1e40fd6067f01bf94eb42a67294f4 # v0.15
302303
with:
303304
installMirrorNode: true
305+
hieroVersion: v0.69.0-alpha.1
304306

305307
- name: Build SDK
306308
run: ./gradlew assemble

examples/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
* [Staking](../examples/src/main/java/org/hiero/sdk/java/examples/StakingExample.java)
4141
* [Staking (with update)](../examples/src/main/java/org/hiero/sdk/java/examples/StakingWithUpdateExample.java)
4242
* [Multisig](../examples/src/main/java/org/hiero/sdk/java/examples/MultiSigOfflineExample.java)
43+
* [Account hooks](../examples/src/main/java/com/hedera/hashgraph/sdk/examples/AccountHooksExample.java)
44+
* [Lambda SStore](../examples/src/main/java/com/hedera/hashgraph/sdk/examples/LambdaSStoreExample.java)
4345

4446
### Consensus Service
4547

@@ -75,6 +77,7 @@
7577
* [Create a stateful contract](../examples/src/main/java/com/hedera/hashgraph/sdk/examples/CreateStatefulContractExample.java)
7678
* [Contract nonce (HIP-729)](../examples/src/main/java/com/hedera/hashgraph/sdk/examples/ContractNoncesExample.java)
7779
* [Interaction with a contract](../examples/src/main/java/com/hedera/hashgraph/sdk/examples/SolidityPrecompileExample.java)
80+
* [Contract hooks](../examples/src/main/java/com/hedera/hashgraph/sdk/examples/ContractHooksExample.java)
7881

7982
### Miscellaneous
8083

@@ -106,6 +109,10 @@ for configuring the logging. If the `SDK_LOG_LEVEL` is not set, it will default
106109
Important pre-requisite to see logs: set simple logger log level to same level as the `SDK_LOG_LEVEL`,
107110
for example via VM options: `-Dorg.slf4j.simpleLogger.log.org.hiero=trace`.
108111

112+
For hook-related examples (like `AddHooksToAccountExample`), you also need to set the `HOOK_CONTRACT_ID` variable:
113+
- In our examples we use a bytecote from a specific smart contract which can be found in `/contracts/HieroHookContract.sol`.
114+
- Note: Hook functionality may not be available on all networks. The example will demonstrate the configuration even if hooks are not supported.
115+
109116
Therefore, the format of the configuration file should be as follows:
110117

111118
```.properties
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
package com.hedera.hashgraph.sdk.examples;
3+
4+
import com.hedera.hashgraph.sdk.*;
5+
import com.hedera.hashgraph.sdk.logger.LogLevel;
6+
import com.hedera.hashgraph.sdk.logger.Logger;
7+
import io.github.cdimascio.dotenv.Dotenv;
8+
import java.nio.charset.StandardCharsets;
9+
import java.util.Objects;
10+
11+
/**
12+
* How to work with account hooks.
13+
* <p>
14+
* This example demonstrates how to create accounts with hooks and add hooks to existing accounts.
15+
* It shows different types of hook configurations including basic lambda hooks and hooks with storage updates.
16+
*/
17+
class AccountHooksExample {
18+
19+
/*
20+
* See .env.sample in the examples folder root for how to specify values below
21+
* or set environment variables with the same names.
22+
*/
23+
24+
/**
25+
* Operator's account ID.
26+
* Used to sign and pay for operations on Hedera.
27+
*/
28+
private static final AccountId OPERATOR_ID =
29+
AccountId.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_ID")));
30+
31+
/**
32+
* Operator's private key.
33+
*/
34+
private static final PrivateKey OPERATOR_KEY =
35+
PrivateKey.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_KEY")));
36+
37+
/**
38+
* HEDERA_NETWORK defaults to testnet if not specified in dotenv file.
39+
* Network can be: localhost, testnet, previewnet or mainnet.
40+
*/
41+
private static final String HEDERA_NETWORK = Dotenv.load().get("HEDERA_NETWORK", "testnet");
42+
43+
/**
44+
* SDK_LOG_LEVEL defaults to SILENT if not specified in dotenv file.
45+
* Log levels can be: TRACE, DEBUG, INFO, WARN, ERROR, SILENT.
46+
* <p>
47+
* Important pre-requisite: set simple logger log level to same level as the SDK_LOG_LEVEL,
48+
* for example via VM options: -Dorg.slf4j.simpleLogger.log.org.hiero=trace
49+
*/
50+
private static final String SDK_LOG_LEVEL = Dotenv.load().get("SDK_LOG_LEVEL", "SILENT");
51+
52+
public static void main(String[] args) throws Exception {
53+
System.out.println("Account Hooks Example Start!");
54+
55+
/*
56+
* Step 0:
57+
* Create and configure the SDK Client.
58+
*/
59+
Client client = ClientHelper.forName(HEDERA_NETWORK);
60+
// All generated transactions will be paid by this account and signed by this key.
61+
client.setOperator(OPERATOR_ID, OPERATOR_KEY);
62+
// Attach logger to the SDK Client.
63+
client.setLogger(new Logger(LogLevel.valueOf(SDK_LOG_LEVEL)));
64+
65+
/*
66+
* Step 1:
67+
* Create the hook contract.
68+
*/
69+
System.out.println("Creating hook contract...");
70+
ContractId contractId = createContractId(client);
71+
System.out.println("Hook contract created with ID: " + contractId);
72+
73+
/*
74+
* Step 2:
75+
* Demonstrate creating an account with hooks.
76+
*/
77+
System.out.println("\n=== Creating Account with Hooks ===");
78+
AccountWithKey accountWithKey = createAccountWithHooks(client, contractId);
79+
AccountId accountId = accountWithKey.accountId;
80+
PrivateKey accountKey = accountWithKey.privateKey;
81+
82+
/*
83+
* Step 3:
84+
* Demonstrate adding hooks to an existing account.
85+
*/
86+
System.out.println("\n=== Adding Hooks to Existing Account ===");
87+
addHooksToAccount(client, contractId, accountId, accountKey);
88+
89+
/*
90+
* Step 4:
91+
* Demonstrate hook deletion.
92+
*/
93+
System.out.println("\n=== Deleting Hooks from Account ===");
94+
deleteHooksFromAccount(client, accountId, accountKey);
95+
96+
client.close();
97+
System.out.println("Account Hooks Example Complete!");
98+
}
99+
100+
/**
101+
* Simple class to hold both account ID and private key.
102+
*/
103+
private static class AccountWithKey {
104+
final AccountId accountId;
105+
final PrivateKey privateKey;
106+
107+
AccountWithKey(AccountId accountId, PrivateKey privateKey) {
108+
this.accountId = accountId;
109+
this.privateKey = privateKey;
110+
}
111+
}
112+
113+
/**
114+
* Creates an account with hooks from the start.
115+
*/
116+
private static AccountWithKey createAccountWithHooks(Client client, ContractId contractId) throws Exception {
117+
System.out.println("Creating account with lambda EVM hook...");
118+
119+
LambdaEvmHook lambdaHook = new LambdaEvmHook(contractId);
120+
121+
Key adminKey = OPERATOR_KEY.getPublicKey();
122+
HookCreationDetails hookDetails =
123+
new HookCreationDetails(HookExtensionPoint.ACCOUNT_ALLOWANCE_HOOK, 1002L, lambdaHook, adminKey);
124+
125+
PrivateKey accountKey = PrivateKey.generateED25519();
126+
PublicKey accountPublicKey = accountKey.getPublicKey();
127+
128+
try {
129+
TransactionResponse accountCreateResponse = new AccountCreateTransaction()
130+
.setKeyWithoutAlias(accountPublicKey)
131+
.setInitialBalance(Hbar.from(1))
132+
.addHook(hookDetails)
133+
.freezeWith(client)
134+
.sign(accountKey)
135+
.execute(client);
136+
137+
TransactionReceipt accountCreateReceipt = accountCreateResponse.getReceipt(client);
138+
AccountId accountId = accountCreateReceipt.accountId;
139+
Objects.requireNonNull(accountId);
140+
System.out.println("Created account with ID: " + accountId);
141+
System.out.println("Successfully created account with lambda hook!");
142+
143+
return new AccountWithKey(accountId, accountKey);
144+
} catch (Exception e) {
145+
System.err.println("Failed to execute account creation with hook: " + e.getMessage());
146+
throw e;
147+
}
148+
}
149+
150+
/**
151+
* Adds hooks to an existing account.
152+
*/
153+
private static void addHooksToAccount(
154+
Client client, ContractId contractId, AccountId accountId, PrivateKey accountKey) throws Exception {
155+
System.out.println("Adding hooks to existing account...");
156+
157+
Key adminKey = OPERATOR_KEY.getPublicKey();
158+
159+
// Create basic lambda hooks with no storage updates
160+
LambdaEvmHook basicHook = new LambdaEvmHook(contractId);
161+
HookCreationDetails hook1 =
162+
new HookCreationDetails(HookExtensionPoint.ACCOUNT_ALLOWANCE_HOOK, 1L, basicHook, adminKey);
163+
164+
LambdaEvmHook basicHook2 = new LambdaEvmHook(contractId);
165+
HookCreationDetails hook2 =
166+
new HookCreationDetails(HookExtensionPoint.ACCOUNT_ALLOWANCE_HOOK, 2L, basicHook2, adminKey);
167+
168+
try {
169+
TransactionResponse accountUpdateResponse = new AccountUpdateTransaction()
170+
.setAccountId(accountId)
171+
.addHookToCreate(hook1)
172+
.addHookToCreate(hook2)
173+
.freezeWith(client)
174+
.sign(accountKey)
175+
.execute(client);
176+
177+
TransactionReceipt accountUpdateReceipt = accountUpdateResponse.getReceipt(client);
178+
179+
// Throws on failure; success if we reached here
180+
System.out.println("Successfully added hooks to account!");
181+
} catch (Exception e) {
182+
System.err.println("Failed to execute hook transaction: " + e.getMessage());
183+
}
184+
185+
// Verify the hooks were added by querying account info
186+
AccountInfo accountInfo = new AccountInfoQuery().setAccountId(accountId).execute(client);
187+
188+
System.out.println("Account ID: " + accountInfo.accountId);
189+
System.out.println("Account balance: " + accountInfo.balance);
190+
}
191+
192+
/**
193+
* Deletes hooks from an account.
194+
*/
195+
private static void deleteHooksFromAccount(Client client, AccountId accountId, PrivateKey accountKey) {
196+
System.out.println("Deleting hooks from account...");
197+
198+
// Delete the basic hooks (no storage)
199+
try {
200+
TransactionResponse deleteHookResponse = new AccountUpdateTransaction()
201+
.setAccountId(accountId)
202+
.addHookToDelete(1L)
203+
.addHookToDelete(2L)
204+
.freezeWith(client)
205+
.sign(accountKey)
206+
.execute(client);
207+
208+
deleteHookResponse.getReceipt(client);
209+
210+
// Throws on failure; success if we reached here
211+
System.out.println("Successfully deleted hooks (IDs: 1, 2)");
212+
} catch (Exception e) {
213+
System.err.println("Failed to execute hook 1 deletion: " + e.getMessage());
214+
}
215+
}
216+
217+
private static FileId createBytecodeFile(Client client) throws Exception {
218+
String contractBytecodeHex = ContractHelper.getBytecodeHex("contracts/hiero_hook/hiero_hook.json");
219+
220+
var response = new FileCreateTransaction()
221+
.setKeys(OPERATOR_KEY)
222+
.setContents(contractBytecodeHex.getBytes(StandardCharsets.UTF_8))
223+
.setMaxTransactionFee(Hbar.from(2))
224+
.execute(client);
225+
return Objects.requireNonNull(response.getReceipt(client).fileId);
226+
}
227+
228+
private static ContractId createContractId(Client client) throws Exception {
229+
var fileId = createBytecodeFile(client);
230+
231+
var response = new ContractCreateTransaction()
232+
.setAdminKey(OPERATOR_KEY)
233+
.setGas(500_000)
234+
.setBytecodeFileId(fileId)
235+
.execute(client);
236+
237+
var receipt = response.getReceipt(client);
238+
return receipt.contractId;
239+
}
240+
}

0 commit comments

Comments
 (0)