Skip to content
Closed
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
62 changes: 35 additions & 27 deletions packages/client/src/components/transfer/Authorize.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { requestSessionKey } from "@happy.tech/core";
import { useEffect, useState } from "react";
import { FaClipboard, FaExclamationCircle, FaEye, FaEyeSlash, FaInfoCircle, FaTimes, FaUnlink } from "react-icons/fa";
import { Address, Hex } from "viem";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import { Address } from "viem";

import { STORAGE_PREFIX } from "@primodiumxyz/core";
import { useAccountClient } from "@primodiumxyz/core/react";
Expand All @@ -11,14 +11,21 @@ import { SecondaryCard } from "@/components/core/Card";
import { TransactionQueueMask } from "@/components/shared/TransactionQueueMask";
import { useContractCalls } from "@/hooks/useContractCalls";
import { copyToClipboard } from "@/util/clipboard";
import { findEntriesWithPrefix } from "@/util/localStorage";

const sessionWalletTooltip =
"Bypass annoying confirmation popups by authorizing a session account. This allows you to securely perform certain actions without external confirmation.";
const sessionWalletTooltip = (
<>
Bypass confirmation popups by authorizing a session key. Powered by{" "}
<span className="text-yellow-400">Happy Wallet</span>, this lets you securely perform actions without repeated
approvals.
</>
);

export function Authorize() {
const { sessionAccount } = useAccountClient();
const { grantAccessWithSignature, revokeAccess } = useContractCalls();
const {
playerAccount: { worldContract },
sessionAccount,
} = useAccountClient();
const { revokeAccess } = useContractCalls();
const [showDetails, setShowDetails] = useState(false);
const [showHelp, setShowHelp] = useState(!localStorage.getItem("hideHelp"));

Expand All @@ -29,26 +36,26 @@ export function Authorize() {
// Function to handle private key validation and connection
const sessionAddress = sessionAccount?.address;

const submitPrivateKey = async (privateKey: Hex) => {
// Validate the private key format here
// This is a basic example, adjust the validation according to your requirements
const isValid = /^0x[a-fA-F0-9]{64}$/.test(privateKey);
if (!isValid) return;
const account = privateKeyToAccount(privateKey as Hex);
// const _submitPrivateKey = async (privateKey: Hex) => {
// // Validate the private key format here
// // This is a basic example, adjust the validation according to your requirements
// const isValid = /^0x[a-fA-F0-9]{64}$/.test(privateKey);
// if (!isValid) return;
// const account = privateKeyToAccount(privateKey as Hex);

if (sessionAddress && sessionAddress === account.address) return;
else await grantAccessWithSignature(privateKey, { id: defaultEntity });
};
// if (sessionAddress && sessionAddress === account.address) return;
// else await grantAccessWithSignature(privateKey, { id: defaultEntity });
// };

const handleRandomPress = () => {
const storedKeys = findEntriesWithPrefix();
const privateKey = storedKeys.length > 0 ? storedKeys[0].privateKey : generatePrivateKey();
// const _handleRandomPress = () => {
// const storedKeys = findEntriesWithPrefix();
// const privateKey = storedKeys.length > 0 ? storedKeys[0].privateKey : generatePrivateKey();

const account = privateKeyToAccount(privateKey as Hex);
localStorage.setItem(STORAGE_PREFIX + account.address, privateKey);
// const account = privateKeyToAccount(privateKey as Hex);
// localStorage.setItem(STORAGE_PREFIX + account.address, privateKey);

return privateKey;
};
// return privateKey;
// };

const removeSessionKey = async (publicKey: Address) => {
await revokeAccess(publicKey);
Expand Down Expand Up @@ -136,12 +143,13 @@ export function Authorize() {
variant="primary"
size="md"
className="w-full"
onClick={() => {
const key = handleRandomPress();
submitPrivateKey(key);
onClick={async () => {
// const key = handleRandomPress();
// submitPrivateKey(key);
await requestSessionKey(worldContract.address);
}}
>
CLICK TO AUTHORIZE SESSION ACCOUNT
CLICK TO AUTHORIZE SESSION KEY
</Button>
)}
</TransactionQueueMask>
Expand Down
44 changes: 25 additions & 19 deletions packages/client/src/screens/Enter.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
import { requestSessionKey } from "@happy.tech/core";
import { useEffect, useState } from "react";
import { FaExclamationTriangle, FaInfoCircle } from "react-icons/fa";
import { useLocation, useNavigate } from "react-router-dom";
import { toast } from "react-toastify";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import { Address } from "viem";

import { STORAGE_PREFIX } from "@primodiumxyz/core";
import { useAccountClient, useCore } from "@primodiumxyz/core/react";
import { defaultEntity } from "@primodiumxyz/reactive-tables";
import { Tooltip } from "@/components/core/Tooltip";
import { TransactionQueueMask } from "@/components/shared/TransactionQueueMask";
import { useContractCalls } from "@/hooks/useContractCalls";
import { findEntriesWithPrefix } from "@/util/localStorage";
import { HAPPY_STORAGE_PREFIX } from "@/util/localStorage";

import { Landing } from "./Landing";

export const Enter: React.FC = () => {
const { tables } = useCore();
const {
playerAccount: { entity: playerEntity },
sessionAccount,
playerAccount: { address: playerAddress, worldContract, entity: playerEntity },
} = useAccountClient();

const { grantAccessWithSignature, spawn } = useContractCalls();
const { spawn } = useContractCalls();
const navigate = useNavigate();
const location = useLocation();
const [showingToast, setShowingToast] = useState(false);
Expand All @@ -37,6 +36,9 @@ export const Enter: React.FC = () => {
<div className="flex flex-col text-center justify-center items-center gap-2 w-full">
<FaExclamationTriangle size={24} className="text-warning" />
Are you sure you want to skip? You will need to confirm every action with your external wallet.
<br />
<br />
You can still enable a session key within the game settings.
</div>

<div className="flex justify-center w-full gap-2">
Expand Down Expand Up @@ -73,17 +75,17 @@ export const Enter: React.FC = () => {
);
};
useEffect(() => {
if (!sessionAccount) {
if (!isSessionRegistered(playerAddress)) {
setState("delegate");
} else {
setState("play");
}
}, [sessionAccount]);
}, [playerAddress]);

useEffect(() => {
if (!sessionAccount) return;
toast.success(`Session account detected! (${sessionAccount.address.slice(0, 7)})`);
}, [sessionAccount]);
if (!isSessionRegistered(playerAddress)) return;
toast.success(`Session account detected! (${playerAddress.slice(0, 7)})`);
Copy link

Choose a reason for hiding this comment

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

nit: keep the terminology consistent: account -> key

}, [playerAddress]);

const handlePlay = async () => {
const hasSpawned = !!tables.Home.get(playerEntity)?.value;
Expand All @@ -93,13 +95,17 @@ export const Enter: React.FC = () => {
navigate("/game" + location.search);
};

const handleDelegate = async () => {
const storedKeys = findEntriesWithPrefix();
const privateKey = storedKeys.length > 0 ? storedKeys[0].privateKey : generatePrivateKey();
const account = privateKeyToAccount(privateKey);
localStorage.setItem(STORAGE_PREFIX + account.address, privateKey);
const isSessionRegistered = (address: Address): boolean => {
return localStorage.getItem(HAPPY_STORAGE_PREFIX + address) === "true";
};

await grantAccessWithSignature(privateKey, { id: defaultEntity });
const handleRegisterHappySessionKey = async () => {
const isRegistered = isSessionRegistered(playerAddress);
if (!isRegistered) {
await requestSessionKey(worldContract.address);
localStorage.setItem(HAPPY_STORAGE_PREFIX + playerAddress, "true");
setState("play");
} else return;
};

return (
Expand All @@ -108,7 +114,7 @@ export const Enter: React.FC = () => {
{state === "delegate" && (
<div className="grid grid-cols-7 gap-2 items-center pointer-events-auto">
<button
onClick={handleDelegate}
onClick={handleRegisterHappySessionKey}
className="relative btn col-span-6 font-bold outline-none h-fit btn-secondary w-full star-background hover:scale-105"
>
<Tooltip
Expand All @@ -120,7 +126,7 @@ export const Enter: React.FC = () => {
<FaInfoCircle className="w-6 text-info" />
</div>
</Tooltip>
Authorize Delegate
Register Session Key
</button>
<button onClick={confirmSkip} className="btn btn-neutral opacity-80 hover:scale-110">
Skip
Expand Down
9 changes: 8 additions & 1 deletion packages/client/src/util/localStorage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Hex } from "viem";
import { Address, Hex } from "viem";

import { STORAGE_PREFIX } from "@primodiumxyz/core";

Expand Down Expand Up @@ -35,3 +35,10 @@ export function getPrivateKey(publicKey: Hex): Hex | undefined {
if (!entry) return;
return entry as Hex;
}

// [HAPPY_PRIM] localstorage helpers todotodotodo
export const HAPPY_STORAGE_PREFIX = "happySessionRegistered:";

export const isSessionRegistered = (address: Address): boolean => {
return localStorage.getItem(HAPPY_STORAGE_PREFIX + address) === "true";
};
2 changes: 1 addition & 1 deletion packages/core/src/network/config/chainConfigs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const baseSepolia: ChainConfig = {

export const happyChainSepolia: ChainConfig = {
...happyChainDef,
indexerUrl: "http://localhost:3001", // ## [happyTODO :: deploy indexer and then add the URL here] ##
indexerUrl: "https://primodium-indexer.happy.tech/",
};

export type ChainConfig = MUDChain & { indexerUrl?: string };
Expand Down