|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | + |
| 3 | +use clap::Parser; |
| 4 | +use hedera::{ |
| 5 | + AccountCreateTransaction, AccountId, Client, ContractCreateTransaction, ContractId, EvmHookCall, EvmHookSpec, FungibleHookCall, FungibleHookType, Hbar, HookCall, HookCreationDetails, HookExtensionPoint, LambdaEvmHook, NftHookCall, NftHookType, PrivateKey, TokenCreateTransaction, TokenMintTransaction, TokenSupplyType, TokenType, TransferTransaction |
| 6 | +}; |
| 7 | + |
| 8 | +#[derive(Parser, Debug)] |
| 9 | +struct Args { |
| 10 | + #[clap(long, env)] |
| 11 | + operator_account_id: AccountId, |
| 12 | + |
| 13 | + #[clap(long, env)] |
| 14 | + operator_key: PrivateKey, |
| 15 | + |
| 16 | + #[clap(long, env, default_value = "testnet")] |
| 17 | + hedera_network: String, |
| 18 | +} |
| 19 | + |
| 20 | +const HOOK_BYTECODE: &str = "6080604052348015600e575f5ffd5b506107d18061001c5f395ff3fe608060405260043610610033575f3560e01c8063124d8b301461003757806394112e2f14610067578063bd0dd0b614610097575b5f5ffd5b610051600480360381019061004c91906106f2565b6100c7565b60405161005e9190610782565b60405180910390f35b610081600480360381019061007c91906106f2565b6100d2565b60405161008e9190610782565b60405180910390f35b6100b160048036038101906100ac91906106f2565b6100dd565b6040516100be9190610782565b60405180910390f35b5f6001905092915050565b5f6001905092915050565b5f6001905092915050565b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b5f60a08284031215610112576101116100f9565b5b81905092915050565b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6101658261011f565b810181811067ffffffffffffffff821117156101845761018361012f565b5b80604052505050565b5f6101966100e8565b90506101a2828261015c565b919050565b5f5ffd5b5f5ffd5b5f67ffffffffffffffff8211156101c9576101c861012f565b5b602082029050602081019050919050565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610207826101de565b9050919050565b610217816101fd565b8114610221575f5ffd5b50565b5f813590506102328161020e565b92915050565b5f8160070b9050919050565b61024d81610238565b8114610257575f5ffd5b50565b5f8135905061026881610244565b92915050565b5f604082840312156102835761028261011b565b5b61028d604061018d565b90505f61029c84828501610224565b5f8301525060206102af8482850161025a565b60208301525092915050565b5f6102cd6102c8846101af565b61018d565b905080838252602082019050604084028301858111156102f0576102ef6101da565b5b835b818110156103195780610305888261026e565b8452602084019350506040810190506102f2565b5050509392505050565b5f82601f830112610337576103366101ab565b5b81356103478482602086016102bb565b91505092915050565b5f67ffffffffffffffff82111561036a5761036961012f565b5b602082029050602081019050919050565b5f67ffffffffffffffff8211156103955761039461012f565b5b602082029050602081019050919050565b5f606082840312156103bb576103ba61011b565b5b6103c5606061018d565b90505f6103d484828501610224565b5f8301525060206103e784828501610224565b60208301525060406103fb8482850161025a565b60408301525092915050565b5f6104196104148461037b565b61018d565b9050808382526020820190506060840283018581111561043c5761043b6101da565b5b835b81811015610465578061045188826103a6565b84526020840193505060608101905061043e565b5050509392505050565b5f82601f830112610483576104826101ab565b5b8135610493848260208601610407565b91505092915050565b5f606082840312156104b1576104b061011b565b5b6104bb606061018d565b90505f6104ca84828501610224565b5f83015250602082013567ffffffffffffffff8111156104ed576104ec6101a7565b5b6104f984828501610323565b602083015250604082013567ffffffffffffffff81111561051d5761051c6101a7565b5b6105298482850161046f565b60408301525092915050565b5f61054761054284610350565b61018d565b9050808382526020820190506020840283018581111561056a576105696101da565b5b835b818110156105b157803567ffffffffffffffff81111561058f5761058e6101ab565b5b80860161059c898261049c565b8552602085019450505060208101905061056c565b5050509392505050565b5f82601f8301126105cf576105ce6101ab565b5b81356105df848260208601610535565b91505092915050565b5f604082840312156105fd576105fc61011b565b5b610607604061018d565b90505f82013567ffffffffffffffff811115610626576106256101a7565b5b61063284828501610323565b5f83015250602082013567ffffffffffffffff811115610655576106546101a7565b5b610661848285016105bb565b60208301525092915050565b5f604082840312156106825761068161011b565b5b61068c604061018d565b90505f82013567ffffffffffffffff8111156106ab576106aa6101a7565b5b6106b7848285016105e8565b5f83015250602082013567ffffffffffffffff8111156106da576106d96101a7565b5b6106e6848285016105e8565b60208301525092915050565b5f5f60408385031215610708576107076100f1565b5b5f83013567ffffffffffffffff811115610725576107246100f5565b5b610731858286016100fd565b925050602083013567ffffffffffffffff811115610752576107516100f5565b5b61075e8582860161066d565b9150509250929050565b5f8115159050919050565b61077c81610768565b82525050565b5f6020820190506107955f830184610773565b9291505056fea26469706673582212207dfe7723f6d6869419b1cb0619758b439da0cf4ffd9520997c40a3946299d4dc64736f6c634300081e0033"; |
| 21 | + |
| 22 | +async fn create_hook_contract(client: &Client) -> anyhow::Result<ContractId> { |
| 23 | + let bytecode = hex::decode(HOOK_BYTECODE)?; |
| 24 | + |
| 25 | + let receipt = ContractCreateTransaction::new() |
| 26 | + .bytecode(bytecode) |
| 27 | + .gas(1_700_000) |
| 28 | + .execute(client) |
| 29 | + .await? |
| 30 | + .get_receipt(client) |
| 31 | + .await?; |
| 32 | + |
| 33 | + Ok(receipt.contract_id.unwrap()) |
| 34 | +} |
| 35 | + |
| 36 | +#[tokio::main] |
| 37 | +async fn main() -> anyhow::Result<()> { |
| 38 | + let _ = dotenvy::dotenv(); |
| 39 | + let Args { |
| 40 | + operator_account_id, |
| 41 | + operator_key, |
| 42 | + hedera_network, |
| 43 | + } = Args::parse(); |
| 44 | + |
| 45 | + let client = Client::for_name(&hedera_network)?; |
| 46 | + client.set_operator(operator_account_id, operator_key); |
| 47 | + |
| 48 | + println!("Transfer Transaction Hooks Example Start!"); |
| 49 | + |
| 50 | + // Step 1: Set up prerequisites - create hook contract |
| 51 | + println!("Setting up prerequisites..."); |
| 52 | + |
| 53 | + let hook_contract_id = create_hook_contract(&client).await?; |
| 54 | + println!("Created hook contract: {hook_contract_id}"); |
| 55 | + |
| 56 | + // Create hook details |
| 57 | + let hook_id = 1; |
| 58 | + let spec = EvmHookSpec::new(Some(hook_contract_id)); |
| 59 | + let lambda_hook = LambdaEvmHook::new(spec, vec![]); |
| 60 | + let hook_details = HookCreationDetails::new( |
| 61 | + HookExtensionPoint::AccountAllowanceHook, |
| 62 | + hook_id, |
| 63 | + Some(lambda_hook), |
| 64 | + ); |
| 65 | + |
| 66 | + // Create sender account with hook |
| 67 | + let sender_key = PrivateKey::generate_ed25519(); |
| 68 | + let sender_receipt = AccountCreateTransaction::new() |
| 69 | + .set_key_without_alias(sender_key.public_key()) |
| 70 | + .initial_balance(Hbar::new(10)) |
| 71 | + .add_hook(hook_details.clone()) |
| 72 | + .freeze_with(&client)? |
| 73 | + .sign(sender_key.clone()) |
| 74 | + .execute(&client) |
| 75 | + .await? |
| 76 | + .get_receipt(&client) |
| 77 | + .await?; |
| 78 | + |
| 79 | + let sender_account_id = sender_receipt.account_id.unwrap(); |
| 80 | + println!("Created sender account: {sender_account_id}"); |
| 81 | + |
| 82 | + // Create receiver account with hook and unlimited token associations |
| 83 | + let receiver_key = PrivateKey::generate_ed25519(); |
| 84 | + let receiver_receipt = AccountCreateTransaction::new() |
| 85 | + .set_key_without_alias(receiver_key.public_key()) |
| 86 | + .initial_balance(Hbar::new(10)) |
| 87 | + .max_automatic_token_associations(-1) |
| 88 | + .add_hook(hook_details) |
| 89 | + .execute(&client) |
| 90 | + .await? |
| 91 | + .get_receipt(&client) |
| 92 | + .await?; |
| 93 | + |
| 94 | + let receiver_account_id = receiver_receipt.account_id.unwrap(); |
| 95 | + println!("Created receiver account: {receiver_account_id}"); |
| 96 | + |
| 97 | + // Create fungible token |
| 98 | + println!("Creating fungible token..."); |
| 99 | + let fungible_token_id = TokenCreateTransaction::new() |
| 100 | + .name("Example Fungible Token") |
| 101 | + .symbol("EFT") |
| 102 | + .decimals(2) |
| 103 | + .initial_supply(10_000) |
| 104 | + .treasury_account_id(sender_account_id) |
| 105 | + .admin_key(sender_key.public_key()) |
| 106 | + .supply_key(sender_key.public_key()) |
| 107 | + .token_type(TokenType::FungibleCommon) |
| 108 | + .token_supply_type(TokenSupplyType::Infinite) |
| 109 | + .freeze_with(&client)? |
| 110 | + .sign(sender_key.clone()) |
| 111 | + .execute(&client) |
| 112 | + .await? |
| 113 | + .get_receipt(&client) |
| 114 | + .await? |
| 115 | + .token_id |
| 116 | + .unwrap(); |
| 117 | + |
| 118 | + println!("Created fungible token: {fungible_token_id}"); |
| 119 | + |
| 120 | + // Create NFT token |
| 121 | + println!("Creating NFT token..."); |
| 122 | + let nft_token_id = TokenCreateTransaction::new() |
| 123 | + .name("Example NFT Token") |
| 124 | + .symbol("ENT") |
| 125 | + .treasury_account_id(sender_account_id) |
| 126 | + .admin_key(sender_key.public_key()) |
| 127 | + .supply_key(sender_key.public_key()) |
| 128 | + .token_type(TokenType::NonFungibleUnique) |
| 129 | + .token_supply_type(TokenSupplyType::Infinite) |
| 130 | + .freeze_with(&client)? |
| 131 | + .sign(sender_key.clone()) |
| 132 | + .execute(&client) |
| 133 | + .await? |
| 134 | + .get_receipt(&client) |
| 135 | + .await? |
| 136 | + .token_id |
| 137 | + .unwrap(); |
| 138 | + |
| 139 | + println!("Created NFT token: {nft_token_id}"); |
| 140 | + |
| 141 | + // Mint NFT |
| 142 | + println!("Minting NFT..."); |
| 143 | + let nft_serial = TokenMintTransaction::new() |
| 144 | + .token_id(nft_token_id) |
| 145 | + .metadata(vec![b"Example NFT Metadata".to_vec()]) |
| 146 | + .freeze_with(&client)? |
| 147 | + .sign(sender_key.clone()) |
| 148 | + .execute(&client) |
| 149 | + .await? |
| 150 | + .get_receipt(&client) |
| 151 | + .await? |
| 152 | + .serials[0] as u64; |
| 153 | + |
| 154 | + let nft_id = nft_token_id.nft(nft_serial); |
| 155 | + println!("Minted NFT: {nft_id}"); |
| 156 | + |
| 157 | + // Step 2: Demonstrate TransferTransaction API with hooks |
| 158 | + println!("\n=== TransferTransaction with Hooks API Demonstration ==="); |
| 159 | + |
| 160 | + // Create hook call objects |
| 161 | + println!("Creating hook call objects..."); |
| 162 | + |
| 163 | + // HBAR transfer with pre-tx allowance hook |
| 164 | + let hbar_hook = FungibleHookCall { |
| 165 | + hook_call: HookCall::new(Some(hook_id), { |
| 166 | + let mut evm_call = EvmHookCall::new(Some(vec![0x01, 0x02])); |
| 167 | + evm_call.set_gas_limit(20_000); |
| 168 | + Some(evm_call) |
| 169 | + }), |
| 170 | + hook_type: FungibleHookType::PreTxAllowanceHook, |
| 171 | + }; |
| 172 | + |
| 173 | + // NFT sender hook (pre-hook) |
| 174 | + let nft_sender_hook = NftHookCall { |
| 175 | + hook_call: HookCall::new(Some(hook_id), { |
| 176 | + let mut evm_call = EvmHookCall::new(Some(vec![0x03, 0x04])); |
| 177 | + evm_call.set_gas_limit(20_000); |
| 178 | + Some(evm_call) |
| 179 | + }), |
| 180 | + hook_type: NftHookType::PreHookSender, |
| 181 | + }; |
| 182 | + |
| 183 | + // NFT receiver hook (pre-hook) |
| 184 | + let nft_receiver_hook = NftHookCall { |
| 185 | + hook_call: HookCall::new(Some(hook_id), { |
| 186 | + let mut evm_call = EvmHookCall::new(Some(vec![0x05, 0x06])); |
| 187 | + evm_call.set_gas_limit(20_000); |
| 188 | + Some(evm_call) |
| 189 | + }), |
| 190 | + hook_type: NftHookType::PreHookReceiver, |
| 191 | + }; |
| 192 | + |
| 193 | + // Fungible token transfer with pre-post allowance hook |
| 194 | + let fungible_token_hook = FungibleHookCall { |
| 195 | + hook_call: HookCall::new(Some(hook_id), { |
| 196 | + let mut evm_call = EvmHookCall::new(Some(vec![0x07, 0x08])); |
| 197 | + evm_call.set_gas_limit(20_000); |
| 198 | + Some(evm_call) |
| 199 | + }), |
| 200 | + hook_type: FungibleHookType::PrePostTxAllowanceHook, |
| 201 | + }; |
| 202 | + |
| 203 | + // Build separate TransferTransactions with hooks |
| 204 | + println!("Building separate TransferTransactions with hooks..."); |
| 205 | + |
| 206 | + // Transaction 1: HBAR transfers with hook |
| 207 | + println!("\n1. Executing HBAR TransferTransaction with hook..."); |
| 208 | + TransferTransaction::new() |
| 209 | + .add_hbar_transfer_with_hook(sender_account_id, Hbar::from_tinybars(-1), hbar_hook) |
| 210 | + .hbar_transfer(receiver_account_id, Hbar::from_tinybars(1)) |
| 211 | + .freeze_with(&client)? |
| 212 | + .sign(sender_key.clone()) |
| 213 | + .execute(&client) |
| 214 | + .await? |
| 215 | + .get_receipt(&client) |
| 216 | + .await?; |
| 217 | + println!(" ✓ HBAR transfer with pre-tx allowance hook completed"); |
| 218 | + |
| 219 | + // Transaction 2: NFT transfer with sender and receiver hooks |
| 220 | + println!("\n2. Executing NFT TransferTransaction with hooks..."); |
| 221 | + TransferTransaction::new() |
| 222 | + .add_nft_transfer_with_hook( |
| 223 | + nft_id, |
| 224 | + sender_account_id, |
| 225 | + receiver_account_id, |
| 226 | + nft_sender_hook, |
| 227 | + nft_receiver_hook, |
| 228 | + ) |
| 229 | + .freeze_with(&client)? |
| 230 | + .sign(sender_key.clone()) |
| 231 | + .execute(&client) |
| 232 | + .await? |
| 233 | + .get_receipt(&client) |
| 234 | + .await?; |
| 235 | + println!(" ✓ NFT transfer with sender and receiver hooks completed"); |
| 236 | + |
| 237 | + // Transaction 3: Fungible token transfers with hook |
| 238 | + println!("\n3. Executing Fungible Token TransferTransaction with hook..."); |
| 239 | + TransferTransaction::new() |
| 240 | + .add_token_transfer_with_hook( |
| 241 | + fungible_token_id, |
| 242 | + sender_account_id, |
| 243 | + -1_000, |
| 244 | + fungible_token_hook, |
| 245 | + ) |
| 246 | + .token_transfer(fungible_token_id, receiver_account_id, 1_000) |
| 247 | + .freeze_with(&client)? |
| 248 | + .sign(sender_key.clone()) |
| 249 | + .execute(&client) |
| 250 | + .await? |
| 251 | + .get_receipt(&client) |
| 252 | + .await?; |
| 253 | + println!(" ✓ Fungible token transfer with pre-post allowance hook completed"); |
| 254 | + |
| 255 | + println!("\nAll TransferTransactions executed successfully with the following hook calls:"); |
| 256 | + println!(" - Transaction 1: HBAR transfer with pre-tx allowance hook"); |
| 257 | + println!(" - Transaction 2: NFT transfer with sender and receiver hooks"); |
| 258 | + println!(" - Transaction 3: Fungible token transfer with pre-post allowance hook"); |
| 259 | + |
| 260 | + println!("Transfer Transaction Hooks Example Complete!"); |
| 261 | + |
| 262 | + Ok(()) |
| 263 | +} |
0 commit comments