Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sessionStorage for burner wallet #24

Merged
merged 7 commits into from
Dec 20, 2024
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
5 changes: 5 additions & 0 deletions .changeset/dirty-wasps-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"burner-connector": patch
---

feat: sessionStorage for burner wallet
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()],
Expand All @@ -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(
[
{
Expand Down
6 changes: 4 additions & 2 deletions example/app/components/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions example/app/contracts/deployedContracts.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const deployedContracts = {
11155111: {
11155420: {
YourContract: {
address: "0x0D25b202D1B5126ECFcaeFa85f7a37ed86EF79ea",
address: "0xFB30C0790128b97e3aC540E6124e512E37c47D00",
abi: [
{
inputs: [
Expand Down
9 changes: 6 additions & 3 deletions example/app/wagmiConfig.ts
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions packages/burner-connector/src/burnerConnector/burner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class ChainNotConfiguredError extends BaseError {

type Provider = ReturnType<Transport<"custom", Record<any, any>, EIP1193RequestFn<WalletRpcSchema>>>;

export const burner = () => {
export const burner = ({ useSessionStorage = false }: { useSessionStorage?: boolean } = {}) => {
let connected = true;
let connectedChainId: number;
return createConnector<Provider>((config) => ({
Expand All @@ -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,
Expand Down
24 changes: 16 additions & 8 deletions packages/burner-connector/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,10 @@ export const rainbowkitBurnerConnector = (walletDetails: WalletDetailsParams) =>
...walletDetails,
}));
};

export const rainbowkitSessionStorageBurnerConnector = (walletDetails: WalletDetailsParams) => {
return createConnector<Provider>((config) => ({
...burner({ useSessionStorage: true })(config),
...walletDetails,
}));
};
Original file line number Diff line number Diff line change
@@ -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,
});
Loading