@hashgraphonline/standards-sdk
Version:
The Hashgraph Online Standards SDK provides a complete implementation of the Hashgraph Consensus Standards (HCS), giving developers all the tools needed to build applications on Hedera.
1,122 lines (1,121 loc) • 38 kB
JavaScript
import { KeyList, TopicCreateTransaction, AccountId, Transaction, TopicMessageSubmitTransaction, Hbar } from "@hashgraph/sdk";
import { Logger } from "./standards-sdk.es22.js";
import { I as InscriptionSDK } from "./standards-sdk.es33.js";
import { HCS10BaseClient, Hcs10MemoType } from "./standards-sdk.es5.js";
import * as mime from "mime-types";
import { AgentBuilder } from "./standards-sdk.es11.js";
import { HCS11Client } from "./standards-sdk.es14.js";
import { ProgressReporter } from "./standards-sdk.es24.js";
import { inscribeWithSigner } from "./standards-sdk.es28.js";
const isBrowser = typeof window !== "undefined";
class BrowserHCSClient extends HCS10BaseClient {
constructor(config) {
super({
network: config.network,
logLevel: config.logLevel,
prettyPrint: config.prettyPrint,
feeAmount: config.feeAmount,
mirrorNode: config.mirrorNode,
silent: config.silent
});
this.hcs11Client = null;
this.hwc = config.hwc;
if (!config.guardedRegistryBaseUrl) {
this.guardedRegistryBaseUrl = "https://moonscape.tech";
} else {
this.guardedRegistryBaseUrl = config.guardedRegistryBaseUrl;
}
let logLevel;
if (config.logLevel) {
logLevel = config.logLevel;
} else {
logLevel = "info";
}
this.logger = Logger.getInstance({
level: logLevel,
module: "HCS-Browser",
prettyPrint: config.prettyPrint,
silent: config.silent
});
if (isBrowser) {
try {
const { accountId, signer } = this.getAccountAndSigner();
this.hcs11Client = new HCS11Client({
network: config.network,
auth: {
operatorId: accountId,
signer
},
logLevel: config.logLevel,
silent: config.silent
});
} catch (err) {
this.logger.warn(`Failed to initialize HCS11Client: ${err}`);
}
} else {
this.logger.error(
"BrowserHCSClient initialized in server environment - browser-specific features will not be available. Use HCS10Client instead."
);
}
}
async sendMessage(connectionTopicId, data, memo, submitKey, options) {
this.logger.info("Sending message");
const operatorId = await this.getOperatorId();
const payload = {
p: "hcs-10",
op: "message",
operator_id: operatorId,
data,
m: memo
};
const submissionCheck = await this.canSubmitToTopic(
connectionTopicId,
this.hwc.getAccountInfo().accountId
);
const payloadString = JSON.stringify(payload);
const isLargePayload = Buffer.from(payloadString).length > 1e3;
if (isLargePayload) {
this.logger.info(
"Message payload exceeds 1000 bytes, storing via inscription"
);
try {
const contentBuffer = Buffer.from(data);
const fileName = `message-${Date.now()}.json`;
const inscriptionResult = await this.inscribeFile(
contentBuffer,
fileName,
{
progressCallback: options?.progressCallback,
waitMaxAttempts: options?.waitMaxAttempts,
waitIntervalMs: options?.waitIntervalMs
}
);
if (inscriptionResult?.topic_id) {
payload.data = `hcs://1/${inscriptionResult.topic_id}`;
this.logger.info(
`Large message inscribed with topic ID: ${inscriptionResult.topic_id}`
);
} else {
throw new Error("Failed to inscribe large message content");
}
} catch (error) {
this.logger.error("Error inscribing large message:", error);
throw new Error(
`Failed to handle large message: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
return await this.submitPayload(
connectionTopicId,
payload,
submitKey,
submissionCheck.requiresFee
);
}
async getPublicKey(accountId) {
return await this.mirrorNode.getPublicKey(accountId);
}
async handleConnectionRequest(inboundTopicId, requestingAccountId, connectionId, connectionMemo = "Connection accepted. Looking forward to collaborating!", ttl = 60) {
this.logger.info("Handling connection request");
const userAccountId = this.hwc.getAccountInfo().accountId;
if (!userAccountId) {
throw new Error("Failed to retrieve user account ID");
}
const requesterKey = await this.mirrorNode.getPublicKey(requestingAccountId);
const accountKey = await this.mirrorNode.getPublicKey(userAccountId);
if (!accountKey) {
throw new Error("Failed to retrieve public key");
}
const thresholdKey = new KeyList([accountKey, requesterKey], 1);
const memo = this._generateHcs10Memo(Hcs10MemoType.CONNECTION, {
ttl,
inboundTopicId,
connectionId
});
const transaction = new TopicCreateTransaction().setTopicMemo(memo).setAutoRenewAccountId(AccountId.fromString(userAccountId)).setAdminKey(thresholdKey).setSubmitKey(thresholdKey);
this.logger.debug("Executing topic creation transaction");
const txResponse = await this.hwc.executeTransactionWithErrorHandling(
transaction,
false
);
if (txResponse?.error) {
this.logger.error(txResponse.error);
throw new Error(txResponse.error);
}
const resultReceipt = txResponse?.result;
if (!resultReceipt?.topicId) {
this.logger.error("Failed to create topic: topicId is null");
throw new Error("Failed to create topic: topicId is null");
}
const connectionTopicId = resultReceipt.topicId.toString();
const operatorId = `${inboundTopicId}@${userAccountId}`;
const confirmedConnectionSequenceNumber = await this.confirmConnection(
inboundTopicId,
connectionTopicId,
requestingAccountId,
connectionId,
operatorId,
connectionMemo
);
const accountTopics = await this.retrieveCommunicationTopics(userAccountId);
const requestingAccountTopics = await this.retrieveCommunicationTopics(requestingAccountId);
const requestingAccountOperatorId = `${requestingAccountTopics.inboundTopic}@${requestingAccountId}`;
await this.recordOutboundConnectionConfirmation({
outboundTopicId: accountTopics.outboundTopic,
requestorOutboundTopicId: requestingAccountTopics.outboundTopic,
connectionRequestId: connectionId,
confirmedRequestId: confirmedConnectionSequenceNumber,
connectionTopicId,
operatorId: requestingAccountOperatorId,
memo: `Connection established with ${requestingAccountId}`
});
return {
connectionTopicId,
confirmedConnectionSequenceNumber,
operatorId
};
}
async confirmConnection(inboundTopicId, connectionTopicId, connectedAccountId, connectionId, operatorId, memo) {
this.logger.info("Confirming connection");
const payload = {
p: "hcs-10",
op: "connection_created",
connection_topic_id: connectionTopicId,
connected_account_id: connectedAccountId,
operator_id: operatorId,
connection_id: connectionId,
m: memo
};
const transactionResponse = await this.submitPayload(
inboundTopicId,
payload
);
if (!transactionResponse?.topicSequenceNumber) {
this.logger.error(
"Failed to confirm connection: sequence number is null"
);
throw new Error("Failed to confirm connection: sequence number is null");
}
return transactionResponse.topicSequenceNumber.toNumber();
}
async create(builder, options) {
const progressCallback = options?.progressCallback;
const progressReporter = new ProgressReporter({
module: "ProfileCreate",
logger: this.logger,
callback: progressCallback
});
try {
const isAgentBuilder = builder instanceof AgentBuilder;
let state;
if (options?.existingState) {
state = options.existingState;
} else {
state = {
currentStage: "init",
completedPercentage: 0,
createdResources: []
};
}
if (isAgentBuilder) {
this.logger.info("Creating Agent Profile and HCS-10 Topics");
const agentConfig = builder.build();
state.agentMetadata = agentConfig.metadata;
} else {
this.logger.info("Creating Person HCS-11 Profile");
}
progressReporter.preparing(
`Starting ${isAgentBuilder ? "agent" : "person"} resource creation`,
0,
{
state
}
);
const {
inboundTopicId,
outboundTopicId,
state: updatedState
} = await this.createCommunicationTopics(options, progressReporter);
state = updatedState;
if (!isAgentBuilder) {
builder.setInboundTopicId(inboundTopicId);
builder.setOutboundTopicId(outboundTopicId);
}
let pfpTopicId;
let hasPfpBuffer;
let pfpFileName;
if (isAgentBuilder) {
const agentProfile = builder.build();
pfpTopicId = agentProfile.existingPfpTopicId || state.pfpTopicId;
hasPfpBuffer = agentProfile.pfpBuffer;
pfpFileName = agentProfile.pfpFileName || "pfp.png";
} else {
const personProfile = builder.build();
pfpTopicId = state.pfpTopicId;
hasPfpBuffer = personProfile.pfpBuffer;
pfpFileName = personProfile.pfpFileName;
}
if (!pfpTopicId && hasPfpBuffer && pfpFileName) {
pfpTopicId = await this.handleProfilePictureCreation(
hasPfpBuffer,
pfpFileName,
state,
progressReporter
);
} else if (pfpTopicId) {
progressReporter.preparing(
`Using existing profile picture: ${pfpTopicId}`,
50,
{ state }
);
state.pfpTopicId = pfpTopicId;
}
await this.createAndInscribeProfile(
isAgentBuilder,
builder,
pfpTopicId,
state,
inboundTopicId,
outboundTopicId,
options,
progressReporter
);
state.currentStage = "complete";
state.completedPercentage = 100;
progressReporter.completed(
`${isAgentBuilder ? "Agent" : "Person"} profile created successfully`,
{
profileTopicId: state.profileTopicId,
inboundTopicId,
outboundTopicId,
pfpTopicId,
state
}
);
let outTopicId = "";
if (state.outboundTopicId) {
outTopicId = state.outboundTopicId;
}
let inTopicId = "";
if (state.inboundTopicId) {
inTopicId = state.inboundTopicId;
}
let profilePicTopicId = "";
if (state.pfpTopicId) {
profilePicTopicId = state.pfpTopicId;
}
let profTopicId = "";
if (state.profileTopicId) {
profTopicId = state.profileTopicId;
}
return {
outboundTopicId: outTopicId,
inboundTopicId: inTopicId,
pfpTopicId: profilePicTopicId,
profileTopicId: profTopicId,
success: true,
state
};
} catch (error) {
progressReporter.failed("Error during profile creation", {
error: error.message
});
return {
outboundTopicId: "",
inboundTopicId: "",
pfpTopicId: "",
profileTopicId: "",
success: false,
error: error.message,
state: {
currentStage: "init",
completedPercentage: 0,
error: error.message
}
};
}
}
async handleProfilePictureCreation(pfpBuffer, pfpFileName, state, progressReporter) {
state.currentStage = "pfp";
progressReporter.preparing("Creating profile picture", 30, {
state
});
const pfpProgress = progressReporter.createSubProgress({
minPercent: 30,
maxPercent: 50,
logPrefix: "PFP"
});
const pfpResult = await this.inscribePfp(pfpBuffer, pfpFileName, {
progressCallback: (data) => pfpProgress.report({
...data,
progressPercent: data.progressPercent ?? 0,
details: { ...data.details, state }
})
});
if (!pfpResult.success) {
let errorMessage = "Failed to inscribe profile picture";
if (pfpResult.error) {
errorMessage = pfpResult.error;
}
throw new Error(errorMessage);
}
const pfpTopicId = pfpResult.pfpTopicId;
state.pfpTopicId = pfpTopicId;
if (state.createdResources) {
state.createdResources.push(`pfp:${state.pfpTopicId}`);
}
progressReporter.preparing("Profile picture created", 50, { state });
return pfpTopicId;
}
async createAndInscribeProfile(isAgentBuilder, builder, pfpTopicId, state, inboundTopicId, outboundTopicId, options, progressReporter) {
if (!this.hcs11Client) {
if (progressReporter) {
progressReporter.failed("HCS11Client is not available");
}
throw new Error("HCS11Client is not available");
}
this.logger.info("Creating and inscribing profile");
if (!state.profileTopicId) {
if (progressReporter) {
progressReporter.preparing(
`Storing HCS-11 ${isAgentBuilder ? "agent" : "person"} profile`,
80
);
}
const profileProgress = progressReporter?.createSubProgress({
minPercent: 80,
maxPercent: 95,
logPrefix: "StoreProfile"
});
let hcs11Profile;
if (isAgentBuilder) {
const agentProfile = builder.build();
const socialLinks = agentProfile.metadata?.socials ? Object.entries(agentProfile.metadata.socials).map(
([platform, handle]) => ({
platform,
handle
})
) : [];
hcs11Profile = this.hcs11Client.createAIAgentProfile(
agentProfile.name,
agentProfile.metadata?.type === "manual" ? 0 : 1,
agentProfile.capabilities || [],
agentProfile.metadata?.model || "unknown",
{
alias: agentProfile.name.toLowerCase().replace(/\s+/g, "_"),
bio: agentProfile.bio,
profileImage: pfpTopicId ? `hcs://1/${pfpTopicId}` : void 0,
socials: socialLinks,
properties: agentProfile.metadata?.properties || {},
inboundTopicId,
outboundTopicId,
creator: agentProfile.metadata?.creator
}
);
} else {
const personProfile = builder.build();
const { pfpBuffer, pfpFileName, ...cleanProfile } = personProfile;
hcs11Profile = this.hcs11Client.createPersonalProfile(
personProfile.display_name,
{
alias: personProfile.alias,
bio: personProfile.bio,
socials: personProfile.socials,
profileImage: pfpTopicId ? `hcs://1/${pfpTopicId}` : personProfile.profileImage,
properties: personProfile.properties,
inboundTopicId,
outboundTopicId
}
);
}
const profileResult = await this.hcs11Client.createAndInscribeProfile(
hcs11Profile,
options?.updateAccountMemo ?? true,
{
progressCallback: (data) => profileProgress?.report({
...data,
progressPercent: data.progressPercent ?? 0
})
}
);
if (!profileResult.success) {
if (progressReporter) {
progressReporter.failed(
`Failed to inscribe ${isAgentBuilder ? "agent" : "person"} profile`,
{
error: profileResult.error
}
);
}
let errorMessage = `Failed to inscribe ${isAgentBuilder ? "agent" : "person"} profile`;
if (profileResult.error) {
errorMessage = profileResult.error;
}
throw new Error(errorMessage);
}
state.profileTopicId = profileResult.profileTopicId;
if (state.createdResources) {
state.createdResources.push(`profile:${profileResult.profileTopicId}`);
}
if (progressReporter) {
progressReporter.preparing("HCS-11 Profile stored", 95, { state });
}
} else if (progressReporter) {
progressReporter.preparing(
`Using existing ${isAgentBuilder ? "agent" : "person"} profile`,
95,
{
state
}
);
}
}
initializeRegistrationState(inboundTopicId, existingState) {
const state = existingState || {
inboundTopicId,
currentStage: "registration",
completedPercentage: 0,
createdResources: []
};
if (state.currentStage !== "registration" && state.currentStage !== "complete") {
state.currentStage = "registration";
}
return state;
}
updateStateForCompletedRegistration(state, inboundTopicId) {
state.currentStage = "complete";
state.completedPercentage = 100;
if (state.createdResources) {
state.createdResources.push(`registration:${inboundTopicId}`);
}
}
async registerAgentWithGuardedRegistry(accountId, network = this.network, options) {
try {
this.logger.info("Registering agent with guarded registry");
const agentProfile = await this.retrieveProfile(accountId);
const inboundTopicId = agentProfile.topicInfo.inboundTopic;
const state = this.initializeRegistrationState(
inboundTopicId,
options?.existingState
);
const progressReporter = new ProgressReporter({
module: "AgentRegistration",
logger: this.logger,
callback: options?.progressCallback
});
progressReporter.preparing("Preparing agent registration", 10, {
inboundTopicId,
accountId
});
const registrationResult = await this.executeRegistration(
accountId,
network,
this.guardedRegistryBaseUrl,
this.logger
);
if (!registrationResult.success) {
return {
...registrationResult,
state
};
}
progressReporter.submitting("Submitting registration to registry", 30, {
transactionId: registrationResult.transactionId
});
if (registrationResult.transaction) {
const transaction = Transaction.fromBytes(
Buffer.from(registrationResult.transaction, "base64")
);
this.logger.info(`Processing registration transaction`);
const txResult = await this.hwc.executeTransactionWithErrorHandling(
transaction,
true
);
if (txResult.error) {
return {
...registrationResult,
error: txResult.error,
success: false,
state
};
}
this.logger.info(`Successfully processed registration transaction`);
}
progressReporter.confirming("Confirming registration transaction", 60, {
accountId,
inboundTopicId,
transactionId: registrationResult.transactionId
});
const maxAttempts = options?.maxAttempts ?? 60;
const delayMs = options?.delayMs ?? 2e3;
const confirmed = await this.waitForRegistrationConfirmation(
registrationResult.transactionId,
network,
this.guardedRegistryBaseUrl,
maxAttempts,
delayMs,
this.logger
);
this.updateStateForCompletedRegistration(state, inboundTopicId);
progressReporter.completed("Agent registration complete", {
transactionId: registrationResult.transactionId,
inboundTopicId,
state,
confirmed
});
return {
...registrationResult,
confirmed,
state
};
} catch (error) {
this.logger.error(`Registration error: ${error.message}`);
return {
error: `Error during registration: ${error.message}`,
success: false,
state: {
currentStage: "registration",
completedPercentage: 0,
error: error.message
}
};
}
}
async createAndRegisterAgent(builder, options) {
try {
const agentConfig = builder.build();
const progressCallback = options?.progressCallback;
const progressReporter = new ProgressReporter({
module: "AgentCreateRegister",
logger: this.logger,
callback: progressCallback
});
let state = options?.existingState || {
currentStage: "init",
completedPercentage: 0,
createdResources: []
};
state.agentMetadata = agentConfig.metadata;
progressReporter.preparing("Starting agent creation process", 0, {
state
});
if (state.currentStage !== "complete" || !state.inboundTopicId || !state.outboundTopicId || !state.profileTopicId) {
const createResult = await this.create(builder, {
progressCallback: (progress) => {
const adjustedPercent = (progress.progressPercent || 0) * 0.3;
progressReporter.report({
...progress,
progressPercent: adjustedPercent,
details: {
...progress.details,
state: progress.details?.state || state
}
});
},
existingState: state,
updateAccountMemo: false
});
if (!("state" in createResult)) {
throw new Error("Create method did not return expected agent state.");
}
if (!createResult.success) {
throw new Error(
createResult.error || "Failed to create agent resources"
);
}
state = createResult.state;
state.agentMetadata = agentConfig.metadata;
}
progressReporter.preparing(
`Agent creation status: ${state.currentStage}, ${state.completedPercentage}%`,
30,
{ state }
);
const { accountId } = this.getAccountAndSigner();
if (state.currentStage !== "complete" || !state.createdResources?.includes(
`registration:${state.inboundTopicId}`
)) {
if (options?.baseUrl) {
this.guardedRegistryBaseUrl = options.baseUrl;
}
const registrationResult = await this.registerAgentWithGuardedRegistry(
accountId,
agentConfig.network,
{
progressCallback: (progress) => {
const adjustedPercent = 30 + (progress.progressPercent || 0) * 0.7;
progressReporter.report({
...progress,
progressPercent: adjustedPercent,
details: {
...progress.details,
state: progress.details?.state || state
}
});
},
maxAttempts: options?.maxAttempts,
delayMs: options?.delayMs,
existingState: state
}
);
if (!registrationResult.success) {
throw new Error(
registrationResult.error || "Failed to register agent with registry"
);
}
state = registrationResult.state;
if (state.profileTopicId) {
await this.hcs11Client?.updateAccountMemoWithProfile(
accountId,
state.profileTopicId
);
}
}
progressReporter.completed("Agent creation and registration complete", {
state
});
return {
success: true,
state,
metadata: {
accountId,
operatorId: `${state.inboundTopicId}@${accountId}`,
inboundTopicId: state.inboundTopicId,
outboundTopicId: state.outboundTopicId,
profileTopicId: state.profileTopicId,
pfpTopicId: state.pfpTopicId,
privateKey: null,
...state.agentMetadata
}
};
} catch (error) {
this.logger.error(
`Failed to create and register agent: ${error.message}`
);
return {
success: false,
error: `Failed to create and register agent: ${error.message}`,
state: options?.existingState || {
currentStage: "init",
completedPercentage: 0,
error: error.message
}
};
}
}
async storeHCS11Profile(agentName, agentBio, inboundTopicId, outboundTopicId, capabilities = [], metadata = {}, pfpBuffer, pfpFileName, existingPfpTopicId, options) {
try {
const progressCallback = options?.progressCallback;
const progressReporter = new ProgressReporter({
module: "StoreHCS11Profile",
logger: this.logger,
callback: progressCallback
});
progressReporter.preparing("Preparing agent profile data", 0);
let pfpTopicId = existingPfpTopicId;
if (!pfpTopicId && pfpBuffer && pfpFileName) {
const pfpProgress = progressReporter.createSubProgress({
minPercent: 0,
maxPercent: 60,
logPrefix: "PFP"
});
const pfpResult = await this.inscribePfp(pfpBuffer, pfpFileName, {
progressCallback: (data) => {
pfpProgress.report({
stage: data.stage,
message: data.message,
progressPercent: data.progressPercent || 0,
details: data.details
});
}
});
if (!pfpResult.success) {
progressReporter.failed(
"Failed to inscribe profile picture, continuing without PFP"
);
} else {
pfpTopicId = pfpResult.pfpTopicId;
}
} else if (existingPfpTopicId) {
progressReporter.preparing(
`Using existing profile picture: ${existingPfpTopicId}`,
30
);
} else {
progressReporter.preparing("No profile picture provided", 30);
}
if (!this.hcs11Client) {
progressReporter.failed(
"HCS11Client is not available in this environment"
);
return {
profileTopicId: "",
success: false,
error: "HCS11Client is not available in this environment",
transactionId: ""
};
}
const agentType = this.hcs11Client.getAgentTypeFromMetadata({
type: metadata.type || "autonomous"
});
progressReporter.preparing("Building agent profile", 65);
const formattedSocials = metadata.socials ? Object.entries(metadata.socials).filter(([_, handle]) => handle).map(([platform, handle]) => ({
platform,
handle
})) : void 0;
const profile = this.hcs11Client.createAIAgentProfile(
agentName,
agentType,
capabilities,
metadata.model || "unknown",
{
alias: agentName.toLowerCase().replace(/\s+/g, "_"),
bio: agentBio,
profileImage: pfpTopicId ? `hcs://1/${pfpTopicId}` : void 0,
socials: formattedSocials,
properties: {
version: metadata.version || "1.0.0",
creator: metadata.creator || "Unknown",
supported_languages: metadata.supported_languages || ["en"],
permissions: metadata.permissions || [],
model_details: metadata.model_details,
training: metadata.training,
capabilities_description: metadata.capabilities_description,
...metadata
},
inboundTopicId,
outboundTopicId,
creator: metadata.creator
}
);
const profileProgress = progressReporter.createSubProgress({
minPercent: 65,
maxPercent: 100,
logPrefix: "Profile"
});
const profileResult = await this.hcs11Client.createAndInscribeProfile(
profile,
true,
{
progressCallback: (profileData) => {
profileProgress.report({
stage: profileData.stage,
message: profileData.message,
progressPercent: profileData.progressPercent || 0,
details: profileData.details
});
}
}
);
if (!profileResult.success) {
progressReporter.failed("Failed to inscribe profile");
return {
profileTopicId: "",
success: false,
error: profileResult.error || "Failed to inscribe profile",
transactionId: profileResult.transactionId || ""
};
}
progressReporter.completed("Profile stored successfully", {
profileTopicId: profileResult.profileTopicId
});
return {
profileTopicId: profileResult.profileTopicId,
pfpTopicId,
success: true,
transactionId: profileResult.transactionId || ""
};
} catch (error) {
this.logger.error(`Error storing HCS11 profile: ${error.message}`);
return {
profileTopicId: "",
success: false,
error: error.message,
transactionId: ""
};
}
}
async createTopic(memo, adminKey, submitKey) {
this.logger.info("Creating topic");
const { accountId, signer } = this.getAccountAndSigner();
const transaction = new TopicCreateTransaction().setTopicMemo(memo);
const publicKey = await this.mirrorNode.getPublicKey(accountId);
if (adminKey && publicKey) {
transaction.setAdminKey(publicKey);
transaction.setAutoRenewAccountId(accountId);
}
if (submitKey && publicKey) {
transaction.setSubmitKey(publicKey);
}
const transactionResponse = await this.hwc.executeTransactionWithErrorHandling(
transaction,
false
);
const error = transactionResponse.error;
if (error) {
this.logger.error(error);
return {
success: false,
error
};
}
const resultReceipt = transactionResponse.result;
if (!resultReceipt?.topicId) {
this.logger.error("Failed to create topic: topicId is null");
return {
success: false,
error: "Failed to create topic: topicId is null"
};
}
return {
success: true,
topicId: resultReceipt.topicId.toString()
};
}
async submitPayload(topicId, payload, submitKey, requiresFee) {
this.logger.debug(`Submitting payload to topic ${topicId}`);
let message;
if (typeof payload === "string") {
message = payload;
} else {
message = JSON.stringify(payload);
}
const transaction = new TopicMessageSubmitTransaction().setTopicId(topicId).setMessage(message);
const transactionMemo = this.getHcs10TransactionMemo(payload);
if (transactionMemo) {
transaction.setTransactionMemo(transactionMemo);
}
let transactionResponse;
if (requiresFee) {
this.logger.info(
"Topic requires fee payment, setting max transaction fee"
);
transaction.setMaxTransactionFee(new Hbar(this.feeAmount));
}
if (submitKey) {
const { signer } = this.getAccountAndSigner();
transaction.freezeWithSigner(signer);
const signedTransaction = await transaction.sign(submitKey);
transactionResponse = await this.hwc.executeTransactionWithErrorHandling(
signedTransaction,
true
);
} else {
transactionResponse = await this.hwc.executeTransactionWithErrorHandling(
transaction,
false
);
}
if (transactionResponse?.error) {
this.logger.error(
`Failed to submit payload: ${transactionResponse.error}`
);
throw new Error(`Failed to submit payload: ${transactionResponse.error}`);
}
if (!transactionResponse?.result) {
this.logger.error(
"Failed to submit message: receipt is null or undefined"
);
throw new Error("Failed to submit message: receipt is null or undefined");
}
this.logger.debug("Payload submitted successfully via HWC");
return transactionResponse.result;
}
async inscribeFile(buffer, fileName, options) {
const { accountId, signer } = this.getAccountAndSigner();
const mimeType = mime.lookup(fileName) || "application/octet-stream";
const sdk = await InscriptionSDK.createWithAuth({
type: "client",
accountId,
signer,
network: this.network
});
const inscriptionOptions = {
mode: "file",
waitForConfirmation: true,
waitMaxAttempts: options?.waitMaxAttempts || 30,
waitIntervalMs: options?.waitIntervalMs || 4e3,
progressCallback: options?.progressCallback,
logging: {
level: this.logger.getLevel ? this.logger.getLevel() : "info"
}
};
const response = await inscribeWithSigner(
{
type: "buffer",
buffer,
fileName,
mimeType
},
signer,
{
...inscriptionOptions,
network: this.network
},
sdk
);
if (!response.confirmed || !response.inscription) {
throw new Error("Inscription was not confirmed");
}
return response.inscription;
}
getAccountAndSigner() {
const accountInfo = this?.hwc?.getAccountInfo();
const accountId = accountInfo?.accountId?.toString();
const signer = this?.hwc?.dAppConnector?.signers?.find((s) => {
return s.getAccountId().toString() === accountId;
});
if (!signer) {
this.logger.error("Failed to find signer", {
accountId,
signers: this?.hwc?.dAppConnector?.signers,
accountInfo
});
throw new Error("Failed to find signer");
}
return { accountId, signer };
}
/**
* Inscribes a profile picture (PFP) on HCS-11.
*
* @param buffer - The buffer containing the PFP image.
* @param fileName - The name of the file containing the PFP image.
* @param options - Optional configuration options.
* @returns A promise that resolves to the topic ID of the inscribed PFP.
*/
async inscribePfp(buffer, fileName, options) {
try {
const progressCallback = options?.progressCallback;
const progressReporter = new ProgressReporter({
module: "PFP-Inscription",
logger: this.logger,
callback: progressCallback
});
if (!this.hcs11Client) {
progressReporter.failed(
"HCS11Client is not available in this environment"
);
return {
pfpTopicId: "",
success: false,
error: "HCS11Client is not available in this environment",
transactionId: ""
};
}
progressReporter.preparing("Preparing to inscribe profile picture", 10);
this.logger.info("Inscribing profile picture using HCS-11 client");
const wrappedProgressCallback = (data) => {
progressReporter.report({
stage: data.stage || "confirming",
message: data.message || "Processing PFP inscription",
progressPercent: data.progressPercent || 50,
details: data.details
});
};
const imageResult = await this.hcs11Client.inscribeImage(
buffer,
fileName,
{ progressCallback: wrappedProgressCallback }
);
if (!imageResult.success) {
let errorMessage = "Failed to inscribe profile picture";
if (imageResult.error) {
errorMessage = imageResult.error;
}
let txId = "";
if (imageResult.transactionId) {
txId = imageResult.transactionId;
}
return {
pfpTopicId: "",
success: false,
error: errorMessage,
transactionId: txId
};
}
progressReporter.completed("Successfully inscribed profile picture", {
pfpTopicId: imageResult.imageTopicId
});
this.logger.info(
`Successfully inscribed profile picture with topic ID: ${imageResult.imageTopicId}`
);
return {
pfpTopicId: imageResult.imageTopicId,
success: true,
transactionId: imageResult.transactionId || ""
};
} catch (error) {
this.logger.error(`Error inscribing profile picture: ${error.message}`);
return {
pfpTopicId: "",
success: false,
error: error.message,
transactionId: ""
};
}
}
async createCommunicationTopics(options, progressReporter) {
let state = options?.existingState || {
currentStage: "init",
completedPercentage: 0,
createdResources: []
};
if (progressReporter) {
progressReporter.preparing("Starting communication topic creation", 0, {
state
});
}
const { accountId } = this.getAccountAndSigner();
if (!state.outboundTopicId) {
state.currentStage = "topics";
if (progressReporter) {
progressReporter.preparing("Creating outbound topic", 5, {
state
});
}
const outboundMemo = this._generateHcs10Memo(Hcs10MemoType.OUTBOUND, {
ttl: options?.ttl,
accountId
});
const outboundResult = await this.createTopic(outboundMemo, true, true);
if (!outboundResult.success || !outboundResult.topicId) {
throw new Error(
outboundResult.error || "Failed to create outbound topic"
);
}
state.outboundTopicId = outboundResult.topicId;
if (state.createdResources)
state.createdResources.push(`outbound:${state.outboundTopicId}`);
}
if (!state.inboundTopicId) {
state.currentStage = "topics";
if (progressReporter) {
progressReporter.preparing("Creating inbound topic", 10, {
state
});
}
const inboundMemo = this._generateHcs10Memo(Hcs10MemoType.INBOUND, {
ttl: options?.ttl,
accountId
});
const inboundResult = await this.createTopic(inboundMemo, true, false);
if (!inboundResult.success || !inboundResult.topicId) {
throw new Error(
inboundResult.error || "Failed to create inbound topic"
);
}
state.inboundTopicId = inboundResult.topicId;
if (state.createdResources)
state.createdResources.push(`inbound:${state.inboundTopicId}`);
}
return {
inboundTopicId: state.inboundTopicId,
outboundTopicId: state.outboundTopicId,
state
};
}
}
export {
BrowserHCSClient
};
//# sourceMappingURL=standards-sdk.es8.js.map