Skip to content
Merged
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
77 changes: 51 additions & 26 deletions context/HashConnectProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -267,22 +267,16 @@ export default function HashConnectProvider({
if (debug)
console.info("===============Saving to localstorage::=============");
const { metadata, ...restData } = data;
const walletType = extensionType || connectedAccountType || "hashpack";
const saveObj: SaveData = {
...saveData,
pairedWalletData: metadata,
pairedAccounts: restData.accountIds,
walletExtensionType: extensionType || connectedAccountType,
walletExtensionType: walletType,
...restData,
};
// console.log(saveObj, "saveObj");
// await setSaveData((prevSaveData) => {
// prevSaveData.pairedWalletData = metadata;
// console.log("restData", { ...prevSaveData, ...restData });
// return { ...prevSaveData, ...restData };
// });
// console.log("saveData", saveData);
setSaveData(saveObj);
// setAccountId(restData.accountIds[0]);
setConnectedAccountType(walletType);
StorageService.saveData(saveObj);
if (
saveObj !== undefined &&
Expand Down Expand Up @@ -451,6 +445,11 @@ export default function HashConnectProvider({
};

const getAccounts = async (accountId: string) => {
// Set status to connected first, even if balance query fails
if (accountId && connectedAccountType) {
setStatus(WalletStatus.WALLET_CONNECTED);
}

//Create the account info query
try {
if (connectedAccountType === "hashpack") {
Expand All @@ -463,12 +462,11 @@ export default function HashConnectProvider({
const balance = await bladeService.getBalance();
setAccountBalance(balance);
}
if (connectedAccountType !== "") {
setStatus("WALLET_CONNECTED");
}
} catch (error: any) {
setNetworkError(true);
console.log(error.message);
// Status is already set to WALLET_CONNECTED above, so wallet connection is still recognized
// even if balance query fails
}
};

Expand All @@ -487,31 +485,35 @@ export default function HashConnectProvider({
};

const signTransaction = async (transactionString: string) => {
// console.log("transactionString", transactionString);
const transaction = Buffer.from(transactionString, "base64");
if (!transactionString || transactionString.trim() === "") {
throw new Error("Transaction string is empty");
}

if (!selectedAccount) {
throw new Error("No account selected. Please connect your wallet.");
}

// console.log("transaction", transaction.buffer);
// Convert base64 string to Uint8Array
const transactionBuffer = Buffer.from(transactionString.trim(), "base64");
const transaction = new Uint8Array(transactionBuffer);

const response: MessageTypes.TransactionResponse = await sendTransaction(
transaction,
selectedAccount,
true
);

// console.log("response", response);
if (response.success && response.signedTransaction) {
// console.log("signedTransaction", signedTransaction);

const signedTransaction = Buffer.from(
response.signedTransaction
).toString("base64");
// console.log(encodedSignature);
// const output: signedTransactionParams = {
// userId: selectedAccount,
// signature: encodedSignature,
// };
// console.log("output", output);
return signedTransaction;
} else if (response.error) {
throw new Error(`Transaction signing failed: ${response.error}`);
} else if (!response.success) {
throw new Error(
"Transaction signing was cancelled or rejected. Please check your wallet."
);
}

return null;
Expand Down Expand Up @@ -681,17 +683,40 @@ export default function HashConnectProvider({
return_trans: boolean = false
) => {
const topic = saveData.topic;

if (!topic) {
throw new Error(
"HashConnect is not properly initialized. Topic is missing. Please reconnect your wallet."
);
}

if (!saveData.privateKey) {
throw new Error(
"HashConnect is not properly initialized. Private key is missing. Please reconnect your wallet."
);
}

// Ensure HashConnect is initialized with private key for encryption
// This is required before sending transactions
try {
await hashConnect.init(metadata ?? APP_CONFIG, saveData.privateKey);
} catch (error: any) {
// If already initialized, that's fine - continue
if (!error.message?.includes("already initialized")) {
console.warn("HashConnect init warning:", error.message);
}
}

const transaction: MessageTypes.Transaction = {
topic: topic,
byteArray: trans,

metadata: {
accountToSign: acctToSign,
returnTransaction: return_trans,
},
};
const response = await hashConnect.sendTransaction(topic, transaction);

const response = await hashConnect.sendTransaction(topic, transaction);
return response;
};

Expand Down
185 changes: 185 additions & 0 deletions hooks/useDAppConnector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import { useState, useEffect, useCallback } from "react";
import { LedgerId } from "@hashgraph/sdk";
import { config } from "../config/config";

type WalletStatus =
| "INITIALIZING"
| "WALLET_NOT_CONNECTED"
| "WALLET_CONNECTED";

interface UseDAppConnectorReturn {
connect: () => Promise<void>;
signTransaction: (transactionString: string) => Promise<string | null>;
status: WalletStatus;
accountId: string | null;
isConnecting: boolean;
error: string | null;
}

const PROJECT_ID = "f891b15efe53e71351537c2c62cd024d";

export const useDAppConnector = (): UseDAppConnectorReturn => {
const [status, setStatus] = useState<WalletStatus>("INITIALIZING");
const [accountId, setAccountId] = useState<string | null>(null);
const [isConnecting, setIsConnecting] = useState(false);
const [error, setError] = useState<string | null>(null);
const [dAppConnector, setDAppConnector] = useState<any>(null);
const [signer, setSigner] = useState<any>(null);

useEffect(() => {
if (typeof window === "undefined") return;

let mounted = true;
let connectionInterval: NodeJS.Timeout;

const initializeDAppConnector = async () => {
try {
setStatus("INITIALIZING");

// Dynamic import for client-side only
const {
DAppConnector,
HederaChainId,
HederaJsonRpcMethod,
HederaSessionEvent,
} = await import("@hashgraph/hedera-wallet-connect");

const networkName = config.network.name;
const ledgerId =
networkName === "mainnet" ? LedgerId.MAINNET : LedgerId.TESTNET;
const chainId =
networkName === "mainnet"
? HederaChainId.Mainnet
: HederaChainId.Testnet;

const metadata = {
name: "Stader HBAR staking",
description:
"Liquid staking with Stader. Stake HBAR with Stader to earn rewards.",
url: window.location.origin,
icons: ["https://hedera.staderlabs.com/static/stader_logo.svg"],
};

const connector = new DAppConnector(
metadata,
ledgerId,
PROJECT_ID,
Object.values(HederaJsonRpcMethod),
[HederaSessionEvent.AccountsChanged, HederaSessionEvent.ChainChanged],
[chainId]
);

await connector.init({ logger: "error" });

const checkConnection = () => {
if (connector.signers && connector.signers.length > 0) {
const firstSigner = connector.signers[0];
const accId = firstSigner.getAccountId()?.toString() || null;

if (mounted && accId) {
setAccountId(accId);
setSigner(firstSigner);
setStatus("WALLET_CONNECTED");
setError(null);
}
} else if (mounted) {
setAccountId(null);
setSigner(null);
setStatus("WALLET_NOT_CONNECTED");
}
};

checkConnection();

connectionInterval = setInterval(() => {
if (mounted) checkConnection();
}, 1000);

if (mounted) {
setDAppConnector(connector);
}
} catch (err) {
console.error("Failed to initialize DAppConnector:", err);
if (mounted) {
setError("Failed to initialize wallet connector");
setStatus("WALLET_NOT_CONNECTED");
}
}
};

initializeDAppConnector();

return () => {
mounted = false;
if (connectionInterval) {
clearInterval(connectionInterval);
}
};
}, []);

const connect = useCallback(async () => {
if (!dAppConnector) {
throw new Error(
"DAppConnector is not initialized. Please refresh the page."
);
}

setIsConnecting(true);
setError(null);

try {
await dAppConnector.openModal();
} catch (err) {
console.error("Failed to connect:", err);
setError("Failed to connect wallet");
} finally {
setTimeout(() => setIsConnecting(false), 2000);
}
}, [dAppConnector]);

const signTransaction = useCallback(
async (transactionString: string): Promise<string | null> => {
if (!signer || !accountId || !dAppConnector) {
const errorMsg =
"Wallet not connected. Please connect your wallet first.";
setError(errorMsg);
throw new Error(errorMsg);
}

try {
const { HederaJsonRpcMethod } = await import(
"@hashgraph/hedera-wallet-connect"
);

const network = dAppConnector.ledgerId?.toString() || "testnet";
const signerAccountId = `hedera:${network}:${accountId}`;

const result = await signer.request({
method: HederaJsonRpcMethod.SignTransaction,
params: {
signerAccountId: signerAccountId,
transactionBytes: transactionString,
},
});

return result.signedTransaction || result;
} catch (err) {
const errorMsg =
err instanceof Error ? err.message : "Failed to sign transaction";
console.error("Transaction signing failed:", errorMsg, err);
setError(errorMsg);
throw err;
}
},
[signer, accountId, dAppConnector]
);

return {
connect,
signTransaction,
status,
accountId,
isConnecting,
error,
};
};
Loading
Loading