|
| 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