Skip to content

Commit

Permalink
enable wallet connection with keplr
Browse files Browse the repository at this point in the history
  • Loading branch information
dredshep committed Apr 4, 2024
1 parent 4d34a59 commit 51db962
Show file tree
Hide file tree
Showing 14 changed files with 233 additions and 66 deletions.
50 changes: 33 additions & 17 deletions components/app/UserWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,43 @@ import React from "react";
import { RxCaretDown } from "react-icons/rx";
import PlaceholderImageFromSeed from "@/components/app/molecules/PlaceholderImageFromSeed";
import { SecretString } from "@/types";
import useKeplrConnect from "@/utils/hooks/useKeplrConnect";
import keplrConnect from "@/utils/wallet/keplrConnect";
import { useStore } from "@/store/swapStore";
import keplrDisconnect from "@/utils/wallet/keplrDisconnect";

interface UserWalletProps {
isConnected: boolean;
userAddress: SecretString;
balanceSCRT: number;
balanceADMT: number;
onConnect: () => void;
// isConnected: boolean;
// userAddress: SecretString;
// balanceSCRT: number;
// balanceADMT: number;
// onConnect: () => void;
}

const UserWallet: React.FC<UserWalletProps> = ({
isConnected,
userAddress,
balanceSCRT,
balanceADMT,
onConnect,
}) => {
const UserWallet: React.FC<UserWalletProps> = (
{
// isConnected,
// userAddress,
// balanceSCRT,
// balanceADMT,
// onConnect,
}
) => {
const {
wallet: { address: userAddress, ADMTBalance, SCRTBalance },
} = useStore();
const truncatedAddress =
userAddress.slice(0, 6) + "..." + userAddress.slice(-4);
userAddress === null
? ""
: userAddress.slice(0, 6) + "..." + userAddress.slice(-4);
// useKeplrConnect();
const isConnected = userAddress !== null;

return (
<div className="flex items-center gap-4">
{isConnected ? (
<>
<div className="relative">
<div className="relative" onClick={() => keplrDisconnect()}>
<PlaceholderImageFromSeed seed={userAddress} size={48} />
</div>
<div>
Expand All @@ -35,13 +48,16 @@ const UserWallet: React.FC<UserWalletProps> = ({
<RxCaretDown className="text-white h-5 w-5" />
</div>
<div className="hidden md:block font-normal opacity-50">
{balanceSCRT} SCRT / {balanceADMT} ADMT
{SCRTBalance} SCRT / {ADMTBalance} ADMT
</div>
</div>
</>
) : (
<button onClick={onConnect} className="text-white px-4 py-2 rounded">
Connect
<button
onClick={() => keplrConnect()}
className="text-black bg-white px-8 pt-2 pb-2 rounded-lg font-bold leading-6 fle hover:bg-adamant-accentBg transition-all ease-in-out"
>
CONNECT
</button>
)}
</div>
Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion components/app/molecules/TokenSelectionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const TokenSelectionItem: React.FC<TokenSelectionItemProps> = ({
<span className="font-bold">{token.symbol}</span>
<span className="text-gray-500 text-xs font-medium">{network}</span>
</div>
<span className="font-medium">{balance}</span>
<span className="font-bold">{balance}</span>
</div>
</Dialog.Close>
);
Expand Down
8 changes: 1 addition & 7 deletions components/app/organisms/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,7 @@ const Navbar: React.FC = () => {
</div>
</Link>
</div>
<UserWallet
isConnected={true}
userAddress="secret1234567890abcdef"
balanceSCRT={0}
balanceADMT={0}
onConnect={() => console.log("connect")}
/>
<UserWallet />
</div>
);
};
Expand Down
8 changes: 0 additions & 8 deletions components/app/organisms/SwapForm/RawAttempt.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ const TokenSelectionModal: React.FC<TokenSelectionModalProps> = ({
<>
<Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-50" />

<Dialog.Content className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-adamant-app-box rounded-lg w-1/3 py-6 outline-none z-10">
<Dialog.Content className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-adamant-app-box rounded-lg w-1/3 py-6 outline-none z-10 text-base font-medium normal-case">
<div className="flex justify-between items-center px-6">
<Dialog.Title className="text-lg leading-[21px]">
<Dialog.Title className="text-lg leading-[21px] font-bold">
Select a token
</Dialog.Title>
<Dialog.Close asChild>
<button>
<Cross1Icon />
<button className="outline-none">
<Cross1Icon className="" />
</button>
</Dialog.Close>
</div>
Expand Down
5 changes: 2 additions & 3 deletions pages/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Jura } from "next/font/google";
import AppLayout from "../../components/app/compositions/AppLayout";
import RawAttempt from "@/components/app/organisms/SwapForm/RawAttempt";
import SwapForm from "@/components/app/organisms/SwapForm/SwapForm";

const jura = Jura({ subsets: ["latin"] });

Expand All @@ -15,7 +15,6 @@ export default function Swap() {
}
>
<AppLayout>
{/* container div simply setting max width and perhaps other settings for content only */}
<div className="max-w-xl mx-auto mt-28">
<div className="flex gap-4 justify-between leading-6 px-5">
<div className="flex gap-4">
Expand All @@ -35,7 +34,7 @@ export default function Swap() {
<div>+ Add liquidity for SRCT/SHD</div>
</div>
<div className="bg-adamant-app-box leading-none rounded-xl text-xl uppercase mt-2">
<RawAttempt />
<SwapForm />
</div>
</div>
</AppLayout>
Expand Down
52 changes: 34 additions & 18 deletions store/swapStore.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { create } from "zustand";
import {
SecretString,
SharedSettings,
StoreState,
Token,
TokenInputState,
TokenInputs,
} from "@/types";
import updateState from "@/store/utils/updateState";

export const useStore = create<StoreState>((set) => ({
tokenInputs: {
Expand All @@ -28,29 +29,44 @@ export const useStore = create<StoreState>((set) => ({
slippage: 0.5,
gas: 0,
},
wallet: {
address: null,
SCRTBalance: "0",
ADMTBalance: "0",
},
swappableTokens: [],
chainId: "secret-4",
connectionRefused: false,
setTokenInputProperty: <T extends keyof TokenInputState>(
inputIdentifier: keyof TokenInputs,
property: T,
value: TokenInputState[T]
) =>
set((state) => ({
...state,
tokenInputs: {
...state.tokenInputs,
[inputIdentifier]: {
...state.tokenInputs[inputIdentifier],
[property]: value,
},
},
})),
set((state) =>
updateState(state, "tokenInputs", inputIdentifier, {
token: state.tokenInputs[inputIdentifier].token,
amount: state.tokenInputs[inputIdentifier].amount,
[property]: value,
})
),

setSharedSetting: <T extends keyof SharedSettings>(
setting: T,
value: SharedSettings[T]
) =>
set((state) => ({
...state,
sharedSettings: { ...state.sharedSettings, [setting]: value },
})),
swappableTokens: [], // Add this line to initialize the swappable tokens list
setSwappableTokens: (tokens) => set({ swappableTokens: tokens }), // Method to update the list
) => set((state) => updateState(state, "sharedSettings", setting, value)),

connectWallet: (address: SecretString) =>
set((state) => updateState(state, "wallet", "address", address)),

disconnectWallet: () =>
set((state) => updateState(state, "wallet", "address", null)),

updateBalance: (tokenSymbol: "SCRT" | "ADMT", balance: string) =>
set((state) =>
updateState(state, "wallet", `${tokenSymbol}Balance`, balance)
),

setSwappableTokens: (tokens) => set({ swappableTokens: tokens }),
setChainId: (chainId) => set({ chainId }),
setConnectionRefused: (refused) => set({ connectionRefused: refused }),
}));
39 changes: 39 additions & 0 deletions store/utils/updateState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Safely updates a nested property within a state object, ensuring type safety.
*
* @param currentState - The current state object.
* @param topLevelKey - The key of the top-level property within the state object to update.
* @param nestedKey - The key of the nested property within the top-level property to update.
* @param newValue - The new value to set for the nested property.
* @returns A new state object with the nested property updated.
*/
function updateState<
CurrentStateType extends Record<string, any>,
TopLevelKey extends keyof CurrentStateType,
NestedKey extends keyof CurrentStateType[TopLevelKey]
>(
currentState: CurrentStateType,
topLevelKey: TopLevelKey,
nestedKey: NestedKey,
newValue: CurrentStateType[TopLevelKey][NestedKey] | Record<string, any>
): CurrentStateType {
// Determine if newValue is a plain object for a potential merge operation.
const isObject = (obj: any): obj is Record<string, any> =>
obj && typeof obj === "object" && !Array.isArray(obj);

// Perform the update operation.
const updatedState = {
...currentState,
[topLevelKey]: {
...currentState[topLevelKey],
[nestedKey]:
isObject(newValue) && isObject(currentState[topLevelKey][nestedKey])
? { ...currentState[topLevelKey][nestedKey], ...newValue }
: newValue,
},
};

return updatedState;
}

export default updateState;
14 changes: 12 additions & 2 deletions types/store/StoreState.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { Token } from "@/types/Token";
import { WalletState } from "@/types/store/WalletState";
import { SharedSettings } from "@/types/store/SharedSettings";
import { TokenInputState } from "@/types/store/TokenInputState";
import { TokenInputs } from "@/types/store/TokenInputs";
import { TokenInputState } from "@/types/store/TokenInputState";
import { SecretString } from "../SecretString";

export interface StoreState {
tokenInputs: TokenInputs;
sharedSettings: SharedSettings;
wallet: WalletState;
chainId: string;
swappableTokens: Token[];
connectionRefused: boolean;
setTokenInputProperty: <T extends keyof TokenInputState>(
inputIdentifier: keyof TokenInputs,
property: T,
Expand All @@ -15,6 +21,10 @@ export interface StoreState {
setting: T,
value: SharedSettings[T]
) => void;
swappableTokens: Token[];
connectWallet: (address: SecretString) => void;
disconnectWallet: () => void;
updateBalance: (tokenSymbol: "SCRT" | "ADMT", balance: string) => void;
setChainId: (chainId: string) => void;
setSwappableTokens: (tokens: Token[]) => void;
setConnectionRefused: (refused: boolean) => void;
}
7 changes: 7 additions & 0 deletions types/store/WalletState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { SecretString } from "../SecretString";

export interface WalletState {
address: SecretString | null;
SCRTBalance: string;
ADMTBalance: string;
}
58 changes: 58 additions & 0 deletions utils/hooks/useKeplrConnect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// hooks/useKeplrConnection.ts
import { useEffect } from "react";
import { useStore } from "@/store/swapStore";

type CustomWindow = typeof window & {
keplr: any;
};

const useKeplrConnect = (manual: boolean) => {
useEffect(() => {
// check if user already rejected connection
let attemptedConnection = false;

const attemptConnection = async () => {
const { keplr }: CustomWindow = window as CustomWindow;
const connectionRefused = useStore.getState().connectionRefused;

// if not manually connecting and user already rejected connection, do not attempt connection
if (keplr && !manual && connectionRefused) {
attemptedConnection = true;
try {
const chainId = "secret-4";
await keplr.enable(chainId);
const offlineSigner = keplr.getOfflineSigner(chainId);
const accounts = await offlineSigner.getAccounts();

if (accounts && accounts.length > 0) {
const { address } = accounts[0];
// Update the store with the user's address
useStore.getState().connectWallet(address);

// Here, you might also want to fetch and update the SCRT and ADMT balances
// For example:
// useStore.getState().updateBalance('SCRT', '100'); // Fetch and use actual balance
// useStore.getState().updateBalance('ADMT', '200'); // Fetch and use actual balance
}
} catch (error) {
console.error("Error connecting to Keplr:", error);
// set connection refused to true so we only connect again if the user clicks the connect button
useStore.getState().setConnectionRefused(true);
}
}
};

if (manual) {
attemptConnection();
}

// Optional: Listen for account change
window.addEventListener("keplr_keystorechange", attemptConnection);

return () => {
window.removeEventListener("keplr_keystorechange", attemptConnection);
};
}, [manual]); // Ensure this runs whenever the manual param changes
};

export default useKeplrConnect;
Loading

0 comments on commit 51db962

Please sign in to comment.