Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HIP-631 - add SDK support for virtual addresses #1315

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- `TRANSACTION_HAS_UNKNOWN_FIELDS` and `ACCOUNT_IS_IMMUTABLE` in `Status`
- `TransactionRecord.evmAddress`
- `PublicKeyECDSA.toEvmAddress()`
- `AccountCreateTransaction.setEvmAddress()`
- `AccountId.fromEvmAddress()`
- `AccountId.fromString()` now supports EVM address
- `TransferTransaction.addHbarTransfer()` now supports EVM address
- `AccountCreateEvmAddressExample`
- `CreateAccountWithAliasExample`
- `LazyCreateAccountTransactionExample`
- `LazyCreateTransferTransactionExample`
- `TransferUsingEvmAddressExample`
- `AccountCreationWaysExample`

### Deprecated

- `AccountCreateTransaction.setAliasEvmAddress()` use `AccountCreateTransaction.setEvmAddress()` instead

### Fixed
- Misleading logging when an unhealthy node is hit
Expand Down
134 changes: 134 additions & 0 deletions examples/src/main/java/AccountCreateEvmAddressExample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.hedera.hashgraph.sdk.*;
import io.github.cdimascio.dotenv.Dotenv;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.Collections;
import java.util.concurrent.TimeoutException;

public class AccountCreateEvmAddressExample {
// see `.env.sample` in the repository root for how to specify these values
// or set environment variables with the same names
private static final AccountId OPERATOR_ID = AccountId.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_ID")));
private static final PrivateKey OPERATOR_KEY = PrivateKey.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_KEY")));
// HEDERA_NETWORK defaults to testnet if not specified in dotenv
private static final String HEDERA_NETWORK = Dotenv.load().get("HEDERA_NETWORK", "testnet");

private AccountCreateEvmAddressExample() {
}

/*
Create an account and set an EVM address using the `AccountCreateTransaction`
Reference: [HIP-583 Expand alias support in CryptoCreate & CryptoTransfer Transactions](https://hips.hedera.com/hip/hip-583)
## Example 1
- Create an ECSDA private key
- Extract the ECDSA public key
- Extract the Ethereum public address
- Add function in the SDK to calculate the Ethereum Address
- Ethereum account address / public-address - This is the rightmost 20 bytes of the 32 byte Keccak-256 hash of the ECDSA public key of the account. This calculation is in the manner described by the Ethereum Yellow Paper.
- Use the `AccountCreateTransaction` and set the EVM address field to the Ethereum public address
- Sign the transaction with the key that us paying for the transaction
- Get the account ID from the receipt
- Get the `AccountInfo` and return the account details
- Verify the evm address provided for the account matches what is in the mirror node
*/
public static void main(String[] args) throws PrecheckStatusException, TimeoutException, ReceiptStatusException, InterruptedException, IOException {
Client client = Client.forName(HEDERA_NETWORK);

// Defaults the operator account ID and key such that all generated transactions will be paid for
// by this account and be signed by this key
client.setOperator(OPERATOR_ID, OPERATOR_KEY);

/*
* Step 1
* Create an ECSDA private key
*/
PrivateKey privateKey = PrivateKey.generateECDSA();

/*
* Step 2
* Extract the ECDSA public key
*/
PublicKey publicKey = privateKey.getPublicKey();

/*
* Step 3
* Extract the Ethereum public address
*/
EvmAddress evmAddress = publicKey.toEvmAddress();
System.out.println(evmAddress);

/*
* Step 4
* Use the `AccountCreateTransaction` and set the EVM address field to the Ethereum public address
*/
AccountCreateTransaction accountCreateTransaction = new AccountCreateTransaction()
.setEvmAddress(evmAddress)
.setInitialBalance(new Hbar(10));

/*
* Step 5
* Sign the transaction with the key that is paying for the transaction
*/
TransactionResponse response = accountCreateTransaction.execute(client);

/*
* Step 6
* Get the account ID from the receipt
*/
AccountId newAccountId = response.getReceipt(client).accountId;
System.out.println(newAccountId);

/*
* Step 7
* Get the `AccountInfo` and return the account details
*/
AccountInfo info = new AccountInfoQuery()
.setAccountId(newAccountId)
.execute(client);

if (info.contractAccountId.equals(evmAddress.toString())) {
System.out.println("The addresses match");
} else {
System.out.println("The addresses don't match");
}

/*
* Step 8
* Verify the evm address provided for the account matches what is in the mirror node
*/
Thread.sleep(5000);
String link = "https://" + HEDERA_NETWORK + ".mirrornode.hedera.com/api/v1/accounts?account.id=" + newAccountId;
URL url = new URL(link);
HttpURLConnection con = (HttpURLConnection)url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Accept", "application/json");
try(BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder builder = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
builder.append(responseLine.trim());
}
JsonObject jsonObject = JsonParser.parseString(builder.toString()).getAsJsonObject();
String mirrorNodeEvmAddress = jsonObject
.getAsJsonArray("accounts")
.get(0).getAsJsonObject()
.get("evm_address")
.getAsString();

if (mirrorNodeEvmAddress.equals("0x" + evmAddress)) {
System.out.println("The addresses match");
} else {
System.out.println("The addresses don't match");
}
}
}
}
47 changes: 47 additions & 0 deletions examples/src/main/java/AccountCreationWaysExample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import com.hedera.hashgraph.sdk.*;

public class AccountCreationWaysExample {

private AccountCreationWaysExample() {
}

/*
Reference: [HIP-583 Expand alias support in CryptoCreate & CryptoTransfer Transactions](https://hips.hedera.com/hip/hip-583)
## In Hedera we have the concept of 4 different account representations
- an account can have an account ID in shard.realm.accountNumber format (0.0.10)
- an account can have a public key alias in 0.0.CIQNOWUYAGBLCCVX2VF75U6JMQDTUDXBOLZ5VJRDEWXQEGTI64DVCGQ format
- an account can have an AccountId that is represented in 0x000000000000000000000000000000000000000a (for account ID 0.0.10) long zero format
- an account can be represented by an Ethereum public address 0xb794f5ea0ba39494ce839613fffba74279579268
*/
public static void main(String[] args) {
/*
* Account ID - shard.realm.number format, i.e. `0.0.10` with the corresponding `0x000000000000000000000000000000000000000a` ethereum address
*/
AccountId hederaFormat = AccountId.fromString("0.0.10");
System.out.println("Account ID: " + hederaFormat);
System.out.println("Account 0.0.10 corresponding Long-Zero address: " + hederaFormat.toSolidityAddress());

/*
* Hedera Long-Form Account ID - 0.0.aliasPublicKey, i.e. `0.0.CIQNOWUYAGBLCCVX2VF75U6JMQDTUDXBOLZ5VJRDEWXQEGTI64DVCGQ`
*/
PrivateKey privateKey = PrivateKey.generateECDSA();
PublicKey publicKey = privateKey.getPublicKey();

// Assuming that the target shard and realm are known.
// For now they are virtually always 0.
AccountId aliasAccountId = publicKey.toAccountId(0, 0);
System.out.println("Hedera Long-Form Account ID: " + aliasAccountId.toString());

/*
* Hedera Account Long-Zero address - 0x000000000000000000000000000000000000000a (for accountId 0.0.10)
*/
AccountId longZeroAddress = AccountId.fromString("0x000000000000000000000000000000000000000a");
System.out.println("Hedera Account Long-Zero address: " + longZeroAddress);

/*
* Ethereum Account Address / public-address - 0xb794f5ea0ba39494ce839613fffba74279579268
*/
AccountId evmAddress = AccountId.fromString("0xb794f5ea0ba39494ce839613fffba74279579268");
System.out.println("Ethereum Account Address / public-address: " + evmAddress);
}
}
Loading