diff --git a/.changeset/dirty-wasps-jam.md b/.changeset/dirty-wasps-jam.md new file mode 100644 index 0000000..50686ec --- /dev/null +++ b/.changeset/dirty-wasps-jam.md @@ -0,0 +1,5 @@ +--- +"burner-connector": patch +--- + +feat: sessionStorage for burner wallet diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 90b779f..7f7debe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ pnpm run dev This will start a local server on `http://localhost:3000` with the example app linked to local package -The burner wallet should be automatically connected to sepolia network, and can interact with the [`YourContract`](https://sepolia.etherscan.io/address/0x0D25b202D1B5126ECFcaeFa85f7a37ed86EF79ea) deployed on the sepolia. +The burner wallet should be automatically connected to optimism sepolia network, and can interact with the [`YourContract`](https://optimism-sepolia.blockscout.com/address/0xFB30C0790128b97e3aC540E6124e512E37c47D00). ## Testing with hardhat diff --git a/README.md b/README.md index 18601ce..7cdc9c0 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,11 @@ pnpm add burner-connector ```ts import { burner } from "burner-connector"; import { mainnet, base } from "viem/chains"; + +// burner function can also called with param `{ useSessionStorage: true }` to create a new wallet for each browser tab +// - `useSessionStorage` to false (default) to persist wallet across browser tabs(incognito window will have different wallet) +// - `useSessionStorage` to true to create a new wallet for each browser tab + export const config = createConfig({ chains: [mainnet, base], connectors: [burner()], @@ -49,6 +54,13 @@ import { mainnet, base } from "viem/chains"; const wallets = [metaMaskWallet, rainbowkitBurnerWallet]; +// Configure burner wallet storage +// - `useSessionStorage` to false (default) to persist wallet across browser tabs(incognito window will have different wallet) +// - `useSessionStorage` to true to create a new wallet for each browser tab +// rainbowkitBurnerWallet.useSessionStorage = true; + +const wallets = [metaMaskWallet, rainbowkitBurnerWallet]; + const wagmiConnectors = connectorsForWallets( [ { diff --git a/example/app/components/Example.tsx b/example/app/components/Example.tsx index 56da377..f7de96c 100644 --- a/example/app/components/Example.tsx +++ b/example/app/components/Example.tsx @@ -7,6 +7,7 @@ import { import { FaucetButton } from "./FaucetButton"; import toast from "react-hot-toast"; import deployedContracts from "../contracts/deployedContracts"; +import { optimismSepolia } from "viem/chains"; export const Example = () => { const { isConnected, chain } = useAccount(); @@ -17,8 +18,9 @@ export const Example = () => { console.log("isPending", isPending); const yourContract = chain?.id && chain.id in deployedContracts - ? deployedContracts[chain.id as 11155111 | 31337].YourContract - : deployedContracts["11155111"].YourContract; + ? deployedContracts[chain.id as keyof typeof deployedContracts] + .YourContract + : deployedContracts[optimismSepolia.id].YourContract; const { data: totalCounter } = useReadContract({ ...yourContract, diff --git a/example/app/contracts/deployedContracts.ts b/example/app/contracts/deployedContracts.ts index 1431689..bc5b694 100644 --- a/example/app/contracts/deployedContracts.ts +++ b/example/app/contracts/deployedContracts.ts @@ -1,7 +1,7 @@ const deployedContracts = { - 11155111: { + 11155420: { YourContract: { - address: "0x0D25b202D1B5126ECFcaeFa85f7a37ed86EF79ea", + address: "0xFB30C0790128b97e3aC540E6124e512E37c47D00", abi: [ { inputs: [ diff --git a/example/app/wagmiConfig.ts b/example/app/wagmiConfig.ts index c9e20c1..9a0f45d 100644 --- a/example/app/wagmiConfig.ts +++ b/example/app/wagmiConfig.ts @@ -1,10 +1,13 @@ import { connectorsForWallets } from "@rainbow-me/rainbowkit"; import { createConfig } from "wagmi"; -import { hardhat, sepolia } from "wagmi/chains"; +import { hardhat, optimismSepolia } from "wagmi/chains"; import { metaMaskWallet } from "@rainbow-me/rainbowkit/wallets"; import { createClient, http } from "viem"; import { rainbowkitBurnerWallet } from "burner-connector"; +// Use this if you want to enable session storage +// rainbowkitBurnerWallet.useSessionStorage = true; + const wallets = [metaMaskWallet, rainbowkitBurnerWallet]; const walletConnectProjectID = "3a8170812b534d0ff9d794f19a901d64"; const wagmiConnectors = connectorsForWallets( @@ -18,10 +21,10 @@ const wagmiConnectors = connectorsForWallets( { appName: "scaffold-eth-2", projectId: walletConnectProjectID, - }, + } ); -export const chains = [sepolia, hardhat] as const; +export const chains = [optimismSepolia, hardhat] as const; export const wagmiConfig = createConfig({ chains: chains, diff --git a/packages/burner-connector/src/burnerConnector/burner.ts b/packages/burner-connector/src/burnerConnector/burner.ts index fc17f23..0d0e472 100644 --- a/packages/burner-connector/src/burnerConnector/burner.ts +++ b/packages/burner-connector/src/burnerConnector/burner.ts @@ -30,7 +30,7 @@ export class ChainNotConfiguredError extends BaseError { type Provider = ReturnType, EIP1193RequestFn>>; -export const burner = () => { +export const burner = ({ useSessionStorage = false }: { useSessionStorage?: boolean } = {}) => { let connected = true; let connectedChainId: number; return createConnector((config) => ({ @@ -55,7 +55,7 @@ export const burner = () => { const url = chain.rpcUrls.default.http[0]; if (!url) throw new Error("No rpc url found for chain"); - const burnerAccount = privateKeyToAccount(loadBurnerPK()); + const burnerAccount = privateKeyToAccount(loadBurnerPK({ useSessionStorage })); const client = createWalletClient({ chain: chain, account: burnerAccount, diff --git a/packages/burner-connector/src/utils/index.ts b/packages/burner-connector/src/utils/index.ts index 053278b..6b0e754 100644 --- a/packages/burner-connector/src/utils/index.ts +++ b/packages/burner-connector/src/utils/index.ts @@ -13,28 +13,36 @@ const isValidPK = (pk: Hex | string | undefined | null): boolean => { }; /** - * Save the current burner private key to local storage + * Save the current burner private key to storage */ -export const saveBurnerPK = (privateKey: Hex): void => { +const saveBurnerPK = ({ + privateKey, + useSessionStorage = false, +}: { + privateKey: Hex; + useSessionStorage?: boolean; +}): void => { if (typeof window !== "undefined" && window != null) { - window?.localStorage?.setItem(burnerStorageKey, privateKey); + const storage = useSessionStorage ? window.sessionStorage : window.localStorage; + storage?.setItem(burnerStorageKey, privateKey); } }; /** - * Gets the current burner private key from local storage + * Gets the current burner private key from local/session storage */ -export const loadBurnerPK = (): Hex => { +export const loadBurnerPK = ({ useSessionStorage = false }: { useSessionStorage?: boolean } = {}): Hex => { let currentSk: Hex = "0x"; if (typeof window !== "undefined" && window != null) { - currentSk = (window?.localStorage?.getItem?.(burnerStorageKey)?.replaceAll('"', "") ?? "0x") as Hex; + const storage = useSessionStorage ? window.sessionStorage : window.localStorage; + currentSk = (storage?.getItem?.(burnerStorageKey)?.replaceAll('"', "") ?? "0x") as Hex; } if (!!currentSk && isValidPK(currentSk)) { return currentSk; } - // If no burner is found in localstorage, we will generate a random private key + // If no burner is found in storage, we will generate a random private key const newDefaultPrivateKey = generatePrivateKey(); - saveBurnerPK(newDefaultPrivateKey); + saveBurnerPK({ privateKey: newDefaultPrivateKey, useSessionStorage }); return newDefaultPrivateKey; }; diff --git a/packages/burner-connector/src/wallets/rainbowkit/rainbowkitBurnerConnector.ts b/packages/burner-connector/src/wallets/rainbowkit/rainbowkitBurnerConnector.ts index b7dc7fa..cd54000 100644 --- a/packages/burner-connector/src/wallets/rainbowkit/rainbowkitBurnerConnector.ts +++ b/packages/burner-connector/src/wallets/rainbowkit/rainbowkitBurnerConnector.ts @@ -11,3 +11,10 @@ export const rainbowkitBurnerConnector = (walletDetails: WalletDetailsParams) => ...walletDetails, })); }; + +export const rainbowkitSessionStorageBurnerConnector = (walletDetails: WalletDetailsParams) => { + return createConnector((config) => ({ + ...burner({ useSessionStorage: true })(config), + ...walletDetails, + })); +}; diff --git a/packages/burner-connector/src/wallets/rainbowkit/rainbowkitBurnerWallet.ts b/packages/burner-connector/src/wallets/rainbowkit/rainbowkitBurnerWallet.ts index 7163624..4b51767 100644 --- a/packages/burner-connector/src/wallets/rainbowkit/rainbowkitBurnerWallet.ts +++ b/packages/burner-connector/src/wallets/rainbowkit/rainbowkitBurnerWallet.ts @@ -1,17 +1,24 @@ import type { Wallet } from "@rainbow-me/rainbowkit"; import { burnerWalletId, burnerWalletName } from "../../utils/index.js"; -import { rainbowkitBurnerConnector } from "./rainbowkitBurnerConnector.js"; +import { rainbowkitBurnerConnector, rainbowkitSessionStorageBurnerConnector } from "./rainbowkitBurnerConnector.js"; const burnerWalletIconBase64 = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzUzIiBoZWlnaHQ9IjM1MiIgdmlld0JveD0iMCAwIDM1MyAzNTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHg9IjAuNzE2MzA5IiB5PSIwLjMxNzEzOSIgd2lkdGg9IjM1MS4zOTQiIGhlaWdodD0iMzUxLjM5NCIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzNfMTUxKSIvPgo8Y2lyY2xlIGN4PSIzNC40OTUzIiBjeT0iMzQuNDk1MyIgcj0iMzQuNDk1MyIgdHJhbnNmb3JtPSJtYXRyaXgoLTEgMCAwIDEgMjA3LjAxOCAyNTQuMTIpIiBmaWxsPSIjRkY2NjBBIi8+CjxwYXRoIGQ9Ik0xNTQuMzE4IDMxNy45NTVDMTcxLjI3MyAzMTAuODkgMTc2LjU4MiAyOTAuNzE1IDE3Ni4xNTcgMjgzLjQ4N0wyMDcuMDE4IDI4OC44NjRDMjA3LjAxOCAzMDMuMzE0IDIwMC4yMTIgMzA5LjQwMiAxOTcuODI0IDMxMi40MzNDMTkzLjQ3NCAzMTcuOTU1IDE3My4zNTEgMzMwLjAzIDE1NC4zMTggMzE3Ljk1NVoiIGZpbGw9InVybCgjcGFpbnQxX3JhZGlhbF8zXzE1MSkiLz4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjBfZF8zXzE1MSkiPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTIyNy4zNzcgMzAyLjI3NkMyMjYuNDI2IDMwNS44OTcgMjMwLjMxNSAzMDkuNDA1IDIzMy4zOTYgMzA3LjI3OUMyNTQuNTM4IDI5Mi42ODQgMjcwLjQ3OSAyNjkuOTQ1IDI3NC44OSAyNDcuNDg5QzI4Mi4yNCAyMTAuMDcxIDI3Mi4yMzUgMTc1LjcyNyAyMzguMDI4IDE0NS45MjVDMjAwLjg3NCAxMTMuNTU2IDE5MS44NDQgODguNDU2MSAxOTAuMTYyIDUwLjg3MThDMTg5Ljc5NyA0Mi43MjE4IDE4MS42MDQgMzcuMjk0NyAxNzQuODI0IDQxLjgzMTdDMTUyLjY2OCA1Ni42NTc0IDEzMi41MTIgODQuNDk5IDEzOC45MTEgMTIwLjc1OEMxNDEuMDA0IDEzMi42MjEgMTQ2Ljc5NCAxNDEuMDE2IDE1MS45NyAxNDguNTIzQzE1OC40OTEgMTU3Ljk3OCAxNjQuMDM5IDE2Ni4wMjMgMTU5Ljk5NyAxNzcuODFDMTU1LjIwMyAxOTEuNzk0IDEzOS4xMzQgMTk5LjE2MiAxMjguNzQ3IDE5Mi40MjlDMTE0LjE3IDE4Mi45ODEgMTEzLjI1MyAxNjYuNjUxIDExNy45NjkgMTQ5LjQ1NkMxMTguOTAyIDE0Ni4wNTUgMTE1LjQ3MSAxNDMuMjA0IDExMi42OCAxNDUuMzU5QzkxLjM2MDQgMTYxLjgyMSA2OS4xNTMyIDE5OS4yNjcgNzcuNjY0NyAyNDcuNDg5Qzg1Ljk3OTIgMjc2LjIxMiA5Ny45Mjc3IDI5Mi41MzcgMTEwLjk3MSAzMDEuNTQxQzExMy43NjMgMzAzLjQ2OCAxMTcuMTU5IDMwMC42MzEgMTE2LjU5NyAyOTcuMjg2QzExNi4wODEgMjk0LjIxMiAxMTUuODEzIDI5MS4wNTQgMTE1LjgxMyAyODcuODMzQzExNS44MTMgMjU2LjUxMyAxNDEuMjAzIDIzMS4xMjMgMTcyLjUyMyAyMzEuMTIzQzIwMy44NDIgMjMxLjEyMyAyMjkuMjMyIDI1Ni41MTMgMjI5LjIzMiAyODcuODMzQzIyOS4yMzIgMjkyLjgyNCAyMjguNTg3IDI5Ny42NjUgMjI3LjM3NyAzMDIuMjc2WiIgZmlsbD0idXJsKCNwYWludDJfbGluZWFyXzNfMTUxKSIvPgo8L2c+CjxkZWZzPgo8ZmlsdGVyIGlkPSJmaWx0ZXIwX2RfM18xNTEiIHg9IjcyLjExMTIiIHk9IjM2LjQ5NCIgd2lkdGg9IjIwOC43NDIiIGhlaWdodD0iMjc1LjEyIiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIiByZXN1bHQ9ImhhcmRBbHBoYSIvPgo8ZmVPZmZzZXQvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIxLjg0NTA2Ii8+CjxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9Im91dCIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAxIDAgMCAwIDAgMC40MiAwIDAgMCAwIDAgMCAwIDAgMC43IDAiLz4KPGZlQmxlbmQgbW9kZT0ibXVsdGlwbHkiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9ImVmZmVjdDFfZHJvcFNoYWRvd18zXzE1MSIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvd18zXzE1MSIgcmVzdWx0PSJzaGFwZSIvPgo8L2ZpbHRlcj4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzNfMTUxIiB4MT0iMTc2LjQxMyIgeTE9IjAuMzE3MTM5IiB4Mj0iMTc2LjQxMyIgeTI9IjM1MS43MTEiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0ZGRjI3OSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRkQzMzYiLz4KPC9saW5lYXJHcmFkaWVudD4KPHJhZGlhbEdyYWRpZW50IGlkPSJwYWludDFfcmFkaWFsXzNfMTUxIiBjeD0iMCIgY3k9IjAiIHI9IjEiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKDIxOC4wNDggMjQ5LjM0Nykgcm90YXRlKDEyNC4wMTgpIHNjYWxlKDg5LjI5NTUgMjY0LjgwOSkiPgo8c3RvcCBvZmZzZXQ9IjAuNjQwODUiIHN0b3AtY29sb3I9IiNGRjY2MEEiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkZCRTE1Ii8+CjwvcmFkaWFsR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQyX2xpbmVhcl8zXzE1MSIgeDE9IjE3Ni40ODIiIHkxPSI0MC4xODQxIiB4Mj0iMTc2LjQ4MiIgeTI9IjMxNy4yNzgiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agb2Zmc2V0PSIwLjMzODU0MiIgc3RvcC1jb2xvcj0iI0ZGOEYzRiIvPgo8c3RvcCBvZmZzZXQ9IjAuNjU2MjUiIHN0b3AtY29sb3I9IiNGRjcwMjAiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkYzRDAwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg=="; +type RainbowkitBurnerWallet = { + (): Wallet; + useSessionStorage?: boolean; +}; + /** * Wagmi config for burner wallet */ -export const rainbowkitBurnerWallet = (): Wallet => ({ +export const rainbowkitBurnerWallet: RainbowkitBurnerWallet = () => ({ id: burnerWalletId, name: burnerWalletName, iconUrl: burnerWalletIconBase64, iconBackground: "#ffffff", - createConnector: rainbowkitBurnerConnector, + createConnector: rainbowkitBurnerWallet.useSessionStorage + ? rainbowkitSessionStorageBurnerConnector + : rainbowkitBurnerConnector, });