Skip to content

Commit

Permalink
feature: change or remove existing keys from a token [HIP-540] (#2299)
Browse files Browse the repository at this point in the history
* feature: change or remove token keys + tests + example

Signed-off-by: svetoslav-nikol0v <[email protected]>

* chore: remove import

Signed-off-by: svetoslav-nikol0v <[email protected]>

* fix: unit test

Signed-off-by: svetoslav-nikol0v <[email protected]>

* chore: remove only

Signed-off-by: svetoslav-nikol0v <[email protected]>

* update: public key class and tests

Signed-off-by: svetoslav-nikol0v <[email protected]>

* update: example

Signed-off-by: svetoslav-nikol0v <[email protected]>

---------

Signed-off-by: svetoslav-nikol0v <[email protected]>
  • Loading branch information
svetoslav-nikol0v authored Jun 14, 2024
1 parent 484f0f9 commit 72f4656
Show file tree
Hide file tree
Showing 9 changed files with 2,376 additions and 7 deletions.
214 changes: 214 additions & 0 deletions examples/change-or-remove-token-keys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import {
AccountId,
Client,
PrivateKey,
Logger,
LogLevel,
PublicKey,
KeyList,
TokenUpdateTransaction,
TokenKeyValidation,
TokenCreateTransaction,
TokenType,
TokenInfoQuery,
} from "@hashgraph/sdk";
import dotenv from "dotenv";

/**
* @description Change ot remove token keys
*/

async function main() {
// Ensure required environment variables are available
dotenv.config();
if (
!process.env.OPERATOR_KEY ||
!process.env.OPERATOR_ID ||
!process.env.HEDERA_NETWORK
) {
throw new Error("Please set required keys in .env file.");
}

const network = process.env.HEDERA_NETWORK;

// Configure client using environment variables
const operatorId = AccountId.fromString(process.env.OPERATOR_ID);
const operatorKey = PrivateKey.fromStringED25519(process.env.OPERATOR_KEY);

const client = Client.forName(network).setOperator(operatorId, operatorKey);

// Set logger
const infoLogger = new Logger(LogLevel.Info);
client.setLogger(infoLogger);


const adminKey = PrivateKey.generateED25519();
const supplyKey = PrivateKey.generateED25519();
const newSupplyKey = PrivateKey.generateED25519();
const wipeKey = PrivateKey.generateED25519();
const freezeKey = PrivateKey.generateED25519();
const pauseKey = PrivateKey.generateED25519();
const feeScheduleKey = PrivateKey.generateED25519();
const metadataKey = PrivateKey.generateED25519();

// This HIP introduces ability to remove lower-privilege keys (Wipe, KYC, Freeze, Pause, Supply, Fee Schedule, Metadata) from a Token:
// - using an update with the empty KeyList;
const emptyKeyList = KeyList.of();
// - updating with an “invalid” key such as an Ed25519 0x0000000000000000000000000000000000000000000000000000000000000000 public key,
// since it is (presumably) impossible to find the 32-byte string whose SHA-512 hash begins with 32 bytes of zeros.
const unusableKey = PublicKey.unusableKey();

console.log("=====================================================");
console.log("Initializing token keys...");
console.log("-----------------------------------------------------");
console.log('Admin key:', adminKey.publicKey.toString());
console.log('Supply key:', supplyKey.publicKey.toString());
console.log('New supply key:', newSupplyKey.publicKey.toString());
console.log('Wipe key:', wipeKey.publicKey.toString());
console.log('Freeze key:', freezeKey.publicKey.toString());
console.log('Pause key:', pauseKey.publicKey.toString());
console.log('Fee schedule key:', feeScheduleKey.publicKey.toString());
console.log('Metadata key:', metadataKey.publicKey.toString());
console.log("Unusable key:", unusableKey.toString());
console.log("=====================================================");
console.log("\n");

let token, tokenInfo, response, receipt, update;

console.log("=====================================================");
console.log("Creating token...");
console.log("-----------------------------------------------------");
token = new TokenCreateTransaction()
.setTokenName("Token")
.setTokenSymbol("T")
.setTokenType(TokenType.NonFungibleUnique)
.setTreasuryAccountId(operatorId)
.setAdminKey(adminKey)
.setWipeKey(wipeKey)
.setFreezeKey(freezeKey)
.setPauseKey(pauseKey)
.setSupplyKey(supplyKey)
.setFeeScheduleKey(feeScheduleKey)
.setMetadataKey(metadataKey)
.freezeWith(client);

response = await (
await token.sign(adminKey)
).execute(client);

receipt = await response.getReceipt(client)
console.log('Token create transction status:', receipt.status.toString());

const tokenId = receipt.tokenId;
console.log('Token id:', tokenId.toString());
console.log("=====================================================");
console.log("\n");
console.log("=====================================================");
console.log("Token keys:");
console.log("-----------------------------------------------------");
tokenInfo = await new TokenInfoQuery()
.setTokenId(tokenId)
.execute(client);

console.log('Token admin key:', tokenInfo.adminKey.toString());
console.log('Token pause key:', tokenInfo.pauseKey.toString());
console.log('Token freeze key:', tokenInfo.freezeKey.toString());
console.log('Token wipe key:', tokenInfo.wipeKey.toString());
console.log('Token supply key:', tokenInfo.supplyKey.toString());
console.log('Token fee schedule key:', tokenInfo.feeScheduleKey.toString());
console.log('Token metadata key:', tokenInfo.metadataKey.toString());
console.log("=====================================================");
console.log("\n");
console.log("=====================================================");
console.log("Removing Wipe Key...");
console.log("-----------------------------------------------------");
update = new TokenUpdateTransaction()
.setTokenId(tokenId)
.setWipeKey(emptyKeyList)
.setKeyVerificationMode(TokenKeyValidation.FullValidation)
.freezeWith(client)
response = await (
await update.sign(adminKey)
).execute(client);

receipt = await response.getReceipt(client)
console.log("Token update transaction status:", receipt.status.toString());

tokenInfo = await new TokenInfoQuery()
.setTokenId(tokenId)
.execute(client);
console.log('Token wipeKey is', tokenInfo.wipeKey);
console.log("=====================================================");
console.log("\n");
console.log("=====================================================");
console.log("Removing Admin Key...");
console.log("-----------------------------------------------------");
update = new TokenUpdateTransaction()
.setTokenId(tokenId)
.setAdminKey(emptyKeyList)
.setKeyVerificationMode(TokenKeyValidation.NoValidation)
.freezeWith(client)
response = await (
await update.sign(adminKey)
).execute(client);

receipt = await response.getReceipt(client)
console.log("Token update transaction status:", receipt.status.toString());

tokenInfo = await new TokenInfoQuery()
.setTokenId(tokenId)
.execute(client);
console.log('Token adminKey is', tokenInfo.adminKey);
console.log("=====================================================");
console.log("\n");
console.log("=====================================================");
console.log("Updating Supply Key...");
console.log("-----------------------------------------------------");
console.log('Token supplyKey (before update) is', tokenInfo.supplyKey.toString())
update = new TokenUpdateTransaction()
.setTokenId(tokenId)
.setSupplyKey(newSupplyKey)
.setKeyVerificationMode(TokenKeyValidation.FullValidation)
.freezeWith(client)
response = await (
await(
await update.sign(supplyKey)
).sign(newSupplyKey)
).execute(client);

receipt = await response.getReceipt(client)
console.log("Token update transaction status:", receipt.status.toString());

tokenInfo = await new TokenInfoQuery()
.setTokenId(tokenId)
.execute(client);
console.log('Token suppleyKey (after update) is', tokenInfo.supplyKey.toString());
console.log("=====================================================");
console.log("\n");
console.log("=====================================================");
console.log("Updating Supply Key to unusable format...");
console.log("-----------------------------------------------------");
console.log('Token supplyKey (before update) is', tokenInfo.supplyKey.toString())
update = new TokenUpdateTransaction()
.setTokenId(tokenId)
.setSupplyKey(unusableKey)
.setKeyVerificationMode(TokenKeyValidation.NoValidation)
.freezeWith(client);
response = await(
await update.sign(newSupplyKey)
).execute(client);

receipt = await response.getReceipt(client)
console.log("Token update transaction status:", receipt.status.toString());

tokenInfo = await new TokenInfoQuery()
.setTokenId(tokenId)
.execute(client);

console.log('Token suppleyKey (after update) is', tokenInfo.supplyKey.toString());
console.log("=====================================================");

client.close();
}

main();
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,4 @@
"optional": true
}
}
}
}
13 changes: 13 additions & 0 deletions src/PublicKey.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,19 @@ export default class PublicKey extends Key {
toAccountId(shard, realm) {
return CACHE.accountIdConstructor(shard, realm, this);
}

/**
* Returns an "unusable" public key.
* “Unusable” refers to a key such as an Ed25519 0x00000... public key,
* since it is (presumably) impossible to find the 32-byte string whose SHA-512 hash begins with 32 bytes of zeros.
*
* @returns {PublicKey}
*/
static unusableKey() {
return PublicKey.fromStringED25519(
"0000000000000000000000000000000000000000000000000000000000000000",
);
}
}

CACHE.setPublicKeyED25519((key) => PublicKey.fromBytesED25519(key));
Expand Down
8 changes: 4 additions & 4 deletions src/Status.js
Original file line number Diff line number Diff line change
Expand Up @@ -2919,11 +2919,11 @@ Status.InvalidEndpoint = new Status(351);
Status.GossipEndpointsExceededLimit = new Status(352);

/**
* The number of service endpoints exceeds the limit
*/
Status.ServiceEndpointsExceededLimit = new Status(356)
* The number of service endpoints exceeds the limit
*/
Status.ServiceEndpointsExceededLimit = new Status(356);

/*
* The IPv4 address is invalid
*/
Status.InvalidIpv4Address = new Status(357)
Status.InvalidIpv4Address = new Status(357);
1 change: 1 addition & 0 deletions src/exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export { default as LogLevel } from "./logger/LogLevel.js";
export { EntityIdHelper };
export { default as Long } from "long";
export { default as FreezeType } from "./FreezeType.js";
export { default as TokenKeyValidation } from "./token/TokenKeyValidation.js";

export { default as StatusError } from "./StatusError.js";
export { default as PrecheckStatusError } from "./PrecheckStatusError.js";
Expand Down
88 changes: 88 additions & 0 deletions src/token/TokenKeyValidation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*-
* ‌
* Hedera JavaScript SDK
* ​
* Copyright (C) 2020 - 2023 Hedera Hashgraph, LLC
* ​
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ‍
*/

/**
* @namespace proto
* @typedef {import("@hashgraph/proto").proto.TokenKeyValidation} HashgraphProto.proto.TokenKeyValidation
*/

/** Types of validation strategies for token keys. */
export default class TokenKeyValidation {
/**
* @hideconstructor
* @internal
* @param {number} code
*/
constructor(code) {
/** @readonly */
this._code = code;

Object.freeze(this);
}

/**
* @returns {string}
*/
toString() {
switch (this) {
case TokenKeyValidation.FullValidation:
return "FULL_VALIDATION";
case TokenKeyValidation.NoValidation:
return "NO_VALIDATION";
default:
return `UNKNOWN (${this._code})`;
}
}

/**
* @internal
* @param {number} code
* @returns {TokenKeyValidation}
*/
static _fromCode(code) {
switch (code) {
case 0:
return TokenKeyValidation.FullValidation;
case 1:
return TokenKeyValidation.NoValidation;
}

throw new Error(
`(BUG) TokenKeyValidation.fromCode() does not handle code: ${code}`,
);
}

/**
* @returns {HashgraphProto.proto.TokenKeyValidation}
*/
valueOf() {
return this._code;
}
}

/**
* Currently the default behaviour. It will perform all token key validations.
*/
TokenKeyValidation.FullValidation = new TokenKeyValidation(0);

/**
* Perform no validations at all for all passed token keys.
*/
TokenKeyValidation.NoValidation = new TokenKeyValidation(1);
Loading

0 comments on commit 72f4656

Please sign in to comment.