utvoluptas
Version:
322 lines (321 loc) • 14.9 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HashgraphNames = void 0;
const sdk_1 = require("@hashgraph/sdk");
const keccak256_1 = __importDefault(require("keccak256"));
const constants_config_1 = require("./config/constants.config");
const logger_config_1 = require("./config/logger.config");
const contract_utils_1 = require("./contract.utils");
const manager_1 = require("./manager");
class HashgraphNames {
constructor(operatorId, operatorKey, supplyKey) {
this.tokenId = sdk_1.TokenId.fromString(constants_config_1.TOKEN_ID);
/**
* @description Simple wrapper around HTS TokenMintTransaction()
* @param metadata: {Buffer} The metadata to include on the newly minted NFT
* @returns {Promise<TransactionReceipt>}
*/
this.mintNFT = async (metadata) => {
try {
const mintTx = new sdk_1.TokenMintTransaction()
.setTokenId(this.tokenId)
.setMetadata([Buffer.from(JSON.stringify(metadata))])
.freezeWith(this.client);
const mintTxSign = await mintTx.sign(this.supplyKey);
const mintTxSubmit = await mintTxSign.execute(this.client);
const mintRx = await mintTxSubmit.getReceipt(this.client);
if (mintRx.status._code !== sdk_1.Status.Success._code) {
throw new Error('TokenMintTransaction failed');
}
return mintRx;
}
catch (err) {
logger_config_1.logger.error(err);
throw new Error('Failed to mint NFT');
}
};
/**
* @description Check if a token is associated with a specific account
* @param accountId: {AccountId} The account to check if the domain NFT is associated
* @returns {Promise<boolean>}
*/
this.isTokenAssociatedToAccount = async (accountId) => {
try {
const balanceCheckTx = await new sdk_1.AccountBalanceQuery()
.setAccountId(accountId)
.execute(this.client);
if (!balanceCheckTx) {
throw new Error('AccountBalanceQuery Failed');
}
const { tokens } = balanceCheckTx;
if (tokens) {
const tokenOfInterest = tokens._map.get(this.tokenId.toString());
return tokenOfInterest !== undefined;
}
return false;
}
catch (err) {
logger_config_1.logger.error(err);
throw new Error('Failed to determine if token is associated to account');
}
};
/**
* @description Check if a domain exists in the registry
* @param domainHash: {Buffer} The hash of the domain to check
* @returns {Promise<boolean>}
*/
this.checkDomainExists = async (domainHash) => {
try {
const { serial } = await this.getDomainSerial(domainHash);
return Number(serial) !== 0;
}
catch (err) {
logger_config_1.logger.error(err);
throw new Error('Failed to check if domains exists');
}
};
/**
* @description Register a domain in the smart contract Registry
* @param domainHash: {Buffer} The hash of the domain to add to the Registry
* @param serial: {number} The serial of the NFT to register
* @returns {Promise<number>}
*/
this.registerDomain = async (domainHash, serial) => {
try {
// Get manager contract from env
const managerInfo = (0, manager_1.getManagerInfo)();
// Add if not present
await (0, contract_utils_1.callContractFunc)(managerInfo.contract.id, managerInfo.abi, 'addRecord', [`0x${domainHash.toString('hex')}`, `${serial}`], this.client);
return constants_config_1.CONFIRMATION_STATUS;
}
catch (err) {
logger_config_1.logger.error(err);
throw new Error('Failed to register Domain');
}
};
/**
* @description Mints a new domain NFT and records it in the registry
* @throws {@link InternalServerError}
* @param domain {string} The domain to mint
* @param ownerId {string} The owner of the domain to mint
* @returns {Promise<number>}
*/
this.mintDomain = async (domain, ownerId) => {
let NFTSerial;
let domainHash;
const accountId = sdk_1.AccountId.fromString(ownerId);
try {
domainHash = HashgraphNames.generateNFTHash(domain);
const domainExists = await this.checkDomainExists(domainHash);
if (domainExists)
throw new Error('Domain already exists in the registry');
const isAssociated = await this.isTokenAssociatedToAccount(accountId);
if (!isAssociated)
throw new Error('Wallet must first be associated before a token can be minted');
// Mint the NFT
const metadata = HashgraphNames.generateMetadata(domain);
const mintRx = await this.mintNFT(metadata);
NFTSerial = Number(mintRx.serials[0]);
// Register the domain in the Registry
await this.registerDomain(domainHash, NFTSerial);
return constants_config_1.CONFIRMATION_STATUS;
}
catch (err) {
logger_config_1.logger.error(err);
throw new Error('Failed to mint domain.');
}
};
/**
* @description Executes an HTS TransferTransaction
* @param ownerSignature: {TransactionSignature} The signature information for the NFT owner
* @param receiverSignature: {TransactionSignature} The signature information for the NFT receiver
* @param transactionBytes: {Uint8Array} The transaction bytes to be executed
* @returns {Promise<number>}
*/
this.transferDomain = async (ownerSignature, receiverSignature, transactionBytes) => {
try {
const transaction = HashgraphNames.bytesToTransaction(transactionBytes);
transaction
.addSignature(ownerSignature.signerPublicKey, ownerSignature.signature)
.addSignature(receiverSignature.signerPublicKey, receiverSignature.signature);
const submitTransaction = await transaction.execute(this.client);
const receipt = await submitTransaction.getReceipt(this.client);
if (receipt.status._code !== sdk_1.Status.Success._code) {
throw new Error('TransferTransaction failed');
}
}
catch (err) {
throw new Error('Transfer Domain failed');
}
return constants_config_1.CONFIRMATION_STATUS;
};
/**
* @description Creates a HTS TransferTransaction and returns it as an Uint8Array
* @param domain: {string} The domain for the NFT to transfer
* @param NFTOwner: {string} The account id of the NFT owner
* @param NFTReceiver: {string} The account id of the NFT receiver
* @param purchasePrice: {number} The amount in tinyBar for which the NFT is being purchased
* @returns {Uint8Array}
*/
this.transferTransactionCreate = async (domain, NFTOwner, NFTReceiver, purchasePrice) => {
try {
const fromIdNFT = sdk_1.AccountId.fromString(NFTOwner);
const toIdNFT = sdk_1.AccountId.fromString(NFTReceiver);
const { serial } = await this.getNFTSerialString(domain);
const nodeId = [new sdk_1.AccountId(3)];
const tokenTransferTx = new sdk_1.TransferTransaction()
.addNftTransfer(this.tokenId, serial, fromIdNFT, toIdNFT)
.addHbarTransfer(toIdNFT, sdk_1.Hbar.fromTinybars(-1 * purchasePrice))
.addHbarTransfer(fromIdNFT, sdk_1.Hbar.fromTinybars(purchasePrice))
.setNodeAccountIds(nodeId)
.freezeWith(this.client);
return tokenTransferTx.toBytes();
}
catch (err) {
throw new Error('MultiSig transaction create failed');
}
};
/**
* @description Simple wrapper around callContractFunc for the getSerial smart contract function
* @param domainHash: {Buffer} The hash of the domain to query
* @param begin: {number} The begin index in the array of nodes of the manager
* @param end: {number} The end index in the array of nodes of the manager
* @returns {Promise<SerialInfo>}
*/
this.callGetSerial = async (domainHash, begin, end) => {
try {
const managerInfo = (0, manager_1.getManagerInfo)();
const result = await (0, contract_utils_1.callContractFunc)(managerInfo.contract.id, managerInfo.abi, 'getSerial', [`0x${domainHash.toString('hex')}`, `${begin}`, `${end}`], this.client);
return { serial: result[0], node: result[1] };
}
catch (err) {
logger_config_1.logger.error(err);
throw new Error('Failed to get owner');
}
};
/**
* @description Query the registry for the owner of a domain
* @param domainHash: {Buffer} The hash of the domain to query
* @returns {Promise<SerialInfo>}
*/
this.getDomainSerial = async (domainHash) => {
let decodedResult = { serial: '0', node: '0' };
try {
const managerInfo = (0, manager_1.getManagerInfo)();
const numNodes = (await (0, contract_utils_1.callContractFunc)(managerInfo.contract.id, managerInfo.abi, 'getNumNodes', [], this.client))[0];
const chunkSize = 100;
let begin = 0;
let end = 0;
for (let i = 0; end < numNodes; i += 1) {
end = Number((i + 1) * chunkSize);
// eslint-disable-next-line no-await-in-loop
decodedResult = await this.callGetSerial(domainHash, begin, end);
if (Number(decodedResult.serial) !== Number(0)) {
// Found the owner
break;
}
begin = end;
}
return decodedResult;
}
catch (err) {
logger_config_1.logger.error(err);
throw new Error('Failed to get owner');
}
};
/**
* @description Simple wrapper around HTS TokenNftInfoQuery()
* @param serial: {number} The serial of the NFT to query
* @returns {Promise<TokenNftInfo>}
*/
this.getTokenNFTInfo = async (serial) => {
try {
const nftId = new sdk_1.NftId(this.tokenId, serial);
const nftInfo = await new sdk_1.TokenNftInfoQuery()
.setNftId(nftId)
.execute(this.client);
return nftInfo[0];
}
catch (err) {
logger_config_1.logger.error(err);
throw new Error('Get NFT info failed');
}
};
/**
* @description Wrapper around getDomainSerial() that takes a string of the domain
* @param domain: {string} The domain to query
* @returns {Promise<SerialInfo>}
*/
this.getNFTSerialString = async (domain) => this.getDomainSerial(HashgraphNames.generateNFTHash(domain));
/**
* @description Gets the serial for the domain, then queries for the AccountId who owns
* that domain.
* @param domain: {string} The domain to query
* @returns {Promise<AccountId>}
*/
this.getWallet = async (domain) => {
try {
const { serial } = await this.getNFTSerialString(domain);
const { accountId } = await this.getTokenNFTInfo(Number(serial));
return accountId;
}
catch (err) {
logger_config_1.logger.error(err);
throw new Error('Failed to get wallet');
}
};
this.operatorId = sdk_1.AccountId.fromString(operatorId);
this.operatorKey = sdk_1.PrivateKey.fromString(operatorKey);
this.supplyKey = sdk_1.PrivateKey.fromString(supplyKey);
this.client = sdk_1.Client.forTestnet().setOperator(this.operatorId, this.operatorKey);
}
}
exports.HashgraphNames = HashgraphNames;
HashgraphNames.generateMetadata = (domain) => {
const metadata = {
name: domain,
creator: 'piefi labs',
// creatorDID: '',
description: 'Hashgraph Naming service domain',
// image: '[cid or path to NFT\'s image]',
// type: 'image/jpeg', // TODO: Change this to whatever file type we end up generating for the NFT images
// files: [],
// format: 'none',
// properties: [],
// localization: [],
};
return metadata;
};
/**
* @description Helper function to convert an Uint8Array into an Hedera Transaction type
* @param transactionBytes: {Uint8Array} The transaction bytes to be converted
*/
HashgraphNames.bytesToTransaction = (transactionBytes) => {
const uint8Array = new Uint8Array(transactionBytes);
const transaction = sdk_1.Transaction.fromBytes(uint8Array);
return transaction;
};
/**
* @description Signs a Hedera transaction
* @param signerKey: {string} The private key with which to sign the transaction
* @param transactionBytes: {Uint8Array} The bytes for the transaction to be signed
* @returns {Promise<Uint8Array>}
*/
HashgraphNames.transferTransactionSign = (signerKey, transactionBytes) => {
const transaction = HashgraphNames.bytesToTransaction(transactionBytes);
const signerPVKey = sdk_1.PrivateKey.fromString(signerKey);
const signature = signerPVKey.signTransaction(transaction);
return { signerPublicKey: signerPVKey.publicKey, signature };
};
/**
* @description Generate a hash of the provided domain string
* @param domain: {string} The domain string to hash
* @returns {Buffer}
*/
HashgraphNames.generateNFTHash = (domain) => {
const subDomains = domain.split('.').reverse();
return subDomains.reduce((prev, curr) => (0, keccak256_1.default)(prev + curr), Buffer.from([0]));
};