Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,6 @@ mise.toml

# Sentry Config File
.env.sentry-build-plugin

# asdf
.tool-versions
2 changes: 2 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
nodejs 23.0.0
yarn 1.22.22
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
},
"dependencies": {
"@amplitude/analytics-browser": "^2.20.1",
"@creit.tech/stellar-wallets-kit": "^1.9.3",
"@creit-tech/stellar-wallets-kit": "jsr:2.0.0-beta.9",
"@ledgerhq/hw-app-str": "^7.2.9",
"@ledgerhq/hw-transport-webhid": "^6.30.9",
"@monaco-editor/react": "^4.7.0",
Expand Down
2,392 changes: 1,174 additions & 1,218 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
import { BASE_FEE, contract } from "@stellar/stellar-sdk";
import { Api } from "@stellar/stellar-sdk/rpc";
import { JSONSchema7 } from "json-schema";
import { StellarWalletsKit } from "@creit-tech/stellar-wallets-kit/sdk";

import { RpcErrorResponse } from "@/components/TxErrorResponse";

Expand Down Expand Up @@ -156,7 +157,7 @@ export const InvokeContractForm = ({
reset: resetSubmitRpc,
} = useSubmitRpcTx();

const walletKitInstance = useContext(WalletKitContext);
const walletKitContext = useContext(WalletKitContext);

const IS_BLOCK_EXPLORER_ENABLED =
network.id === "testnet" || network.id === "mainnet";
Expand All @@ -165,7 +166,7 @@ export const InvokeContractForm = ({
const responseErrorEl = useRef<HTMLDivElement | null>(null);

const signTx = async (xdr: string): Promise<string | null> => {
if (!walletKitInstance?.walletKit || !walletKit?.publicKey) {
if (!walletKitContext.isInitialized || !walletKit?.publicKey) {
return null;
}

Expand All @@ -185,13 +186,10 @@ export const InvokeContractForm = ({
}, 180000);
});

const signPromise = walletKitInstance.walletKit.signTransaction(
xdr || "",
{
address: walletKit.publicKey,
networkPassphrase: network.passphrase,
},
);
const signPromise = StellarWalletsKit.signTransaction(xdr || "", {
address: walletKit.publicKey,
networkPassphrase: network.passphrase,
});

const result = await Promise.race([signPromise, timeoutPromise]);

Expand Down
6 changes: 3 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Metadata } from "next";

import { LayoutMain } from "@/components/layout/LayoutMain";
import { LayoutContextProvider } from "@/components/layout/LayoutContextProvider";
import { WalletKitContextProvider } from "@/components/WalletKit/WalletKitContextProvider";
import { DynamicWalletKitProvider } from "@/components/WalletKit/DynamicWalletKitProvider";
import { CustomAiButton } from "@/components/CustomAiButton";

import { QueryProvider } from "@/query/QueryProvider";
Expand Down Expand Up @@ -35,9 +35,9 @@ export default function RootLayout({
<StoreProvider>
<QueryProvider>
<LayoutContextProvider>
<WalletKitContextProvider>
<DynamicWalletKitProvider>
<LayoutMain>{children}</LayoutMain>
</WalletKitContextProvider>
</DynamicWalletKitProvider>
<CustomAiButton />
</LayoutContextProvider>
</QueryProvider>
Expand Down
96 changes: 50 additions & 46 deletions src/components/WalletKit/ConnectWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useContext, useEffect, useState } from "react";
import { Button, Modal, Text } from "@stellar/design-system";
import { ISupportedWallet } from "@creit.tech/stellar-wallets-kit";
import { StellarWalletsKit } from "@creit-tech/stellar-wallets-kit/sdk";
import { useStore } from "@/store/useStore";

import { useAccountInfo } from "@/query/useAccountInfo";
Expand All @@ -23,7 +23,7 @@ export const ConnectWallet = () => {
const [errorMessageOnConnect, setErrorMessageOnConnect] = useState("");
const [hasAttemptedAutoConnect, setHasAttemptedAutoConnect] =
useState<boolean>(false);
const walletKitInstance = useContext(WalletKitContext);
const walletKitContext = useContext(WalletKitContext);
const savedWallet = localStorageSavedWallet.get();

const { data: accountInfo, refetch: fetchAccountInfo } = useAccountInfo({
Expand All @@ -38,6 +38,7 @@ export const ConnectWallet = () => {
walletType: undefined,
});

StellarWalletsKit.disconnect();
setShowModal(false);
setConnected(false);
setHasAttemptedAutoConnect(false);
Expand All @@ -52,18 +53,13 @@ export const ConnectWallet = () => {
!hasAttemptedAutoConnect &&
!!savedWallet?.id &&
![undefined, "false", "wallet_connect"].includes(savedWallet?.id) &&
savedWallet.network.id === network.id
savedWallet.network.id === network.id &&
walletKitContext.isInitialized
) {
t = setTimeout(async () => {
if (!walletKitInstance?.walletKit) {
return;
}

try {
walletKitInstance.walletKit?.setWallet(savedWallet.id);
const success = await handleSetWalletAddress({
skipRequestAccess: true,
});
StellarWalletsKit.setWallet(savedWallet.id);
const success = await handleSetWalletAddress();

// Only set the flag if connection failed, so we can retry on successful connections
if (!success) {
Expand All @@ -83,22 +79,21 @@ export const ConnectWallet = () => {
};
// Not including savedWallet.network.id
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [savedWallet?.id, connected, hasAttemptedAutoConnect, walletKitInstance]);
}, [
savedWallet?.id,
connected,
hasAttemptedAutoConnect,
walletKitContext.isInitialized,
]);

// Reset auto-connect attempt when network changes
useEffect(() => {
setHasAttemptedAutoConnect(false);
}, [network.id]);

const handleSetWalletAddress = async ({
skipRequestAccess,
}: {
skipRequestAccess: boolean;
}): Promise<boolean> => {
const handleSetWalletAddress = async (): Promise<boolean> => {
try {
const addressResult = await walletKitInstance.walletKit?.getAddress({
skipRequestAccess,
});
const addressResult = await StellarWalletsKit.getAddress();

if (!addressResult?.address) {
return false;
Expand All @@ -124,38 +119,43 @@ export const ConnectWallet = () => {

const connectWallet = async () => {
try {
await walletKitInstance.walletKit?.openModal({
onWalletSelected: async (option: ISupportedWallet) => {
walletKitInstance.walletKit?.setWallet(option.id);
const isWalletConnected = await handleSetWalletAddress({
skipRequestAccess: false,
});

if (!isWalletConnected) {
const errorMessage = "Unable to load wallet information";
setErrorMessageOnConnect(errorMessage);
disconnect();
return;
}
const { address } = await StellarWalletsKit.authModal();

Comment on lines 120 to +123
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

connectWallet() calls StellarWalletsKit.authModal() without checking walletKitContext.isInitialized, even though other flows (auto-connect, signing) explicitly gate SDK calls on initialization. If the user clicks “Connect Wallet” before the provider finishes StellarWalletsKit.init(), this can fail and show a confusing error. Consider early-returning (or disabling the button) until walletKitContext.isInitialized is true.

Copilot uses AI. Check for mistakes.
if (!address) {
const errorMessage = "Unable to load wallet information";
setErrorMessageOnConnect(errorMessage);
disconnect();
return;
}

localStorageSavedWallet.set({
id: option.id,
network: {
id: network.id,
label: network.label,
},
});

trackEvent(TrackingEvent.WALLET_KIT_SELECTED, {
walletType: option.id,
});
},
updateWalletKit({
publicKey: address,
walletType: walletKitContext.selectedWalletId,
});
setConnected(true);

// Use the wallet ID from the kit event (set via WALLET_SELECTED event in context)
if (walletKitContext.selectedWalletId) {
localStorageSavedWallet.set({
id: walletKitContext.selectedWalletId,
network: {
id: network.id,
label: network.label,
},
});

trackEvent(TrackingEvent.WALLET_KIT_SELECTED, {
walletType: walletKitContext.selectedWalletId,
});
}
Comment on lines +122 to +150
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

connectWallet() saves availableWallet.id by picking the first wallet where isAvailable is true. This is not necessarily the wallet the user actually selected in authModal(), so the persisted wallet ID (and the tracking event) can be incorrect and break auto-connect on reload. Prefer using the wallet identifier returned by authModal() (if provided), or otherwise capture the actual selected wallet during the auth flow instead of guessing from availability.

Copilot uses AI. Check for mistakes.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
const errorMessage =
(e as { message?: string })?.message || "Unknown error occurred";
setErrorMessageOnConnect(errorMessage);
// Don't show error if user just closed the modal
if (errorMessage !== "The user closed the modal.") {
setErrorMessageOnConnect(errorMessage);
}
disconnect();
}
};
Expand Down Expand Up @@ -199,6 +199,10 @@ export const ConnectWallet = () => {
);
};

if (!walletKitContext.isInitialized) {
return;
}

return walletKit?.publicKey ? (
<>
<Button
Expand Down
15 changes: 15 additions & 0 deletions src/components/WalletKit/DynamicWalletKitProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";

import dynamic from "next/dynamic";

// Dynamically import WalletKitContextProvider with SSR disabled
// This ensures the StellarWalletsKit only loads on the client side
export const DynamicWalletKitProvider = dynamic(
() =>
import("@/components/WalletKit/WalletKitContextProvider").then(
(mod) => mod.WalletKitContextProvider,
),
{
ssr: false,
},
);
Loading
Loading