Skip to content

Commit

Permalink
feat(TCK): Add AccountCreate method (#2476)
Browse files Browse the repository at this point in the history
* feat: Added TCK server json-rpc methods for account create transaction

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* fix: Renamed some interfaces, refactored processKeyRecursively func, moved some functions outside Key method.

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* fix: Splitted models folder into params/response and renamed the Input interface

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* fix: Removed unnecessary async, introduced CustomError class, refacoted account method

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* refactor: Removed logs, unnecessary error handling logic, refactored processKeyRecursively method

Signed-off-by: ivaylogarnev-limechain <[email protected]>

* refactor: Introduced asn1DecodedKey interface and JSONRPCErrorCode custom enum, refactored getEvmAddressFromKey, getKeyFromString, handleEd25519andEcdsa methods and SDK setup

Signed-off-by: ivaylogarnev-limechain <[email protected]>

---------

Signed-off-by: ivaylogarnev-limechain <[email protected]>
  • Loading branch information
ivaylogarnev-limechain authored Aug 28, 2024
1 parent d756199 commit 8885cf3
Show file tree
Hide file tree
Showing 25 changed files with 5,652 additions and 0 deletions.
9 changes: 9 additions & 0 deletions tck/enums/account-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export enum AccountKey {
ED25519_PRIVATE_KEY = "ed25519PrivateKey",
ECDSA_SECP256K1_PRIVATE_KEY = "ecdsaSecp256k1PrivateKey",
ED25519_PUBLIC_KEY = "ed25519PublicKey",
ECDSA_SECP256K1_PUBLIC_KEY = "ecdsaSecp256k1PublicKey",
KEY_LIST = "keyList",
THRESHOLD_KEY = "thresholdKey",
EVM_ADDRESS = "evmAddress",
}
32 changes: 32 additions & 0 deletions tck/mapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { sdk } from "./sdk_data";
import { RpcMethodParams } from "./params/sdk";

/**
* Very primitive catch-all mapping prototype
* @returns {Promise<*>}
* @param {Input} input
*/
export default async function mapMethods({
callClass,
methods,
}: RpcMethodParams): Promise<string> {
const cl: any = (await import("@hashgraph/sdk"))[callClass];

let currentObject: any = new cl();
for (let { name, param } of methods) {
if (param === "client") {
param = sdk.getClient();
}

if (typeof currentObject[name] === "function") {
currentObject = await currentObject[name](param);
} else if (typeof cl[name] === "function") {
currentObject = await cl[name](param);
} else if (typeof currentObject[name] === "object") {
currentObject = await currentObject[name];
} else {
throw Error(`${callClass}.${name}() isn't a function`);
}
}
return currentObject;
}
83 changes: 83 additions & 0 deletions tck/methods/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { AccountCreateTransaction, Hbar, AccountId } from "@hashgraph/sdk";

import { sdk } from "../sdk_data";
import { getKeyFromString } from "../utils/key";
import { CreateAccountResponse } from "../response/account";

import { CreateAccountParams } from "../params/account";
import { applyCommonTransactionParams } from "../params/common-tx-params";

export const createAccount = async ({
key,
initialBalance,
receiverSignatureRequired,
maxAutoTokenAssociations,
commonTransactionParams,
stakedAccountId,
stakedNodeId,
declineStakingReward,
memo,
autoRenewPeriod,
alias,
}: CreateAccountParams): Promise<CreateAccountResponse> => {
let transaction = new AccountCreateTransaction().setGrpcDeadline(30000);

if (key != null) {
transaction.setKey(getKeyFromString(key));
}

if (initialBalance != null) {
transaction.setInitialBalance(Hbar.fromTinybars(initialBalance));
}

if (receiverSignatureRequired != null) {
transaction.setReceiverSignatureRequired(receiverSignatureRequired);
}

if (maxAutoTokenAssociations != null) {
transaction.setMaxAutomaticTokenAssociations(maxAutoTokenAssociations);
}

if (stakedAccountId != null) {
const accountId = AccountId.fromString(stakedAccountId);

transaction.setStakedAccountId(accountId);
}

if (stakedNodeId != null) {
transaction.setStakedNodeId(stakedNodeId);
}

if (declineStakingReward != null) {
transaction.setDeclineStakingReward(declineStakingReward);
}

if (memo != null) {
transaction.setAccountMemo(memo);
}

if (autoRenewPeriod != null) {
transaction.setAutoRenewPeriod(autoRenewPeriod);
}

if (alias != null) {
transaction.setAlias(alias);
}

if (commonTransactionParams != null) {
applyCommonTransactionParams(
commonTransactionParams,
transaction,
sdk.getClient(),
);
}

// Sign the transaction with the client operator private key if not already signed and submit to a Hedera network
const txResponse = await transaction.execute(sdk.getClient());
const receipt = await txResponse.getReceipt(sdk.getClient());

return {
accountId: receipt.accountId.toString(),
status: receipt.status.toString(),
};
};
11 changes: 11 additions & 0 deletions tck/methods/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import glob from "glob";
import path from "path";

// Require all files in this folder in one module.export

let allMethods: Record<string, string> = {};
glob.sync(path.join(__dirname, "**/*.ts")).forEach((file) => {
allMethods = { ...allMethods, ...require(path.resolve(file)) };
});

export default allMethods;
208 changes: 208 additions & 0 deletions tck/methods/key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import { PrivateKey, KeyList, PublicKey } from "@hashgraph/sdk";

import { invalidParamError } from "../utils/invalid-param-error";
import {
getKeyFromString,
convertToKeyListHex,
getEvmAddressFromKey,
getKeyPairFromParams,
} from "../utils/key";

import { AccountKey } from "../enums/account-key";

import { KeyGenerationParams } from "../params/key";
import { KeyGenerationResponse } from "../response/key";

export const generateKey = (
params: KeyGenerationParams,
): KeyGenerationResponse => {
const isValidFromKeyType =
params.fromKey &&
![
AccountKey.ED25519_PUBLIC_KEY,
AccountKey.ECDSA_SECP256K1_PUBLIC_KEY,
AccountKey.EVM_ADDRESS,
].includes(params.type as AccountKey);

if (isValidFromKeyType) {
return invalidParamError<KeyGenerationResponse>(
"fromKey should only be provided for specific public key types or EVM address",
);
}

const isValidThresholdParam =
params.threshold && params.type !== AccountKey.THRESHOLD_KEY;

if (isValidThresholdParam) {
return invalidParamError<KeyGenerationResponse>(
"threshold should only be provided for thresholdKey types",
);
}

const isValidKeysParam =
params.keys &&
![AccountKey.KEY_LIST, AccountKey.THRESHOLD_KEY].includes(
params.type as AccountKey,
);

if (isValidKeysParam) {
return invalidParamError<KeyGenerationResponse>(
"keys should only be provided for keyList or thresholdKey types",
);
}

const isValidThresholdKeysParam =
(params.type === AccountKey.THRESHOLD_KEY ||
params.type === AccountKey.KEY_LIST) &&
!params.keys;

if (isValidThresholdKeysParam) {
return invalidParamError<KeyGenerationResponse>(
"keys list is required for generating a KeyList type",
);
}

const isValidThresholdParamForThresholdKey =
params.type === AccountKey.THRESHOLD_KEY && params.threshold === 0;

if (isValidThresholdParamForThresholdKey) {
return invalidParamError<KeyGenerationResponse>(
"threshold is required for generating a ThresholdKey type",
);
}

const response: KeyGenerationResponse = {
key: "",
privateKeys: [],
};

try {
const { key, privateKeys } = processKeyRecursively(
params,
response,
false,
);

response.key = key;
response.privateKeys = privateKeys;
} catch (error) {
return invalidParamError<KeyGenerationResponse>(error.message);
}

return response;
};

const handleED25519PrivateKey = (
response: KeyGenerationResponse,
params?: KeyGenerationParams,
isList?: boolean,
) => {
const ed25519PrivateKey = PrivateKey.generateED25519();
if (isList) response.privateKeys.push(ed25519PrivateKey.toStringDer());
return {
key: ed25519PrivateKey.toStringDer(),
privateKeys: response.privateKeys,
};
};

const handleECDSA_SECP256K1PrivateKey = (
response: KeyGenerationResponse,
params?: KeyGenerationParams,
isList?: boolean,
) => {
const ecdsaPrivateKey = PrivateKey.generateECDSA();
if (isList) response.privateKeys.push(ecdsaPrivateKey.toStringDer());
return {
key: ecdsaPrivateKey.toStringDer(),
privateKeys: response.privateKeys,
};
};

const handleEd25519andEcdsaPublicKey = (
response: KeyGenerationResponse,
params?: KeyGenerationParams,
isList?: boolean,
): KeyGenerationResponse => {
const { privateKey, publicKey } = getKeyPairFromParams(params);

const privateKeyDer = privateKey.toStringDer();
const publicKeyDer = publicKey.toStringDer();

if (isList) {
response.privateKeys.push(privateKeyDer);
}

return { key: publicKeyDer, privateKeys: response.privateKeys };
};

const handleKeyList = (
response: KeyGenerationResponse,
params?: KeyGenerationParams,
) => {
const keyList = new KeyList();

for (const keyParam of params.keys!) {
const { key: keyStr } = processKeyRecursively(keyParam, response, true);
const key = getKeyFromString(keyStr);
if (key) {
keyList.push(key);
}
}
if (params.type === AccountKey.THRESHOLD_KEY) {
keyList.setThreshold(params.threshold!);
}

return {
key: convertToKeyListHex(keyList) || "",
privateKeys: response.privateKeys,
};
};

const handleEvmAddress = (
response: KeyGenerationResponse,
params?: KeyGenerationParams,
isList?: boolean,
): KeyGenerationResponse => {
if (params.fromKey) {
return getEvmAddressFromKey(params.fromKey, response);
}

// Generate new EVM address if fromKey is not provided
const privateKey = PrivateKey.generateECDSA();
return {
key: privateKey.publicKey.toEvmAddress(),
privateKeys: [privateKey.toStringDer()],
};
};

const keyHandlers: {
[key: string]: (
response: KeyGenerationResponse,
params?: KeyGenerationParams,
isList?: boolean,
) => KeyGenerationResponse;
} = {
[AccountKey.ED25519_PRIVATE_KEY]: handleED25519PrivateKey,
[AccountKey.ECDSA_SECP256K1_PRIVATE_KEY]: handleECDSA_SECP256K1PrivateKey,
[AccountKey.ED25519_PUBLIC_KEY]: handleEd25519andEcdsaPublicKey,
[AccountKey.ECDSA_SECP256K1_PUBLIC_KEY]: handleEd25519andEcdsaPublicKey,
[AccountKey.KEY_LIST]: handleKeyList,
[AccountKey.THRESHOLD_KEY]: handleKeyList,
[AccountKey.EVM_ADDRESS]: handleEvmAddress,
};

const processKeyRecursively = (
params: KeyGenerationParams,
response: KeyGenerationResponse,
isList: boolean,
): KeyGenerationResponse => {
const handleKey = keyHandlers[params.type];

if (handleKey) {
return handleKey(response, params, isList);
} else {
return invalidParamError<KeyGenerationResponse>(
"key type not recognized",
);
}
};
38 changes: 38 additions & 0 deletions tck/methods/sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Client, AccountId } from "@hashgraph/sdk";

import { sdk } from "../sdk_data";
import { SdkResponse } from "../response/sdk";
import { SdkSetupParams } from "../params/sdk";

export default {
setup: ({
operatorAccountId,
operatorPrivateKey,
nodeIp,
nodeAccountId,
mirrorNetworkIp,
}: SdkSetupParams): SdkResponse => {
let client: Client;

if (nodeIp && nodeAccountId && mirrorNetworkIp) {
const node = { [nodeIp]: AccountId.fromString(nodeAccountId) };
client = Client.forNetwork(node);
} else {
client = Client.forTestnet();
}

client.setOperator(operatorAccountId, operatorPrivateKey);
client.setRequestTimeout(30000);

sdk.client = client;

return {
message: `Successfully setup ${client} client.`,
status: "SUCCESS",
};
},
reset: (): SdkResponse => {
sdk.client = null;
return { status: "SUCCESS" };
},
};
Loading

0 comments on commit 8885cf3

Please sign in to comment.