Skip to content

Commit

Permalink
BuidlGuidl#1 Burner Wallet (#8)
Browse files Browse the repository at this point in the history
* BuidlGuidl#1 started creating BurnerConnector.  additionally edited some config

* BuidlGuidl#1 changing rainbow connectors.  extracted connectors to its own folder

* BuidlGuidl#1 burner wallet is working

* BuidlGuidl#1 preminimnarly autoconnect working.  added some temp files to help check.  added useBurnerSigner

* BuidlGuidl#1 Finished autoconnect and burner wallet

* BuidlGuidl#1 Finished autoconnect and burner wallet

* BuidlGuidl#1 Finished autoconnect and burner wallet

* BuidlGuidl#1 better comments

* BuidlGuidl#1 better comments

* BuidlGuidl#1 better comments and cleaned up structure

* BuidlGuidl#1 simplified options

* minor

* Remove unrelated/extra stuff

* Prettify with exiting rules

* We want prettier to fix errors with eol without causing errors.  warnings will cause prettier to autofix errors without unecessary issues.

* Revert "We want prettier to fix errors with eol without causing errors.  warnings will cause prettier to autofix errors without unecessary issues."

This reverts commit 1282c22614aeceab6dbe5e0b84841b9ec94e6bfa.

* BuidlGuidl#1 We want prettier to fix errors with eol without causing errors.  warnings will cause prettier to autofix errors without unecessary issues.

* We want vscode to have linting properlly work on all of the workspaces.  We also want it to exclude settings files so that search and files are easier to use.

* BuidlGuidl#1 fixed issue with eslint

* BuidlGuidl#1 fixed nots

* BuidlGuidl#1 remove unused variables

* BuidlGuidl#1 removed comments

* BuidlGuidl#1 fixes for bugs.  TODO: write is no longer working with burner, need to find out why signer doesn't work

* BuidlGuidl#1 pushed changes to get the signer to work properly

* BuidlGuidl#1 removed imports

* BuidlGuidl#1 changed how initial save of sk was happening

* updated git ignore

Co-authored-by: Carlos Sánchez <oceanrdn@gmail.com>
  • Loading branch information
ShravanSunder and carletex committed Oct 14, 2022
1 parent ab34f5e commit 7c28a34
Show file tree
Hide file tree
Showing 23 changed files with 700 additions and 55 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ node_modules
!.yarn/sdks
!.yarn/versions
.eslintcache
.vscode/**
.DS_Store
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# se-2

A new version of scaffold-eth with its core functionality. Using NextJS, RainbowKit & Wagmi.

A new version of scaffold-eth with its core functionality. Using NextJS, RainbowKit, Wagmi and Typescript.

## Set up

Expand All @@ -25,6 +24,7 @@ yarn chain
```

3rd terminal, deploy test contract

```
yarn deploy
```
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"hardhat:test": "yarn workspace @se-2/hardhat test",
"start": "yarn workspace @se-2/frontend dev",
"next:lint": "yarn workspace @se-2/frontend lint",
"next:format" : "yarn workspace @se-2/frontend format",
"next:format": "yarn workspace @se-2/frontend format",
"postinstall": "husky install",
"precommit": "lint-staged"
},
Expand Down
8 changes: 7 additions & 1 deletion packages/frontend/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
{
"extends": ["next/core-web-vitals", "plugin:prettier/recommended"],
"rules": {
"no-unused-vars": "error"
"no-unused-vars": "error",
"prettier/prettier": [
"warn",
{
"endOfLine": "auto"
}
]
}
}
2 changes: 2 additions & 0 deletions packages/frontend/components/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./useAutoConnect";
export * from "./useBurnerWallet";
79 changes: 79 additions & 0 deletions packages/frontend/components/hooks/useAutoConnect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useEffect } from "react";
import { Connector, useAccount, useConnect } from "wagmi";
import { useEffectOnce, useLocalStorage } from "usehooks-ts";
import { burnerWalletId, defaultBurnerChainId } from "~~/web3/wagmi-burner";

export type TAutoConnect = {
/**
* Enable the burner wallet. If this is disabled, burner wallet is entierly disabled
*/
enableBurnerWallet: boolean;
/**
* Auto connect:
* 1. If the user was connected into a wallet before, on page reload reconnect automatically
* 2. If user is not connected to any wallet: On reload, connect to burner wallet
*/
autoConnect: boolean;
};

const walletIdStorageKey = "scaffoldEth2.wallet";

/**
* This function will get the initial connector (if any), the app will connect to
* @param config
* @param previousWalletId
* @param connectors
* @returns
*/
const getInitialConnector = (
config: TAutoConnect,
previousWalletId: string,
connectors: Connector<any, any, any>[],
): { connector: Connector | undefined; chainId?: number } | undefined => {
const allowBurner = config.enableBurnerWallet;

if (!previousWalletId) {
// The user was not connected to a wallet
if (allowBurner && config.autoConnect) {
const connector = connectors.find(f => f.id === burnerWalletId);
return { connector, chainId: defaultBurnerChainId };
}
} else {
// the user was connected to wallet
if (config.autoConnect) {
const connector = connectors.find(f => f.id === previousWalletId);
return { connector };
}
}

return undefined;
};

/**
* Automatically connect to a wallet/connector based on config and prior wallet
* @param config
*/
export const useAutoConnect = (config: TAutoConnect): void => {
const [walletId, setWalletId] = useLocalStorage<string>(walletIdStorageKey, "");
const connectState = useConnect();
const accountState = useAccount();

useEffect(() => {
if (accountState.isConnected) {
// user is connected, set walletName
setWalletId(accountState.connector?.id ?? "");
} else {
// user has disconnected, reset walletName
setWalletId("");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [accountState.isConnected, accountState.connector?.name]);

useEffectOnce(() => {
const initialConnector = getInitialConnector(config, walletId, connectState.connectors);

if (initialConnector?.connector) {
connectState.connect({ connector: initialConnector.connector, chainId: initialConnector.chainId });
}
});
};
160 changes: 160 additions & 0 deletions packages/frontend/components/hooks/useBurnerWallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { BytesLike, ethers, Signer, Wallet } from "ethers";
import { useEffect, useCallback, useRef } from "react";
import { useProvider } from "wagmi";
import { useDebounce } from "use-debounce";
import { useLocalStorage } from "usehooks-ts";

const burnerStorageKey = "scaffoldEth2.burnerWallet.sk";

/**
* Is the private key valid
* @internal
* @param pk
* @returns
*/
const isValidSk = (pk: BytesLike | undefined | null): boolean => {
return pk?.length === 64 || pk?.length === 66;
};

/**
* If no burner is found in localstorage, we will use a new default wallet
*/
const newDefaultWallet = ethers.Wallet.createRandom();

/**
* Save the current burner private key from storage
* Can be used outside of react. Used by the burnerConnector.
* @internal
* @returns
*/
export const saveBurnerSK = (wallet: Wallet): void => {
if (typeof window != "undefined" && window != null) {
window?.localStorage?.setItem(burnerStorageKey, wallet.privateKey);
}
};

/**
* Gets the current burner private key from storage
* Can be used outside of react. Used by the burnerConnector.
* @internal
* @returns
*/
export const loadBurnerSK = (): string => {
let currentSk = "";
if (typeof window != "undefined" && window != null) {
currentSk = window?.localStorage?.getItem?.(burnerStorageKey)?.replaceAll('"', "") ?? "";
}

if (!!currentSk && isValidSk(currentSk)) {
return currentSk;
} else {
saveBurnerSK(newDefaultWallet);
return newDefaultWallet.privateKey;
}
};

/**
* #### Summary
* Return type of useBurnerSigner:
*
* ##### ✏️ Notes
* - provides signer
* - methods of interacting with burner signer
* - methods to save and loadd signer from local storage
*
* @category Hooks
*/
export type TBurnerSigner = {
signer: Signer | undefined;
account: string | undefined;
/**
* create a new burner signer
*/
generateNewBurner: () => void;
/**
* explictly save burner to storage
*/
saveBurner: () => void;
};

/**
* #### Summary
* A hook that creates a burner signer/address and provides ways of interacting with
* and updating the signer
*
* @category Hooks
*
* @param localProvider localhost provider
* @returns IBurnerSigner
*/
export const useBurnerWallet = (): TBurnerSigner => {
const [burnerSk, setBurnerSk] = useLocalStorage<BytesLike>(burnerStorageKey, newDefaultWallet.privateKey);

const provider = useProvider();
const walletRef = useRef<Wallet>();
const isCreatingNewBurnerRef = useRef(false);

const [signer] = useDebounce(walletRef.current, 200, {
trailing: true,
equalityFn: (a, b) => a?.address === b?.address && a != null && b != null,
});
const account = walletRef.current?.address;

/**
* callback to save current wallet sk
*/
const saveBurner = useCallback(() => {
setBurnerSk(walletRef.current?.privateKey ?? "");
}, [setBurnerSk]);

/**
* create a new burnerkey
*/
const generateNewBurner = useCallback(() => {
if (provider && !isCreatingNewBurnerRef.current) {
console.log("🔑 Create new burner wallet...");
isCreatingNewBurnerRef.current = true;

const wallet = Wallet.createRandom().connect(provider);
setBurnerSk(() => {
console.log("🔥 ...Save new burner wallet");
isCreatingNewBurnerRef.current = false;
return wallet.privateKey;
});
return wallet;
} else {
console.log("⚠ Could not create burner wallet");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [provider?.network?.chainId]);

/**
* Load wallet with burnerSk
* connect and set wallet, once we have burnerSk and valid provider
*/
useEffect(() => {
if (burnerSk && provider.network.chainId) {
let wallet: Wallet | undefined = undefined;
if (isValidSk(burnerSk)) {
wallet = new ethers.Wallet(burnerSk, provider);
} else {
wallet = generateNewBurner?.();
}

if (wallet == null) {
throw "Error: Could not create burner wallet";
}
walletRef.current = wallet;
saveBurner?.();
}

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [burnerSk, provider?.network?.chainId]);

return {
signer,
account,
generateNewBurner,
saveBurner,
};
};
40 changes: 40 additions & 0 deletions packages/frontend/components/useTempTestContract.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* eslint-disable no-unused-vars */
import { useEffect } from "react";
import { usePrepareContractWrite, useContractWrite, useContractRead, chain } from "wagmi";
import { tempContract } from "~~/generated/tempContract";

// todo remove this, this is until we have contract element

const testChainId = chain.hardhat.id;

export const useTempTestContract = () => {
const cRead = useContractRead({
addressOrName: tempContract.address,
contractInterface: tempContract.abi,
functionName: "purpose",
chainId: testChainId,
watch: true,
cacheOnBlock: false,
});

const cWrite = useContractWrite({
mode: "recklesslyUnprepared",
addressOrName: tempContract.address,
contractInterface: tempContract.abi,
functionName: "setPurpose",
args: "new purpose",
chainId: testChainId,
});

useEffect(() => {
if (cRead.isSuccess) {
console.log("read contract: ", cRead.data);
}
}, [cRead.data, cRead.isSuccess]);

const onClick = () => {
console.log("...attempting to write");
cWrite.write?.();
};
return { onClick };
};
60 changes: 60 additions & 0 deletions packages/frontend/generated/tempContract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// todo remove this, this is until we have contract element

/**
* This is just for testing. you have to deploy the contract yourself and change the address.
*/
export const tempContract = {
address: "0x5FbDB2315678afecb367f032d93F642f64180aa3",
abi: [
{
inputs: [],
stateMutability: "nonpayable",
type: "constructor",
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: "address",
name: "sender",
type: "address",
},
{
indexed: false,
internalType: "string",
name: "purpose",
type: "string",
},
],
name: "SetPurpose",
type: "event",
},
{
inputs: [],
name: "purpose",
outputs: [
{
internalType: "string",
name: "",
type: "string",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "string",
name: "newPurpose",
type: "string",
},
],
name: "setPurpose",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
],
};
2 changes: 2 additions & 0 deletions packages/frontend/next.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// @ts-check

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
Expand Down
Loading

0 comments on commit 7c28a34

Please sign in to comment.